代码之家  ›  专栏  ›  技术社区  ›  Sean Patrick Floyd

AOP或APT,用于重写超类中的方法

  •  3
  • Sean Patrick Floyd  · 技术社区  · 14 年前

    我有一个用自定义注释注释的wicket组件的大型库 @ReferencedResource @ReferencedResources ,有一个 ReferencedResouce[] value() 参数以允许多个批注。

    下面是一个示例代码段:

    @ReferencedResources({
        @ReferencedResource(value = Libraries.MOO_TOOLS, type = ResourceType.JAVASCRIPT),
        @ReferencedResource(value = "behaviors/promoteSelectOptions", type = ResourceType.JAVASCRIPT) })
    public class PromoteSelectOptionsBehavior extends AbstractBehavior{
     ...
    }
    

    apt

    @ReferencedResource(value = "behaviors/promoteSelectOptions",
                         type = ResourceType.JAVASCRIPT)
    

    将导致编译失败,除非 js/behaviors/promoteSelectOptions.js 可以在类路径上找到。这部分很好用。

    现在我也是DRY的粉丝,我希望在创建对象时使用相同的注释将资源注入到对象中。使用AspectJ,我实现了其中的一部分。

    带注释的对象总是 Component AbstractBehavior

    对于组件,事情很简单,只需在构造函数之后匹配即可。以下是一条建议:

    pointcut singleAnnotation() : @within(ReferencedResource);
    
    pointcut multiAnnotation() : @within(ReferencedResources);
    
    after() : execution(Component+.new(..)) && (singleAnnotation() || multiAnnotation()){
        final Component component = (Component) thisJoinPoint.getTarget();
        final Collection<ReferencedResource> resourceAnnotations =
            // gather annotations from cache
            this.getResourceAnnotations(component.getClass());
        for(final ReferencedResource annotation : resourceAnnotations){
            // helper utility that handles the creation of statements like
            // component.add(JavascriptPackageResource.getHeaderContribution(path))
            this.resourceInjector.inject(component, annotation);
        }
    }
    

    但是对于行为,我需要将资源附加到响应,而不是行为本身。以下是我使用的切入点:

    pointcut renderHead(IHeaderResponse response) :
        execution(* org.apache.wicket.behavior.AbstractBehavior+.renderHead(*))
            && args(response);
    

    before(final IHeaderResponse response) : 
        renderHead(response) && (multiAnnotation() || singleAnnotation()) {
        final Collection<ReferencedResource> resourceAnnotations =
            this.getResourceAnnotations(thisJoinPoint.getTarget().getClass());
        for(final ReferencedResource resource : resourceAnnotations){
            this.resourceInjector.inject(response, resource);
        }
    }
    

    如果类重写 renderHead(response) 方法,但在许多情况下这并不是必需的,因为一个超类已经实现了基类功能,而子类只添加了一些配置。因此,一种解决方案是让这些类定义如下方法:

    @Override
    public void renderHead(IHeaderResponse response){
        super.renderHead(response);
    }
    

    我已经使用APT和sunjavac调用创建了一个有效的解决方案。但是,这会导致下一个问题: Running APT and AspectJ in the same project using maven .

    1 回复  |  直到 7 年前
        1
  •  4
  •   Sean Patrick Floyd    14 年前

    回答我自己的问题:

    这些字段都在中初始化 init(env) process(annotations, roundEnv) :

    private static Filer filer;
    private static JavacProcessingEnvironment environment;
    private static Messager messager;
    private static Types types;
    private static JavacElements elementUtils;
    private Trees trees;
    private TreeMaker treeMaker;
    private IdentityHashMap<JCCompilationUnit, Void> compilationUnits;
    private Map<String, JCCompilationUnit> typeMap;
    

    这就是所谓的逻辑,如果 AbstractBehavior 具有注释的不重写 renderHead(response) 方法:

    private void addMissingSuperCall(final TypeElement element){
        final String className = element.getQualifiedName().toString();
        final JCClassDecl classDeclaration =
            // look up class declaration from a local map 
            this.findClassDeclarationForName(className);
        if(classDeclaration == null){
            this.error(element, "Can't find class declaration for " + className);
        } else{
            this.info(element, "Creating renderHead(response) method");
            final JCTree extending = classDeclaration.extending;
            if(extending != null){
                final String p = extending.toString();
                if(p.startsWith("com.myclient")){
                    // leave it alone, we'll edit the super class instead, if
                    // necessary
                    return;
                } else{
                    // @formatter:off (turns off eclipse formatter if configured)
    
                    // define method parameter name
                    final com.sun.tools.javac.util.Name paramName =
                        elementUtils.getName("response");
                    // Create @Override annotation
                    final JCAnnotation overrideAnnotation =
                        this.treeMaker.Annotation(
                            Processor.buildTypeExpressionForClass(
                                this.treeMaker,
                                elementUtils,
                                Override.class
                            ),
                            // with no annotation parameters
                            List.<JCExpression> nil()
                        );
                    // public
                    final JCModifiers mods =
                        this.treeMaker.Modifiers(Flags.PUBLIC,
                            List.of(overrideAnnotation));
                    // parameters:(final IHeaderResponse response)
                    final List<JCVariableDecl> params =
                        List.of(this.treeMaker.VarDef(this.treeMaker.Modifiers(Flags.FINAL),
                            paramName,
                            Processor.buildTypeExpressionForClass(this.treeMaker,
                                elementUtils,
                                IHeaderResponse.class),
                            null));
    
                    //method return type: void
                    final JCExpression returnType =
                        this.treeMaker.TypeIdent(TypeTags.VOID);
    
    
                    // super.renderHead(response);
                    final List<JCStatement> statements =
                        List.<JCStatement> of(
                            // Execute this:
                            this.treeMaker.Exec(
                                // Create a Method call:
                                this.treeMaker.Apply(
                                    // (no generic type arguments)
                                    List.<JCExpression> nil(),
                                    // super.renderHead
                                    this.treeMaker.Select(
                                        this.treeMaker.Ident(
                                            elementUtils.getName("super")
                                        ),
                                        elementUtils.getName("renderHead")
                                    ),
                                    // (response)
                                    List.<JCExpression> of(this.treeMaker.Ident(paramName)))
                                )
                         );
                    // build code block from statements
                    final JCBlock body = this.treeMaker.Block(0, statements);
                    // build method
                    final JCMethodDecl methodDef =
                        this.treeMaker.MethodDef(
                            // public
                            mods,
                            // renderHead
                            elementUtils.getName("renderHead"),
                            // void
                            returnType,
                            // <no generic parameters>
                            List.<JCTypeParameter> nil(),
                            // (final IHeaderResponse response)
                            params,
                            // <no declared exceptions>
                            List.<JCExpression> nil(),
                            // super.renderHead(response);
                            body,
                            // <no default value>
                            null);
    
                    // add this method to the class tree
                    classDeclaration.defs =
                        classDeclaration.defs.append(methodDef);
    
                    // @formatter:on turn eclipse formatter on again
                    this.info(element,
                        "Created renderHead(response) method successfully");
    
                }
            }
    
        }
    }