Design pattern: structural pattern -- Proxy Pattern

Design pattern: structural pattern – Proxy Pattern

1. Introduction

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

Because the coupling degree of combination relation or aggregation relation is lower than that of inheritance relation and meets the "composite Reuse Principle (CRP)", the object structured pattern has more flexibility than the class structured pattern.

Structural modes are divided into the following 7 types:

  • Proxy Pattern
  • Adapter Pattern
  • Bridge Pattern
  • Filter mode (filter, Criteria Pattern)
  • Composite Pattern
  • Decorator Pattern
  • Facade Pattern
  • Flyweight Pattern

2. General

For some reason, we need to provide a proxy to an object to control access to that object. At this time, the access object is not suitable or cannot directly reference the target object. The proxy object acts as the intermediary between the access object and the target object.

Agents in Java can be divided into static agents and dynamic agents according to the generation timing of agent classes:

  • Static proxy class: generated at compile time.
  • Dynamic proxy class: dynamically generated in Java runtime. Dynamic agent is divided into JDK agent and CGLib agent.

3. Composition

The Proxy mode mainly includes the following three roles:

  • Abstract Subject: business methods implemented by declaring real subjects and proxy objects through interfaces or abstract classes.
  • Real Subject: it implements the specific business in the abstract subject. It is the real object represented by the proxy object and the object to be referenced finally.
  • Proxy class: it provides the same interface as the real topic. It contains references to the real topic. It can access, control or expand the functions of the real topic.

4. Concrete realization

There are three implementation modes of agent mode:

  • Static proxy
  • JDK dynamic agent
  • CGLIB dynamic proxy

4.1 static agent

Here, we use a case to feel the static agent.

[example] ticket selling at railway station:

If you want to buy a train ticket, you need to go to the railway station to buy a ticket, take the bus to the railway station, queue up and a series of operations, which is obviously more troublesome. The railway station has consignment points in many places, so it's much more convenient for us to buy tickets at the consignment point. This example is actually a typical agency model. The railway station is the target object and the consignment point is the agent object. The class diagram is as follows:

The specific implementation code is as follows:

  • Abstract topic class SellTickets:

    Declare business methods implemented by real topics and proxy objects through interfaces or abstract classes.

    /**
     * @author zzay
     * @interfaceName SellTickets
     * @description Structural pattern - proxy pattern: static proxy (abstract topic class)
     * @create 2022/02/27 13:09
     */
    public interface SellTickets {
    
        // Declare business methods implemented by real topics and proxy objects through interfaces or abstract classes.
        void sell();
    
    }
    
  • Specific topic TrainStation:

    It realizes the specific business in the abstract topic. It is the real object represented by the proxy object and the object to be referenced finally.

    /**
     * @author zzay
     * @className TrainStation
     * @description Structural mode - agent mode: static agent (specific subject class)
     * @create 2022/02/27 13:10
     */
    public class TrainStation implements SellTickets {
    
        // It realizes the specific business in the abstract topic. It is the real object represented by the proxy object and the object to be referenced finally.
        public void sell() {
            System.out.println("Sold a ticket.");
        }
    
    }
    
  • Proxy ProxyPoint:

    It provides the same interface as the real topic, which contains references to the real topic. It can access, control or extend the functions of real topics.

    /**
     * @author zzay
     * @className ProxyPoint
     * @description Structured mode - agent mode: static agent (agent class)
     * @create 2022/02/27 13:10
     */
    public class ProxyPoint implements SellTickets {
    
        // Object structured pattern, which uses combination or aggregation to combine objects
        private TrainStation trainStation = new TrainStation();
        
        // It provides the same interface as the real topic, which contains references to the real topic.
        // It can access, control or extend the functions of real topics.
        public void sell() {
            System.out.println("Add some fees at the proxy point.");
            trainStation.sell();
        }
    
    }
    
  • Test Client:

    /**
     * @author zzay
     * @className Client
     * @description Structured mode - agent mode: static agent (test class)
     * @create 2022/02/27 13:11
     */
    public class Client {
    
        public static void main(String[] args) {
            ProxyPoint proxyPoint = new ProxyPoint();
            proxyPoint.sell();
        }
    
    }
    

As can be seen from the above code, the test class Client directly accesses the proxy class ProxyPoint class object. That is, ProxyPoint acts as a mediator between the access object Client and the target object TrainStation. At the same time, the sell() method is also enhanced (charging some proxy service fees).

4.2 dynamic agent

4.2.1 JDK implementation

Next, we use JDK dynamic agent to implement the above case.

