【问题标题】:Java Reflection: Find method usage in custom AbstractProcessorJava 反射:在自定义 AbstractProcessor 中查找方法使用情况
【发布时间】:2015-01-14 16:57:19
【问题描述】:

我是反思的新手。有什么方法可以检测在哪里调用了特定的方法?例如:

public class MyClass {

   public static void method(){ 
       //DO SOMETHING
   }

}

public class Test {

    public test(){
       MyClass.method();
    }

}

public class MyProcessor extends AbstractProcessor {

   public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

      Method method = MyClass.class.getDeclaredMethod("method");

      Class classWhereMethodIsInvoked = obtainClassWhereMethodIsInvoked(method); 

   }

   public Class obtainClassWhereMethodIsInvoked(Method method) {
      //here I want to search one class that invoke that method, in this case Test.class
   }

}

这样的事情是可能的还是我要疯了?

【问题讨论】:

  • 恐怕这种信息无法通过通常的反射 API 获得,它检索类和实例变量、方法签名等,但不是 实际 字节码(需要确定您的陈述出现在哪里)。为此,您需要Apache BCEL
  • 谢谢,我会调查 BCEL :)
  • 我认为这是不可能的,因为一个方法可以从多个不同的类中调用。
  • 我认为这并不难做到,可以通过使用常规的旧 JDK 搜索您的程序可以访问的类文件来完成。

标签: java reflection annotations


【解决方案1】:

如 cmets 中所述,Apache BCEL 适合您的问题。这样的库通常特别用于确定编译时信息,例如从生成的字节码中的方法使用和控制流分析,并且如果不是不可能的话,使用反射来检索这些信息是很困难的。如果您使用 BCEL 解决方案,您可能不再需要自定义注释处理器。

但是由于您似乎已经在使用自定义注释处理器,所以它的全部意义在于能够处理源文件中的注释。所以一种方法是定义一个自定义注解来标记被调用的方法,并让自定义处理器读取这些注解以了解哪些类调用了哪些方法:

@CallerClass("MyClass.method")
public class Test {

    public test() {
       MyClass.method();
    }

} 

在上面的(简单的)示例中,自定义CallerClass 注释标记一个类调用括号内注释元素中指定的方法。注解处理器可以读取这个注解并构造调用者信息。

