Bytecode enhancement

 On section describes the Java byte code structure, this section describes the bytecode enhancement technology. Java bytecode enhancement refers to the generation after the Java bytecode, modify, enhance its functionality, this embodiment corresponds to the application binary file to be modified.

 Common bytecode enhancement techniques include:

  • Java comes with dynamic proxies
  • ASM
  • Javassist

1. Dynamic Agent

 Before introducing dynamic agent, to introduce proxy mode. Below, is a UML class diagram proxy mode:

file

Proxy pattern is a design pattern that provides additional access to the target object is a way that access to the target object through a proxy object, so you can without modifying the original target object is the premise of providing additional functionality operation, the expansion of the target object features. ProxyAction proxy class and the proxy class CoreActionImpl implement the same interface Action, ProxyAction also holds a CoreActionImpl, to invoke the core of the operating agent class. Proxy mode implementations can be divided into static and dynamic proxy agent.

1.1. Static Proxy

 Acting entirely literally above the static UML class diagram, if the proxy class before running the program has existed, then this is the way to become static proxy proxy, the proxy class in this case we are usually defined in Java code. Under normal circumstances, the static agent in the proxy class and delegate class will implement the same interface or derived from the same parent, then implemented by polymerization, so that the proxy class to hold a reference to a delegate class. Examples are as follows:

public interface Action {
    void say();
}

public class CoreActionImpl implements Action {

    @Override
    public void say() {
        System.out.println("hello world");
    }
}

public class ProxyAction implements Action {
    private Action action = new CoreActionImpl();
    @Override
    public void say() {
        System.out.println("before core action");
        action.say();
        System.out.println("after core action");
    }
    
}
1.2. Dynamic Proxy

 Agent proxy class created in the way the program is running is called dynamic proxy. In other words, in this case, the proxy class is not defined in the Java code, but generated dynamically at runtime. Dynamic proxies use of JDK API, dynamic proxy objects do not implement the interface, but requires that the target object must implement the interface, or can not use dynamic proxies. Examples are as follows:

public class DynamicProxyAction implements InvocationHandler {

    private Object obj;

    public DynamicProxyAction(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before core action");
        Object res = method.invoke(obj,args);
        System.out.println("after core action");
        return res;
    }
}

Use the following method call:

DynamicProxyAction proxyAction = new DynamicProxyAction(new CoreActionImpl());
Action action = (Action) Proxy.newProxyInstance(DynamicProxyAction.class.getClassLoader(),new Class[]{Action.class},proxyAction);
action.say()

Carrying dynamic proxy implementation requirements InvocationHandler proxy class interfaces implemented, and a specific method to complete the agent process in the invoke method.

 You can use the following type of content output agent

byte[] clazzData = ProxyGenerator.generateProxyClass(DynamicProxyAction.class.getCanonicalName() + "$Proxy0", new Class[]{Action.class});
OutputStream out = new FileOutputStream(DynamicProxyAction.class.getCanonicalName() + "$Proxy0" + ".class");
out.write(clazzData);
out.close();

Obtained as follows:

public final class DynamicProxyAction$Proxy0 extends Proxy implements Action {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public DynamicProxyAction$Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void say() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("demo.Action").getMethod("say");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

We can see, the proxy class inherits java.lang.reflect.Proxy class and implements the agent interface. The method of internal addition to the proxy class interfaces the proxy also adds additional three methods equals, toString and hashCode. All the internal implementation of the method are entrusted to InvocationHandler to perform the InvocationHandler for incoming DynamicProxyAction.

1.3. Cglib

 java dynamic proxy comes an obvious requirement is that there be a proxy class that implements the interface, Cglib agent is to solve this problem exists. CGLIB- (Code Generator Library) is a powerful, high-performance code generation libraries. Which are widely used AOP framework (Spring, dynaop), a method for providing interception, the underlying operation using ASM bytecode to generate a new class.

 Subclasses Cglib mainly to generate a dynamic proxy class, not all method final subclass to override the proxy class. In the subclass method call interception techniques intercept all parent class method, the flow cross weaving logic, for the final method, the agent can not be performed.

 In order to achieve the effect of the agent on the face of CoreActionImpl can be achieved as follows:

public class CglibProxyAction {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(CoreActionImpl.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("before core action");
                Object res = methodProxy.invokeSuper(o, objects);
                System.out.println("after core action");
                return res;
            }
        });
        CoreActionImpl action = (CoreActionImpl) enhancer.create();
        action.say();
    }
}

Cglib can open the debug parameter cglib.debugLocation (DebuggingClassWriter.DEBUG_LOCATION_PROPERTY), the output agent class object, since the content is too long, there are not all put, put only the core part, as follows:

public final void say() {
    MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    if (var10000 == null) {
        CGLIB$BIND_CALLBACKS(this);
        var10000 = this.CGLIB$CALLBACK_0;
    }

    if (var10000 != null) {
        var10000.intercept(this, CGLIB$say$0$Method, CGLIB$emptyArgs, CGLIB$say$0$Proxy);
    } else {
        super.say();
    }
}

