Proxy mode-static proxy, dynamic proxy (JDK proxy and CGLib proxy)

Overview

For some reasons, it is necessary to provide a proxy for an object 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 an intermediary between the access object and the target object .

The proxy in Java is divided into static proxy and dynamic proxy according to the different generation time of proxy class .

The static proxy proxy class is generated at compile time, while the dynamic proxy proxy class is dynamically generated during Java runtime.

There are two dynamic agents, JDK agent and CGLib agent.

structure

Proxy mode is divided into three roles:

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

Static proxy

Let's get a feel for static proxy through cases.

[Example] Selling tickets at train stations

If you want to buy a train ticket, you need to go to the train station to buy a ticket, take a train to the train station, and wait in line for a series of operations, which is obviously more troublesome. And the railway station has consignment points in many places, so it is much more convenient for us to go to the consignment point to buy tickets.

This example is actually a typical agency model. The train station is the target and the sales agent is the agency.

The class diagram is as follows:

SellTickets.java

package com.itheima.pattern.proxy.static_proxy;

/**
 * @version v1.0
 * @ClassName: SellTickets
 * @Description: 卖火车票的接口
 * @Author: dym
 */
public interface SellTickets {

    void sell();
}

TrainStation.java

package com.itheima.pattern.proxy.static_proxy;

/**
 * @version v1.0
 * @ClassName: TrainStation
 * @Description: 火车站类
 * @Author: dym
 */
public class TrainStation implements SellTickets {

    public void sell() {
        System.out.println("火车站卖票");
    }
}

ProxyPoint.java

package com.itheima.pattern.proxy.static_proxy;

/**
 * @version v1.0
 * @ClassName: ProxyPoint
 * @Description: 代售点类
 * @Author: dym
 */
public class ProxyPoint implements SellTickets {

    //声明火车站类对象
    private TrainStation trainStation  = new TrainStation();

    public void sell() {
        System.out.println("代售点收取一些服务费用");
        trainStation.sell();
    }

}

Client.java

package com.itheima.pattern.proxy.static_proxy;

/**
 * @version v1.0
 * @ClassName: Client
 * @Description: TODO(一句话描述该类的功能)
 * @Author: dym
 */
public class Client {
    public static void main(String[] args) {
        //创建代售点类对象
        ProxyPoint proxyPoint = new ProxyPoint();
        //调用方法进行买票
        proxyPoint.sell();
    }
}

It can be seen from the above code that the test class directly accesses the ProxyPoint class object,

In other words, ProxyPoint acts as an intermediary between the access object and the target object.

At the same time, the sell method has been enhanced (the agency charges some service fees).



JDK dynamic proxy

Next, we use dynamic proxy to implement the above case, let's talk about the dynamic proxy provided by JDK first.

Java provides a dynamic proxy class Proxy. Proxy is not the class of proxy objects we mentioned above.

Instead, it provides a static method ( newProxyInstance method) to create a proxy object to get the proxy object.

code show as below:

SellTickets.java

package com.itheima.pattern.proxy.jdk_proxy;

/**
 * @version v1.0
 * @ClassName: SellTickets
 * @Description: 卖火车票的接口
 * @Author: dym
 */
public interface SellTickets {

    void sell();
}

TrainStation.java

package com.itheima.pattern.proxy.jdk_proxy;

/**
 * @version v1.0
 * @ClassName: TrainStation
 * @Description: 火车站类
 * @Author: dym
 */
public class TrainStation implements SellTickets {

    public void sell() {
        System.out.println("火车站卖票");
    }
}

ProxyFactory.java

package com.itheima.pattern.proxy.jdk_proxy;

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

/**
 * @version v1.0
 * @ClassName: ProxyFactory
 * @Description: 获取代理对象的工厂类
 *      代理类也实现了对应的接口
 * @Author: dym
 */
public class ProxyFactory {

    //声明目标对象
    private TrainStation station = new TrainStation();

