Zen of design pattern: structural agent pattern, static agent, JDK dynamic agent, cglib and source code analysis, hand typing 2w words

Posted by AdamBrill on Mon, 21 Feb 2022 12:58:33 +0100

𝑰'π’Ž π’‰π’‰π’ˆ, 𝑰 π’‚π’Ž 𝒂 π’ˆπ’“π’‚π’…π’–π’‚π’•π’† 𝒔𝒕𝒖𝒅𝒆𝒏𝒕 π’‡π’“π’π’Ž π‘΅π’‚π’π’‹π’Šπ’π’ˆ, π‘ͺπ’‰π’Šπ’π’‚.

  • 🏫 𝑺𝒉𝒄𝒐𝒐𝒍: π‘―π’π’‰π’‚π’Š π‘Όπ’π’Šπ’—π’†π’“π’”π’Šπ’•π’š
  • 🌱 π‘³π’†π’‚π’“π’π’Šπ’π’ˆ: 𝑰'π’Ž π’„π’–π’“π’“π’†π’π’•π’π’š π’π’†π’‚π’“π’π’Šπ’π’ˆ π’…π’†π’”π’Šπ’ˆπ’ 𝒑𝒂𝒕𝒕𝒆𝒓𝒏, 𝑳𝒆𝒆𝒕𝒄𝒐𝒅𝒆, π’…π’Šπ’”π’•π’“π’Šπ’ƒπ’–π’•π’†π’… π’”π’šπ’”π’•π’†π’Ž, π’Žπ’Šπ’…π’…π’π’†π’˜π’‚π’“π’† 𝒂𝒏𝒅 𝒔𝒐 𝒐𝒏.
  • πŸ’“ π‘―π’π’˜ 𝒕𝒐 𝒓𝒆𝒂𝒄𝒉 π’Žπ’†: 𝑽𝑿
  • πŸ“š π‘΄π’š π’ƒπ’π’π’ˆ: 𝒉𝒕𝒕𝒑𝒔://π’‰π’‰π’ˆπ’šπ’šπ’…π’”.π’ƒπ’π’π’ˆ.𝒄𝒔𝒅𝒏.𝒏𝒆𝒕/
  • πŸ’Ό π‘·π’“π’π’‡π’†π’”π’”π’Šπ’π’π’‚π’ π’”π’Œπ’Šπ’π’π’”: π’Žπ’š π’…π’“π’†π’‚π’Ž

According to the purpose; Through what work is completed, it can be divided into three types: creation mode, structure mode and behavior mode
1. Creation mode: it acts on the creation of objects and separates the creation and use of objects.
2. Structural mode: form a larger structure of classes or objects according to a certain layout
3. Behavioral mode: it acts on the cooperation between classes or objects to jointly complete tasks that cannot be completed by a single object, and how to allocate responsibilities.

Definition

Provide a surrogate or placeholder for another object to control access to it. Provide a proxy for other objects to control access to this object

1: Static Proxy Pattern

1-1: Demo

This static agent mode is described through an example of players playing games and on-line agent training. It is very vivid!

An interface to define player behavior.

package com.company.design.proxy;

public interface IGamePlayer {
    void login(String username, String password);

    void killBoss();

    void upgrade();
}

Player implementation class

package com.company.design.proxy;

public class GamePlayer implements IGamePlayer {
    @Override
    public void login(String username, String password) {
        System.out.println("welcome, " + username);
    }

    @Override
    public void killBoss() {
        System.out.println("Playing strange!!!");
    }

    @Override
    public void upgrade() {
        System.out.println("Upgrade again!!!");
    }
}

proxy class

package com.company.design.proxy;

public class GamePlayerProxy implements IGamePlayer {
    private IGamePlayer player = null;

    public GamePlayerProxy(IGamePlayer player) {
        this.player = player;
    }

    @Override
    public void login(String username, String password) {
        System.out.println("Dad's on the number. He'll die");
        this.player.login(username, password);
    }

    @Override
    public void killBoss() {
        this.player.killBoss();
    }

    @Override
    public void upgrade() {
        this.player.upgrade();
    }
}

Client test class

package com.company.design.proxy;

public class Client {
    public static void main(String[] args) {
        IGamePlayer player = new GamePlayer();
        IGamePlayer proxy = new GamePlayerProxy(player);
        proxy.login("hhg", "111");
        proxy.killBoss();
        proxy.upgrade();
    }
}
----output----
Dad's on the number. He'll die
 welcome, hhg
 Playing strange!!!
Upgrade again!!!

1-2: Advantages or Usage of Proxy