As said, the generated subclass overrides a superclass method, and the intercept.

2. ASM

 The ASM ( https://asm.ow2.io/ ) is a Java bytecode manipulation framework. It can be used to dynamically generate the class or classes of existing enhancements. ASM can have a direct binary class files can also be loaded into the Java virtual machine in the class before the class to change behavior.

 ASM tool provides two ways to generate and convert the compiled class files, which are event-based and object-based representation model. Among them, the model-based approach represents an event similar to SAX processing XML. It uses an ordered sequence of events represent a class file, each element of the class file using an event to represent, such as the head of the class, variables, method declarations JVM command has a corresponding event indicates, ASM own use the event parser can parse each class file into a sequence of events. The object-based representation model is similar to the DOM processing XML, which uses object tree structure to parse each file. The following is a timing diagram describing the event models from the Internet to find, relatively clear introduction to the process flow of ASM:

file

Official website and there are many online introduction to ASM, and not repeat them here, examples are given below.

For CoreActionImpl mentioned above, it bytecode follows:

public class demo/CoreActionImpl implements demo/Action  {

  // compiled from: CoreActionImpl.java

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Ldemo/CoreActionImpl; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public say()V
   L0
    LINENUMBER 7 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "hello world"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L1
    LINENUMBER 8 L1
    RETURN
   L2
    LOCALVARIABLE this Ldemo/CoreActionImpl; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1
}

We should say intercept method, before and after the increase in the log, it is necessary in the process at the entrance and at the return code bytes corresponding to the increase may be achieved using the following code:

public class ASMProxyAction {

    public static void main(String[] args) throws IOException {
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        ClassReader cr = new ClassReader(Thread.currentThread().getContextClassLoader().getResourceAsStream("demo/CoreActionImpl.class"));
        cr.accept(new ClassVisitor(Opcodes.ASM6, cw) {
            @Override
            public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
                MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
                if (!"say".equals(name)) {
                    return mv;
                }
                MethodVisitor aopMV = new MethodVisitor(super.api, mv) {
                    @Override
                    public void visitCode() {
                        super.visitCode();
                        mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                        mv.visitLdcInsn("before core action");
                        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                    }

                    @Override
                    public void visitInsn(int opcode) {
                        if (Opcodes.RETURN == opcode) {
                            mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                            mv.visitLdcInsn("after core action");
                            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                        }
                        super.visitInsn(opcode);
                    }
                };
                return aopMV;
            }
        }, ClassReader.SKIP_DEBUG);
        File file = new File("CoreActionImpl.class");
        FileOutputStream fos = new FileOutputStream(file);
        fos.write(cw.toByteArray());
        fos.close();
    }
}

The code rewriting of the MethodVisitor visitCode and visitInsn (Opcodes.RETURN == opcode), the corresponding increase in the log.

The modified bytecode as follows:

public class demo/CoreActionImpl implements demo/Action  {


  // access flags 0x1
  public <init>()V
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public say()V
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "before core action"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "hello world"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "after core action"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 1
}

3. Javassist

 Javassist is a dynamic library that can be used to check, "live" editing and creating Java classes. Its function jdk own reflection function similar to, but more powerful than the reflection function. Compared ASM, Javassist java coded form directly, without the need to understand the virtual machine instructions, can dynamically change the structure of the class or classes dynamically generated. Tell me what more can network http://www.javassist.org/ , directly explain the following example.

 In order to achieve the effect of the agent on the face of CoreActionImpl can be achieved as follows:

public class JavassistProxyAction {

    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("demo.CoreActionImpl");
        CtMethod methodSay = cc.getDeclaredMethod("say");
        methodSay.insertBefore("System.out.println(\"before core action\");");
        methodSay.insertAfter("System.out.println(\"after core action\");");
        File file = new File("CoreActionImpl.class");
        FileOutputStream fos = new FileOutputStream(file);
        fos.write(cc.toBytecode());
        fos.close();
    }

}

The above code is relatively straightforward, direct access to the target method, which is then inserted around the object logic, and Java code. After the contents of the above code generation decompiled class as follows:

public class demo/CoreActionImpl implements demo/Action  {

  // compiled from: CoreActionImpl.java

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Ldemo/CoreActionImpl; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public say()V
   L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "before core action"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L1
    LINENUMBER 7 L1
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "hello world"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L2
    LINENUMBER 8 L2
    GOTO L3
   L3
   FRAME SAME
    ACONST_NULL
    ASTORE 2
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "after core action"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    RETURN
    LOCALVARIABLE this Ldemo/CoreActionImpl; L0 L3 0
    MAXSTACK = 5
    MAXLOCALS = 3
}

Please search for more original content micro-channel public number: ah camel (doubaotaizi)

Guess you like

Origin www.cnblogs.com/cxyAtuo/p/12037421.html