Agent mode of design mode (structural type)

Posted by HIV on Fri, 18 Feb 2022 02:14:21 +0100

Before introducing the agent mode, the blogger wants to say something "out of the question". The blogger likes playing games, especially hero League, especially compression. The blogger's compression can be said to be "fantastic". The blogger's friends call the blogger "tuoersuo". In other words, although the blogger's compression is fantastic and likes waves, the mending knife is not good, As a programmer, the blogger decided to count the number of complementary knives of the landlord.

Upper Code:

public class YaSuo implements Mendable{

    @Override
    public int mend() {
        int killNum = new Random().nextInt(100);
        return killNum;
    }

    public static void main(String[] args) {
        new YaSuo().mend();
    }
}

interface Mendable {
    int mend();
}

The code is very simple. When you say "YaSuo", there is a method of mending the knife, which is to mend the knife with the edge. Here's the problem. I want to record the number of "YaSuo" repair knives. I think you have figured it out. Just add a log and print the number of repair knives. Here's the code:

public class YaSuo implements Mendable{

    @Override
    public int mend() {
        System.out.println("YaSuo killing batman...");
        int killNum = new Random().nextInt(100);
        System.out.println("YaSuo 10 min mend:" + killNum);
        return killNum;
    }

    public static void main(String[] args) {
        new YaSuo().mend();
    }
}

interface Mendable {
    int mend();
}

Let's take a look at the results. The blogger's method of mending the knife with luck:

YaSuo killing batman...
YaSuo 10 min mend:67

After passing the test, the blogger's ability to mend the knife has greatly increased. In the past, it was 40 or 50 knives in 10 min utes.

Then the problem comes again. Bloggers don't want to modify the source code or can't modify the source code at all. What should we do?

Someone said that inheritance can be used to inherit:

public class YaSuo implements Mendable {

    @Override
    public int mend() {
        int killNum = new Random().nextInt(100);
        return killNum;
    }

    public static void main(String[] args) {
        new YaSuo1().mend();
    }
}

class YaSuo1 extends YaSuo {

    @Override
    public int mend() {
        System.out.println("YaSuo killing batman...");
        int killNum = super.mend();
        System.out.println("YaSuo 10 min mend:" + killNum);
        return killNum;
    }
}

interface Mendable {
    int mend();
}

Although inheritance can achieve results, it has disadvantages. The coupling degree is too high, which destroys the encapsulation. We should use inheritance with caution.

What should I do? Back to our focus today: agent model.

It is implemented by proxy. The above code:

public class YaSuo implements Mendable {

    @Override
    public int mend() {
        int killNum = new Random().nextInt(100);
        return killNum;
    }

    public static void main(String[] args) {
        new YaSuoProxy(new YaSuo()).mend();
    }
}

class YaSuoProxy implements Mendable {

    YaSuo yaSuo;

    public YaSuoProxy(YaSuo yaSuo) {
        this.yaSuo = yaSuo;
    }

    @Override
    public int mend() {
        System.out.println("YaSuo killing batman...");
        int killNum = yaSuo.mend();
        System.out.println("YaSuo 10 min mend:" + killNum);
        return killNum;
    }
}

interface Mendable {
    int mend();
}

This is the static proxy.

Then the problem comes again. As a good program, it should have good scalability. Now I count the number of supplementary knives. We also want to count the number of homicides and assists. What should we do?

Then we can add another agent class to count the number of assists. The code is as follows:

public class YaSuo implements Mendable {

    @Override
    public int mend() {
        int killNum = new Random().nextInt(100);
        return killNum;
    }

    public static void main(String[] args) {
        new YaSuoProxy(new YaSuo()).mend();
    }
}

class YaSuoProxy implements Mendable {

    YaSuo yaSuo;

    public YaSuoProxy(YaSuo yaSuo) {
        this.yaSuo = yaSuo;
    }

    @Override
    public int mend() {
        System.out.println("YaSuo killing batman...");
        int killNum = yaSuo.mend();
        System.out.println("YaSuo 10 min mend:" + killNum);
        return killNum;
    }
}

class YaSuoAssistProxy implements Mendable {

    Mendable m;

    public YaSuoAssistProxy(Mendable m) {
        this.m = m;
    }

    @Override
    public int mend() {
        System.out.println("YaSuo assist...");
        int killNum = m.mend();
        System.out.println("YaSuo assist:" + killNum);
        return killNum;
    }
}

interface Mendable {
    int mend();
}

Now we want to flexibly combine and count the number of supplementary knives and assists. What should we do? Look at the code:

public class YaSuo implements Mendable {

    @Override
    public int mend() {
        int killNum = new Random().nextInt(100);
        return killNum;
    }

    public static void main(String[] args) {
        new YaSuoProxy(new YaSuoAssistProxy(new YaSuo())).mend();
    }
}

class YaSuoProxy implements Mendable {

    Mendable m;

    public YaSuoProxy(Mendable m) {
        this.m = m;
    }

    @Override
    public int mend() {
        System.out.println("YaSuo killing batman...");
        int killNum = m.mend();
        System.out.println("YaSuo 10 min mend:" + killNum);
        return killNum;
    }
}

class YaSuoAssistProxy implements Mendable {

    Mendable m;

    public YaSuoAssistProxy(Mendable m) {
        this.m = m;
    }

    @Override
    public int mend() {
        System.out.println("YaSuo assist...");
        int killNum = m.mend();
        System.out.println("YaSuo assist:" + killNum);
        return killNum;
    }
}

