用于覆盖超类方法的 AOP 或 APT [英] AOP or APT for overriding methods from super classes

查看:20
本文介绍了用于覆盖超类方法的 AOP 或 APT的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个大型 wicket 组件库,这些组件使用自定义注释 @ReferencedResource 或其他注释 @ReferencedResources 进行注释,该注释具有 ReferencedResouce[] 值() 参数允许多个注释.

I have a large library of wicket components that are annotated with a custom annotation @ReferencedResource or another annotation @ReferencedResources, that has a ReferencedResouce[] value() parameter to allow multiple annotations.

这是一个示例代码片段:

Here is a sample code snippet:

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

到目前为止,我使用 apt 来检查引用的资源是否确实存在.例如.

So far, I use apt to check that the referenced resources actually exist. E.g.

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

将导致编译失败,除非可以在类路径中找到文件 js/behaviors/promoteSelectOptions.js.这部分效果很好.

will cause a compilation failure unless the file js/behaviors/promoteSelectOptions.js can be found on the class path. This part works nicely.

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

Now I am also a fan of DRY and I would like to use the same annotation to actually inject the resources into the Objects when they are created. Using AspectJ, I have implemented a part of this.

带注释的对象总是组件AbstractBehavior.

The annotated Objects are always either instances of Component or AbstractBehavior.

对于组件来说,事情很简单,只需在构造函数之后匹配即可.这是执行此操作的建议:

For components, things are easy, just match after the constructor. Here's an advice that does this:

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);
    }
}

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

For behaviors however, I need to attach the resources to a response, not to the behavior itself. Here are the pointcuts I use:

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) 方法,但在很多情况下这不是必需的,因为超类已经实现了基本功能,而子类只添加了一些配置.因此,一种解决方案是让这些类定义这样的方法:

This also works nicely if the class overrides the renderHead(response) method, but in many cases that's just not necessary because a super class already implements the base functionality while the child class only adds some configuration. So one solution would be to let these classes define a method like this:

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

我讨厌这个,因为这是死代码,但目前这是我看到的唯一可行的选择,所以我正在寻找其他解决方案.

I would hate this, because this is dead code, but currently this is the only working option I see, so I am looking for other solutions.

我使用 APT 和 sun javac 调用创建了一个有效的解决方案.然而,这会导致下一个问题:在使用 maven 的同一个项目.

I have created a working solution using APT and sun javac calls. However, this leads to the next problem: Running APT and AspectJ in the same project using maven.

不管怎样,一有空闲,我就会发布这个问题(或部分)的答案.

Anyway, as soon as I have some free time, I'll post the answer to this question (or parts of it).

推荐答案

回答我自己的问题:

这是插入超级调用的相关代码:

Here is the relevant bit of code to insert the super call:

这些字段都在 init(env)process(annotations, roundEnv):

these fields are all initialized in init(env) or 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) 方法时调用的逻辑:

And here is the logic that is called if a subtype of AbstractBehavior that has the annotation does not override the renderHead(response) method:

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");

            }
        }

    }
}

这篇关于用于覆盖超类方法的 AOP 或 APT的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