Pretty boy, you are the most handsome!

Source code has been included in github check source code, don’t forget star oh!

What is the proxy pattern

In the proxy mode, one class represents the functionality of another class.

In daily life, there is no lack of examples of agent mode, such as train ticket sales agency, which acts as an agent for railway stations to sell train tickets. Real estate agents sell houses on behalf of homeowners. They do not own houses, but sell houses on behalf of homeowners.

So the question is, why do you need a proxy for the same function? Buying a house takes a lot of time and energy. If you don’t want to do that, you can use an agent. Buying a home may involve complicated procedures that you may not be familiar with, so you need an agent to help you handle them. Selling a house may require a certain amount of publicity, and you probably won’t either, so you need an agent. As you can see, the proxy can do a lot of things on the basis of the proxy function, except that it does not have the function of the proxy itself. That is the function of the proxy mode: separation of the caller and the called, enhanced functionality

Static agent

A static proxy is one in which the process of creating and compiling a class is determined before the program runs. That is, the proxy is carried out through manual coding. The structure of the case class diagram is as follows.In this case, SellHouse is the interface that defines the method of selling the house, HouseMaster is the owner of the house that sells the house, and SellProxy is the sales agent that agents and enhances the sales of the owner. The specific code is as follows:

SellHouse code

package demo.pattren.proxy.statics;

public interface SellHouse {
    void sell(a);
}
Copy the code

The implementation class HouseMaster

package demo.pattren.proxy.statics;

// The owner of the house
public class HouseMaster implements SellHouse {
    @Override
    public void sell(a) {
        System.out.println("I'm the owner of the house. I'm buying the house."); }}Copy the code

The proxy class SellProxy

package demo.pattren.proxy.statics;

// Sales agent
public class SellProxy implements SellHouse{
    // Implement proxying by injecting the proxied object
    private SellHouse sellHouse;
    public SellProxy(SellHouse sellHouse){
        this.sellHouse = sellHouse;
    }
    @Override
    public void sell(a) {
        before();
        // Buy the house
        sellHouse.sell();
        after();
    }
    // Pre-enhanced
    private void before(a){
        System.out.println("New Agent Sales");
        System.out.println("Let me make a pitch.");
        System.out.println("Adjust the selling price according to the actual situation and communicate with the owner.");
    }
    // post-enhance
    private void after(a){
        System.out.println("When the house is sold, I take my commission."); }}Copy the code

The test class

package demo.pattren.proxy.statics;

public class Test {
    public static void main(String[] args) {
        SellHouse house = new SellProxy(newHouseMaster()); house.sell(); }}Copy the code

A dynamic proxy

The implementation of static proxy is completed by programmers coding code, although static proxy makes the code has a certain degree of decoupling, but very flexible, if you do proxy, then each interface needs to write proxy class, resulting in code redundancy, let’s look at another implementation of proxy mode – dynamic proxy. The interface and implementation classes remain the same and a different proxy is used. DynamicProxy class SellDynamicProxy

package demo.pattren.proxy.dynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
// Dynamic proxy
public class SellDynamicProxy implements InvocationHandler {
    private Object object;

    public SellDynamicProxy(Object object){
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        // Buy the house
        Object result = method.invoke(object, args);
        after();
        return result;
    }
    // Pre-enhanced
    private void before(a){
        System.out.println("New Agent Sales");
        System.out.println("Let me make a pitch.");
        System.out.println("Adjust the selling price according to the actual situation and communicate with the owner.");
    }
    // post-enhance
    private void after(a){
        System.out.println("When the house is sold, I take my commission."); }}Copy the code

Dynamic proxy test classes


package demo.pattren.proxy.dynamic;

import demo.pattren.proxy.statics.HouseMaster;
import demo.pattren.proxy.statics.SellHouse;
import java.io.IOException;
import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) throws IOException {
        SellHouse sellHouse = new HouseMaster();
        SellHouse proxy  = (SellHouse)Proxy.newProxyInstance(
                SellHouse.class.getClassLoader(),
                new Class[]{SellHouse.class},
                newSellDynamicProxy(sellHouse)); proxy.sell(); }}Copy the code

The class diagram is structured as follows.First, the static proxy class needs to implement the same interface as the business class, and then invoke the function of the business class through the form of injection, while the dynamic proxy is different, and the dynamic proxy is the implementationInvocationHandlerClass to override the Invoke method to implement the proxy; The calls are also different, using the ones provided by the JDKProxythenewProxyInstanceMethod to create an object.

Underlying principles of dynamic proxy

Dynamic proxy creates a subclass of SellHouse. Debug to see what object is being created.A class ***$Proxy0@572*** was generated, so how does the JDK create such a non-existent Java class? Let’s find out through the code entry proxy.newProxyInstance.

