Agent model of structured model

summary

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 can not directly reference the target object. The proxy object acts as an 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 time of agent classes. Static proxy classes are generated at compile time, while dynamic proxy classes are generated dynamically at Java runtime. Dynamic agents include JDK agent and CGLib agent.

structure

The Proxy mode is divided into three roles:

  • Abstract Subject class: business methods implemented by declaring real subjects and proxy objects through interfaces or abstract classes.
  • Real Subject class: 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 extend the functions of the real topic.

Static proxy

First, let's take a look at static proxies.

Train station ticket selling

If you want to buy a train ticket, you need to go to the railway station to buy a ticket, take a 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. 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 code is as follows:

/**
 * @author: xuzhilei
 * @create: 2021-12-30
 * @description: Ticket selling interface
 **/
public interface SellTickets {
    /**
     * Selling tickets
     */
    void sell();
}
/**
 * @author: xuzhilei
 * @create: 2021-12-30
 * @description: Railway station
 **/
public class TrainStation implements SellTickets{
    @Override
    public void sell() {
        System.out.println("Train station ticket");
    }
}
/**
 * @author: xuzhilei
 * @create: 2021-12-30
 * @description: Consignment point
 **/
public class ProxyPoint implements SellTickets{
    /**
     * Declare variables of railway station type
     */
    private TrainStation trainStation;

    /**
     * Inject the railway station object through the construction method
     * @param trainStation Parameter is an object of railway station type
     */
    public ProxyPoint(TrainStation trainStation) {
        this.trainStation = trainStation;
    }

    /**
     * Ticket selling method of agent class
     */
    @Override
    public void sell() {
        //Additional behavior of proxy objects
        System.out.println("Some service fees are charged at the consignment point");
        //Calling the ticket selling method of the railway station object
        trainStation.sell();
    }
}
public class Client {
    public static void main(String[] args) {
        //Create a railway station object
        TrainStation trainStation = new TrainStation();
        //Create proxy object
        ProxyPoint proxyPoint = new ProxyPoint(trainStation);
        //Ticket selling point
        proxyPoint.sell();
    }
}

Test results:

From the above code, we can see that the test class directly accesses the ProxyPoint class object, that is, ProxyPoint acts as the intermediary between the access object and the target object. The sell method is also enhanced (the proxy point charges some service fees).

JDK dynamic agent

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

The code is as follows:

/**
 * @author: xuzhilei
 * @create: 2021-12-30
 * @description: Ticket selling interface
 **/
public interface SellTickets {
    /**
     * Selling tickets
     */
    void sell();
}
/**
 * @author: xuzhilei
 * @create: 2021-12-30 
 * @description: Railway station
 **/
public class TrainStation implements SellTickets {
    @Override
    public void sell() {
        System.out.println("Train station ticket");
    }
}
/**
 * @author: xuzhilei
 * @create: 2021-12-30
 * @description: Agent factory class
 * Dynamic proxy implementation using JDK
 **/
public class ProxyFactory {

    private SellTickets sellTickets;

    /**
     * Construct a method to inject the specific object to be represented
     * @param sellTickets The specific proxy object of SellTickets interface is implemented
     */
    public ProxyFactory(SellTickets sellTickets) {
        this.sellTickets = sellTickets;
    }