interface Mendable {
    int mend();
}

Do you feel familiar? Yes, it's the Decorator mode -- Decorator. Much like Decorator mode.

As an excellent program, it should not only proxy one method and one type, but also any other proxy type Object. What should I do? Since there is a static agent, is there a dynamic agent? The answer is yes. Look at dynamic agents:

public class YaSuo implements Mendable {

    @Override
    public int mend() {
        int killNum = new Random().nextInt(100);
        return killNum;
    }

    public static void main(String[] args) {
        YaSuo yaSuo = new YaSuo();
        Mendable m = (Mendable)Proxy.newProxyInstance(YaSuo.class.getClassLoader(),
                new Class[]{Mendable.class}, (proxy, method, args1) -> {
                    System.out.println("YaSuo killing batman...");
                    Object o = method.invoke(yaSuo, args1);
                    System.out.println("YaSuo 10 min mend:" + o);
                    return o;
                }
        );
        m.mend();
    }
}

interface Mendable {
    int mend();
}

We can also write this:

public class YaSuo implements Mendable {

    @Override
    public int mend() {
        int killNum = new Random().nextInt(100);
        return killNum;
    }

    public static void main(String[] args) {
        YaSuo yaSuo = new YaSuo();
        Mendable m = (Mendable)Proxy.newProxyInstance(YaSuo.class.getClassLoader(),
                new Class[]{Mendable.class}, new MyHandler(yaSuo));
        m.mend();
    }
}

class MyHandler implements  InvocationHandler {

    Mendable m;

    public MyHandler(Mendable m) {
        this.m = m;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("YaSuo killing batman...");
        Object o = method.invoke(m,args);
        System.out.println("YaSuo 10 min mend:" + o);
        return o;
    }
}

interface Mendable {
    int mend();
}

We can see that the amount of code is very small. The code above has its own proxy classes, such as YaSuoProxy and YaSuoAssistProxy. Does the dynamic proxy implement the proxy class? Yes, in memory, of course, we can also generate it. By:

public class ProxyTest {
    public static void main(String[] args) {
        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Mendable.class});
        try {
        FileOutputStream fileOutputStream = new FileOutputStream("D://$Proxy0.class");
            fileOutputStream.write(bytes);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Generate to disk D. Open it with the decompile tool.

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 Mendable {
    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 int mend() throws  {
        try {
            return (Integer)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.wangkai.design.structure.proxy.v4.Mendable").getMethod("mend");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

This is the proxy class we generated. You can see that the # InvocationHandler is passed to the constructor and the equals method, toString method and hashCode method are generated. Of course, there must be our mend method. For example, when the proxy method invokes the $invoke method, it will be added by the proxy method before and after the proxy method invokes the $invoke method. For example, when the proxy method invokes the $invoke method, it can be verified by the proxy method before and after the proxy method is called, This is the logic of jdk dynamic proxy.

It can be seen that the jdk dynamic proxy} proxy class needs to implement the interface.

After reading the dynamic agent, take another look at the cglib agent:

cglib (Code Generation Library) is a powerful, high-performance and high-quality code generation class library.

public class Client {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(YaSuo.class);
        enhancer.setCallback(new TimeMethodInterceptor());
        YaSuo yaSuo = (YaSuo)enhancer.create();
        yaSuo.mend();
    }
}

class TimeMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println(o.getClass().getSuperclass().getName());
        System.out.println("YaSuo killing batman...");
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("YaSuo 10 min mend:" + result);
        return result;
    }
}

class YaSuo {
    public int mend() {
        int killNum = new Random().nextInt(100);
        return killNum;
    }
}

To use cglib proxy, you need to use POM XML file plus

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.12</version>
        </dependency>

The bottom layer of CGLIB uses ASM (a short and concise bytecode operation framework) to directly operate bytecode to generate new classes. In addition to the CGLIB library, scripting languages such as Groovy and BeanShell also use ASM to generate bytecode.

cglib proxy intercepts methods through an Enhancer and a MethodInterceptor. TimeMethodInterceptor here is equivalent to the InvocationHandler of jdk dynamic proxy. The proxy class does not implement any interface, so how do we know which method to generate for the generated proxy object? In fact, the proxy object we generate is a subclass of the proxy class, that is, the parameter o in the intercept method. Let's print the parent class of O:

com.wangkai.design.structure.proxy.v5.YaSuo
YaSuo killing batman...
YaSuo 10 min mend:49

You can see that the parent class of the generated proxy class is YaSuo. Now the question arises. Since the generated proxy class is a subclass of the proxy class, can cglib proxy be used to generate proxy classes if the proxy class is final? Let's try:

You can see that the report is wrong. So you can't.

After introducing jdk agent and cglib agent, let's compare them:

1, jdk dynamic proxy is to realize the interface of the proxy object, and invoke InvokeHandler before calling the specific method. If the proxy object implements the interface, the dynamic proxy of jdk will be used to implement AOP by default.

Cglib dynamic proxy uses asm open source package to load the class file of proxy object class and generate subclasses by modifying its bytecode. And cglib can not proxy final modified classes.

2. jdk dynamic agent is built in Java, and cglib dynamic agent is provided by a third-party jar package.

3. The logic of jdk dynamic proxy implementation is that both the target class and the proxy class implement the same interface, and the target class and the proxy class are at the same level. The logic of cglib dynamic proxy is to inherit the target class, which is a subclass of the target class. The target class and proxy class are parent-child inheritance.

Topics: Java Design Pattern