Proxy.newProxyInstance

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h)
    throws IllegalArgumentException
{
    // Key codeClass<? > cl = getProxyClass0(loader, intfs); }Copy the code

Proxy.getProxyClass0

private staticClass<? > getProxyClass0(ClassLoader loader, Class<? >... interfaces) {// The number of interfaces cannot be larger than 65535
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    // Fetch from cache
    return proxyClassCache.get(loader, interfaces);
}
Copy the code

WeakCache.get

public V get(K key, P parameter) {
    Objects.requireNonNull(parameter);
    expungeStaleEntries();
    Object cacheKey = CacheKey.valueOf(key, refQueue);
    ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
    // If the cache is empty, create a new one
    if (valuesMap == null) {
        ConcurrentMap<Object, Supplier<V>> oldValuesMap
            = map.putIfAbsent(cacheKey,
                              valuesMap = new ConcurrentHashMap<>());
        if(oldValuesMap ! =null) { valuesMap = oldValuesMap; }}// Create a new one and place it in the cache
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
}
Copy the code

Here is the key code to create the class

Proxy.ProxyClassFactory.apply

private static final class ProxyClassFactory
    implements BiFunction<ClassLoader.Class<? > [].Class<? >>{
    // Prefixes for all generated class names
    private static final String proxyClassNamePrefix = "$Proxy";
    // The number used for the class name
    private static final AtomicLong nextUniqueNumber = new AtomicLong();
    @Override
    publicClass<? > apply(ClassLoader loader, Class<? >[] interfaces) { Map<Class<? >, Boolean> interfaceSet =new IdentityHashMap<>(interfaces.length);
        for(Class<? > intf : interfaces) { Class<? > interfaceClass =null;
            try {
                interfaceClass = Class.forName(intf.getName(), false, loader);
            } catch (ClassNotFoundException e) {
            }
            if(interfaceClass ! = intf) {throw new IllegalArgumentException(
                    intf + " is not visible from class loader");
            }

            if(! interfaceClass.isInterface()) {throw new IllegalArgumentException(
                    interfaceClass.getName() + " is not an interface");
            }

            if(interfaceSet.put(interfaceClass, Boolean.TRUE) ! =null) {
                throw new IllegalArgumentException(
                    "repeated interface: " + interfaceClass.getName());
            }
        }

        String proxyPkg = null;     // package to define proxy class in
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

        for(Class<? > intf : interfaces) {int flags = intf.getModifiers();
            if(! Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; String name = intf.getName();int n = name.lastIndexOf('. ');
                String pkg = ((n == -1)?"" : name.substring(0, n + 1));
                if (proxyPkg == null) {
                    proxyPkg = pkg;
                } else if(! pkg.equals(proxyPkg)) {throw new IllegalArgumentException(
                        "non-public interfaces from different packages"); }}}if (proxyPkg == null) {
            // If package is empty, use proxy package
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }

        long num = nextUniqueNumber.getAndIncrement();
        String proxyName = proxyPkg + proxyClassNamePrefix + num;
		// Generate a bytecode array
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
        try {
        	// Generate the proxy class through the class loader
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            throw newIllegalArgumentException(e.toString()); }}}Copy the code

Now that the proxy class is created, it’s time to create objects from the class. The proxy. newProxyInstance method takes three arguments: the first is the classloader, the second is the Proxy interface, and the third is an InvocationHandler. Back to the SellDynamicProxy class, which implements the InvocationHandler interface, the object passed in is the SellDynamicProxy type. The InvocationHandler is also related to the generated proxy class, so check out the source code. Proxy.newProxyInstance

 @CallerSensitive
public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h)
    throws IllegalArgumentException
{
    // Create a class proxy objectClass<? > cl = getProxyClass0(loader, intfs);try {
        if(sm ! =null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }
        finalConstructor<? > cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;
        if(! Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run(a) {
                    cons.setAccessible(true);
                    return null; }}); }// The object is created by reflection, and the InvocationHandler is an argument to the constructor
        return cons.newInstance(new Object[]{h});
		/ /... more code
}
Copy the code

Now the structure is basically clear. First, Java reflection and Proxy are used to dynamically create class objects in memory, and then reflection is used to create objects. The creation object takes InvocationHandler as a parameter, proving that the dynamically generated proxy class has a parameter constructor for InvocationHandler. The class bytecode is written to the hard disk as a file stream.

