Как вычислить дизассемблирование класса Java во время выполнения?

Я пытаюсь выяснить, есть ли динамическое переплетение применяется к моим классам.

Во-первых, вот некоторый контекст…


Очень легко доказать, имеет ли место статическое переплетение. Вы используете javap -c MyCoolEntity, чтобы увидеть дизассемблирование некоторого класса Java MyCoolEntity.class.

В этом примере: MyCoolEntity — это класс, описывающий объект JPA. В нашем процессе сборки можно использовать плагин, чтобы «сплести» его байт-код.

Геттер, который представляет отношение сущности JPA, будет — в несплетенном байт-коде — выглядеть как простой поиск поля:

public java.util.List<com.stackoverflow.Friend> getFriends();
  Code:
     0: aload_0
     1: getfield      #222                // Field friends:Ljava/util/List;
     4: areturn

Но сплетенный байт-код делегирует вызов виртуальному методу _persistence_get_*():

public java.util.List<com.stackoverflow.Friend> getFriends();
  Code:
     0: aload_0
     1: invokevirtual #640                // Method _persistence_get_friends:()Ljava/util/List;
     4: areturn

Этот сплетенный байт-код можно использовать для достижения ленивого поиска (извлечение связанного объекта по запросу из базы данных).


Я ожидаю, что смогу применить ту же логику, чтобы выяснить, происходит ли динамическое переплетение в моем приложении.

Насколько я понимаю, агент Java преобразует байт-код класса во время выполнения.

Есть ли способ получить доступ к файлу .class, который Java-агент моей реализации JPA создает во время выполнения? В противном случае: есть ли способ подключиться к работающей JVM и выполнить дизассемблирование класса что он держит в памяти?


Отказ от ответственности: мой вопрос в первую очередь касается того, «как вы захватываете и дизассемблируете файл .class, который создается во время выполнения». Вероятно, есть другие способы ответить на подвопрос «происходит ли динамическое переплетение?», но эта часть не является основной темой моего вопроса. :)

Для чего это стоит: я использую EclipseLink 2.5.1 и TomCat 8.5.


person Birchlabs    schedule 08.03.2017    source источник
comment
Я не думаю, что JVM предоставляет какой-либо механизм для извлечения необработанных данных класса для загруженного класса (в отличие от отражающих абстракций того же самого). В частности, маловероятно, что существует какой-либо фактический файл, содержащий эти данные, но даже в более общем смысле вы, вероятно, имеете в виду, я думаю, вам не повезло.   -  person John Bollinger    schedule 09.03.2017
comment
Взгляните на библиотеку ASM и посмотрите, может ли она принимать объект класса; если это так, вы можете использовать его API для самоанализа   -  person Neil Stockton    schedule 09.03.2017


Ответы (1)


Вы можете использовать Apache Commons BCEL и сделать что-то вроде этого:

package de.scrum_master.stackoverflow;

import java.io.IOException;
import java.io.InputStream;

import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;

public class MethodDisassembler {
  public static void main(String[] args) throws IOException {
    Class<?> clazz = MethodDisassembler.class;
    String classAsPath = clazz.getName().replace('.', '/') + ".class";
    try (InputStream classStream = clazz.getClassLoader().getResourceAsStream(classAsPath)) {
      ClassParser classParser = new ClassParser(classStream, classAsPath);
      JavaClass javaClass = classParser.parse();
      for (Method method : javaClass.getMethods())
        System.out.println(method.getCode());
    }
  }

  public void doSomething() {
    sendEmail();
  }

  public void sendEmail() {
    System.out.println("Sending e-mail");
  }
}

Журнал консоли:

Code(max_stack = 1, max_locals = 1, code_length = 5)
0:    aload_0
1:    invokespecial java.lang.Object.<init>:()V (8)
4:    return

