代理模式
通过代理来访问真实的对象,而不是直接去访问真正干活的对象,比如二房东租房,二房是代理者,而一房东才是真正的房东;或者说生活中的中介。Spring中的AOP就是动态代理
适用场景
- 需要动态修改方法参数
- 方法执行日志功能,比如AOP实现方法拦截记录日志
- 安全检查,比如方法执行时的权限校验
角色说明
抽象角色
:声明代理对象和真实的对象的共同接口(接口或抽象类)代理角色
:代理角色内部包含对真实对象的引用,从而可以操作真实的对象,同时代理对象与真实对象拥有同样的接口以便在任何时候都能够替代真实对象。并且代理对象可以对真实对象附加操作,相当于对真实对象的封装真实角色
:代理角色所代表的真实对象,我们最终要引用的对象(真正干活的对象)
代理方式
目前实现代理的方式共有三种,分别是
JDK静态代理
、JDK动态代理
、Cglib动态代理
,每一种都有各自的使用场景与其优缺点
如何选择合适的代理方法?
我们来看一张表格,会让你一目了然
静态代理 | JDK动态代理 | Cglib动态代理 | |
---|---|---|---|
是否支持动态代理 | 否 | 是 | 是 |
是否提供子类代理 | 否 | 否 | 是 |
提供接口代理(目标类必须实现接口) | 否 | 是 | 否 |
性能 | 三者最高 | JDK1.8中要高于Cglib | JDK1.8之前要高于JDK动态代理 |
可以依据目前i项目情况进行对号入座,很容易确定出该用哪一种代理方式
JDK静态代理
其实就是一个对象中调用另一个对象实现简单的代理功能,没有反射也没有字节码修改,我们平时无形中其实就用了这种代理方式
优点
- 代理类使客户端不需要知道具体的实现是什么,客户端只需要知道代理类(客户端和实现解耦),符合开闭原则
- 静态代理比动态代理性能高
缺点
- 一个静态代理类只能为一个接口服务(如果我们需要一个代理类动态为多个接口服务,则就是动态代理了)
- 每个代理类都需要和目标类实现同样的接口,一旦接口增加,代理类也需要修改
- 代理类每个接口都包含着一个真实对象的引用,如果真实对象很多,则静态代理类就会很臃肿,难以胜任
代码示例
类的一个抽象,代理类和实现类都需要实现同样的接口
public abstract class Subject {
public abstract void visit();
}
代理类,可以看到就只有代理了visit接口,并写死了代理的方法 visit()
public class ProxySubject extends Subject {
private RealSubject realSubject = null;
/**
* 除了代理真实⻆⾊做该做的事情,代理⻆⾊也可以提供附加操作,如:before()和postRequest()
*/
@Override
public void visit() {
before(); //真实⻆⾊操作前的附加操作
if (realSubject == null) {
realSubject = new RealSubject();
}
// 真正干活的对象
realSubject.visit();
after(); //真实⻆⾊操作后的附加操作
}
private void before() {
System.out.println("do something before...");
}
private void after() {
System.out.println("do something after...");
}
}
真正的实现类,代理类中持有这个类的引用
public class RealSubject extends Subject {
@Override
public void visit() {
System.out.println("visit");
}
}
测试中实例化代理类并调用代理类中的方法,然后代理类再调用实现类中的方法
public static void main(String[] args) {
Subject subject = new ProxySubject();
// 调用代理者(实际上是RealSubject在工作)
subject.visit();
}
运行结果
do something before...
visit
do something after...
JDK动态代理
利用拦截器(必须实现InvocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
创建JDK动态代理步骤
- 编写⼀个委托类的接⼝,即静态代理的(Subject接⼝)
- 实现⼀个真正的委托类,即静态代理的(RealSubject类)
- 创建⼀个动态代理类,实现InvocationHandler接⼝,并重写该invoke⽅法
- 在客户端中,⽣成动态代理的对象。
优点
- 在jdk1.8中性能要比cglib高
缺点
- 目标类必须实现接口,只能代理实现了接口的类,而不能实现接口的类就不能实现JDK动态代理
代码示例
实现的抽象类,这也是JDK动态代理的缺点,所有实现都需要实现接口
public interface Subject {
public void visit();
}
代理处理类,可以看到实现了InvocationHandler(这是必须实现),invoke方法中,有一个Method参数,可以根据不通的method的不同代理不同的接口
public class ProxyHandler implements InvocationHandler {
/**
* 代理的对象
*/
private Object proxyObject;
public ProxyHandler(Object target) {
this.proxyObject = target;
}
/**
* 实现invoke方法,实现代理
*
* @param proxy 要代理的对象
* @param method 要代理的方法
* @param args 真实对象方法里的入参
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("do something before...");
method.invoke(proxyObject, args);
System.out.println("do something after...");
return null;
}
public Object getProxyObject() {
return proxyObject;
}
public void setProxyObject(Object proxyObject) {
this.proxyObject = proxyObject;
}
}
最终的实现类
public class RealSubject implements Subject {
@Override
public void visit() {
System.out.println("visit()");
}
}
客户端,代码关键在于Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)⽅法
,该方法会根据指定的参数动态创建代理对象。三个参数的意义如下:
loader
:指定代理对象的类加载器interfaces
:代理对象需要实现的接口,可以同时指定多个接口handler
:【重要】方法调用的实际处理者,代理对象的方法都会转发到这里
public static void main(String[] args) {
// 代理的对象,可以动态切换代理
RealSubject userService = new RealSubject();
// 代理的具体实现
ProxyHandler proxyHandler = new ProxyHandler(userService);
ClassLoader classLoader = proxyHandler.getClass().getClassLoader();
// note 根据指定参数动态创建代理对象
Subject proxyObject = (Subject) Proxy.newProxyInstance(classLoader,
userService.getClass().getInterfaces(), proxyHandler);
proxyObject.visit();
}
执行结果
do something before...
visit()
do something after...
源码分析
Cglib动态代理
Cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
创建代理对象的步骤
- 生成代理类的二进制字节码文件
- 加载二进制字节码,生成Class对象(例如使用Class.forName()方法)
- 通过反射机制获得代理类实例构造,并创建代理对象
优点
- 可以针对类进行代理,而不需要实现接口
- 可以在运行时动态创建字节码文件并加载
缺点
- 在JDK1.8中,性能比不上JDK动态代理
代码示例
执行代码之前,需要引入一个cglib的包,这里使用maven的方式
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
然后直接写一个需要被代理的类
public class RealSubject {
public void visit() {
System.out.println("visit...");
}
}
然后通过实现接口net.sf.cglib.proxy.MethodInterceptor
进行代理处理
public class CglibProxyHandler implements MethodInterceptor {
/**
* 代理的对象
*/
private Object target;
public Object getInstance(Object target) {
this.target = target;
Enhancer enHancer = new Enhancer();
enHancer.setSuperclass(this.target.getClass());
// 回调方法
enHancer.setCallback(this);
// 创建代理对象
return enHancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("############ 我是CGLIB动态代理 ##############");
// 反射方法调用前
System.out.println("我准备调用visit");
Object returnObje = proxy.invokeSuper(obj, args);
// 反射方法调用之后
System.out.println("我调用过visit了");
return returnObje;
}
}
客户端测试类,直接传入RealSubject对象,然后就实现了代理,是不是很方便
public static void main(String[] args) {
CglibProxyHandler cglib = new CglibProxyHandler();
RealSubject subject = (RealSubject) cglib.getInstance(new RealSubject());
subject.visit();
}
运行结果
############ 我是CGLIB动态代理 ##############
我准备调用visit
visit...
我调用过visit了
总结
Spring的AOP动态代理也是使用JDK动态代理+Cglib动态代理实现,只不过会依据不同情况动态选择代理方式,我们在选择的时候也可以参照AOP的逻辑进行选择,有如下几种情况:
- 如果目标对象实现了接口,默认情况下使用JDK的动态代理
- 如果目标对象实现了接口,可以强制使用Cglib实现AOP
- 如果目标对象没有实现接口,则强制使用Cglib库
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/17885.html