AOP或APT用于覆盖超类的方法 [英] AOP or APT for overriding methods from super classes
问题描述
我有一个大型的wicket组件库,它们使用自定义注释 @ReferencedResource
或其他注释 @ReferencedResources
,它有一个 ReferencedResouce [] value()
参数,允许多个注释。
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.
这是一个示例代码snippet:
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调用的工作解决方案。但是,这会导致下一个问题:运行APT和AspectJ in使用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):
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(响应)
方法:
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屋!