Java provides a dynamic Proxy class Proxy, but note that Proxy is not the class of Proxy object mentioned above, but provides a static method newProxyInstance() to create Proxy object to obtain Proxy object. In addition, JDK dynamic Proxy requires that the interface must be defined to Proxy the interface.

The specific implementation code is as follows:

  • Dynamic proxy factory class ProxyFactory:

    This class creates a Proxy through the JDK's own Proxy.

    /**
     * @author zzay
     * @className ProxyFactory
     * @description Structural mode - agent mode: dynamic agent (JDK Implementation)
     * @create 2022/02/27 13:23
     */
    public class ProxyFactory {
    
        // Specific topic class: it implements the specific business in the abstract topic. It is the real object represented by the proxy object and the object to be referenced finally.
        private TrainStation trainStation = new TrainStation();
    
        // Use the JDK's own Proxy to obtain the Proxy object
        public SellTickets getProxyObject() {
            /*
            newProxyInstance()Description of method parameters:
                ClassLoader loader:    
                	Class loader, which is used to load proxy classes. You can use the class loader of real objects
                Class<?>[] interfaces: 
                	The interface implemented by real object, proxy mode, real object and proxy object implement the same interface
                InvocationHandler h:   
                	Call handler for proxy object
             */
            SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(
    				// Fill in the class loader of the real topic object in order to load the proxy class
                    trainStation.getClass().getClassLoader(),   
                    // The interface of the abstract topic implemented by the real topic object, in order to make the proxy object implement the same interface
                	trainStation.getClass().getInterfaces(),    
                	// The proxy object is a program that calls and processes the business interface of the real subject object
                    new InvocationHandler() {   
                        /*
                        InvocationHandler Parameter description of invoke() method in:
                            proxy:  Proxy object
                            method: Corresponds to the Method instance that calls the interface Method on the proxy object
                            args:   The actual parameters passed when the proxy object calls the interface method
                         */
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            // Additional business logic of agent point
                            System.out.println("The agency charges service fees(JDK Dynamic agent mode)");
                            // Execute real object
                            Object result = method.invoke(trainStation, args);
                            return result;
                        }
                    });
            return sellTickets;
        }
    
    }
    
  • Test Client:

    /**
     * @author zzay
     * @className Client
     * @description Structural mode - agent mode: dynamic agent (JDK Implementation)
     * @create 2022/02/27 13:33
     */
    public class Client {
    
        public static void main(String[] args) {
            // Get proxy object
            ProxyFactory proxyFactory = new ProxyFactory();
            SellTickets proxyObject = proxyFactory.getProxyObject();
            // Execute business logic
            proxyObject.sell();
        }
    
    }
    