First, why use proxy classes? What are the benefits of proxy classes?

  • The details are hidden. The caller does not need to pay attention to the proxy class. What needs to be done is to find a proxy proxy.
  • The principle of opening and closing. Since it implements the same interface as the proxy class, as long as the interface remains unchanged, the proxy class can continue to use without any change when the interior of the proxy class changes.
  • Enhanced methods, proxy methods can enhance the methods of the agent, such as security testing and protection, which is equivalent to an additional layer of barrier.

1-3: Disadvantages

  • The proxy class and the proxied class need to implement the same interface. If the interface changes, the proxy class should also change.
  • An interface corresponds to a proxy class. If there are multiple types that need proxy, you need to implement multiple proxy classes, and there will be many proxy classes.

1-4: JDK Source Code-Thread

We all know that there is a method to create a Thread: new Thread(Your RunnableImpl). Implement a Runnable interface by yourself, and then pass it to Thread to run. Here is a typical static proxy mode. What you implement is the proxy class.

// The proxy implements the same interface with the proxy object written by itself
class Thread implements Runnable{}

/* What will be run. */Proxy class
private Runnable target;

// Agent construction method
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

// Call the run method of the proxy class target
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

2: Dynamic Proxy

Dynamic Proxy is compared with static Proxy. Static Proxy is its own handwritten Proxy object, while dynamic Proxy dynamically creates the Proxy object of the target class as needed when the program is running. In Java lang.reflect. In the Proxy class, proxygenerator is used Generateproxyclass to generate binary byte stream of Proxy class in the form of * $Proxy for a specific interface. Then it is loaded into the JVM for use.

2-1: Demo

Dynamic Proxy mainly depends on a class Proxy and an interface InvocationHandler. Look at the code first and then analyze it slowly.

MyLogHandler.java, each instance of the agent has an InvocationHandler implementation class associated with it. If the method of the agent is called, the agent will notify and forward it to the invoke method of the internal InvocationHandler implementation class, which decides to process it.

package com.company.design.proxy.dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;

public class MyLogHandler implements InvocationHandler {
    // Proxied object
    private Object target;

    public MyLogHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        // Let the proxied object execute specific methods. The first parameter of invoke means who will call this method.
        Object result = method.invoke(target, args);
        after();
        return result;
    }

    public void before() {
        System.out.println(String.format("log start [%s]", new Date()));
    }

    public void after() {
        System.out.println(String.format("log end [%s]", new Date()));
    }
}

ProxyUtils.java

package com.company.design.proxy.dynamicProxy;

import sun.misc.ProxyGenerator;

import java.io.FileOutputStream;
import java.io.IOException;

