"Design Patterns" - Agent Pattern

1. What is the agency model?

The proxy pattern is a relatively easy-to-understand design pattern. To put it simply, we use proxy objects to replace access to real objects, so that we can provide additional functional operations and expand the functions of the target object without modifying the original target object.

The main function of the proxy mode is to extend the functionality of the target object. For example, you can add some custom operations before and after a method of the target object is executed.

For example: If you ask Xiaohong to help you ask questions, Xiaohong can be regarded as a proxy object acting on your behalf, and the agent's behavior (method) is to ask questions.

[Image upload failed...(image-e75331-1650446995215)]

The proxy mode has two implementation methods: static proxy and dynamic proxy. Let's first look at the implementation of the static proxy mode.

2. Static proxy

In static proxy, we enhance each method of the target object manually (the code will be demonstrated later), which is very inflexible (for example, once a new method is added to the interface, both the target object and the proxy object must be modified) and troublesome (You need to write a separate proxy class for each target class). There are very few actual application scenarios, and there are almost no scenarios where static proxies are used in daily development.

Above we are talking about static proxies from the perspective of implementation and application. From the JVM level, static proxies turn interfaces, implementation classes, and proxy classes into actual class files during compilation.

2.1. Static proxy implementation steps:

  1. Define an interface and its implementation class;
  2. Create a proxy class that also implements this interface
  3. Inject the target object into the proxy class, and then call the corresponding method in the target class in the corresponding method of the proxy class. In this case, we can shield access to the target object through the proxy class, and do what we want to do before and after the target method is executed.

Shown below through code! 

2.2. Case 1

1. Define the interface for sending text messages

public interface SmsService {
    String send(String message);
}

2. Implement the interface for sending text messages

public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

3. Create a proxy class and also implement the interface for sending text messages.

public class SmsProxy implements SmsService {

    private final SmsService smsService;

    public SmsProxy(SmsService smsService) {
        this.smsService = smsService;
    }

    @Override
    public String send(String message) {
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method send()");
        smsService.send(message);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method send()");
        return null;
    }
}

4. Actual use

public class Main {
    public static void main(String[] args) {
        SmsService smsService = new SmsServiceImpl();
        SmsProxy smsProxy = new SmsProxy(smsService);
        smsProxy.send("java");
    }
}

After running the above code, the console prints out:

before method send()
send message:java
after method send()

You can see from the output that we have added  SmsServiceImpl methods send()

2.2. Case 2

package com.company.proxy.v07;


import java.util.Random;

/**
 * 问题:我想记录坦克的移动时间
 * 最简单的办法:修改代码,记录时间
 * 问题2:如果无法改变方法源码呢?
 * 用继承?
 * v05:使用代理
 * v06:代理有各种类型
 * 问题:如何实现代理的各种组合?继承?Decorator?
 * v07:代理的对象改成Movable类型-越来越像decorator了
 *
 */
public class Tank implements Movable {

