设计模式-代理模式

Posted vbirdbest

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式-代理模式相关的知识,希望对你有一定的参考价值。

如果你有钱,你可以改变规则,
如果你有权,你可以制定规则,
如果你没钱也没权,恭喜你,规则就是为你量身定做的。

一:代理设计模式简介

当访问对象不适合或者不能直接引用目标对象,此时需要通过一个中间商来作为中介来完成。如买房找中介,相亲找媒婆,买火车票找代售点。代理提供了与被代理相同的接口,其内部含有对被代理的引用,可以访问、控制或者扩展真实主题的功能。

二:静态代理:在编译期生成

public interface SellTickets 
    void sell();


// 真正售卖火车票官方售票点
public class TranStationImpl implements SellTickets 
    @Override
    public void sell() 
        System.out.println("火车站卖票");
    

public class TranProxy implements SellTickets
    private TranStationImpl tranStation = new TranStationImpl();

    @Override
    public void sell() 
        System.out.println("收取服务费");
        tranStation.sell();
        System.out.println("送888元SPA");
    


public class Client 
    public static void main(String[] args) 
        TranProxy tranProxy = new TranProxy();
        tranProxy.sell();
    


三:JDK动态代理:在运行时动态生成

/**
 * 代理工厂不是代理类,它提供了一个方法用户获取代理对象,而代理对象是在程序运行中动态生成的一个对象。
 */
public class ProxyFactory 
	// 真正售票的目标对象
    private TranStationImpl tranStation = new TranStationImpl();

    public SellTickets getProxyObject() 
        SellTickets proxyObject = (SellTickets) Proxy.newProxyInstance(
                tranStation.getClass().getClassLoader(),
                tranStation.getClass().getInterfaces(),
                new InvocationHandler() 
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
                        // proxy: 代理对象,和Proxy.newProxyInstance()方法的返回值是一个对象
                        // method: 代理对象调用的方法,这里是sell()方法
                        // args: 调用方法的实参
                        // return 表示代理方法的返回值
                        System.out.println("当执行代理对象的某个方法时调用, 处理一些自己的业务逻辑");
                        Object object = method.invoke(tranStation, args);
                        System.out.println("JDK动态代理:收取服务费");
                        return object;
                    
                
        );
        return proxyObject;
    

public class Client 
    public static void main(String[] args) 
        ProxyFactory proxyFactory = new ProxyFactory();

        SellTickets proxyObject = proxyFactory.getProxyObject();
        proxyObject.sell();

        // 动态代理返回的类:com.sun.proxy.$Proxy0
        System.out.println(proxyObject.getClass());
        
        // 辅助代码,通过阿里工具arthas-boot.jar实时获取运行的com.sun.proxy.$Proxy0源码
        while (true)
    


通过阿里工具arthas-boot.jar实时获取运行的com.sun.proxy.$Proxy0源码

精简后的$Proxy0源码为:

// JDK动态代理必须实现接口
public final class $Proxy0 extends Proxy implements SellTickets 
    private static Method m3;

    static 
        m3 = Class.forName("com.example.proxy.SellTickets").getMethod("sell", new Class[0]);
    

    public $Proxy0(InvocationHandler invocationHandler) 
        super(invocationHandler);
    

    public final void sell() 
        this.h.invoke(this, m3, null);
    

package java.lang.reflect;
public class Proxy implements java.io.Serializable 
    protected InvocationHandler h;

  • 代理工厂不是代理类,它提供了一个方法用户获取代理对象,而代理对象是在程序运行中动态生成的一个对象。
  • 动态代理对象$Proxy0也需要实现SellTickets接口,所以 $Proxy0也是SellTickets的一个实现类。
  • 当客户端运行proxyFactory.getProxyObject()时就会将InvocationHandler对象复制给父类java.lang.reflect.Proxy中的h引用。
  • 当客户端运行proxyObject.sell()会调用$Proxy0.sell() -> invocationHandler.invoke() , 在这一系列调用之前其实都没有真正的执行到真正的目标售票对象的售票方法,真正调用目标对象的方法还是自己在InvocationHandler对象中通过反射手动调用,同时也可以在invocationHandler.invoke()方法中增加自己的逻辑。

四: 静态代理和JDK动态代理比较

  • 静态代理:通过硬编码定义一个代理对象 TranProxy.java.
  • JDK动态代理:定义一个代理工程ProxyFactory.java用于通过执行方法获取代理对象com.sun.proxy.$Proxy0,原先静态代理需要代理的方法放在InvocationHandler中通过反射来实现。
  • 当有多个方法需要代理时此时静态代理需要定义多个方法,而动态代理可以统一在同一个方法里处理InvocationHandler.invoke()
  • 如果接口中增加了方法,那么每个静态代理类都要增加相应的实现,增加了维护成本,如果是动态代理,因为是通过反射直接调用的不需要做任何修改,这是动态代理最大的好处

五:CGLib动态代理

JDK动态代理要求必须实现接口,而CGLib对接口没有要求,可以直接对对象进行代理。CGLib是一个第三方jar,是用来动态生成代码的工具了,是对JDK动态代理的一种补充。CGLib生成的代理对象是代理对象的子类,所以不能代理final修饰的类和方法。

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>
import com.example.proxy.TranStationImpl;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class ProxyFactory implements MethodInterceptor 
    private TranStationImpl tranStation = new TranStationImpl();

    public TranStationImpl getProxyObject() 
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(TranStationImpl.class);
        enhancer.setCallback(this);
        TranStationImpl proxyObjec = (TranStationImpl)enhancer.create();
        return proxyObjec;
    

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable 
        System.out.println("当执行代理对象的某个方法时调用, 处理一些自己的业务逻辑");
        Object object = method.invoke(tranStation, objects);
        System.out.println("CGLIB动态代理:收取服务费");
        return object;
    

public class Client 
    public static void main(String[] args) 
        ProxyFactory proxyFactory = new ProxyFactory();
        TranStationImpl proxyObject = proxyFactory.getProxyObject();
        proxyObject.sell();

		System.out.println(proxyObject);
    

六:JDK动态代理和CGLIB动态代理比较

  • JDK动态代理必须实现接口,而CGLIB动态代理不需要实现接口。
  • JDK动态代理生成代理对象是接口的实现类,CGLib生成的代理对象是代理对象的子类
  • 两者的实现方式都差不多,如果有接口就使用JDK代理,如果没有接口就使用CGLib代理。

七:代理模式的优缺点

优点:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护作用。
  • 代理对象可以扩展目标对象的功能。
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度。

缺点:

  • 增加了系统的复杂度。

八:使用场景

  • 远程(Remote)代理:本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通过部分隐藏起来,只暴露本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关注通信细节部分。RPC远程过程调用就是典型的远程代理。
  • 防火墙(Firewall)代理:当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。VPN就是典型的防火墙带你管理。
  • 保护(Protect or Access)代理:控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。

以上是关于设计模式-代理模式的主要内容,如果未能解决你的问题,请参考以下文章

Solidity代理/实现模式中实现合约回调函数的使用

代理/实现模式下合约插槽索引计算

模式的秘密-代理模式-静态代理

软件架构模式--代理模式

Java设计模式Proxy代理模式

Seata执行整体流程(AT模式)| Seata源码 - 自动配置数据库代理 | AT和XA的区别