public class ProxyUtils {
    /**
     * Save the binary bytecode dynamically generated according to the class information to the hard disk. The default is in the clazz directory
     * params: clazz Classes that need to generate dynamic proxy classes
     * proxyName: The name of the dynamically generated proxy class for
     */
    public static void generateClassFile(Class clazz, String proxyName) {
        // Generate bytecode according to the class information and the provided proxy class name
        byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
        String paths = ProxyUtils.class.getResource("").getPath();
        System.out.println(paths);
        FileOutputStream out = null;
        try {
            //Keep to hard disk
            out = new FileOutputStream(paths + proxyName + ".class");
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Client.java

package com.company.design.proxy.dynamicProxy;

import com.company.design.proxy.staticProxy.GamePlayer;
import com.company.design.proxy.staticProxy.IGamePlayer;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Client {
    public static void main(String[] args) {
        IGamePlayer player = new GamePlayer();
        ClassLoader classLoader = player.getClass().getClassLoader();
        Class<?>[] interfaces = player.getClass().getInterfaces();
        InvocationHandler myLogHandler = new MyLogHandler(player);
        // Using Proxy to generate a Proxy
        IGamePlayer playerProxy = (IGamePlayer) Proxy.newProxyInstance(classLoader, interfaces, myLogHandler);
        playerProxy.login("hhg", "123");
        playerProxy.killBoss();
        playerProxy.upgrade();
        // Generate playerproxy Class file
        ProxyUtils.generateClassFile(playerProxy.getClass(), "playerProxy");
    }
}

playerProxy.java

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

import com.company.design.proxy.staticProxy.IGamePlayer;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class playerProxy extends Proxy implements IGamePlayer {
    private static Method m1;
    private static Method m2;
    private static Method m5;
    private static Method m4;
    private static Method m3;
    private static Method m0;

    public playerProxy(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 upgrade() throws  {
        try {
            super.h.invoke(this, m5, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

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

    public final void login(String var1, String var2) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    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");
            m5 = Class.forName("com.company.design.proxy.staticProxy.IGamePlayer").getMethod("upgrade");
            m4 = Class.forName("com.company.design.proxy.staticProxy.IGamePlayer").getMethod("killBoss");
            m3 = Class.forName("com.company.design.proxy.staticProxy.IGamePlayer").getMethod("login", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

The first is the overall code flow chart:

First, in client, we call login method, then we need to find the proxy object generated by dynamic proxy. We use playerProxy to represent it, but playProxy generates dynamic proxy class, and it has no action. The concrete behavior is to give InvocationHandler implementation class (what we write MyLogHandler) to perform specific behavior. After adding some operations to the method in the Handler, finally let the proxy object GamePlayer execute the login method. The overall process is like this.

2-2: Advantages

The most intuitive feeling is the amount of code. Compared with the previous static proxy, if there are many methods, you have to write many methods in the proxy class. As many classes need proxy, you need to write as many proxy classes. Here, you only need to implement one interface, and through reflection, you can achieve the unified proxy of all methods. Agent logic and business logic are independent of each other without coupling. There is no difference in what one class or 100 classes need to do.

2-3: Usage in Source Code

Leave a hole in the spring AOP source code, and then supplement it when looking at the spring source code

2-4: Source Code Analysis

Let's analyze the source code of the two methods of dynamic agent.
The first is newProxyInstance, the creation of proxy class

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    Objects.requireNonNull(h);

    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Look up or generate the designated proxy class.Find cache
     */
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }
		
		// Obtain construction method
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        // Check whether it is public, otherwise you have to cons setAccessible(true); This is a common means of reflection
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        // Create instance
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

It is worth mentioning that a cache is used here.

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
    proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    // If the proxy class defined by the given loader implementing
    // the given interfaces exists, this will simply return the cached copy;
    // otherwise, it will create the proxy class via the ProxyClassFactory
    return proxyClassCache.get(loader, interfaces);
}

The first level cache and second level cache here will be opened in two days. The key is
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
If there is no cache, it will pass ProxyClassFactory Apply method to create one. Let's go to ProxyClassFactory to see how to create it.

private static final class ProxyClassFactory
    implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
    // prefix for all proxy class names, so the beginning of $Proxy is the dynamic Proxy used
    private static final String proxyClassNamePrefix = "$Proxy";

    // next number to use for generation of unique proxy class names
    private static final AtomicLong nextUniqueNumber = new AtomicLong();

    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        for (Class<?> intf : interfaces) {
            /*
             * Verify that the class loader resolves the name of this
             * interface to the same Class object.
             */
            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");
            }
            /*
             * Verify that the Class object actually represents an
             * interface.
             */
            if (!interfaceClass.isInterface()) {
                throw new IllegalArgumentException(
                    interfaceClass.getName() + " is not an interface");
            }
            /*
             * Verify that this interface is not a duplicate.
             */
            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;

        /*
         * Record the package of a non-public proxy interface so that the
         * proxy class will be defined in the same package.  Verify that
         * all non-public proxy interfaces are in the same package.
         */
        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 no non-public proxy interfaces, use com.sun.proxy package
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }

        /*
         * Choose a name for the proxy class to generate.
         */
        long num = nextUniqueNumber.getAndIncrement();
        String proxyName = proxyPkg + proxyClassNamePrefix + num;

        /*
         * Generate the specified proxy class.Here is the method to generate proxy class and bytecode file
         */
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
        try {
        	// Obtain the corresponding class object through local methods
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            /*
             * A ClassFormatError here means that (barring bugs in the
             * proxy class generation code) there was some other
             * invalid aspect of the arguments supplied to the proxy
             * class creation (such as virtual machine limitations
             * exceeded).
             */
            throw new IllegalArgumentException(e.toString());
        }
    }
}

In the Proxy, this class is a static internal class. In this way, you can see how the class file of the Proxy class comes. It really generates a binary byte array, then obtains the corresponding class object, and then creates this object through the class constructor. Seconds! Seconds! In this way, the construction of Proxy object should be known. Method, we can also see it by looking at the class of the generated Proxy object, which is given here.

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

import com.company.design.proxy.staticProxy.IGamePlayer;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class playerProxy extends Proxy implements IGamePlayer {
    private static Method m1;
    private static Method m2;
    private static Method m5;
    private static Method m4;
    private static Method m3;
    private static Method m0;

    public playerProxy(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 upgrade() throws  {
        try {
            super.h.invoke(this, m5, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

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

    public final void login(String var1, String var2) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    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");
            m5 = Class.forName("com.company.design.proxy.staticProxy.IGamePlayer").getMethod("upgrade");
            m4 = Class.forName("com.company.design.proxy.staticProxy.IGamePlayer").getMethod("killBoss");
            m3 = Class.forName("com.company.design.proxy.staticProxy.IGamePlayer").getMethod("login", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

It can be clearly seen here that the corresponding method is obtained through the class file and filled into the attribute. When calling the corresponding proxy method, let the handler written by yourself execute. =. =, Seconds, seconds. Dynamic agent source code, to this simple and ingenious end!

2-5: Q1: Why JDK Dynamic Proxy is interface oriented?

As for why the dynamic Proxy of jdk is interface oriented, it can be seen from the source code that the implementation interface of the Proxy class needs to be scanned in the process of creating the Proxy class. If the Proxy object does not implement the interface, it will report a forced conversion failure. Why? The answer given by many people is that because they have inherited the Proxy and single inheritance, they can only implement the Proxy through the interface. I don't think so. Compared with this statement, I think it is Java's customary interface oriented design. Only in this way can there be the emergence of subsequent inheritance Proxy, in other words, design first and then code. I think it's a little impractical to have Proxy first and then interface oriented..

In addition, if it is an inherited class, how to maintain the variables in it? Maintaining the variables between the proxy object and the proxy object will also be more troublesome, and the proxy object does not need those variables of the proxy object, you think. All calls eventually fall on the proxy object, and the proxy object does not need to hold these variables. It saves a lot of space and performance, doesn't it? I think it makes sense

2-6: Q2: Why JDK Dynamic Proxy extends Proxy?

After looking at the generated Proxy objects, we can find that they all inherit the Proxy class. Why should we inherit the Proxy class? I think the most fundamental thing is the template method. Extract the common methods or common attributes of all agent classes, which can save a lot of code, can't it? For example, for caching Proxy classes, all Proxy classes must be used. I feel that this is the most persuasive answer. I personally feel that the answer is not what I asked. I think it should not be... To be verified.

Two questions are written mainly for the serial gun after the interview.

3: Inheritance-oriented Cglib

As we all know, cglib is an inheritance oriented proxy. JDK dynamic proxy cannot be implemented for ordinary classes that do not implement an interface, so cglib can make up for this gap.

3-1: Demo

Let's have a basic understanding through a small demo.

  • Proxy class: player Java does not implement any interfaces.
    public class Player {
        public void login(String username, String password) {
            System.out.println("welcome, " + username);
        }
    
        public void killBoss() {
            System.out.println("Playing strange!!!");
        }
    
        public void upgrade() {
            System.out.println("Upgrade again!!!");
        }
    }
    
  • It implements the interface of MethodInterceptor and self-made factory method myproxyfactory java
    public class MyProxyFactory implements MethodInterceptor {
        private Object target;
    
        public MyProxyFactory(Object target) {
            this.target = target;
        }
    
        public Object getProxy() {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(this.target.getClass());
            enhancer.setCallback(this);
            return enhancer.create();
        }
    
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            before();
    //        Object returnValue = method.invoke(target, args); This is the reflection of walking
            Object returnValue = proxy.invokeSuper(obj, args); // This goes through the internal FastClass mechanism
            after();
            return returnValue;
        }
    
        public void before() {
            System.out.println(String.format("log start [%s]", new Date()));
        }
    
        public void after() {
            System.out.println(String.format("log end [%s]", new Date()));
        }
    }
    
  • client.java
    public class Client {
        public static void main(String[] args) {
        	// Get player Class path
            String path = Player.class.getResource("").getPath();
    		// The generated proxy class is displayed in the target
            System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, path);
            Player player = new Player();
            Player proxy = (Player) new MyProxyFactory(player).getProxy();
            proxy.login("xx", "123");
            proxy.killBoss();
            proxy.upgrade();
        }
    }
    
    ----output----
    CGLIB debugging enabled, writing to '/home/hhg/java_project/LeetCodeLearning/target/classes/com/company/design/proxy/cglib/'
    log start [Tue Feb 08 21:38:34 CST 2022]
    welcome, xx
    log end [Tue Feb 08 21:38:35 CST 2022]
    log start [Tue Feb 08 21:38:35 CST 2022]
    Playing strange!!!
    log end [Tue Feb 08 21:38:35 CST 2022]
    log start [Tue Feb 08 21:38:35 CST 2022]
    Upgrade again!!!
    log end [Tue Feb 08 21:38:35 CST 2022]
    

3-2: Source Code Analysis

First, take a look at several class es dynamically generated by cglib

From top to bottom, they are the proxy class, the fastclass of the proxy class and the fastclass of the proxied class. cglib what we need to know is the fastclass mechanism. Let's look at it step by step from the interceptor. How to load dynamically generated classes into the class loader is not our focus here.

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    before();
    Object returnValue = proxy.invokeSuper(obj, args);
    after();
    return returnValue;
}

The interceptor will intercept the target method here, and then call invokeSuper to enter the agent's target to perform.

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
    try {
        init();
        FastClassInfo fci = fastClassInfo;
        return fci.f2.invoke(fci.i2, obj, args);
    } catch (InvocationTargetException e) {
        throw e.getTargetException();
    }
}

First look at init():

private void init()
{
    /* 
     * Using a volatile invariant allows us to initialize the FastClass and
     * method index pairs atomically.
     * 
     * Double-checked locking is safe with volatile in Java 5.  Before 1.5 this 
     * code could allow fastClassInfo to be instantiated more than once, which
     * appears to be benign.
     */
    if (fastClassInfo == null)
    {
        synchronized (initLock)
        {
            if (fastClassInfo == null)
            {
                CreateInfo ci = createInfo;

                FastClassInfo fci = new FastClassInfo();
                fci.f1 = helper(ci, ci.c1);
                fci.f2 = helper(ci, ci.c2);
                fci.i1 = fci.f1.getIndex(sig1);
                fci.i2 = fci.f2.getIndex(sig2);
                fastClassInfo = fci;
                createInfo = null;
            }
        }
    }
}

helper(): after setting up some necessary information, the create method is invoked and a Fastclass is generated.

private static FastClass helper(CreateInfo ci, Class type) {
    FastClass.Generator g = new FastClass.Generator();
    g.setType(type);
    g.setClassLoader(ci.c2.getClassLoader());
    g.setNamingPolicy(ci.namingPolicy);
    g.setStrategy(ci.strategy);
    g.setAttemptLoad(ci.attemptLoad);
    return g.create();
}

The following getIndex method will not be studied first. Let's write a simple demo of fastclass mechanism to feel how to quickly obtain classes and call methods without reflection.

public class MyFastClass {

    public Object invoke(int index, Object o) {
        MyObj obj = (MyObj) o;
        switch (index) {
            case 1:
                obj.f1();
                return null;
            case 2:
                obj.f2();
                return null;
        }
        return null;
    }

    public int getIndex(String signature) {
        switch (signature.hashCode()) {
            // f1()
            case 3087052:
                return 1;
            // f2()
            case 3088013:
                return 2;
        }
        return -1;
    }

    public static void main(String[] args) {
        MyObj obj = new MyObj();
        MyFastClass fastClass = new MyFastClass();
        int f1Index = fastClass.getIndex("f1()");
        int f2Index = fastClass.getIndex("f2()");
        fastClass.invoke(f1Index, obj);
        fastClass.invoke(f2Index, obj);
    }
}

class MyObj {
    public void f1() {
        System.out.println("invoke f1");
    }

    public void f2() {
        System.out.println("invoke f2");
    }
}

The general process is like this. Each method has a unique number called index. Their index is calculated according to their unique identification: Signature hashcode, which is the corresponding relationship. A signature corresponds to a method corresponding to an index. Find the index according to hashcode, and then find the corresponding method through index, In this way, the corresponding method can be executed quickly!

In this way, you can understand what the invoke is doing after getindex. Of course, here is just a simple look at the source code of the execution. There are many details, and the author's ability is limited.

3-3: Usage in Source Code

It seems that cdlib has an application in spring. I'll keep the pit first and supplement it when I look back at the spring source code.

4: Conclusion

From my own observation, the three agents are like slowly optimizing and slowly changing their disadvantages step by step. Among them, static agent and dynamic agent are a watershed, which mainly solves the problem of class expansion and liberates programmers.

5: Reference

Detailed explanation of the use and principle of dynamic agent in Java , mainly JDK dynamic agent and static agent.
Cglib source code analysis , mainly cglib related.
CGLib dynamic proxy , mainly cglib related.
Functions and benefits of Java Dynamic agent , the advantages and disadvantages of static agent and dynamic agent are compared and summarized.
Detailed explanation of Java Dynamic Proxy , there are all three modes.
JAVA Dynamic Proxy , the JDK dynamic agent is described in detail.
Proxy mode and proxy class I understand , static proxy and JDK dynamic proxy.
Application summary of agent mode , there are all three kinds. This is the best among them, 1471230 visits.
Why should the dynamic proxy of JDK be implemented based on interface instead of inheritance?
Why do classes generated by java Dynamic Proxy have to inherit proxy? The proxy that makes class impossible to implement?
FastClass mechanism of dynamic agent series Cglib (4) , cglib is pretty good.

Topics: Java