簡體   English   中英

自定義注釋處理器-帶注釋的檢測方法

[英]Custom Annotation Processor - Detect Method with Annotations

我正在嘗試編寫注釋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);
        }
    }
}

我也對此感到很好奇,因此我決定嘗試解決這個問題。 原來比我預期的要容易。 您需要做的就是利用專有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 對於每種方法,我們只需對方法methodTree進行println() methodTree獲得方法的內容。

使用您的示例,這是編譯期間程序的輸出:

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

使它在您的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文件進行了測試。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM