代理模式详解

Posted 鱼翔空

tags:

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

什么叫代理模式?

代理模式是在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类增加功能。代理对象在在原始类和代理类之间起到了中介作用。

代理模式的目的:

  • 保护原始类;

  • 增强原始类;

我们在生活中的经常遇到这样的场景如:房屋中介、猎头等。

中介保护了我们的隐私,我们除了提供基本的房屋买卖、找工作等需求,中介会通过自己的资源、渠道等帮我们实现,我们本来的意愿并没有改变。

静态代理

了解下yxkong要通过贝壳去买房的过程。

买房的流程

/**
 * 买房要求
 * @author yxkong
 * @create 2021/5/13
 * @since 1.0.0
 */
public interface BuyHouse {
    /**
     * 买房要求
     * @param totalPrice 总价
     */
    public void buy(double totalPrice);
}

yxkong买房实现,目标类(原始类)

public class YxkongBuyHouse implements BuyHouse {
    @Override
    public void buy(double totalPrice) {
       System.out.println(String.format("yxkong买了100平,三室总价为%s的房子",totalPrice));
    }
}

yxkong通过中介ke买房子的代理实现

public class KeBuyHouseProxy implements BuyHouse {
    private YxkongBuyHouse buyHouse;

    public KeBuyHouseProxy(YxkongBuyHouse buyHouse) {
        this.buyHouse = buyHouse;
    }
    @Override
    public void buy(double totalPrice) {
        System.out.println("ke先了解购房者的需求");
        System.out.println("ke确认购买房屋");
        System.out.println("ke帮忙砍价");
        //最终执行还是yxkong自己买房,只不过在yxkong买房的基础上增加了一些其他功能
        buyHouse.buy(totalPrice);
        System.out.println("ke收取买家手续费");
        System.out.println("ke收取卖家手续费");
    }
}

这上面的代码实现就是一个静态代理的实现。静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。

动态代理

那我们能不能不一个个的创建代理类来实现代理呢?答案肯定是可以的

JDK动态代理

jdk给我们提供了一套机制

原始类,还是原来的,我们用jdk实现动态代理

public class JDKBuyHouseHandler implements InvocationHandler {

    private Object targetObj;
    public JDKBuyHouseHandler(Object targetObj) {
        this.targetObj = targetObj;
    }
    private void beforeBuy(){
        System.out.println("中介先了解购房者的需求");
        System.out.println("中介确认购买房屋");
        System.out.println("中介帮忙砍价");
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        beforeBuy();
        Object obj = method.invoke(this.targetObj,args);
        afterBuy();
        return obj;
    }
    private void afterBuy(){
        System.out.println("中介收取买家手续费");
        System.out.println("中介收取卖家手续费");
    }
}
// 代理工厂,只要同一类型的事,我们只要一套流程机制
public class ProxyFactory {
    public Object createProxy(Object targetObj) {
        //1,获得目标对象的所有接口
        Class[] interfaces = targetObj.getClass().getInterfaces();
        //2,通过jdk的InvocationHandler定义代理的扩展模板handler处理器
        JDKBuyHouseHandler handler = new JDKBuyHouseHandler(targetObj);
        //3,通过Proxy 利用目标对象的classLoader创建代理实例
        return Proxy.newProxyInstance(targetObj.getClass().getClassLoader(), interfaces, handler);
    }

    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();
        //中介可以代理鱼翔空,也可以代理别人的买房
        BuyHouse buyHouse = (BuyHouse)proxyFactory.createProxy(new YxkongBuyHouse());
        buyHouse.buy(1000000);
    }
}
输出结果
中介先了解购房者的需求
中介确认购买房屋
中介帮忙砍价
yxkong买了100平,三室总价为1000000.0的房子
中介收取买家手续费
中介收取卖家手续费

JDK Proxy采用字节码重组,重新生成新的对象来替代原始的对象以达到动态代理的目的,JDK Proxy生成对象的步骤如下:

  • 通过实现InvocationHandler接口定义自己的InvocationHandler;

  • 通过Proxy.newProxyInstance 获得动态代理对象(直接绕过了)

也可以通过Proxy.getProxyClass生成代理类,然后通过反射的机制获取实例和执行方法。

看下代码

   public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
{
        //InvocationHandler 必须存在
        Objects.requireNonNull(h);
        //
        final Class<?>[] intfs = interfaces.clone();
        /*
         * 创建代理类
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        //获取代理类的构造器
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        //实例化
        return cons.newInstance(new Object[]{h});
    }

感兴趣的可以深入的翻一翻源码。

CGLIB 动态代理

不知大家发现了没,上面不管是静态代理还是动态代理,都需要目标对象必须实现接口。但是现实情况并不是所有的目标对象都是通过面向接口编程的。

这时,我们可以使用CGLIB,使用目标对象子类的方式实现代理。

CGLIB核心类

net.sf.cglib.proxy.Enhancer – 主要的增强类
net.sf.cglib.proxy.MethodInterceptor – 主要的方法拦截类,它是Callback接口的子接口,需要用户实现
net.sf.cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用,如使用:
Object o = methodProxy.invokeSuper(proxy, args);//虽然第一个参数是被代理对象,也不会出现死循环的问题。
net.sf.cglib.proxy.MethodInterceptor接口是最通用的回调(callback)类型,它经常被基于代理的AOP用来实现拦截(intercept)方法的调用。这个接口只定义了一个方法
public Object intercept(Object object, java.lang.reflect.Method method,
Object[] args, MethodProxy proxy) throws Throwable;
第一个参数是代理对像,第二和第三个参数分别是拦截的方法和方法的参数。原来的方法可能通过使用java.lang.reflect.Method对象的一般反射调用,或者使用 net.sf.cglib.proxy.MethodProxy对象调用。net.sf.cglib.proxy.MethodProxy通常被首选使用,因为它更快。

还以买房为例

public class CglibBuyHouseProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        beforeBuy();
        System.out.println(method.getName());
        //spring的aop就是用这种方式
        Object result = proxy.invoke(obj,args);
        // 这种方式不会纳入spring的ioc管理,因为执行的父方法
        // Object result = proxy.invokeSuper(obj,args);
        afterBuy();
        return result;
    }
    private void beforeBuy(){
        System.out.println("中介先了解购房者的需求");
        System.out.println("中介确认购买房屋");
        System.out.println("中介帮忙砍价");
    }
    private void afterBuy(){
        System.out.println("中介收取买家手续费");
        System.out.println("中介收取卖家手续费");
    }

    public static void main(String[] args) {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/yxk/logs/");
        CglibBuyHouseProxy proxy = new CglibBuyHouseProxy();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(YxkongBuyHouse.class);
        enhancer.setCallback(proxy);
        YxkongBuyHouse buyHouseProxy =(YxkongBuyHouse) enhancer.create();
        buyHouseProxy.buy(1880000);
    }
}

CGLIB debugging enabled, writing to '/Users/yxk/logs/'
中介先了解购房者的需求
中介确认购买房屋
中介帮忙砍价
buy
yxkong买了100平,三室总价为1880000.0的房子
中介收取买家手续费
中介收取卖家手续费

把接口去掉,也可以的,大家可以试试。

不管是jdk proxy还是cglib,都是在运行时执行的。

还有一种技术手段,我觉得也可以当成是动态代理的一种。

字节码增强技术。

比如利用byte-buddy。这种手段是在类加载的时候,通过插桩的方式增强目标类的功能。相对来说比较麻烦。

大家可以看下之前的线程池监控的实现。

线程池监控-bytebuddy-agent模式

如果你对文章比较满意,可以关注公众号:5ycode

公众号图片

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

代理设计模式之静态代理与动态代理(超..)详解

详解 Java 中的三种代理模式

详解 Java 中的三种代理模式!

详解 Java 中的三种代理模式

设计模式----代理模式

代理模式详解