Attribute(s) = 
LineNumber(0, 10)
LocalVariable(start_pc = 0, length = 5, index = 0:de.scrum_master.stackoverflow.MethodDisassembler this)
Code(max_stack = 5, max_locals = 12, code_length = 165)
0:    ldc       de.scrum_master.stackoverflow.MethodDisassembler (1)
2:    astore_1
3:    new       <java.lang.StringBuilder> (19)
6:    dup
7:    aload_1
8:    invokevirtual java.lang.Class.getName:()Ljava/lang/String; (21)
11:   bipush        46
13:   bipush        47
15:   invokevirtual java.lang.String.replace:(CC)Ljava/lang/String; (27)
18:   invokestatic  java.lang.String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; (33)
21:   invokespecial java.lang.StringBuilder.<init>:(Ljava/lang/String;)V (37)
24:   ldc       ".class" (40)
26:   invokevirtual java.lang.StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; (42)
29:   invokevirtual java.lang.StringBuilder.toString:()Ljava/lang/String; (46)
32:   astore_2
33:   aconst_null
34:   astore_3
35:   aconst_null
36:   astore        %4
38:   aload_1
39:   invokevirtual java.lang.Class.getClassLoader:()Ljava/lang/ClassLoader; (49)
42:   aload_2
43:   invokevirtual java.lang.ClassLoader.getResourceAsStream:(Ljava/lang/String;)Ljava/io/InputStream; (53)
46:   astore        %5
48:   new       <org.apache.bcel.classfile.ClassParser> (59)
51:   dup
52:   aload     %5
54:   aload_2
55:   invokespecial org.apache.bcel.classfile.ClassParser.<init>:(Ljava/io/InputStream;Ljava/lang/String;)V (61)
58:   astore        %6
60:   aload     %6
62:   invokevirtual org.apache.bcel.classfile.ClassParser.parse:()Lorg/apache/bcel/classfile/JavaClass; (64)
65:   astore        %7
67:   aload     %7
69:   invokevirtual org.apache.bcel.classfile.JavaClass.getMethods:()[Lorg/apache/bcel/classfile/Method; (68)
72:   dup
73:   astore        %11
75:   arraylength
76:   istore        %10
78:   iconst_0
79:   istore        %9
81:   goto      #105
84:   aload     %11
86:   iload     %9
88:   aaload
89:   astore        %8
91:   getstatic     java.lang.System.out:Ljava/io/PrintStream; (74)
94:   aload     %8
96:   invokevirtual org.apache.bcel.classfile.Method.getCode:()Lorg/apache/bcel/classfile/Code; (80)
99:   invokevirtual java.io.PrintStream.println:(Ljava/lang/Object;)V (86)
102:  iinc      %9  1
105:  iload     %9
107:  iload     %10
109:  if_icmplt     #84
112:  aload     %5
114:  ifnull        #164
117:  aload     %5
119:  invokevirtual java.io.InputStream.close:()V (92)
122:  goto      #164
125:  astore_3
126:  aload     %5
128:  ifnull        #136
131:  aload     %5
133:  invokevirtual java.io.InputStream.close:()V (92)
136:  aload_3
137:  athrow
138:  astore        %4
140:  aload_3
141:  ifnonnull     #150
144:  aload     %4
146:  astore_3
147:  goto      #162
150:  aload_3
151:  aload     %4
153:  if_acmpeq     #162
156:  aload_3
157:  aload     %4
159:  invokevirtual java.lang.Throwable.addSuppressed:(Ljava/lang/Throwable;)V (97)
162:  aload_3
163:  athrow
164:  return

Exception handler(s) = 
From    To  Handler Type
48  112 125 <Any exception>(0)
38  138 138 <Any exception>(0)

Attribute(s) = 
LineNumber(0, 12), LineNumber(3, 13), LineNumber(33, 14), LineNumber(38, 14), 
LineNumber(48, 15), LineNumber(60, 16), LineNumber(67, 17), LineNumber(91, 18), 
LineNumber(102, 17), LineNumber(112, 19), LineNumber(164, 20)
LocalVariable(start_pc = 0, length = 165, index = 0:java.lang.String[] args)
LocalVariable(start_pc = 3, length = 162, index = 1:java.lang.Class clazz)
LocalVariable(start_pc = 33, length = 132, index = 2:java.lang.String classAsPath)
LocalVariable(start_pc = 48, length = 88, index = 5:java.io.InputStream classStream)
LocalVariable(start_pc = 60, length = 52, index = 6:org.apache.bcel.classfile.ClassParser classParser)
LocalVariable(start_pc = 67, length = 45, index = 7:org.apache.bcel.classfile.JavaClass javaClass)
LocalVariable(start_pc = 91, length = 11, index = 8:org.apache.bcel.classfile.Method method)
LocalVariableTypes(start_pc = 3, length = 162, index = 1:java.lang.Class<?>... clazz)
StackMap((FULL, offset delta=84, locals={(type=Object, class=[Ljava.lang.String;), (type=Object, class=java.lang.Class), (type=Object, class=java.lang.String), (type=Object, class=java.lang.Throwable), (type=Object, class=java.lang.Throwable), (type=Object, class=java.io.InputStream), (type=Object, class=org.apache.bcel.classfile.ClassParser), (type=Object, class=org.apache.bcel.classfile.JavaClass), (type=Bogus), (type=Integer), (type=Integer), (type=Object, class=[Lorg.apache.bcel.classfile.Method;)}), (SAME, offset delta=20), (FULL, offset delta=19, locals={(type=Object, class=[Ljava.lang.String;), (type=Object, class=java.lang.Class), (type=Object, class=java.lang.String), (type=Object, class=java.lang.Throwable), (type=Object, class=java.lang.Throwable), (type=Object, class=java.io.InputStream)}, stack items={(type=Object, class=java.lang.Throwable)}), (CHOP 1, offset delta=10), (SAME_LOCALS_1_STACK, offset delta=1, stack items={(type=Object, class=java.lang.Throwable)}), (SAME, offset delta=11), (SAME, offset delta=11), (CHOP 2, offset delta=1))
Code(max_stack = 1, max_locals = 1, code_length = 5)
0:    aload_0
1:    invokevirtual de.scrum_master.stackoverflow.MethodDisassembler.sendEmail:()V (124)
4:    return

Attribute(s) = 
LineNumber(0, 23), LineNumber(4, 24)
LocalVariable(start_pc = 0, length = 5, index = 0:de.scrum_master.stackoverflow.MethodDisassembler this)
Code(max_stack = 2, max_locals = 1, code_length = 9)
0:    getstatic     java.lang.System.out:Ljava/io/PrintStream; (74)
3:    ldc       "Sending e-mail" (127)
5:    invokevirtual java.io.PrintStream.println:(Ljava/lang/String;)V (129)
8:    return

Attribute(s) = 
LineNumber(0, 27), LineNumber(8, 28)
LocalVariable(start_pc = 0, length = 9, index = 0:de.scrum_master.stackoverflow.MethodDisassembler this)

Я думаю, вы можете понять все остальное самостоятельно.


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

Обновление 2: здесь вы видите, как можно сбросить двоичный класс из агента инструментирования, как было предложено выше. Вы все еще можете решить, хотите ли вы хранить байты на диске или хотите буферизовать их в массиве байтов в памяти и читать их оттуда через BCEL через ByteArrayInputStream. Но это кажется довольно надуманным, если вам это нужно вне интеграционных тестов. Во время выполнения в производственной среде вы, возможно, не хотите использовать его или, по крайней мере, только экономно в качестве быстрого дымового теста при загрузке вашего приложения.

person kriegaex    schedule 10.03.2017
comment
Итак, мы не собираемся использовать его на самом деле, а просто для расширения моих знаний: действительно ли мне нужно использовать агент Java для включения динамического переплетения? Разве я не могу просто заменить ClassLoader на свой собственный, который будет преобразовывать байт-код перед его возвратом? - person nikita2206; 10.03.2017
comment
Это очень общий вопрос, так что я тоже могу ответить в очень общем виде: да, наверное. Но тогда вам нужно убедиться, что ваши целевые классы действительно загружены этим самым загрузчиком классов. Как насчет того, чтобы описать, какую проблему вы на самом деле хотите решить, вместо того, чтобы задавать вопросы о техническом решении, которое вы считаете правильным, но на самом деле, возможно, не подходит для вашей проблемы? - person kriegaex; 10.03.2017
comment
мы только что перешли с GlassFish на TomCat. Насколько я понимаю, GlassFish поддерживает плетение во время загрузки из коробки, а TomCat - нет (для этого вам нужно запустить его с помощью определенного java-агента). Из-за этого мы включили статическое плетение, но также обнаружили другие параметры, которые не требуют указания агента Java (это PITA с maven). Я спросил о загрузчике классов просто из любопытства (я новичок в Java). Спасибо. - person nikita2206; 10.03.2017
comment
Плетение AspectJ должно хорошо работать с Tomcat. Но мы уходим от темы. Возможно, вы захотите открыть другой, более конкретный вопрос, если у вас есть конкретный вариант использования и проблемы с ним. Я думаю, что правильно ответил на ваш первоначальный вопрос, поэтому буду признателен, если вы примете мой ответ. - person kriegaex; 10.03.2017