    /**
     * Method to get proxy object
     *
     * @return Proxy object implementing SellTickets interface
     */
    public SellTickets getProxyObject() {
        /*
            Get Proxy object using Proxy
            newProxyInstance()Method parameter description:
                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 the real object, the proxy mode, and the real object and the proxy object implement the same interface
                InvocationHandler h :  Call handler for proxy object
         */
        SellTickets proxyInstance = (SellTickets) Proxy.newProxyInstance(sellTickets.getClass().getClassLoader(), sellTickets.getClass().getInterfaces(), new InvocationHandler() {
            /**
             * InvocationHandler Parameter Description:
             *   proxy :  Proxy object
             *   method :  Method instance corresponding to the interface method called on the proxy object
             *   args :  The actual parameters passed when a proxy object calls an interface method
             * @param proxy Proxy object
             * @param method Method instance corresponding to the interface method called on the proxy object
             * @param args The actual parameters passed when a proxy object calls an interface method
             * @return The return value of the method actually executed in the proxy object
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //Additional behavior of proxy objects
                System.out.println("Some service fees are charged at the consignment point(JDK Dynamic agent mode)");
                //The method to be executed by the proxy object
                return method.invoke(sellTickets, args);
            }
        });
        //Return proxy object
        return proxyInstance;
    }
}
/**
 * @author: xuzhilei
 * @create: 2021-12-30
 * @description: Test the implementation of JDK dynamic agent
 **/
public class Client {
    public static void main(String[] args) {
        //Create the object to be proxied
        TrainStation trainStation = new TrainStation();
        //Create proxy factory object
        ProxyFactory proxyFactory = new ProxyFactory(trainStation);
        //Get proxy object
        SellTickets proxyObject = proxyFactory.getProxyObject();
        //Execute ticket selling method
        proxyObject.sell();
    }
}

Test results:

Using dynamic agents, we consider the following questions:

Is ProxyFactory a proxy class?

ProxyFactory is not the proxy class mentioned in the proxy mode, but the proxy class is a class dynamically generated in memory during the running process of the program. Check the structure of the proxy class through Alibaba's open source Java diagnostic tool (Arthas):

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 SellTickets, which proves that the real class and proxy class we 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 class
public class ProxyFactory {

    private SellTickets sellTickets;

    public ProxyFactory(SellTickets sellTickets) {
        this.sellTickets = sellTickets;
    }
    public SellTickets getProxyObject() {
        SellTickets proxyInstance = (SellTickets) Proxy.newProxyInstance(sellTickets.getClass().getClassLoader(), sellTickets.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //Additional behavior of proxy objects
                System.out.println("Some service fees are charged at the consignment point(JDK Dynamic agent mode)");
                //The method to be executed by the proxy object
                return method.invoke(sellTickets, args);
            }
        });
        return proxyInstance;
    }
}

The execution process is as follows:

  1. Call the sell() method through the proxy object in the test class
  2. According to the 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. The invoke method executes the sell() method in the class to which the real object belongs (TrainStation) through reflection

CGLIB dynamic proxy

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

If the SellTickets interface is not defined, only trainstation (railway station class) is defined. Obviously, JDK proxy cannot be used, because JDK dynamic proxy requires that interfaces must be defined and proxy interfaces.

CGLIB is a powerful and high-performance code generation package. It provides a proxy for classes that do not implement interfaces, and provides a good supplement to the dynamic proxy of JDK.

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

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

The code is as follows:

/**
 * @description: Railway station
 **/
public class TrainStation {
    /**
     * Selling tickets
     */
    public void sell() {
        System.out.println("Train station ticket");
    }
}


/**
 * @author: xuzhilei
 * @create: 2021-12-30
 * @description: Agent factory
 * Dynamic proxy implementation using CGLIB
 **/
public class ProxyFactory implements MethodInterceptor {
    /**
     * Declare the class or its subclasses to delegate
     */
    private TrainStation trainStation;

    /**
     * Injection by construction method
     *
     * @param trainStation The class or subclass object to delegate
     */
    public ProxyFactory(TrainStation trainStation) {
        this.trainStation = trainStation;
    }

    /**
     * Create proxy object
     * @return TrainStation
     */
    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();
        //Sets the bytecode object of the parent class
        enhancer.setSuperclass(trainStation.getClass());
        //Set callback object
        enhancer.setCallback(this);
        //Create proxy object
        TrainStation proxyObject = (TrainStation) enhancer.create();
        return proxyObject;
    }

    /**
     * interceptor method 
     * @param o Proxy object
     * @param method Method instances of methods in real objects
     * @param objects Actual parameters
     * @param methodProxy Method instance of the method in the proxy object
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //Additional behavior of proxy objects
        System.out.println("Some service fees are charged at the consignment point(CGLIB Dynamic agent mode)");
        //The method to be executed by the proxy object
        Object result = method.invoke(trainStation, objects);
        return result;
    }
}


/**
 * @author: xuzhilei
 * @create: 2021-12-30
 * @description: Testing CGLB to implement dynamic agent
 **/
public class Client {
    public static void main(String[] args) {
        //Create proxied object
        TrainStation trainStation = new TrainStation();
        //Create agent factory
        ProxyFactory proxyFactory = new ProxyFactory(trainStation);
        //Get proxy object
        TrainStation proxyObject = proxyFactory.getProxyObject();
        //Execute ticket selling method
        proxyObject.sell();

    }
}

Test results:

Comparison of three agents

*jdk agent and CGLIB agent

CGLib is used to realize dynamic proxy. The bottom layer of CGLib adopts 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 proxied class.

At jdk1 6,JDK1.7,JDK1.8 after gradually optimizing the JDK dynamic agent, the JDK agent efficiency is higher than the cglib agent efficiency 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 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, and if there is no interface, use cglib proxy.

*Dynamic agent and static agent

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 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 static proxy.

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

Advantages and disadvantages

advantage:

-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;
-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

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

Control access to an object. If necessary, different users can be provided with different levels of permissions.

Write it at the end: encourage all CSDN partners to record a little every day and make progress every day

Keywords: Java Design Pattern Algorithm

Added by tariel on Sat, 01 Jan 2022 01:37:10 +0200