Design Patterns_Structural Patterns-《Agent Patterns》

Design Patterns_Structural Patterns-《Agent Patterns》

The notes are organized from the detailed explanation of Java design patterns by dark horse programmers, 23 Java design patterns (diagram + framework source code analysis + actual combat)

Structural patterns describe how to organize classes or objects into larger structures in a certain layout. It is divided into class structure pattern and object structure pattern. The former uses inheritance mechanism to organize interfaces and classes, and the latter uses combination or aggregation to combine objects.

Since the combination or aggregation relationship is less coupled than the inheritance relationship and satisfies the "principle of composite reuse", the object structure model has greater flexibility than the class structure model.

Structural patterns are divided into the following seven types:

  • Proxy mode
  • adapter pattern
  • decorator pattern
  • bridge mode
  • appearance mode
  • combination mode
  • Flyweight mode

overview

Proxy Pattern : For some reason, an object needs to be provided with a proxy to control access to the object. At this time, the access object is not suitable or cannot directly refer to the target object, and the proxy object acts as a link between the access object and the target object 中介.

Proxies in Java are divided into static proxies and dynamic proxies according to the generation timing of proxy classes.

  • Static proxy proxy classes are generated at compile time
  • The dynamic proxy proxy class is dynamically generated at Java runtime. Dynamic proxy has:
    • JDK Proxy
    • CGLib Proxy

structure

The Proxy mode is divided into three roles:

  • Abstract subject (Subject) class: Declare the business methods implemented by real subjects and proxy objects through interfaces or abstract classes.
  • Real Subject (Real Subject) class: realizes the specific business in the abstract subject, is the real object represented by the proxy object, and is the final object to be referenced.
  • Proxy (Proxy) class: Provides the same interface as the real theme, which contains references to the real theme, which can access, control or extend the functions of the real theme.

static proxy

Let's take a look at the static proxy through a case.

[Example] selling tickets at a train station

If you want to buy a train ticket, you need to go to the train station to buy the ticket, take the train to the train station, wait in line and wait for a series of operations, which is obviously more troublesome. And the railway station has sales offices in many places, so it is much more convenient for us to go to the sales offices to buy tickets. This example is actually a typical agent model, the train station is the target object, and the sales agency is the agent object. The class diagram is as follows:

code show as below:

  • Abstract theme class - ticket interface

    public interface SellTickets {
          
          
        void sell();
    }
    
  • Real theme class - railway station: the railway station has the function of selling tickets, so it needs to implement the SellTickets interface

    public class TrainStation implements SellTickets {
          
          
    
        public void sell() {
          
          
            System.out.println("火车站卖票");
        }
    }
    
  • Agent class - point of sale

    public class ProxyPoint implements SellTickets {
          
          
    
        // 声明火车站类对象
        private TrainStation station = new TrainStation();
    
        public void sell() {
          
          
            System.out.println("代售点收取一些服务费用");
            station.sell();
        }
    }
    
  • test class

    public class Client {
          
          
        public static void main(String[] args) {
          
          
            // 创建代理对象
            ProxyPoint pp = new ProxyPoint();
            // 调用代理对象的方法进行买票 最终调用的还是火车站的方法
            pp.sell();
        }
    }
    

    output

    代售点收取一些服务费用
    火车站卖票
    

It can be seen from the above code that the test class directly accesses the ProxyPoint class object, that is to say, the ProxyPoint acts as an intermediary between the access object and the target object. At the same time, the sell method has been enhanced (agents charge some service fees).

JDK Dynamic Proxy

Next, we use dynamic proxies to implement the above case. First, let's talk about the dynamic proxies provided by JDK. Java provides a dynamic proxy class Proxy. Proxy is not the class of the proxy object we mentioned above, but provides a static method (newProxyInstance method) to create a proxy object to obtain the proxy object.