    /**
     * 模拟坦克移动了一段儿时间
     */
    @Override
    public void move() {
        System.out.println("Tank moving claclacla...");
        try {
            Thread.sleep(new Random().nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        Tank t = new Tank();
        TankTimeProxy ttp = new TankTimeProxy(t);
        TankLogProxy tlp = new TankLogProxy(ttp);
        tlp.move();
    }
}

class TankTimeProxy implements Movable {
    Movable m;

    public TankTimeProxy(Movable m) {
        this.m = m;
    }

    @Override
    public void move() {
        long start = System.currentTimeMillis();
        m.move();
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
}

class TankLogProxy implements Movable {
    Movable m;

    public TankLogProxy(Movable m) {
        this.m = m;
    }

    @Override
    public void move() {
        System.out.println("start moving...");
        m.move();
        long end = System.currentTimeMillis();
        System.out.println("stopped!");
    }
}

interface Movable {
    void move();
}

operation result

3. Dynamic proxy

Compared with static proxies, dynamic proxies are more flexible. We do not need to create a separate proxy class for each target class, and we do not need to implement the interface. We can directly proxy the implementation class (  CGLIB dynamic proxy mechanism ).

From a JVM perspective, a dynamic proxy dynamically generates class bytecode at runtime.

When it comes to dynamic proxies, Spring AOP and RPC frameworks should be mentioned. Their implementation relies on dynamic proxies.

Dynamic proxies are relatively rarely used in our daily development, but it is almost a must-use technology in the framework. After learning dynamic agents, it is also very helpful for us to understand and learn the principles of various frameworks.

As far as Java is concerned, there are many ways to implement dynamic proxy, such as  JDK dynamic proxy , CGLIB dynamic proxy and so on.

guide-rpc-framework
uses JDK dynamic proxy. Let's first look at the use of JDK dynamic proxy.

Also, although guide-rpc-framework

CGLIB dynamic proxy is not used   . Here we briefly introduce its use and comparison with JDK dynamic proxy .

3.1. JDK dynamic proxy mechanism

3.1.1. Introduction

InvocationHandler Interfaces and  classes are the core in the Java dynamic proxy mechanism  Proxy .

Proxy The most frequently used method in the class is: newProxyInstance() , this method is mainly used to generate a proxy object.

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        ......
    }

This method has a total of 3 parameters:

  1. loader  : Class loader, used to load proxy objects.
  2. interfaces  : some interfaces implemented by the proxy class;
  3. h  : object that implements  InvocationHandler the interface;

To implement a dynamic proxy, you must also implement InvocationHandler custom processing logic. When our dynamic proxy object calls a method, the call to this method will be forwarded to the method that implements the InvocationHandler interface class  invoke .

public interface InvocationHandler {

    /**
     * 当你使用代理对象调用方法的时候实际会调用到这个方法
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

invoke() The method has the following three parameters:

  1. proxy  : dynamically generated proxy class
  2. method  : corresponds to the method called by the proxy class object
  3. args  : parameters of the current method

That is to say:  when the proxy object you create through Proxy the class  calls a method, it will actually call the method  of the class that implements the interface  . newProxyInstance()InvocationHandlerinvoke() You can  invoke() customize the processing logic in the method, such as what to do before and after the method is executed. 

3.1.2. Steps to use JDK dynamic proxy class

  1. Define an interface and its implementation class;
  2. Customize the InvocationHandler and rewrite the invoke method. In the invoke method, we will call the native method (the method of the proxy class) and customize some processing logic;
  3. Create a proxy object through the Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) method; 

3.1.3. Code example 1

This may be a bit empty and difficult to understand. Let me give you an example. Let’s experience it!

1. Define the interface for sending text messages

public interface SmsService {
    String send(String message);
}

2. Implement the interface for sending text messages

public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

3. Define a JDK dynamic proxy class

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author shuang.kou
 * @createTime 2020年05月11日 11:23:00
 */
public class DebugInvocationHandler implements InvocationHandler {
    /**
     * 代理类中的真实对象
     */
    private final Object target;

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

    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method " + method.getName());
        Object result = method.invoke(target, args);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method " + method.getName());
        return result;
    }
}

invoke() Method: When our dynamic proxy object calls a native method, what is actually called is the  invoke() method, and then  invoke() the method calls the native method of the proxy object on our behalf.

4. Get the factory class of the proxy object

public class JdkProxyFactory {
    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // 目标类的类加载
                target.getClass().getInterfaces(),  // 代理需要实现的接口,可指定多个
                new DebugInvocationHandler(target)   // 代理对象对应的自定义 InvocationHandler
        );
    }
}

getProxy() : Mainly Proxy.newProxyInstance()obtain the proxy object of a certain class through methods

5. Actual use

SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java");

After running the above code, the console prints out:

before method send
send message:java
after method send

3.1.4. Code example 2

Code display

package com.company.proxy.v08;


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Random;

/**
 * 问题:我想记录坦克的移动时间
 * 最简单的办法:修改代码,记录时间
 * 问题2:如果无法改变方法源码呢?
 * 用继承?
 * v05:使用代理
 * v06:代理有各种类型
 * 问题:如何实现代理的各种组合?继承?Decorator?
 * v07:代理的对象改成Movable类型-越来越像decorator了
 * v08:如果有stop方法需要代理...
 * 如果想让LogProxy可以重用,不仅可以代理Tank,还可以代理任何其他可以代理的类型 Object
 * (毕竟日志记录,时间计算是很多方法都需要的东西),这时该怎么做呢?
 * 分离代理行为与被代理对象
 * 使用jdk的动态代理
 */
public class Tank implements Movable {

