前言:本文参考《Spring5核心原理与30个类手写实战》,加入了自己的理解,做了自认为更详细的解释帮助自己和大家理解,如有侵权,联系删除。
本文用于简单理解代理模式,从静态代理到动态代理,了解如何使用jdk和CGLib的动态代理,关于动态代理的原理和源码分析,以及手写JDK动态代理,将放在下一篇文章
代理模式的应用场景
在生活中,中介,黄牛,经纪人;在代码中,事务代理,非侵入性(只对代码的功能进行增强,不去修改源代码)日志监听等,都是代理模式的实际应用。代理模式(Proxy Pattern)的定义很简单,就是指为其他对象提供一个代理,来控制对这个对象的访问,代理对象相当于客户端和目标对象(即被代理对象)的中介,代理模式属于结构类设计模式
使用代理模式主要有两个目的
一是保护目标对象,二是增强目标对象
代理模式类图如下
![](https://img-blog.csdnimg.cn/img_convert/46db55a384612b257bfe679b253b5a04.png)
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();
}
类图
![](https://img-blog.csdnimg.cn/img_convert/d7e3c09fd59d5690466614f58d5d27a6.png)
除了在类图中没有展示客户端(测试类),其他都与上面的类图完全一致
代码运行结果
父亲帮忙物色对象
儿子要求:女的活的
双方同意交往,确认关系
静态代理的缺点
每一个目标都需要自己的代理对象,灵活性不强,且代码重复度高
动态代理
在上面静态代理的例子里,通过父母帮助小明完成了相亲行为,找到了对象,但是静态代理的弊端也显现出来了,父母只能帮儿子找对象,不能帮表妹,不能帮陌生人,通用性不强,如果给每个人都创建一个代理对象,成本太高,大量代码重复,需要一个更为通用的解决方案,就是动态代理。这次模拟一个媒婆(婚介所),可以为不同的人介绍相亲对象,完成一个完整的相亲行为。
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代理的目标对象不需要实现任何接口,他是通过动态基础目标对象实现的
本人第一次写文章,希望能用自己的理解帮助大家,如有错误,请大家指出,如果有说的不清楚的地方请在评论里提出,我看到就会回答