code show as below:

  • Ticket interface

    public interface SellTickets {
          
          
        void sell();
    }
    
  • Railway station: The railway station has the function of selling tickets, so it needs to implement the SellTickets interface

    public class TrainStation implements SellTickets {
          
          
    
        public void sell() {
          
          
            System.out.println("火车站卖票");
        }
    }
    
  • Proxy factory, used to create proxy objects

    public class ProxyFactory {
          
          
    
        private TrainStation station = new TrainStation();
    
        // 获取代理对象的方法
        public SellTickets getProxyObject() {
          
          
            // 使用Proxy获取代理对象
            /*
             * newProxyInstance()方法参数说明:
             * ClassLoader loader:类加载器,用于加载代理类,使用真实对象的类加载器即可
             * Class<?>[] interfaces:真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口
             * InvocationHandler h:代理对象的调用处理程序
             */
            SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(
                	station.getClass().getClassLoader(),
                    station.getClass().getInterfaces(),
                    new InvocationHandler() {
          
          
                        /*
                         * InvocationHandler中invoke方法参数说明:
                         * Object proxy:代理对象,和proxyObject对象是同一个对象,在invoke方法中基本不用
                         * Method method:对应于在代理对象上调用的接口方法的 Method 实例
                         * Object[] args:代理对象调用接口方法时传递的实际参数
                         * 返回值:就是方法的返回值
                         */
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          
          
                            System.out.println("代理点收取一些服务费用(JDK动态代理方式)");
                            // 执行真实对象
                            Object result = method.invoke(station, args);
                            return result;
                        }
                    });
            return sellTickets;
        }
    }
    
  • test class

    public class Client {
          
          
        public static void main(String[] args) {
          
          
            // 获取代理对象
            // 1.创建代理工厂对象
            ProxyFactory factory = new ProxyFactory();
            // 2.使用factory对象的方法获取代理对象
            SellTickets proxyObject = factory.getProxyObject();
            // 3.调用卖票的方法
            proxyObject.sell();
        }
    }
    

    output

    代理点收取一定的服务费用(JDK动态代理方式)
    火车站卖票
    

