在多种类型上声明 ITD 静态方法 [英] declare ITD static method on multiple types

查看:18
本文介绍了在多种类型上声明 ITD 静态方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在我的 App 类的每个子类型上声明一个静态方法(如 void main(String..args)).

I would like to declare a static method (like void main(String..args)) on every subtype of my App 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 实现吗?

Is this possible with AspectJ?

推荐答案

向多个类添加一组非静态方法的通常模式是在一个方面定义一个接口 + 实现方法并使用 declare parents为了让目标类实现接口.

The usual pattern for adding a set of non-static methods to multiple classes is to define an interface + implementing methods within an aspect and use declare parents in order to make the target classes implement the interface.

不幸的是,这不适用于像 main 这样的静态方法,因为静态方法不能通过接口定义.另一方面,如果你有一个 MyApp extends App 类,你可以调用 java -cp ... MyApp,即它的父 main 方法将自动使用.

Unfortunately this does not work for static methods like main because static methods cannot be defined via an interface. On the other hand, if you have a class MyApp extends App you can call java -cp ... MyApp, i.e. its parent main method will automatically be used.

现在让我们假设出于某种原因您希望生成的 main 方法的内容在某种程度上有所不同.在这种情况下,您确实需要为每个类生成一个方法.为此,您可以使用 AspectJ 1.8.2 中引入并在发行说明中描述的新功能:注释处理支持.这是一些自洽的示例代码.我从命令行编译它,因为从 Eclipse 我未能根据 AspectJ 维护者 安迪克莱门特的描述.

Now let us assume that for some reason you want the contents of the generated main methods to be somehow different. In this case you really need to generate one method per class. For that purpose you can use a new feature introduced in AspectJ 1.8.2 and described in the release notes: 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 的基本目录中的某处有两个项目目录.目录布局如下:

Okay, let's say we have two project directories somewhere in a base directory called java-src. The directory layout be like this:

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 类:

In project SO_AJ_ITD_AddMainMethodToAllSubclasses_AJ we have our 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 目录:

In project SO_AJ_ITD_AddMainMethodToAllSubclasses_APT we have our marker annotation, the annotation processor and processor description file for the META-INF directory of our processor.jar:

请注意:当应用于类时,注释是 @Inherited.这对于让注释处理器找到它很重要.

Please note: The annotation is @Inherited when applied to classes. This is important in order to make the annotation processor find it.

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 {}

注解处理器为每个注解类创建一个方面.每个方面执行以下操作:

The annotation processor creates one aspect per annotated class. Each aspect does the following:

  • 添加一个 main 方法,其中
  • 实例化目标类的一个对象,
  • 总是在目标实例上调用基本方法 doSomething()
  • 在目标类中搜索其他简单的不带参数的非静态方法并调用这些方法,只是为了展示一些花哨和动态的东西,使结果方面看起来有点不同.
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 如下所示:

The processor description file src\META-INF\services\javax.annotation.processing.Processor for APT looks like this:

de.scrum_master.aspect.EntryPointProcessor

如何编译和运行:最后但并非最不重要的是这里是一个(Windows)批处理文件SO_AJ_ITD_AddMainMethodToAllSubclasses_AJ\compile_run.bat其中

How to compile and run: Last, but not least here is a (Windows) batch file SO_AJ_ITD_AddMainMethodToAllSubclasses_AJ\compile_run.bat which

  • 编译 APT 项目并将其打包成 JAR,
  • 编译其他项目,包括 APM 方面代码生成,最后
  • 运行三个 Java 类中的每一个,测试它们的 main 方法是否真的按预期工作:
  • compiles the APT project and packages it into a JAR,
  • compiles the other project including APM aspect code generation and finally
  • runs each of the three Java classes, testing if their main methods actually work as expected:
@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

控制台输出:如果您运行批处理文件,输出应如下所示:

Console output: If you run the batch file, the output should look as follows:

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 下的注释处理器生成的文件,您会发现三个类看起来像这样(我只展示其中一个作为示例):

If you look at the files generated by the annotation processor under SO_AJ_ITD_AddMainMethodToAllSubclasses_AJ\.apt_generated, you will find three classes looking like this (I am showing just one of them as a sample):

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 存储库并指向那里之外,我不得不提及这一切以使其可重现.

Sorry for this lenghty answer, but other than creating a GitHub repo and just pointing there, I had to mention it all in order to make it reproducible.

享受吧!

这篇关于在多种类型上声明 ITD 静态方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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