自定义注释处理器 - 带注释的检测方法

时间:2013-08-03 02:15:06

标签: java annotations annotation-processing

我正在尝试编写一个注释Procssor来检测使用@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);
        }
    }
}

2 个答案:

答案 0 :(得分:1)

我对此也很好奇,所以我决定尝试解决这个问题。结果比我想象的要容易。您需要做的就是利用专有tools.jar库中的Trees api。我在这里按照以下几行制作了一个快速注释处理器: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;
}

答案 1 :(得分:0)

让它在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;
  }
}

此代码已在Windows和Unix系统上使用.jar文件进行了测试。它还在Windows上的IntelliJ中使用.java文件进行了测试。