Using dynamic proxy, we think about the following questions:

  • Is ProxyFactory a proxy class?

    ProxyFactory is not the proxy class mentioned in the proxy mode, but 代理类是程序在运行过程中动态的在内存中生成的类. View the structure of the proxy class through Alibaba's open source Java diagnostic tool (Arthas [Alsace]):

    package com.sun.proxy;
    
    import com.itheima.proxy.dynamic.jdk.SellTickets;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    // jdk动态生成的类,实现了SellTickets接口
    public final class $Proxy0 extends Proxy implements SellTickets {
          
          
        private static Method m1;
        private static Method m2;
        private static Method m3;
        private static Method m0;
    
        public $Proxy0(InvocationHandler invocationHandler) {
          
          
            // 构造方法传入的InvocationHandler(也就是我们自己new的InvocationHandler)赋值给父类的InvocationHandler属性
            super(invocationHandler);
        }
    
        static {
          
          
            try {
          
          
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
                // 只有这个m3是我们需要注意的
                // 通过全类名加载了接口 再去拿到接口中的同名方法 将这个Method对象赋值给m3
                m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
                m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
                return;
            }
            catch (NoSuchMethodException noSuchMethodException) {
          
          
                throw new NoSuchMethodError(noSuchMethodException.getMessage());
            }
            catch (ClassNotFoundException classNotFoundException) {
          
          
                throw new NoClassDefFoundError(classNotFoundException.getMessage());
            }
        }
    
        // 省略了equals、toString、hashCode方法...
    
        // 卖票方法
        public final void sell() {
          
          
            try {
          
          
                // 调用h.invoke 其实就是调用我们自己实现的InvocationHandler中的invoke方法
                /*
                 * this:当前对象(代理类$Proxy0)
                 * m3:要执行的方法
                 * null:方法没有参数,所以为null
                 */
                this.h.invoke(this, m3, null);
                return;
            }
            catch (Error | RuntimeException throwable) {
          
          
                throw throwable;
            }
            catch (Throwable throwable) {
          
          
                throw new UndeclaredThrowableException(throwable);
            }
        }
    }
    
    // 父类Proxy
    public class Proxy implements java.io.Serializable {
          
          
        
        // InvocationHandler属性 声明为h
        protected InvocationHandler h;
    }
    

    From the above class, we can see the following information:

    • The proxy class ($Proxy0) implements the SellTickets interface. This also confirms what we said before that the real class and the proxy class implement the same interface.
    • The proxy class ($Proxy0) passes the anonymous inner class object InvocationHandler we provided to the parent class.
  • What is the execution process of dynamic proxy?

    The following is the key code extracted:

    // 程序运行过程中动态生成的代理类
    public final class $Proxy0 extends Proxy implements SellTickets {
          
          
        private static Method m3;
    
        public $Proxy0(InvocationHandler invocationHandler) {
          
          
            super(invocationHandler);
        }
    
        static {
          
          
            m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
        }
    
        public final void sell() {
          
          
            this.h.invoke(this, m3, null);
        }
    }
    
    // Java提供的动态代理相关类
    public class Proxy implements java.io.Serializable {
          
          
    	protected InvocationHandler h;
    	 
    	protected Proxy(InvocationHandler h) {
          
          
            this.h = h;
        }
    }
    
    // 代理工厂类
    public class ProxyFactory {
          
          
    
        private TrainStation station = new TrainStation();
    
        public SellTickets getProxyObject() {
          
          
            SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
                    station.getClass().getInterfaces(),
                    new InvocationHandler() {
          
          
                        
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          
          
    
                            System.out.println("代理点收取一些服务费用(JDK动态代理方式)");
                            Object result = method.invoke(station, args);
                            return result;
                        }
                    });
            return sellTickets;
        }
    }
    
    // 测试访问类
    public class Client {
          
          
        public static void main(String[] args) {
          
          
            // 获取代理对象
            ProxyFactory factory = new ProxyFactory();
            SellTickets proxyObject = factory.getProxyObject();
            proxyObject.sell();
        }
    }
    

The execution process is as follows:

  1. Call the sell() method through the proxy object in the test class
  2. According to the characteristics of polymorphism, the sell() method in the proxy class ($Proxy0) is executed
  3. The sell() method in the proxy class ($Proxy0) calls the invoke method of the sub-implementation class object of the InvocationHandler interface
  4. The invoke method executes the sell() method in the class of the real object (TrainStation) through reflection

CGLIB dynamic proxy

In the same case as above, we again use CGLIB proxy implementation.

If the SellTickets interface is not defined, only TrainStation (the train station class) is defined. Obviously, the JDK proxy cannot be used, because the JDK dynamic proxy requires the definition of an interface and the proxying of the interface.

CGLIB is a powerful, high-performance code generation package. It provides proxies for classes that do not implement interfaces, and is a good complement to JDK's dynamic proxies.

CGLIB is a package provided by a third party, so the coordinates of the jar package need to be introduced:

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

