在多种类型上声明 ITD 静态方法
declare ITD static method on multiple types
我想在我的应用程序的每个子类型上声明一个静态方法(如 void main(String..args))class。
public aspect Aspects pertypewithin(App+) {
protected Class appClass;
after() : staticinitialization(App+) && !staticinitialization(App) {
StaticPart point = thisJoinPointStaticPart;
Signature signature = point.getSignature();
Class declaringType = signature.getDeclaringType();
this.appClass = declaringType;
}
public static void App.main(String...args) {
// how do i make this appear on every subtype of App, not just App
}
}
这可以用 AspectJ 实现吗?
向多个 classes 添加一组非静态方法的通常模式是定义一个接口 + 在一个方面内实现方法并使用 declare parents
以使目标 classes 实现接口。
不幸的是,这不适用于像 main
这样的静态方法,因为不能通过接口定义静态方法。另一方面,如果你有一个 class MyApp extends App
你可以调用 java -cp ... MyApp
,即它的父 main
方法将自动被使用。
现在让我们假设出于某种原因您希望生成的 main
方法的内容有所不同。在这种情况下,您确实需要为每个 class 生成一个方法。为此,您可以使用 AspectJ 1.8.2 中引入并在发行说明中描述的新功能:annotation processing support. Here is some self-consistent sample code. I compiled it from the command line because from Eclipse I failed to get it running according to AspectJ maintainer Andy Clement's description.
好吧,假设我们在名为 java-src
的基本目录中的某处有两个项目目录。目录布局如下:
java-src
SO_AJ_ITD_AddMainMethodToAllSubclasses_AJ
src\de\scrum_master\app\App.java
src\de\scrum_master\app\BarApp.java
src\de\scrum_master\app\FooApp.java
compile_run.bat
SO_AJ_ITD_AddMainMethodToAllSubclasses_APT
src\de\scrum_master\app\EntryPoint.java
src\de\scrum_master\aspect\EntryPointProcessor.java
src\META-INF\services\javax.annotation.processing.Processor
在项目 SO_AJ_ITD_AddMainMethodToAllSubclasses_AJ
中,我们有 Java classes:
package de.scrum_master.app;
@EntryPoint
public class App {
public void doSomething() {
System.out.println("Doing something");
}
}
package de.scrum_master.app;
public class FooApp extends App {
public void doFoo() {
System.out.println("Doing foo");
}
public int add(int a, int b) {
return a + b;
}
}
package de.scrum_master.app;
public class BarApp extends App {
public void doBar() {
System.out.println("Doing bar");
}
public int multiply(int a, int b) {
return a * b;
}
}
在项目 SO_AJ_ITD_AddMainMethodToAllSubclasses_APT
中,我们有我们的 标记注释,processor.jar
的 META-INF
目录的注释处理器和处理器描述文件:
请注意:应用于 classes 时,注释为 @Inherited
。这对于让注释处理器找到它很重要。
package de.scrum_master.app;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface EntryPoint {}
注释处理器为每个注释创建一个方面 class。每个方面执行以下操作:
- 添加一个
main
方法,其中
- 实例化目标的一个对象class,
- 总是在目标实例上调用基方法
doSomething()
,
- 在目标 class 中搜索其他没有参数的简单非静态方法并调用这些方法,只是为了显示一些花哨的动态内容并使结果方面看起来有点不同。
package de.scrum_master.aspect;
import java.io.*;
import javax.tools.*;
import java.util.*;
import javax.annotation.processing.*;
import javax.lang.model.*;
import javax.lang.model.element.*;
import de.scrum_master.app.EntryPoint;
@SupportedAnnotationTypes(value = { "*" })
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class EntryPointProcessor extends AbstractProcessor {
private Filer filer;
@Override
public void init(ProcessingEnvironment env) {
filer = env.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
// Discover elements marked with @EntryPoint
for (Element element : env.getElementsAnnotatedWith(EntryPoint.class)) {
// Skip non-class elements
if (element.getKind() != ElementKind.CLASS)
continue;
String packageName = element.getEnclosingElement().toString().substring(8);
String className = element.getSimpleName().toString();
String aspectName = "MainMethodAspect_" + className;
// For each marked class, create an aspect adding a 'main' method
String aspectSource = createAspectSource(element, packageName, className, aspectName);
writeAspectSourceToDisk(element, packageName, aspectName, aspectSource);
}
return true;
}
private String createAspectSource(Element element, String packageName, String className, String aspectName) {
String variableName = className.substring(0, 1).toLowerCase() + className.substring(1);
StringBuilder aspectSource = new StringBuilder()
.append("package " + packageName + ";\n\n")
.append("public aspect " + aspectName + " {\n")
.append(" public static void " + className + ".main(String[] args) {\n")
.append(" " + className + " " + variableName + " = new " + className + "();\n")
.append(" " + variableName + ".doSomething();\n");
for (Element childElement : element.getEnclosedElements()) {
// Skip everything which is not a non-static method
if (childElement.getKind() != ElementKind.METHOD || childElement.getModifiers().contains(Modifier.STATIC))
continue;
ExecutableElement method = (ExecutableElement) childElement;
// Skip methods with parameters or named 'doSomething'
if (!method.getParameters().isEmpty() || method.getSimpleName().toString().equals("doSomething"))
continue;
// Add call to found method
aspectSource.append(" " + variableName + "." + method.getSimpleName() + "();\n");
}
aspectSource
.append(" }\n")
.append("}\n");
return aspectSource.toString();
}
private void writeAspectSourceToDisk(Element element, String packageName, String aspectName, String aspectSource) {
try {
JavaFileObject file = filer.createSourceFile(packageName + "." + aspectName, element);
file.openWriter().append(aspectSource).close();
System.out.println("Generated aspect " + packageName + "." + aspectName + " to advise " + element);
} catch (IOException ioe) {
// Message "already created" can appear if processor runs more than once
if (!ioe.getMessage().contains("already created"))
ioe.printStackTrace();
}
}
}
APT 的处理器描述文件 src\META-INF\services\javax.annotation.processing.Processor
如下所示:
de.scrum_master.aspect.EntryPointProcessor
如何编译和 运行: 最后但并非最不重要的是这里有一个 (Windows) 批处理文件 SO_AJ_ITD_AddMainMethodToAllSubclasses_AJ\compile_run.bat
其中
- 编译 APT 项目并将其打包成 JAR,
- 编译其他项目,包括 APM 方面代码生成,最后
- 运行 三个 Java classes 中的每一个,测试它们的
main
方法是否确实按预期工作:
@echo off
set SRC_PATH=C:\Users\Alexander\Documents\java-src
set ASPECTJ_HOME=C:\Program Files\Java\AspectJ
echo Building annotation processor
cd "%SRC_PATH%\SO_AJ_ITD_AddMainMethodToAllSubclasses_APT"
rmdir /s /q bin
del /q processor.jar
call "%ASPECTJ_HOME%\bin\ajc.bat" -8 -sourceroots src -d bin -cp "c:\Program Files\Java\AspectJ\lib\aspectjrt.jar"
jar -cvf processor.jar -C src META-INF -C bin .
echo.
echo Generating aspects and building project
cd "%SRC_PATH%\SO_AJ_ITD_AddMainMethodToAllSubclasses_AJ"
rmdir /s /q bin .apt_generated
call "%ASPECTJ_HOME%\bin\ajc.bat" -8 -sourceroots src -d bin -s .apt_generated -cp "c:\Program Files\Java\AspectJ\lib\aspectjrt.jar";..\SO_AJ_ITD_AddMainMethodToAllSubclasses_APT\processor.jar
echo.
echo Running de.scrum_master.app.App
java -cp bin;"c:\Program Files\Java\AspectJ\lib\aspectjrt.jar" de.scrum_master.app.App
echo.
echo Running de.scrum_master.app.FooApp
java -cp bin;"c:\Program Files\Java\AspectJ\lib\aspectjrt.jar" de.scrum_master.app.FooApp
echo.
echo Running de.scrum_master.app.BarApp
java -cp bin;"c:\Program Files\Java\AspectJ\lib\aspectjrt.jar" de.scrum_master.app.BarApp
控制台输出: 如果您 运行 批处理文件,输出应如下所示:
Building annotation processor
Manifest wurde hinzugefügt
Eintrag META-INF/ wird ignoriert
META-INF/services/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
META-INF/services/javax.annotation.processing.Processor wird hinzugefügt(ein = 43) (aus = 45)(-4 % verkleinert)
de/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
de/scrum_master/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
de/scrum_master/app/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
de/scrum_master/app/EntryPoint.class wird hinzugefügt(ein = 430) (aus = 253)(41 % verkleinert)
de/scrum_master/aspect/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
de/scrum_master/aspect/EntryPointProcessor.class wird hinzugefügt(ein = 5782) (aus = 2617)(54 % verkleinert)
Generating aspects and building project
Generated aspect de.scrum_master.app.MainMethodAspect_App to advise de.scrum_master.app.App
Generated aspect de.scrum_master.app.MainMethodAspect_BarApp to advise de.scrum_master.app.BarApp
Generated aspect de.scrum_master.app.MainMethodAspect_FooApp to advise de.scrum_master.app.FooApp
Running de.scrum_master.app.App
Doing something
Running de.scrum_master.app.FooApp
Doing something
Doing foo
Running de.scrum_master.app.BarApp
Doing something
Doing bar
如果你查看 SO_AJ_ITD_AddMainMethodToAllSubclasses_AJ\.apt_generated
下注释处理器 生成的 文件,你会发现三个 class 看起来像这样(我正在展示只是其中一个作为样本):
package de.scrum_master.app;
public aspect MainMethodAspect_FooApp {
public static void FooApp.main(String[] args) {
FooApp fooApp = new FooApp();
fooApp.doSomething();
fooApp.doFoo();
}
}
很抱歉这个冗长的回答,但除了创建一个 GitHub 存储库并指向那里之外,我不得不提及所有内容以使其可重现。
尽情享受吧!
我想在我的应用程序的每个子类型上声明一个静态方法(如 void main(String..args))class。
public aspect Aspects pertypewithin(App+) {
protected Class appClass;
after() : staticinitialization(App+) && !staticinitialization(App) {
StaticPart point = thisJoinPointStaticPart;
Signature signature = point.getSignature();
Class declaringType = signature.getDeclaringType();
this.appClass = declaringType;
}
public static void App.main(String...args) {
// how do i make this appear on every subtype of App, not just App
}
}
这可以用 AspectJ 实现吗?
向多个 classes 添加一组非静态方法的通常模式是定义一个接口 + 在一个方面内实现方法并使用 declare parents
以使目标 classes 实现接口。
不幸的是,这不适用于像 main
这样的静态方法,因为不能通过接口定义静态方法。另一方面,如果你有一个 class MyApp extends App
你可以调用 java -cp ... MyApp
,即它的父 main
方法将自动被使用。
现在让我们假设出于某种原因您希望生成的 main
方法的内容有所不同。在这种情况下,您确实需要为每个 class 生成一个方法。为此,您可以使用 AspectJ 1.8.2 中引入并在发行说明中描述的新功能:annotation processing support. Here is some self-consistent sample code. I compiled it from the command line because from Eclipse I failed to get it running according to AspectJ maintainer Andy Clement's description.
好吧,假设我们在名为 java-src
的基本目录中的某处有两个项目目录。目录布局如下:
java-src
SO_AJ_ITD_AddMainMethodToAllSubclasses_AJ
src\de\scrum_master\app\App.java
src\de\scrum_master\app\BarApp.java
src\de\scrum_master\app\FooApp.java
compile_run.bat
SO_AJ_ITD_AddMainMethodToAllSubclasses_APT
src\de\scrum_master\app\EntryPoint.java
src\de\scrum_master\aspect\EntryPointProcessor.java
src\META-INF\services\javax.annotation.processing.Processor
在项目 SO_AJ_ITD_AddMainMethodToAllSubclasses_AJ
中,我们有 Java classes:
package de.scrum_master.app;
@EntryPoint
public class App {
public void doSomething() {
System.out.println("Doing something");
}
}
package de.scrum_master.app;
public class FooApp extends App {
public void doFoo() {
System.out.println("Doing foo");
}
public int add(int a, int b) {
return a + b;
}
}
package de.scrum_master.app;
public class BarApp extends App {
public void doBar() {
System.out.println("Doing bar");
}
public int multiply(int a, int b) {
return a * b;
}
}
在项目 SO_AJ_ITD_AddMainMethodToAllSubclasses_APT
中,我们有我们的 标记注释,processor.jar
的 META-INF
目录的注释处理器和处理器描述文件:
请注意:应用于 classes 时,注释为 @Inherited
。这对于让注释处理器找到它很重要。
package de.scrum_master.app;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface EntryPoint {}
注释处理器为每个注释创建一个方面 class。每个方面执行以下操作:
- 添加一个
main
方法,其中 - 实例化目标的一个对象class,
- 总是在目标实例上调用基方法
doSomething()
, - 在目标 class 中搜索其他没有参数的简单非静态方法并调用这些方法,只是为了显示一些花哨的动态内容并使结果方面看起来有点不同。
package de.scrum_master.aspect;
import java.io.*;
import javax.tools.*;
import java.util.*;
import javax.annotation.processing.*;
import javax.lang.model.*;
import javax.lang.model.element.*;
import de.scrum_master.app.EntryPoint;
@SupportedAnnotationTypes(value = { "*" })
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class EntryPointProcessor extends AbstractProcessor {
private Filer filer;
@Override
public void init(ProcessingEnvironment env) {
filer = env.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
// Discover elements marked with @EntryPoint
for (Element element : env.getElementsAnnotatedWith(EntryPoint.class)) {
// Skip non-class elements
if (element.getKind() != ElementKind.CLASS)
continue;
String packageName = element.getEnclosingElement().toString().substring(8);
String className = element.getSimpleName().toString();
String aspectName = "MainMethodAspect_" + className;
// For each marked class, create an aspect adding a 'main' method
String aspectSource = createAspectSource(element, packageName, className, aspectName);
writeAspectSourceToDisk(element, packageName, aspectName, aspectSource);
}
return true;
}
private String createAspectSource(Element element, String packageName, String className, String aspectName) {
String variableName = className.substring(0, 1).toLowerCase() + className.substring(1);
StringBuilder aspectSource = new StringBuilder()
.append("package " + packageName + ";\n\n")
.append("public aspect " + aspectName + " {\n")
.append(" public static void " + className + ".main(String[] args) {\n")
.append(" " + className + " " + variableName + " = new " + className + "();\n")
.append(" " + variableName + ".doSomething();\n");
for (Element childElement : element.getEnclosedElements()) {
// Skip everything which is not a non-static method
if (childElement.getKind() != ElementKind.METHOD || childElement.getModifiers().contains(Modifier.STATIC))
continue;
ExecutableElement method = (ExecutableElement) childElement;
// Skip methods with parameters or named 'doSomething'
if (!method.getParameters().isEmpty() || method.getSimpleName().toString().equals("doSomething"))
continue;
// Add call to found method
aspectSource.append(" " + variableName + "." + method.getSimpleName() + "();\n");
}
aspectSource
.append(" }\n")
.append("}\n");
return aspectSource.toString();
}
private void writeAspectSourceToDisk(Element element, String packageName, String aspectName, String aspectSource) {
try {
JavaFileObject file = filer.createSourceFile(packageName + "." + aspectName, element);
file.openWriter().append(aspectSource).close();
System.out.println("Generated aspect " + packageName + "." + aspectName + " to advise " + element);
} catch (IOException ioe) {
// Message "already created" can appear if processor runs more than once
if (!ioe.getMessage().contains("already created"))
ioe.printStackTrace();
}
}
}
APT 的处理器描述文件 src\META-INF\services\javax.annotation.processing.Processor
如下所示:
de.scrum_master.aspect.EntryPointProcessor
如何编译和 运行: 最后但并非最不重要的是这里有一个 (Windows) 批处理文件 SO_AJ_ITD_AddMainMethodToAllSubclasses_AJ\compile_run.bat
其中
- 编译 APT 项目并将其打包成 JAR,
- 编译其他项目,包括 APM 方面代码生成,最后
- 运行 三个 Java classes 中的每一个,测试它们的
main
方法是否确实按预期工作:
@echo off
set SRC_PATH=C:\Users\Alexander\Documents\java-src
set ASPECTJ_HOME=C:\Program Files\Java\AspectJ
echo Building annotation processor
cd "%SRC_PATH%\SO_AJ_ITD_AddMainMethodToAllSubclasses_APT"
rmdir /s /q bin
del /q processor.jar
call "%ASPECTJ_HOME%\bin\ajc.bat" -8 -sourceroots src -d bin -cp "c:\Program Files\Java\AspectJ\lib\aspectjrt.jar"
jar -cvf processor.jar -C src META-INF -C bin .
echo.
echo Generating aspects and building project
cd "%SRC_PATH%\SO_AJ_ITD_AddMainMethodToAllSubclasses_AJ"
rmdir /s /q bin .apt_generated
call "%ASPECTJ_HOME%\bin\ajc.bat" -8 -sourceroots src -d bin -s .apt_generated -cp "c:\Program Files\Java\AspectJ\lib\aspectjrt.jar";..\SO_AJ_ITD_AddMainMethodToAllSubclasses_APT\processor.jar
echo.
echo Running de.scrum_master.app.App
java -cp bin;"c:\Program Files\Java\AspectJ\lib\aspectjrt.jar" de.scrum_master.app.App
echo.
echo Running de.scrum_master.app.FooApp
java -cp bin;"c:\Program Files\Java\AspectJ\lib\aspectjrt.jar" de.scrum_master.app.FooApp
echo.
echo Running de.scrum_master.app.BarApp
java -cp bin;"c:\Program Files\Java\AspectJ\lib\aspectjrt.jar" de.scrum_master.app.BarApp
控制台输出: 如果您 运行 批处理文件,输出应如下所示:
Building annotation processor
Manifest wurde hinzugefügt
Eintrag META-INF/ wird ignoriert
META-INF/services/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
META-INF/services/javax.annotation.processing.Processor wird hinzugefügt(ein = 43) (aus = 45)(-4 % verkleinert)
de/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
de/scrum_master/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
de/scrum_master/app/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
de/scrum_master/app/EntryPoint.class wird hinzugefügt(ein = 430) (aus = 253)(41 % verkleinert)
de/scrum_master/aspect/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
de/scrum_master/aspect/EntryPointProcessor.class wird hinzugefügt(ein = 5782) (aus = 2617)(54 % verkleinert)
Generating aspects and building project
Generated aspect de.scrum_master.app.MainMethodAspect_App to advise de.scrum_master.app.App
Generated aspect de.scrum_master.app.MainMethodAspect_BarApp to advise de.scrum_master.app.BarApp
Generated aspect de.scrum_master.app.MainMethodAspect_FooApp to advise de.scrum_master.app.FooApp
Running de.scrum_master.app.App
Doing something
Running de.scrum_master.app.FooApp
Doing something
Doing foo
Running de.scrum_master.app.BarApp
Doing something
Doing bar
如果你查看 SO_AJ_ITD_AddMainMethodToAllSubclasses_AJ\.apt_generated
下注释处理器 生成的 文件,你会发现三个 class 看起来像这样(我正在展示只是其中一个作为样本):
package de.scrum_master.app;
public aspect MainMethodAspect_FooApp {
public static void FooApp.main(String[] args) {
FooApp fooApp = new FooApp();
fooApp.doSomething();
fooApp.doFoo();
}
}
很抱歉这个冗长的回答,但除了创建一个 GitHub 存储库并指向那里之外,我不得不提及所有内容以使其可重现。
尽情享受吧!