2015-03-04 3 views
0

Я хотел бы объявить статический метод (например, void main (String..args)) для каждого подтипа моего класса App.объявить статический метод ITD для нескольких типов

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?

ответ

1

Обычный шаблон для добавления множества нестатических методов к нескольким классам - это определение интерфейса + методы реализации в рамках аспекта и использование declare parents, чтобы обеспечить реализацию целевых классов интерфейсом.

К сожалению, это не работает для статических методов, таких как main, потому что статические методы не могут быть определены через интерфейс. С другой стороны, если у вас есть класс MyApp extends App, вы можете позвонить java -cp ... MyApp, то есть его родительский метод main будет автоматически использоваться.

Теперь предположим, что по какой-то причине вы хотите, чтобы содержимое сгенерированных методов main было чем-то иным. В этом случае вам действительно нужно создать один метод для каждого класса. Для этой цели вы можете использовать новую функцию, представленную в AspectJ 1.8.2 и описанную в примечаниях к выпуску: annotation processing support. Вот несколько самосогласованных примеров кода. Я скомпилировал его из командной строки, потому что из Eclipse мне не удалось запустить его в соответствии с поддерживающим AspectJ 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 классов:

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 у нас есть маркера аннотация, аннотация процессор и файл описания процессора для каталога META-INF нашего processor.jar:

Обратите внимание: аннотация составляет @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 {} 

Обработчик аннотации создает один аспект для каждого аннотированного класса. Каждый аспект делает следующее:

  • Добавить 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(); 
     } 
    } 
} 

Описание Процессор файл src\META-INF\services\javax.annotation.processing.Processor для APT выглядит следующим образом:

de.scrum_master.aspect.EntryPointProcessor 

Как скомпилировать и запустить: последнее, но не менее здесь есть (Windows) пакетный файл SO_AJ_ITD_AddMainMethodToAllSubclasses_AJ\compile_run.bat который

  • компилирует проект APT и упаковывает его в JAR,
  • компилирует другой проект, включая APM поколения аспект кода и, наконец,
  • прогонов каждого из трех классов Java, тестирование, если их 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, вы найдете три класса, глядя, как это (я показываю только один из них в качестве образца):

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 и просто указав туда, я должен был упомянуть все это, чтобы сделать его воспроизводимым.

Наслаждайтесь!

+0

это * sorta * какой я закончился выше делаю. создавая обработчик аннотации java. однако я сделал это 2 этапа сборки. что (простой javac) обработчик аннотации будет генерировать аспектные файлы, которые затем компилятор aspectj будет использовать вместе со всеми другими источниками. Удивительно, что теперь эти шаги можно объединить! спасибо за очень подробный ответ. – aepurniet

Смежные вопросы