【讨论】:

    【解决方案2】:

    是的,如果你真的想要它是可行的。您可以使用 classLoader 搜索类路径并通过所有类文件扫描方法名称。下面是一个非常简单的示例,表明它是可行的。在下面的示例中,我发现此类中使用了“println”方法的用法。本质上,您可以将范围从我示例中的一个文件扩大到所有类文件。

    public class SearchClasses {
    
        /**
         * @param args the command line arguments
         */
        public static void main(String[] args) throws FileNotFoundException {
    
    
    //      InputStream is = SearchClasses.class.getClassLoader().getResourceAsStream("resources.SearchClasses.class");
            InputStream is = new FileInputStream(new File("build/classes/resources/SearchClasses.class"));
    
            boolean found = false;
            Scanner scanner = new Scanner(is);
            while (scanner.hasNext()) {
                if (scanner.nextLine().contains("println")) {
                    System.out.print("println found");
                    found = true;
                    break;
                }
            }
    
            if (!found) {
                    System.out.print("println NOT found");          
            }
    
        }
    
        public static void testMethod() {
            System.out.println("testing");
        }
    
    }
    

    在我的 IDE 中,我必须使用 FileInputStream 来访问我正在搜索的类文件......但如果您正在搜索 jar 文件,那么您可以使用 classLoader。您需要一种机制来搜索所有类路径...这并非不可能,但为了简洁起见,我将其留给我们。

    编辑:这是让它完全工作的尝试。在类路径中搜索所有文件以查找您的方法。

    public class SearchClasses {
    
        /**
         * @param args the command line arguments
         * @throws java.io.FileNotFoundException
         */
        public static void main(String[] args) throws FileNotFoundException, IOException {
    
            printAllFileWithMethod("println");
        }
    
        public static void printAllFileWithMethod(String methodName) throws FileNotFoundException, IOException {
            Enumeration<URL> roots = SearchClasses.class.getClassLoader().getResources("");
    
            List<File> allClassFiles = new ArrayList<>();
            while (roots.hasMoreElements()) {
                File root = new File(roots.nextElement().getPath());
                allClassFiles.addAll(getFilesInDirectoryWithSuffix(root, "class"));
            }
    
            for (File classFile : allClassFiles) {
                InputStream is = new FileInputStream(classFile);
    
                boolean found = false;
                Scanner scanner = new Scanner(is);
                while (scanner.hasNext()) {
                    if (scanner.nextLine().contains(methodName)) {
                        System.out.print(methodName + " found in " + classFile.getName() + "\n");
                        found = true;
                        break;
                    }
                }
    
            }
        }
    
        public static void testMethod() {
            System.out.println("testing");
        }
    
        static List<File> getFilesInDirectoryWithSuffix(File dir, String suffix) {
            List<File> foundFiles = new ArrayList<>();
            if (!dir.isDirectory()) {
                return foundFiles;
            }
            for (File file : dir.listFiles()) {
                if (file.isDirectory()) {
                    foundFiles.addAll(getFilesInDirectoryWithSuffix(file, suffix));
                } else {
                    String name = file.getName();
                    if (name.endsWith(suffix)) {
                        foundFiles.add(file);
                    }
                }
    
            }
            return foundFiles;
        }
    
    }
    

    【讨论】:

    • 这将找到包含方法名称的任何行。如果您的方法名称不是唯一的,它可能会找到误报匹配,它会找到对具有相同名称的不同方法的调用。它甚至会找到变量名甚至 cmets
    【解决方案3】:

    您可以定义自己的机制。使用Map 来存储每个方法的调用者:

    public static Map<Method, List<String>> callStack = new HashMap<Method, List<String>>();
    
    public static void registerCaller(Method m)
    {
        List<String> callers = callStack.get(m);
        if (callers == null)
        {
            callers = new ArrayList<String>();
            callStack.put(m, callers);
        }
    
        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
        callers.add(stackTraceElements[3].getClassName());
    }
    

    目标类:

    class MyClass
    {   
        public static void method()
        {
            registerCaller(new Object(){}.getClass().getEnclosingMethod());
            // DO SOMETHING
        }
    }
    

    一些调用者类:

    package the.package.of;
    
    class Test
    {
        public void test()
        {
           MyClass.method();
        }
    }
    
    class Foo
    {
        public void bar()
        {
           MyClass.method();
        }
    }
    

    最后是测试:

    new Test().test();
    new Foo().bar();
    
    Method method = MyClass.class.getDeclaredMethod("method");
    for (String clazz : callStack.get(method))
    {
        System.out.println(clazz);
    }
    

    打印:

    the.package.of.Test
    the.package.of.Foo
    

    【讨论】:

    • 感谢您的回答,但我想在编译时在我的自定义 AbstractProcessor 中找到方法调用。这样,在调用方法之前不会注册方法调用。
    • 好的,知道了。我的错。
    【解决方案4】:

    好吧,如果您使用 Eclipse 作为 IDE,您可以通过“Open Call Hierarchy”功能找到完整的调用层次结构。这将在任何打开的 Eclipse 项目中找到您的方法的所有用法。 但是,如果您想在运行时以编程方式找出答案,那么您需要集成一些库,该库可以静态分析您的类路径的字节码以使用您的方法。

    【讨论】:

      【解决方案5】:

      您可以直接在测试方法中获取堆栈跟踪:

      public class Test {
      
          public void test() {
              System.out.println(getCallerClass());
          }
      
          public static String getCallerClass()  {
              for (StackTraceElement e: Thread.currentThread().getStackTrace()) {
                  if (!"java.lang.Thread".equals(e.getClassName()) && !e.getClassName().equals(Test.class.getName()))
                     return e.getClassName();
              }
              return null;
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2011-03-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-04
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多