“This is my 38th day of participating in the First Challenge 2022. For details: First Challenge 2022”

The agent

Agency just as its name implies is to replace a mission, polymorphic + combination, is the nature of the same type (succession or achieved the same class or interface) of the object using the parent class reference, the actual process of using the proxy class to replace the original implementation class for execution, internal use combined by proxy class methods to perform some additional non-core operations.

Note: The environment is JDK1.8.

Usage scenarios

Such as messages, logs, and other operations that are not part of the main function. The ability to decouple main functions from additional operations facilitates code maintenance.

Static agent

interface

Public interface Person {/** ** / void giveMoney(); }Copy the code

By the proxy class

public class Student implements Person { private String name; public Student(String name) { this.name = name; } @override public void giveMoney() {system.out.println (name + "50元 "); }}Copy the code

The proxy class

public class StudentMonitor implements Person{ private Student student; public StudentMonitor(Student student){ this.student=student; } @override public void giveMoney() {system.out.println (); student.giveMoney(); System.out.println(" The student has recently behaved well "); }}Copy the code

nature

It’s essentially polymorphic + combination, the original proxy.

advantages

The most original agent, simple and clear readable more strong.

Disadvantages/Limitations

The example above is cumbersome to use, requiring management interfaces, proxy classes, and proxied classes.

A dynamic proxy

Proxy classes are used instead:

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class StudentInvocationHandler<T> implements InvocationHandler { private T target; public StudentInvocationHandler(T target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {system.out.println (" proxy execution "+ method.getName() +" method "); Object result = method.invoke(target, args); return result; }}Copy the code

Testing:

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class ProxyTest { public static void main(String[] args) { System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); Person Zhangsan = new Student(" zhangsan "); Person Zhangsan = new Student(" zhangsan "); InvocationHandler stuHandler = new StudentInvocationHandler(zhangsan); Person studyProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, stuHandler); studyProxy.giveMoney(); }}Copy the code

Tests can be found, create agent added System. Before the getProperties (), put (” sun. Misc. ProxyGenerator. SaveGeneratedFiles “, “true”); This setting is used to output the dynamic proxy class as follows:

// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package com.sun.proxy; import com.study.proxy.Person; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements Person { private static Method m1; private static Method m2; private static Method m3; private static Method m0; public $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 giveMoney() 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("com.study.proxy.Person").getMethod("giveMoney"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); }}}Copy the code

$Proxy0 implements the Person interface from Proxy, overriding all Stuent methods (including Object’s public non-fianl methods), and calling the invoke method of StudentInvocationHandler. The Invoke method internally uses reflection to call Stuent’s methods to implement the proxy.

nature

The essence is polymorphism + combination + dynamically generated bytecode + reflection. The core is the Proxy class and the InvocationHandler interface.

advantages

Simply implement the InvocationHandler interface and perform proxy operations within the Invoke method. There is no need to write the proxy class to death, it is a generic proxy class, can not fixed interface. But the agent operations are the same, which means that different invocationHandlers need to be created for different agent operations.

Disadvantages/Limitations

Dynamic proxies are generated “AD hoc” in memory at runtime, meaning they are generated at least once per run and consume a certain amount of resources, unlike static proxies. It is also different from the nature of static proxies, but reflection has some overhead, so it is not as good as static proxies in terms of performance.

Disadvantages of reflection and JVM optimization

Reflection is definitely not as fast as a direct call to a method, because Method#invoke does a permission check, generates an Object array with the number of parameters passed in, and is automatically bocked if it is a primitive type. You need to switch from Java to C++ to Java again.

The delegate implementation of reflection, optimized for JDK6, provides an implementation of dynamically generating bytecode, using the invoke directive directly to call the target method with pseudocode as follows:

// Dynamic implementation of pseudo-code, here only enumerates the key call logic, in fact, it also includes the caller detection, parameter detection bytecode. package jdk.internal.reflect; public class GeneratedMethodAccessor1 extends ... { @Overrides public Object invoke(Object obj, Object[] args) throws ... { Test.target((int) args[0]); return null; }}Copy the code

This avoids switching from Java to C++ to Java again. In addition, the JVM’s just-in-time compiler inlines methods based on whether there are escaped objects, further speeding up reflection.

  • How many calls to dynamically generate bytecode threshold Settings:

– Dsun. Reflect. InflationThreashold = defaults to 15.

  • -Dsun.reflect.noInflation=true: Dynamic implementation can be directly generated at the beginning of launch, if the number of calls must be greater than 15 can be considered.

Usage scenarios

A framework, generic component such as Spring AOP, that doesn’t know which interface you will implement, but needs to use proxies, will consider dynamic proxies and use reflection to complete the proxy.

Additional agent

Proxied class (with the Person interface removed) :

public class Student{
    private String name;

    public Student(String name) {
        this.name = name;
    }

    public void giveMoney() {
        System.out.println(name + "交了50元班费");
    }
}
Copy the code

The proxy class is replaced as follows:

import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class StudentCglibProxy implements MethodInterceptor { private Class targetClass; public StudentCglibProxy(Class targetClass) { this.targetClass = targetClass; } public Object getProxyInstance(String name) {Enhancer en = new Enhancer(); // Set the parent class en.setSuperclass(targetClass); // Set the callback function en.setcallback (this); Return en.create(new Class[]{string.class}, new Object[]{name}); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy throws Throwable {system.out.println (" proxy execution "+ method.getName() +" method "); Object returnValue = methodProxy.invokeSuper(obj, args); return returnValue; }}Copy the code

The test class:

import org.springframework.cglib.core.DebuggingClassWriter; public class CglibProxyTest { public static void main(String[] args) { System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./"); Student proxyInstance = (Student) new StudentCglibProxy(student.class).getProxyInstance(" "); proxyInstance.giveMoney(); }}Copy the code

Cglib generates proxy classes:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.study.proxy;

import java.lang.reflect.Method;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.cglib.core.Signature;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

public class Student$$EnhancerByCGLIB$$b56eea6e extends Student implements Factory {
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static Object CGLIB$CALLBACK_FILTER;
    private static final Method CGLIB$giveMoney$0$Method;
    private static final MethodProxy CGLIB$giveMoney$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$equals$1$Method;
    private static final MethodProxy CGLIB$equals$1$Proxy;
    private static final Method CGLIB$toString$2$Method;
    private static final MethodProxy CGLIB$toString$2$Proxy;
    private static final Method CGLIB$hashCode$3$Method;
    private static final MethodProxy CGLIB$hashCode$3$Proxy;
    private static final Method CGLIB$clone$4$Method;
    private static final MethodProxy CGLIB$clone$4$Proxy;

    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("com.study.proxy.Student$$EnhancerByCGLIB$$b56eea6e");
        Class var1;
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$equals$1$Method = var10000[0];
        CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
        CGLIB$toString$2$Method = var10000[1];
        CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
        CGLIB$hashCode$3$Method = var10000[2];
        CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
        CGLIB$clone$4$Method = var10000[3];
        CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
        CGLIB$giveMoney$0$Method = ReflectUtils.findMethods(new String[]{"giveMoney", "()V"}, (var1 = Class.forName("com.study.proxy.Student")).getDeclaredMethods())[0];
        CGLIB$giveMoney$0$Proxy = MethodProxy.create(var1, var0, "()V", "giveMoney", "CGLIB$giveMoney$0");
    }

    final void CGLIB$giveMoney$0() {
        super.giveMoney();
    }

    public final void giveMoney() {
        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$giveMoney$0$Method, CGLIB$emptyArgs, CGLIB$giveMoney$0$Proxy);
        } else {
            super.giveMoney();
        }
    }

    final boolean CGLIB$equals$1(Object var1) {
        return super.equals(var1);
    }

    public final boolean equals(Object var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            Object var2 = var10000.intercept(this, CGLIB$equals$1$Method, new Object[]{var1}, CGLIB$equals$1$Proxy);
            return var2 == null ? false : (Boolean)var2;
        } else {
            return super.equals(var1);
        }
    }

    final String CGLIB$toString$2() {
        return super.toString();
    }

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

        return var10000 != null ? (String)var10000.intercept(this, CGLIB$toString$2$Method, CGLIB$emptyArgs, CGLIB$toString$2$Proxy) : super.toString();
    }

    final int CGLIB$hashCode$3() {
        return super.hashCode();
    }

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

        if (var10000 != null) {
            Object var1 = var10000.intercept(this, CGLIB$hashCode$3$Method, CGLIB$emptyArgs, CGLIB$hashCode$3$Proxy);
            return var1 == null ? 0 : ((Number)var1).intValue();
        } else {
            return super.hashCode();
        }
    }

    final Object CGLIB$clone$4() throws CloneNotSupportedException {
        return super.clone();
    }

    protected final Object clone() throws CloneNotSupportedException {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? var10000.intercept(this, CGLIB$clone$4$Method, CGLIB$emptyArgs, CGLIB$clone$4$Proxy) : super.clone();
    }

    public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
        String var10000 = var0.toString();
        switch(var10000.hashCode()) {
        case -508378822:
            if (var10000.equals("clone()Ljava/lang/Object;")) {
                return CGLIB$clone$4$Proxy;
            }
            break;
        case 858452198:
            if (var10000.equals("giveMoney()V")) {
                return CGLIB$giveMoney$0$Proxy;
            }
            break;
        case 1826985398:
            if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
                return CGLIB$equals$1$Proxy;
            }
            break;
        case 1913648695:
            if (var10000.equals("toString()Ljava/lang/String;")) {
                return CGLIB$toString$2$Proxy;
            }
            break;
        case 1984935277:
            if (var10000.equals("hashCode()I")) {
                return CGLIB$hashCode$3$Proxy;
            }
        }

        return null;
    }

    public Student$$EnhancerByCGLIB$$b56eea6e(String var1) {
        super(var1);
        CGLIB$BIND_CALLBACKS(this);
    }

    public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
        CGLIB$THREAD_CALLBACKS.set(var0);
    }

    public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
        CGLIB$STATIC_CALLBACKS = var0;
    }

    private static final void CGLIB$BIND_CALLBACKS(Object var0) {
        Student$$EnhancerByCGLIB$$b56eea6e var1 = (Student$$EnhancerByCGLIB$$b56eea6e)var0;
        if (!var1.CGLIB$BOUND) {
            var1.CGLIB$BOUND = true;
            Object var10000 = CGLIB$THREAD_CALLBACKS.get();
            if (var10000 == null) {
                var10000 = CGLIB$STATIC_CALLBACKS;
                if (var10000 == null) {
                    return;
                }
            }

            var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
        }

    }

    public Object newInstance(Callback[] var1) {
        CGLIB$SET_THREAD_CALLBACKS(var1);
        Student$$EnhancerByCGLIB$$b56eea6e var10000 = new Student$$EnhancerByCGLIB$$b56eea6e();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

    public Object newInstance(Callback var1) {
        CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});
        Student$$EnhancerByCGLIB$$b56eea6e var10000 = new Student$$EnhancerByCGLIB$$b56eea6e();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

    public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
        CGLIB$SET_THREAD_CALLBACKS(var3);
        Student$$EnhancerByCGLIB$$b56eea6e var10000 = new Student$$EnhancerByCGLIB$$b56eea6e;
        switch(var1.length) {
        case 1:
            if (var1[0].getName().equals("java.lang.String")) {
                var10000.<init>((String)var2[0]);
                CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
                return var10000;
            }
        default:
            throw new IllegalArgumentException("Constructor not found");
        }
    }

    public Callback getCallback(int var1) {
        CGLIB$BIND_CALLBACKS(this);
        MethodInterceptor var10000;
        switch(var1) {
        case 0:
            var10000 = this.CGLIB$CALLBACK_0;
            break;
        default:
            var10000 = null;
        }

        return var10000;
    }

    public void setCallback(int var1, Callback var2) {
        switch(var1) {
        case 0:
            this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
        default:
        }
    }

    public Callback[] getCallbacks() {
        CGLIB$BIND_CALLBACKS(this);
        return new Callback[]{this.CGLIB$CALLBACK_0};
    }

    public void setCallbacks(Callback[] var1) {
        this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
    }

    static {
        CGLIB$STATICHOOK1();
    }
}
Copy the code

You can see that you inherit from Student.

nature

The essence is polymorphism + combination + dynamic generation of bytecode. The runtime generates bytecode dynamically and inherits the proxied class. Use the bytecode processing framework ASM.

advantages

No need to implement the interface, more convenient.

Disadvantages/Limitations

Because it is inheritance, you can only delegate methods that can be inherited. At the same time, dynamic bytecode generation is inefficient.

Usage scenarios

Suitable for use by frameworks and general-purpose components, proxying classes are not constrained to implement interfaces.

JDK dynamic proxy versus Cglib performance comparison

So let’s test that out. The inline threshold is -xx: CompileThreshold= Default is 10000.

The case where the JDK dynamic proxy does not generate dynamic bytecode

Because – Dsun. Reflect. InflationThreashold = defaults to 15, so we first measured, not dynamically generated bytecode.

cglib

import org.springframework.cglib.core.DebuggingClassWriter; public class CglibProxyTest { public static void main(String[] args) { // System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./"); Student proxyInstance = (Student) new StudentCglibProxy(student.class).getProxyInstance(" "); long current = System.currentTimeMillis(); for (int i = 1; i <= 1_5; i++) { if (i % 5 == 0) { long temp = System.currentTimeMillis(); System.out.println(temp - current + "ms"); current = temp; } proxyInstance.giveMoney(); }}}Copy the code

Output (omit irrelevant print below) :

65ms
0ms
0ms
Copy the code

jdk

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class ProxyTest { public static void main(String[] args) { // System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); Person Zhangsan = new Student(" zhangsan "); Person Zhangsan = new Student(" zhangsan "); InvocationHandler stuHandler = new StudentInvocationHandler(zhangsan); Person studyProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, stuHandler); long current = System.currentTimeMillis(); for (int i = 1; i <= 1_5; i++) { if (i % 5 == 0) { long temp = System.currentTimeMillis(); System.out.println(temp - current + "ms"); current = temp; } studyProxy.giveMoney(); }}}Copy the code

Output:

1ms
0ms
0ms
Copy the code

summary

You can see that cglib takes a lot of time when it is called for the first time, and the rest is similar.

Dynamic bytecode generation by JDK dynamic proxies

Change it to I <= 300000 and remove the extra print.

cglib

import org.springframework.cglib.core.DebuggingClassWriter; public class CglibProxyTest { public static void main(String[] args) { // System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./"); Student proxyInstance = (Student) new StudentCglibProxy(student.class).getProxyInstance(" "); long start = System.currentTimeMillis(); long current = start; for (int i = 1; i <= 300000; i++) { if (i % 10000 == 0) { long temp = System.currentTimeMillis(); System.out.println(temp - current + "ms"); current = temp; } if (i == 300000) { System.out.println("end:" + (System.currentTimeMillis() - start) + "ms"); } proxyInstance.giveMoney(); }}}Copy the code

Output:

43ms 4ms 0ms 0ms 1ms 0ms 1ms 0ms 1ms 3ms 1ms 1ms 0ms 2ms 0ms 0ms 1ms 0ms 0ms 0ms 0ms 0ms 0ms 0ms 1ms 0ms 0ms 1ms 0ms 0ms  end:60msCopy the code

jdk

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class ProxyTest { public static void main(String[] args) { // System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); Person Zhangsan = new Student(" zhangsan "); Person Zhangsan = new Student(" zhangsan "); InvocationHandler stuHandler = new StudentInvocationHandler(zhangsan); Person studyProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, stuHandler); long start = System.currentTimeMillis(); long current = start; for (int i = 1; i <= 300000; i++) { if (i % 10000 == 0) { long temp = System.currentTimeMillis(); System.out.println(temp - current + "ms"); current = temp; } if (i == 300000) { System.out.println("end:" + (System.currentTimeMillis() - start) + "ms"); } studyProxy.giveMoney(); }}}Copy the code

Output:

10ms 5ms 1ms 1ms 4ms 1ms 0ms 1ms 1ms 0ms 0ms 1ms 0ms 1ms 1ms 1ms 1ms 0ms 0ms 0ms 0ms 0ms 1ms 0ms 0ms 0ms 0ms 1ms 0ms 0ms  end:32msCopy the code

summary

You can see that cglib takes a lot of time when it is called for the first time, and the rest is similar.

summary

Can be found whether JDK with no dynamically generated bytecode, the JDK dynamic proxy faster than additional agent (the JDK dynamic proxy reflection, as well as the influence of the gap, and later dynamically generated bytecode), because this is additional more complex than the JDK dynamic proxy generated bytecode, generate the bytecode is slow, but almost late.

reference

Java static and dynamic proxies