代理模式详解

前言:本文参考《Spring5核心原理与30个类手写实战》,加入了自己的理解,做了自认为更详细的解释帮助自己和大家理解,如有侵权,联系删除。

本文用于简单理解代理模式,从静态代理到动态代理,了解如何使用jdk和CGLib的动态代理,关于动态代理的原理和源码分析,以及手写JDK动态代理,将放在下一篇文章

代理模式的应用场景

在生活中,中介,黄牛,经纪人;在代码中,事务代理,非侵入性(只对代码的功能进行增强,不去修改源代码)日志监听等,都是代理模式的实际应用。代理模式(Proxy Pattern)的定义很简单,就是指为其他对象提供一个代理,来控制对这个对象的访问,代理对象相当于客户端和目标对象(即被代理对象)的中介,代理模式属于结构类设计模式

使用代理模式主要有两个目的

一是保护目标对象,二是增强目标对象

代理模式类图如下

Subject

顶层接口,目标对象和代理对象都要实现该接口

RealSubject

真实对象,即被代理对象,把该对象交给代理对象,由代理对象调用该对象的方法

Proxy

代理对象

subject属性:由于通过代理对象来是实现目标对象的功能,就要在代理对象内持有被代理对象的引用,即在代理对象内创建一个目标对象的实例,通过这个实例调用目标对象的方法,实现目标对象的功能

request():目标方法

before():在目标方法前执行的方法

after():在目标方法后执行的方法

Proxy代码结构如下

public class Proxy implements Subject
{
    public Subject subject;

    public Proxy(Subject subject)
    {
        this.subject = subject;
    }

    @Override
    public void request()
    {
        before();
        subject.request();
        after();
    }
    public void before(){}
    public void after(){}
}

Client

客户端,即调用代理方法的类

总结:

一般代理可以理解为代码增强,实际上就是在源代码逻辑前后增加一些代码逻辑,而调用者不用感知,代理模式分为静态代理和动态代理

静态代理

以一个相亲的例子引入动态代理,小明要去相亲,通过父母介绍来完成相亲这个行为,在这里例子里,小明(儿子)就是目标对象(被代理对象),父母就是代理对象,相亲这个行为就是目标方法。

代码实现

顶层接口Person类
public interface Person
{
    public void findLove();
}
目标对象Son
public class Son implements Person
{
    @Override
    public void findLove()
    {
        //我工作忙,没有时间,只能提出要求
        System.out.println("儿子要求:女的活的");
    }
}
代理对象Father
public class Father implements Person
{
    private Son son;

    public Father(Son son)
    {
        this.son = son;
    }

    @Override
    public void findLove()
    {
        before();
        son.findLove();
        after();
    }
    public void before()
    {
        System.out.println("父亲帮忙物色对象");
    }
    public void after()
    {
        System.out.println("双方同意交往,确认关系");
    }
}
客户端(测试代码)
    @Test
    public void test()
    {
        Father father = new Father(new Son());
        father.findLove();
    }
类图

除了在类图中没有展示客户端(测试类),其他都与上面的类图完全一致

代码运行结果

父亲帮忙物色对象

儿子要求:女的活的

双方同意交往,确认关系

静态代理的缺点

每一个目标都需要自己的代理对象,灵活性不强,且代码重复度高

动态代理

在上面静态代理的例子里,通过父母帮助小明完成了相亲行为,找到了对象,但是静态代理的弊端也显现出来了,父母只能帮儿子找对象,不能帮表妹,不能帮陌生人,通用性不强,如果给每个人都创建一个代理对象,成本太高,大量代码重复,需要一个更为通用的解决方案,就是动态代理。这次模拟一个媒婆(婚介所),可以为不同的人介绍相亲对象,完成一个完整的相亲行为。

JDK方式实现

顶层接口Person

public interface Person
{
    public void findLove();
}

代理对象JDKMeipo

public class JDKMeipo implements InvocationHandler
{
    //保存被代理对象的引用
    private Object target;