    /**
     * 模拟坦克移动了一段儿时间
     */
    @Override
    public void move() {
        System.out.println("Tank moving claclacla...");
        try {
            Thread.sleep(new Random().nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Tank tank = new Tank();

        //reflection 通过二进制字节码分析类的属性和方法

        Movable m = (Movable)Proxy.newProxyInstance(Tank.class.getClassLoader(),
                new Class[]{Movable.class}, //tank.class.getInterfaces()
                new LogHander(tank)
        );

        m.move();
    }
}

class LogHander implements InvocationHandler {

    Tank tank;

    public LogHander(Tank tank) {
        this.tank = tank;
    }
    //getClass.getMethods[]
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("method " + method.getName() + " start..");
        Object o = method.invoke(tank, args);
        System.out.println("method " + method.getName() + " end!");
        return o;
    }
}


interface Movable {
    void move();
}

operation result:

3.2. CGLIB dynamic proxy mechanism 

3.2.1. Introduction

One of the most fatal problems with JDK dynamic proxy is that it can only proxy classes that implement interfaces.

In order to solve this problem, we can use the CGLIB dynamic proxy mechanism to avoid it.

CGLIB
(Code Generation Library) is an ASM-based bytecode generation library that allows us to modify and dynamically generate bytecode at runtime. CGLIB implements proxies through inheritance. Many well-known open source frameworks use CGLIB, such as the AOP module in Spring: if the target object implements the interface, the JDK dynamic proxy is used by default, otherwise the CGLIB dynamic proxy is used.

MethodInterceptor Interfaces and  classes are the core in the CGLIB dynamic proxy mechanism  Enhancer .

You need to customize  MethodInterceptor and override  intercept methods intercept to intercept methods that enhance the proxied class.

public interface MethodInterceptor
extends Callback{
    // 拦截被代理类中的方法
    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                               MethodProxy proxy) throws Throwable;
}

  1. obj  : the object being proxied (the object that needs to be enhanced)
  2. method  : intercepted method (method that needs to be enhanced)
  3. args  : method input parameters
  4. proxy  : used to call the original method

You can  Enhancerdynamically obtain the proxy class through the class. When the proxy class calls a method,  the method MethodInterceptor in  is actually called intercept . 

3.2.2. Steps to use CGLIB dynamic proxy class

  1. define a class;
  2. Customize  MethodInterceptor and rewrite  intercept methods intercept to intercept methods that enhance the proxy class,  invoke similar to methods in JDK dynamic proxy;
  3. Create proxy classes through  Enhancer classes  ;create()

3.2.3. Code examples 

Unlike JDK, dynamic proxy does not require additional dependencies. CGLIB

( Code Generation Library ) is actually an open source project. If you want to use it, you need to manually add related dependencies.

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

1. Implement a class that uses Alibaba Cloud to send text messages.

package github.javaguide.dynamicProxy.cglibDynamicProxy;

public class AliSmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

2. Customization (method interceptor)

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

import java.lang.reflect.Method;

/**
 * 自定义MethodInterceptor
 */
public class DebugMethodInterceptor implements MethodInterceptor {

    /**
     * @param o           代理对象(增强的对象)
     * @param method      被拦截的方法(需要增强的方法)
     * @param args        方法入参
     * @param methodProxy 用于调用原始方法
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method " + method.getName());
        Object object = methodProxy.invokeSuper(o, args);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method " + method.getName());
        return object;
    }

}

3. Get the proxy class

import net.sf.cglib.proxy.Enhancer;

public class CglibProxyFactory {

    public static Object getProxy(Class<?> clazz) {
        // 创建动态代理增强类
        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 设置被代理类
        enhancer.setSuperclass(clazz);
        // 设置方法拦截器
        enhancer.setCallback(new DebugMethodInterceptor());
        // 创建代理类
        return enhancer.create();
    }
}

 4. Actual use

AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("java");

After running the above code, the console prints out:

before method send
send message:java
after method send

3.3. Comparison between JDK dynamic proxy and CGLIB dynamic proxy 

  • JDK dynamic proxy can only proxy classes that implement interfaces or directly proxy interfaces, while CGLIB can proxy classes that do not implement any interfaces. In addition, CGLIB dynamic proxy intercepts method calls of the proxied class by generating a subclass of the proxied class, so it cannot proxy classes and methods declared as final types.
  • As far as the efficiency of the two is concerned, in most cases the JDK dynamic proxy is better. With the upgrade of the JDK version, this advantage becomes more obvious. 

4. Comparison between static proxy and dynamic proxy 

  • Flexibility: Dynamic proxies are more flexible. They do not need to implement interfaces. They can directly proxy implementation classes and do not need to create a proxy class for each target class. In addition, in a static proxy, once a new method is added to the interface, both the target object and the proxy object must be modified, which is very troublesome!
  • JVM level: The static proxy turns the interface, implementation class, and proxy class into actual class files during compilation. The dynamic proxy dynamically generates class bytecode at runtime and loads it into the JVM.

5. Summary

This article mainly introduces two implementations of the proxy mode: static proxy and dynamic proxy. Covers the actual combat between static proxy and dynamic proxy, the difference between static proxy and dynamic proxy, the difference between JDK dynamic proxy and Cglib dynamic proxy, etc.

Reference article:  Detailed explanation of Java proxy mode_Gerald Newton's blog-CSDN blog_Detailed explanation of Java proxy mode

Guess you like

Origin blog.csdn.net/m0_50370837/article/details/126323005