    //获取代理对象的方法
    public SellTickets getProxyObject() {
        //返回代理对象
        /*
            ClassLoader loader : 类加载器,用于加载代理类。可以通过目标对象获取类加载器
            Class<?>[] interfaces : 代理类实现的接口的字节码对象
            InvocationHandler h : 代理对象的调用处理程序
         */
        SellTickets proxyObject = (SellTickets)Proxy.newProxyInstance(
                station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                new InvocationHandler() {

                    /*
                        Object proxy : 代理对象。和proxyObject对象是同一个对象,在invoke方法中基本不用
                        Method method : 对接口中的方法进行封装的method对象
                        Object[] args : 调用方法的实际参数

                        返回值: 方法的返回值。
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //System.out.println("invoke方法执行了");
                        System.out.println("代售点收取一定的服务费用(jdk动态代理)");
                        //执行目标对象的方法
                        Object obj = method.invoke(station, args);
                        return obj;
                    }
                }
        );
        return proxyObject;
    }
}

Client.java

package com.itheima.pattern.proxy.jdk_proxy;

/**
 * @version v1.0
 * @ClassName: Client
 * @Description: TODO(一句话描述该类的功能)
 * @Author: dym
 */
public class Client {
    public static void main(String[] args) {
        //获取代理对象
        //1,创建代理工厂对象
        ProxyFactory factory = new ProxyFactory();
        //2,使用factory对象的方法获取代理对象
        SellTickets proxyObject = factory.getProxyObject();
        //3,调用卖调用的方法
        proxyObject.sell();

        System.out.println(proxyObject.getClass());


    }
}



Using dynamic agents, we think about the following questions:

  • Is ProxyFactory a proxy class?

    ProxyFactory is not the proxy class mentioned in the proxy mode,

  • The proxy class is a class dynamically generated in memory during the running of the program .

  • Check 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;
​
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 . This also confirms that the real class and proxy class we said before implement the same interface .
    • The proxy class ($Proxy0) passes the anonymous inner class object we provided to the parent class .
  • What is the execution process of the dynamic agent?

    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 to which the real object belongs (TrainStation) through reflection


CGLIB dynamic agent

The same is the case above, we again use the CGLIB proxy implementation.

If the SellTickets interface is not defined, only TrainStation (train station class) is defined.

Obviously, the JDK proxy cannot be used, because the JDK dynamic proxy requires that an interface must be defined to proxy the interface .

CGLIB is a powerful, high-performance code generation package.

It provides a proxy for classes that do not implement an interface, and provides a good supplement to the dynamic proxy of the JDK.

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>

TrainStation.java

package com.itheima.pattern.proxy.cglib_proxy;


/**
 * @version v1.0
 * @ClassName: TrainStation
 * @Description: 火车站类
 * @Author: dym
 */
public class TrainStation {

    public void sell() {
        System.out.println("火车站卖票");
    }
}

ProxyFactory.java

package com.itheima.pattern.proxy.cglib_proxy;

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

import java.lang.reflect.Method;

/**
 * @version v1.0
 * @ClassName: ProxyFactory
 * @Description: 代理对象工厂,用来获取代理对象
 * @Author: dym
 */
public class ProxyFactory implements MethodInterceptor {

    //声明火车站对象
    private TrainStation station = new TrainStation();

    public TrainStation getProxyObject() {
        //创建Enhancer对象,类似于JDK代理中的Proxy类
        Enhancer enhancer = new Enhancer();
        //设置父类的字节码对象。指定父类
        enhancer.setSuperclass(TrainStation.class);
        //设置回调函数
        enhancer.setCallback(this);
        //创建代理对象
        TrainStation proxyObject = (TrainStation) enhancer.create();
        return proxyObject;
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //System.out.println("方法执行了");
        System.out.println("代售点收取一定的服务费用(CGLib代理)");
        //要调用目标对象的方法
        Object obj = method.invoke(station, objects);
        return obj;
    }
}

Client.java

package com.itheima.pattern.proxy.cglib_proxy;

/**
 * @version v1.0
 * @ClassName: Client
 * @Description: TODO(一句话描述该类的功能)
 * @Author: dym
 */
public class Client {
    public static void main(String[] args) {
        //创建代理工厂对象
        ProxyFactory factory = new ProxyFactory();
        //获取代理对象
        TrainStation proxyObject = factory.getProxyObject();
        //调用代理对象中的sell方法卖票
        proxyObject.sell();
    }
}



Comparison of three agents

  • jdk agent and CGLIB agent

    Use CGLib to implement dynamic proxy. The bottom layer of CGLib uses ASM bytecode generation framework and bytecode technology 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 the class or method declared as final, because the principle of CGLib is to dynamically generate subclasses of the proxy class.

After JDK1.6, JDK1.7, and JDK1.8 have gradually optimized the JDK dynamic proxy, when the number of calls is small, the proxy efficiency of JDK is higher than that of CGLib. Only when a large number of calls are made, JDK1.6 and JDK1.7 is a little less efficient than CGLib proxy,

But by the time of JDK1.8, the efficiency of JDK proxy is higher than that of CGLib proxy. So if there is an interface, use the JDK dynamic proxy, if there is no interface, use the 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 the invocation handler (InvocationHandler.invoke) .

In this way, when the number of interface methods is relatively large, we can deal with it flexibly,

There is no need to transfer every method like a static proxy. If an interface adds a method, the static proxy mode needs to implement this method in addition to all implementation classes, and all proxy classes also need to implement this method . Increased the complexity of code maintenance.

The dynamic proxy does not have this problem

 

Pros and cons

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;
  • The proxy mode can separate the client from the target object, which reduces the coupling of the system to a certain extent;

Disadvantages:

  • Increase the complexity of the system;

scenes to be used

  • Remote (Remote) proxy

    Local services request remote services through the network. In order to achieve local to remote communication, we need to implement network communication to deal with possible abnormalities. 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 having to care too much about the details of the communication part.

  • Firewall proxy

    When you configure your browser to use the proxy function, the firewall will forward your browser's request to the Internet; when the Internet returns a response, the proxy server will forward it to your browser.

  • Protection (Protect or Access) agent

    Control access to an object, and if necessary, provide different users with different levels of usage rights.

Guess you like

Origin blog.csdn.net/qq_39368007/article/details/113948288
Recommended