// Generate class bytecode, you can see in the source code also use this method
byte[] $Proxy0s = ProxyGenerator.generateProxyClass("$Proxy0".new Class[]{SellHouse.class});
// Write the bytecode to the hard disk
FileOutputStream fileOutputStream = new FileOutputStream("D://$Proxy0.class");
fileOutputStream.write($Proxy0s);
fileOutputStream.close();
Copy the code

Then by decomcompiling (if it is IDEA, drag the file directly to IDEA to view), view the generated source code. If the code above is obvious, don’t look at it. But be sure to understand what such a method does.

import demo.pattren.proxy.statics.SellHouse; 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 SellHouse { private static Method m1; private static Method m2; private static Method m3; private static Method m0; Public $Proxy0(InvocationHandler var1) throws {// Call the Proxy method and assign the value super(var1) to the Proxy h attribute. } public final Boolean equals(Object var1) {} public final String toString(){ Void sell() throws {try {// Call the sell method of SellDynamicProxy. M3 is the sell method defined by SellHouse. Look at super.hvoke (this, m3, (Object[])null) in a static code block; } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); Public final int hashCode(){} 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.pattren.proxy.statics.SellHouse").getMethod("sell"); 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

I’m getting carsick from all this code, but let me draw a picture.

CgLib dynamic proxy

JDK dynamic proxy technology is very convenient to use, but there is a certain limitation, that is, the propped class must have an interface, when the propped class does not have an interface, can choose another dynamic proxy method – Cglib.

CgLib dynamic proxy uses bytecode technology based on ASM. The proxy structure of CgLib is very simple, that is, Java integration mechanism is used to achieve the proxy effect.

The target class uses HouseMaster from the previous example, and now we write the interceptor class CgLibInteceptor

package demo.pattren.proxy.cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CgLibInteceptor implements MethodInterceptor {
    // Target object to be proxied
    private Object target;
    public Object getInstance(final Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        // Buy the house
        Object result = method.invoke(target, objects);
        after();
        return result;
    }
    // Pre-enhanced
    private void before(a) {
        System.out.println("New Agent Sales");
        System.out.println("Let me make a pitch.");
        System.out.println("Adjust the selling price according to the actual situation and communicate with the owner.");
    }
    // post-enhance
    private void after(a) {
        System.out.println("When the house is sold, I take my commission."); }}Copy the code

Then write the test class:

package demo.pattren.proxy.cglib;

import demo.pattren.proxy.statics.HouseMaster;
import org.springframework.cglib.core.DebuggingClassWriter;

import java.io.IOException;

public class CgLibTest {
    static{
        If cglib is static, it will be executed at runtime
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\ideaWorkspace\\demo2\\src\\main\\java\\demo\\pattren\\proxy\\cglib");
    }
    public static void main(String[] args) throws IOException {
        HouseMaster sellHouse = new HouseMaster();
        CgLibInteceptor cgLibProxy = newCgLibInteceptor(); HouseMaster h = (HouseMaster)cgLibProxy.getInstance(sellHouse); h.sell(); }}Copy the code

As expected, pre and post enhancements to the Sell () method were successfully implemented through the Inteceptor class. Cglib implements proxies through integration. In the test class, we added a static block of code to write the proxies generated during Cglib’s run to disk. Through the decompilation of Idea, we look at the code of the proxy class. Due to too much code, only the key part is intercepted.Decompiled code reveals that the generated proxy class is indeed an implementation of the target class.

In addition, the disadvantage of using inheritance is that it is impossible to implement a proxy for methods that define final properties.

Dynamic proxies for commonly used frameworks

  1. SpringAop

Spring’s section-oriented programming greatly simplifies our daily development work, while Spring’S Aop uses dynamic proxy technology and supports JDK dynamic proxy and Cglib proxy. This question often appears in interview questions.

Q: Explain the Spring dynamic proxy? A :Spring dynamic proxies are available in two ways. The JDK dynamic proxy is used by default when the target class is an interface, and the Cglib proxy is used when there is no interface. Q: What are their differences? A: JDK dynamic proxies are implemented using proxies and InvocationHandler, which use reflection to generate proppant implementation classes, requiring the target class to have an implementation interface. Cglib uses ASM bytecode technology to generate subclasses of proxied classes, which are inherited. At lower JDK versions, JDK dynamic proxies are more efficient to generate and less efficient to run, while Cglib is less efficient to generate and more efficient to run, but with JDK optimization, the difference is very small at 1.8. Reference: blog.csdn.net/xlgen157387…

  1. MyBatis dynamic proxy

I believe that we are very familiar with Mybatis Mapper interface + XML development mode, in the development of time, we only need to write Mapper interface and XML can achieve DAO operation. MyBatis will use JDK dynamic proxy to generate Mapper interface implementation class, so as to complete DAO operation according to XML definition.