在多种类型上声明 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.jarMETA-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 存储库并指向那里之外,我不得不提及所有内容以使其可重现。

尽情享受吧!