【问题标题】:Custom Annotation Processor - Detect Method with Annotations自定义注释处理器 - 使用注释检测方法
【发布时间】:2013-08-03 02:15:06
【问题描述】:

我正在尝试编写一个注释 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);
        }
    }
}

【问题讨论】:

    标签: java annotations annotation-processing


    【解决方案1】:

    我也对此感到好奇,所以我决定尝试弄清楚。结果比我预期的要容易。您需要做的就是利用专有工具.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;
    }
    

    【讨论】:

      【解决方案2】:

      让它在您的 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 文件进行了测试。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多