    /**
     * 获取代理对象的实例
     * @param target 目标对象
     * @return 代理对象的实例
     */
    public Object getInstance(Object target)
    {
        this.target = target;
        Class<?> clazz = target.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }
    //下面会再讲解一下InvocationHandler接口和invoke()方法
    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable
    {
        before();
        Object obj = method.invoke(this.target, objects);
        after();
        return obj;
    }
    public void before()
    {
        System.out.println("我是媒婆,告诉我你的要求我帮你物色对象");
    }
    public void after()
    {
        System.out.println("如果合适的话,准备办事");
    }

目标对象Customer

public class Customer implements Person
{
    @Override
    public void findLove()
    {
        System.out.println("男的,活的");
    }
}

测试类

    @Test
    public void test()
    {
        Person obj = (Person) new JDKMeipo().getInstance(new Customer());
        obj.findLove();
    }

运行结果

我是媒婆,告诉我你的要求我帮你物色对象

男的,活的

如果合适的话,准备办事

动态代理涉及的类和方法

InvocationHandler

翻译为调用处理程序,作用可以理解为将目标对象和代理对象相关联,调用目标对象方法的时候,拦截下来,由代理对象进行处理(invoke方法)。

Proxy.newProxyInstance()方法

用于创建动态代理对象,获取代理对象的实例

该方法的三个参数分别为

ClassLoader 类加载器,Class 类 和 InvocationHandler调用处理程序

Class<?> clazz = target.getClass();
Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);

在本代码中传入的参数为,目标对象的类加载器,目标对象的类接口,和一个调用处理程序,因为自身实现了InvocationHandler接口,自身就是一个调用处理程序,所以可以把自己传进去

invoke(Object o, Method method, Object[] objects)方法

这个方法就是调用目标方法的地方,第一个参数是调用这个方法的代理对象实例,第二个参数是目标方法,第三个方法是目标方法的参数

method.invoke()方法

这个方法用于执行目标方法,一共有两个参数,第一个是目标对象,第二个是目标对象的参数,并返回目标方法的返回值

CGLib代理调用API实现

还是以媒婆为例

引入依赖

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib-nodep</artifactId>
            <version>3.2.10</version>
        </dependency>

代理对象CGLibMeipo

public class CGLibMeipo implements MethodInterceptor
{
    public Object getInstance(Class<?> clazz)
    {
        //下面会解释一下Enhancer及其方法
        //现在可简单理解为将目标对象和代理对象联系起来
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable
    {
        before();
        //与JCK动态代理类似,此步为执行目标方法,两个参数分别为目标对象和目标方法的参数
        Object obj = methodProxy.invokeSuper(o, objects);
        after();
        //返回目标方法的返回值
        return obj;
    }
    public void before()
    {
        System.out.println("我是媒婆,告诉我你的要求我帮你物色对象");
    }
    public void after()
    {
        System.out.println("如果合适的话,准备办事");
    }
}

目标对象Customer

public class Customer
{
    public void findLove()
    {
        System.out.println("男的,活的");
    }
}

 客户端(测试方法)

    @Test
    public void test()
    {
        Customer obj = (Customer) new CGLibMeipo().getInstance(Customer.class);
        obj.findLove();
    }

运行结果

我是媒婆,告诉我你的要求我帮你物色对象

男的,活的

如果合适的话,准备办事

类和方法的简单讲解

MethodInterceptor

方法拦截器,和JDK动态代理的InvocationHandler类似,都是在目标对象的方法被调用的时候,进行拦截,通过自身的方法来执行

intercept

核心方法,与Jdk的invoke方法类似,用于调用目标方法

Enhancer

字节码增强工具

setSuperClass()方法用于设置代理对象实例的父类

setCallBack()方法用于设置目标方法被调用时,使用哪个拦截器来处理,由于自身就是一个拦截器,所以直接设置为this

create()生成代理对象

分析

有没有发现CGlib和Jdk实现的动态代理,大体上是很像的,都是先将代理对象和目标对象联系起来,再通过代理对象调用目标对象的方法

不同点:CGlib比JDK少了一个顶层接口,即CGlib代理的目标对象不需要实现任何接口,他是通过动态基础目标对象实现的

本人第一次写文章,希望能用自己的理解帮助大家,如有错误,请大家指出,如果有说的不清楚的地方请在评论里提出,我看到就会回答

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值