スーパークラスからのメソッドをオーバーライドするためのAOPまたはAPT
-
01-10-2019 - |
質問
カスタム注釈が注釈が付けられた改札コンポーネントの大きなライブラリがあります @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を使用して、これの一部を実装しました。
注釈付きオブジェクトは、常にのインスタンスです 成分 また 抽象的なベハビオール.
コンポーネントの場合、物事は簡単で、コンストラクターの後に一致します。これを行うアドバイスは次のとおりです。
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(応答) 方法ですが、多くの場合、スーパークラスはすでに基本機能を実装している一方で、子のクラスは構成のみを追加しているため、必要ではありません。したがって、1つの解決策は、これらのクラスに次のような方法を定義できるようにすることです。
@Override
public void renderHead(IHeaderResponse response){
super.renderHead(response);
}
これは死んだコードであるため、私はこれを嫌いますが、現在、これは私が見る唯一の作業オプションなので、他のソリューションを探しています。
編集:
APTとSun Javacの呼び出しを使用して、実用的なソリューションを作成しました。ただし、これは次の問題につながります。 Mavenを使用して同じプロジェクトでaptとaspectjを実行する.
とにかく、自由時間があるとすぐに、この質問(またはその一部)に対する答えを投稿します。
解決
私自身の質問に答える:
スーパーコールを挿入するための関連するコードは次のとおりです。
これらのフィールドはすべて初期化されています init(env) また プロセス(アノテーション、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");
}
}
}
}