Пользовательский обработчик аннотаций — метод обнаружения с аннотациями

Я пытаюсь написать обработчик аннотаций для обнаружения методов, аннотированных аннотацией @PrintMethod. Например, в тестовом классе ниже я хочу напечатать коды в тестовом методе. Есть ли способ сделать это?

Из класса AnnotationProcessor, указанного ниже, я могу получить только имя метода, но не подробности метода.

Тестовый класс

public class test {

    public static void main(String[] args) {
        System.out.println("Args");
    }

    @PrintMethod
    private boolean testMethod(String input) {
        if(input!=null) {  
            return true;
        }
        return false; 
    }
}

Класс процессора аннотаций

public class AnnotationProcessor extends AbstractProcessor {
//......
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        //retrieve test Anntoation
        Set<? extends Element> ann =roundEnv.getElementsAnnotatedWith(PrintMethod.class);

        //Print the Method Name
        for(Element e: ann) {
            String msg="Element ee :"+ee.getSimpleName().toString();
            processingEnv.getMessager().printMessage( javax.tools.Diagnostic.Kind.ERROR, msg, e);
        }
    }
}

person user1004413    schedule 03.08.2013    source источник


Ответы (2)


Меня это тоже заинтересовало, и я решил попробовать разобраться. Оказывается проще, чем я ожидал. Все, что вам нужно сделать, это использовать Trees API из проприетарной библиотеки tools.jar. Я сделал быстрый обработчик аннотаций по следующим направлениям: https://github.com/johncarl81/printMethod

Вот его мясо:

@SupportedSourceVersion(SourceVersion.RELEASE_6)
@SupportedAnnotationTypes("org.printMethod.PrintMethod")
public class PrintMethodAnnotationProcessor extends AbstractProcessor {

    private Trees trees;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        trees = Trees.instance(processingEnv); //initialize the Trees api.
    }

    @Override
    public boolean process(Set<? extends TypeElement> typeElements, RoundEnvironment roundEnvironment) {

        MethodPrintScanner visitor = new MethodPrintScanner();

        for (Element e : roundEnvironment.getElementsAnnotatedWith(PrintMethod.class)) {
            TreePath tp = trees.getPath(e);
            // visit the annotated methods
            visitor.scan(tp, trees);
        }
        return true;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}

И MethodPrintScanner:

public class MethodPrintScanner extends TreePathScanner {

    @Override
    public Object visitMethod(MethodTree methodTree, Object o) {
        System.out.println(methodTree);
        return null;
    }
}

Вы можете видеть, что мы можем посетить TreePath, связанный с данным аннотированным элементом. Для каждого метода мы просто println() methodTree получаем содержимое метода.

Используя ваш пример, вот вывод программы во время компиляции:

@PrintMethod()
private boolean testMethod(String input) {
    if (input != null) {
        return true;
    }
    return false;
}
person John Ericksen    schedule 20.06.2014

Одно дело заставить его работать в вашей среде IDE. Но совсем другое — обнаружить их, когда ваш код упакован в jar-файлы. Следующий код может управлять обоими.

public static List<Class> getPackageClassListHavingAnnotation(String pPackageName,
                                                              Class<? extends Annotation> pAnnotation) throws Exception
{
  try
  {
    List<Class> classList = getPackageClassList(pPackageName);
    if ((pAnnotation == null) || (classList == null)) return classList;

    List<Class> resultList = new ArrayList<Class>(classList.size());

    outerLoop:
    for (Class clazz : classList)
    {
      try
      {
        for (Method method : clazz.getMethods())
        {
          if (method.isAnnotationPresent(pAnnotation))
          {
            resultList.add(clazz);
            continue outerLoop;
          }
        }
      }
      catch (Throwable e)
      {
      }
    }
    return (resultList.isEmpty()) ? null : resultList;
  }
  catch (Exception e)
  {
    return null;
  }
}

Требуются следующие вспомогательные методы:

public static List<Class> getPackageClassList(String pPackageName) throws Exception
{
  try
  {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    String path = pPackageName.replace('.', '/');

    List<File> dirs = new ArrayList<File>();
    List<JarFile> jars = new ArrayList<JarFile>();
    Enumeration<URL> resources = classLoader.getResources(path);
    if (resources != null)
    {
      String fileName;
      URL resource;
      File file;
      while (resources.hasMoreElements())
      {
        resource = resources.nextElement();
        fileName = resource.getFile();

        if (fileName.contains("!"))
        {
          // jar file
          resource = new URL(StringUtil.getArrayFromString(fileName, "!")[0]);
          file = urlToFile(resource);
          if (!file.exists()) continue;
          jars.add(new JarFile(file));
        }
        else
        {
          // class file that is not in a jar file
          file = urlToFile(resource);
          if (!file.exists()) continue;
          dirs.add(file);
        }
      }
    }

    List<Class> resultList = new ArrayList<Class>(1000);
    List<Class> tmpClassList;
    for (File directory : dirs)
    {
      tmpClassList = getPckDirClassList(directory, pPackageName);
      if (tmpClassList != null) resultList.addAll(tmpClassList);
    }

    for (JarFile jar : jars)
    {
      tmpClassList = getPckJarClassList(jar, pPackageName);
      if (tmpClassList != null) resultList.addAll(tmpClassList);
    }

    return (resultList.isEmpty()) ? null : resultList;
  }
  catch (Exception e)
  {
    return null;
  }
}

private static List<Class> getPckJarClassList(JarFile pJar, String pPackageName)
{
  if ((pJar == null) || (pPackageName == null)) return null;

  List<Class> resultList = new ArrayList<Class>(100);

  Enumeration<JarEntry> jarEntries = (pJar.entries());
  JarEntry jarEntry;
  String fullClassName;
  while (jarEntries.hasMoreElements())
  {
    jarEntry = jarEntries.nextElement();
    fullClassName = jarEntry.getName().replaceAll("/", ".");
    if (!fullClassName.startsWith(pPackageName)) continue;
    if (!fullClassName.endsWith(".class")) continue;

    // do not do a Class.forName for the following path, this can crash the server
    try
    {
      resultList.add(Class.forName(fullClassName.substring(0, fullClassName.length() - 6)));
    }
    catch (Throwable e)
    {
    }
  }

  return (resultList.isEmpty()) ? null : resultList;
}

/**
 * Recursive method to find all classes in a package directory tree.
 */
private static List<Class> getPckDirClassList(File pDirectory, String pPackageName) throws ClassNotFoundException
{
  try
  {
    if ((pDirectory == null) || (pPackageName == null)) return null;

    if (!pDirectory.exists()) return null;
    File[] files = pDirectory.listFiles();
    if ((files == null) || (files.length == 0)) return null;

    List<Class> resultList = new ArrayList<Class>(100);
    List<Class> tmpClassList;
    for (File file : files)
    {
      if (file.isDirectory())
      {
        tmpClassList = getPckDirClassList(file, pPackageName + "." + file.getName());
        if (tmpClassList != null) resultList.addAll(tmpClassList);
      }
      else if (file.getName().endsWith(".class"))
      {
        try
        {
          resultList.add(Class.forName(pPackageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
        }
        catch (Throwable e)
        {
        }
      }
    }
    return (resultList.isEmpty()) ? null : resultList;
  }
  catch (Exception e)
  {
    return null;
  }
}

Этот код был протестирован с файлами .jar в системах Windows и Unix. Он также был протестирован с файлами .java в IntelliJ в Windows.

person bvdb    schedule 01.06.2015