code show as below:

  • TRAIN STATION

    public class TrainStation {
          
          
    
        public void sell() {
          
          
            System.out.println("火车站卖票");
        }
    }
    
  • Proxy factory, used to obtain proxy objects

    public class ProxyFactory implements MethodInterceptor {
          
          
    
        // 声明火车站对象
        private TrainStation target = new TrainStation();
    
        public TrainStation getProxyObject() {
          
          
            // 创建Enhancer对象,类似于JDK动态代理的Proxy类。下一步就是设置几个参数
            Enhancer enhancer = new Enhancer();
            // 设置父类的字节码对象(CGLIB的代理类属于目标类的子类)所以这里直接用TrainStation的字节码对象
            enhancer.setSuperclass(target.getClass());
            // 设置回调函数,参数是MethodInterceptor子实现类的对象,使代理工厂实现这个接口,
            // 所以这里传入this 该接口的子实现类对象即可。
            enhancer.setCallback(this);
            // 创建代理对象
            TrainStation obj = (TrainStation) enhancer.create();
            return obj;
        }
    
        /*
         * intercept方法参数说明:
         * o:代理对象
         * method:真实对象中的方法的Method实例
         * args:实际参数
         * methodProxy:代理对象中的方法的method实例
         */
        // 通过代理对象调用需要执行的方法,就会调用intercept方法,这个就是上面的回调函数,设置的是方法所属类的对象this。
        public TrainStation intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
          
          
            System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)");
            // 调用目标对象的方法
            //Object obj = method.invoke(station, objects);
            TrainStation result = (TrainStation) methodProxy.invokeSuper(o, args);
            return result;
        }
    }
    
  • test class

    public class Client {
          
          
        public static void main(String[] args) {
          
          
            // 创建代理工厂对象
            ProxyFactory factory = new ProxyFactory();
            // 获取代理对象
            TrainStation proxyObject = factory.getProxyObject();
    		// 调用代理对象的方法卖票
            proxyObject.sell();
        }
    }
    

    output

    代理点收取一些的服务费用(CGLIB动态代理方式)
    火车站卖票
    

    The difference between using MethodProxy#invokeSuper and invoke in cglib dynamic proxy interceptor

Comparison of three agents

  • jdk proxy and CGLIB proxy
    • Use CGLib to implement dynamic proxy. The bottom layer of CGLib adopts ASM bytecode generation framework, and bytecode technology is used to generate proxy classes. Before JDK1.6, it is more efficient than using Java reflection. The only thing to note is that CGLib cannot proxy a class or method declared as final, because the principle of CGLib is to dynamically generate subclasses of the proxied class.
    • After JDK1.6, JDK1.7, and JDK1.8 have gradually optimized the JDK dynamic proxy, the efficiency of the JDK proxy is higher than that of the CGLib proxy when the number of calls is small. Only when a large number of calls are made, JDK1.6 and JDK1.7 is a little less efficient than CGLib proxy, but when it comes to JDK1.8, JDK proxy is more efficient than CGLib proxy. So if there is an interface use JDK dynamic proxy, if there is no interface use CGLIB proxy.
  • Dynamic Proxy and Static Proxy
    • Compared with static proxy, the biggest advantage of dynamic proxy is that all methods declared in the interface are transferred to a centralized method of invocation handler (InvocationHandler.invoke). In this way, when there are a large number of interface methods, we can handle them flexibly, without needing to transfer each method like a static proxy.
    • If the interface adds a method, in addition to all implementation classes needing to implement this method in the static proxy mode, all proxy classes also need to implement this method. Increased the complexity of code maintenance. This problem does not occur with dynamic proxies.

Advantages and disadvantages

advantage

  • The proxy mode plays an intermediary role between the client and the target object and protects the target object;
  • The proxy object can extend the function of the target object (enhance the function);
  • The proxy mode can separate the client from the target object, reducing the coupling of the system to a certain extent;

shortcoming

  • Increased system complexity;

scenes to be used

  • Remote proxy
    • Local services request remote services over the network. In order to achieve local-to-remote communication, we need to implement network communication and handle possible exceptions. For good code design and maintainability, we hide the network communication part and only expose an interface to the local service, through which the functions provided by the remote service can be accessed without paying too much attention to the details of the communication part.
  • Firewall proxy
    • When you configure your browser to use the proxy function, the firewall forwards your browser's request to the Internet; when the Internet returns a response, the proxy server forwards it to your browser.
  • Protect or Access agent
    • Control access to an object and, if desired, provide different levels of access to different users.

Guess you like

Origin blog.csdn.net/weixin_53407527/article/details/128627869