Using dynamic agents, consider the following questions:

  • Is ProxyFactory a proxy class?

    ProxyFactory is not the proxy class in proxy mode. In this implementation, the proxy class is a class dynamically generated in memory during the running process of the program. Through Alibaba's open source Java diagnostic tool Arthas, you can view the structure of the proxy class:

    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;
    
    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) {
            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 = 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());
            }
        }
    
        public final boolean equals(Object object) {
            try {
                return (Boolean)this.h.invoke(this, m1, new Object[]{object});
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final String toString() {
            try {
                return (String)this.h.invoke(this, m2, null);
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final int hashCode() {
            try {
                return (Integer)this.h.invoke(this, m0, null);
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final void sell() {
            try {
                this.h.invoke(this, m3, null);
                return;
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    }
    

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

    • The proxy class $Proxy0 implements the abstract topic class SellTickets. This confirms that the real topic class and proxy class mentioned earlier implement the same interface.
    • The proxy class $Proxy0 passed the anonymous inner class object we provided to the parent class.
  • What is the execution process of dynamic agent?

    Here are the key codes extracted:

    //Agent class dynamically generated during program running
    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);
        }
    }
    
    //Dynamic proxy related classes provided by Java
    public class Proxy implements java.io.Serializable {
    	protected InvocationHandler h;
    	 
    	protected Proxy(InvocationHandler h) {
            this.h = h;
        }
    }
    
    //Agent factory
    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("The agency charges some service fees(JDK Dynamic agent mode)");
                            Object result = method.invoke(station, args);
                            return result;
                        }
                    });
            return sellTickets;
        }
        
    }
    
    
    //Test access class
    public class Client {
        public static void main(String[] args) {
            //Get proxy object
            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 subclass object of the InvocationHandler interface.
4. invoke method executes the sell() method in the class TrainStation to which the real subject object belongs through reflection.

4.2.2 CGLIB implementation

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

If the abstract topic class SellTickets is not defined and only the real topic class TrainStation is defined, the JDK agent cannot be used because the JDK dynamic agent requires that the interface must be defined to proxy the interface.

CGLIB is a powerful and high-performance code generation package. It provides proxies for classes that do not implement interfaces, and provides a good supplement to the dynamic proxy of JDK. Since CGLIB is a package provided by a third party, the coordinates of the jar package need to be introduced:

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

The specific implementation code is as follows:

  • Proxy factory class ProxyFactory:

    This class creates a Proxy through the Enhancer (similar to the Proxy provided by JDK) provided by the third-party package CGLIB.

    /**
     * @author zzay
     * @className ProxyFactory
     * @description Structural mode - agent mode: dynamic agent (CGLIB Implementation)
     * @create 2022/02/27 14:22
     */
    public class ProxyFactory implements MethodInterceptor {
    
        // The real subject object that needs to be created as a proxy
        private TrainStation target = new TrainStation();
    
        // Create proxy object
        public TrainStation getProxyObject() {
            // Create an Enhancer object, which is similar to the Proxy class of JDK dynamic Proxy. The next step is to set several parameters
            Enhancer enhancer = new Enhancer();
            // Set bytecode object (real topic class)
            enhancer.setSuperclass(target.getClass());
            // Setting interface (abstract topic interface implemented by real topic class)
            enhancer.setInterfaces(target.getClass().getInterfaces());
            // Set callback function
            enhancer.setCallback(this);
            // Create proxy object
            TrainStation object = (TrainStation) enhancer.create();
            return object;
        }
    
        /*
        intercept Description of method parameters:
            o :       Proxy object
            method :  Method instances of methods in real objects
            args :    Actual parameters
            methodProxy : Method instance of method in proxy object
         */
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("The agency charges some service fees(CGLIB Dynamic agent mode)");
            TrainStation result = (TrainStation) methodProxy.invokeSuper(o, objects);
            return result;
        }
    
    }
    
  • Test Client:

    /**
     * @author zzay
     * @className Client
     * @description Structured mode - agent mode: dynamic agent (test class)
     * @create 2022/02/27 14:22
     */
    public class Client {
    
        public static void main(String[] args) {
            //Create proxy factory object
            ProxyFactory proxyFactory = new ProxyFactory();
            //Get proxy object
            TrainStation proxyObject = proxyFactory.getProxyObject();
            proxyObject.sell();
        }
    
    }
    

5. Comparison of three agents

  • Dynamic agent vs static agent:

    Compared with static proxy, the biggest advantage of dynamic proxy is that all the methods declared in the interface are transferred to a centralized method of the calling processor (InvocationHandler.invoke(...)). In this way, when there are a large number of interface methods, we can handle them flexibly without transferring each method like a static agent.

    If a method is added to the interface, all proxy classes need to implement this method in addition to all implementation classes in the static proxy mode. It increases the complexity of code maintenance. Dynamic agents do not have this problem. (reason: the proxy class of static proxy mode needs to implement abstract topic class, while the dynamic proxy mode does not)

  • JDK agent vs CGLIB agent:

    The bottom layer of CGLib uses ASM bytecode generation framework and bytecode technology to generate proxy classes in jdk1 Before 6, it was more efficient than using Java reflection. The only thing to note is that CGLib cannot delegate classes or methods declared as final, because the principle of CGLib is to dynamically generate subclasses of the delegated class.

    In jdk1 6,JDK1.7,JDK1.8 after gradually optimizing the JDK dynamic agent, the efficiency of JDK agent is higher than that of CGLib agent under the condition of less calls. Only when a large number of calls are made, jdk1 6 and jdk1 7 is a little less efficient than CGLib agent, but to jdk1 8, the efficiency of JDK agent is higher than that of CGLib agent.

    Therefore, if there is an interface, use JDK dynamic proxy; if there is no interface, use CGLIB proxy.


6. Advantages and disadvantages

  • advantage:

    • The proxy mode plays the role of mediating and protecting the target object between the client and the target object.
    • The proxy object can extend the function of the target object.
    • The proxy mode can separate the client from the target object and reduce the coupling degree of the system to a certain extent.
  • Disadvantages:

    • It increases the complexity of the system.

7. Usage scenario

  • Remote agent:

    Local services request remote services over the network. In order to realize local to remote communication, we need to realize network communication and deal with possible exceptions. For good code design and maintainability, we hide the network communication part and expose only one interface to the local service. Through this interface, we can access the functions provided by the remote service 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 will transfer 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

Keywords: Java Design Pattern

Added by yalag on Sun, 06 Mar 2022 06:58:15 +0200