Отражение Java: использование метода поиска в пользовательском AbstractProcessor

Я новичок в размышлении. Есть ли способ определить, где вызывается конкретный метод? Например:

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
   }

}

что-то вроде этого возможно или я схожу с ума?

Ответ 1

Да, это возможно, если вы действительно этого хотите. Вы можете использовать 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. Вам понадобится механизм для поиска по всему пути класса... это не невозможно, но я оставил его для краткости.

EDIT: вот попытка полностью заставить его работать.. ищет все файлы в пути класса для вашего метода.

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;
    }

}

Ответ 2

Как упоминалось в комментариях, Apache BCEL подходит для вашей проблемы. Такие библиотеки часто особенно используются для определения информации времени компиляции, такой как использование метода и анализ потока управления из генерируемого байт-кода, и такую ​​информацию трудно, если не невозможно, извлекать с помощью отражения. Если вы используете решение BCEL, скорее всего, вам больше не нужен специальный обработчик аннотации.

Но поскольку вы уже, кажется, используете пользовательский обработчик аннотаций, все дело в том, что оно может обрабатывать аннотации в исходных файлах. Таким образом, один из способов состоит в том, чтобы определить пользовательскую аннотацию, которая отмечает метод, который вызывается, и чтобы пользовательский процессор читал эти аннотации, чтобы узнать, какие классы называют, какие методы:

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

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

} 

В приведенном выше (тривиальном) примере пользовательская отметка CallerClass отмечает, что класс вызывает метод, указанный в элементе аннотации в круглых скобках. Обработчик аннотации может прочитать эту аннотацию и построить информацию о вызывающем абоненте.

Ответ 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

Ответ 4

Хорошо, если вы используете Eclipse в качестве среды IDE, вы можете найти полную иерархию вызовов с помощью функции "Иерархия открытых вызовов". Это найдет все способы использования вашего метода в любых открытых проектах 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;
    }
}