Java设计模式-代理模式
代理模式
使用代理模式创建代表(representative)对象,让代理对象控制某对象的访问,被代理的对象可以是远程的对象,创建开销大的对象或需要安全控制的对象。——[Head First 设计模式]
简单的讲就是 :为服务对象提供代理,通过代理控制对服务对象的访问范围
- 生活中的场景
- 代理模式的好处
- 技术上的应用
- 分类
- 静态代理
- JDK动态代理
- Cglib动态代理
- 小结
生活中代理模式的场景
思考一下,其实在我们生活中不乏出现代理模式的场景:
- 滴滴出行;出门搭车并不需要直接联系某个司机,而是通过滴滴下单,滴滴就会帮我们将乘车需求通知到附近的司机;
- 淘宝购物;相信大多数人会在淘宝或其他电商平台上购物,而作为用户,我们可以通过淘宝就可以了解到商品的具体详情,而不需要到线下店了解;
- 链家租房;生活中找租房通常会选择中介代理,比如链家,这样一来,用户不需要四处奔波就可以通过代理获取到租房信息;
为什么需要代理呢?因为代理最大的好处就是方便,上面例子,体现代理的第一个作用:服务透明,屏蔽具体实现,用户无法感知真正的服务提供方;但是通过代理就可以实现自己的需求;
代理模式的好处
- 扩展,逻辑解耦,屏蔽真实实现,更换被代理对象,使用无感知;
- 保护,非直接调用,只提供本该提供的特定服务,防止越界访问;
- 简化,职责专一,被代理者只负责业务逻辑,不关心其他事项;
保护的理解:比如链家,只提供房东的房屋信息,对于房东的其他信息,客户无从知道,从而起到了保护作用
简化的理解:比如滴滴,对于司机来说,只负责正真的运输,不关心开车以外的事情,比如,找客源,讲价钱等等,简化了整个流程。
技术上的应用
代理模式最典型的的应用就是AOP;讲到AOP,相信大家脑海里,都会浮现出很多关键字:面向切面编程、静态代理、JDK动态代理,CGLIB;
AOP(Aspect Orient Programming),面向切面编程,用于在系统各个模块中的业务流程中织入通用的处理逻辑,比如事务管理、操作日志、缓存、异常处理等等。
有关AOP的介绍后续会用心的篇章来讲解,本文主要讲解代理的实现。
分类
从功能的角度划分,可以分为:远程代理、虚拟代理、保护代理、智能引用代理;
按实现的角度划分,则有静态代理和动态代理;动态代理还可以分为JDK动态代理和CGLIB动态代理;
在了解具体实现之前,先详细看一下上图;
首先是Service提供了服务接口,ServiceImpl和Proxy都必须实现Service接口;通过实现同个接口,使得上层调用可以像ServiceImpl一样使用Proxy的服务;
Proxy持有Service的引用,以便后续将请求转发给ServiceImpl;
当然除了接口,基于继承的方式也是可以实现代理模式,比如Cglib动态代理;
了解以上内容,已经大概了解了代理模式的具体实现。
静态代理
假设我们需要提供一个提供新增用户的服务接口给上层业务使用;
我们首先要先制定好接口UserService,将接口 addUser(String user) 开放给上层业务;
/**
* 开放代理的接口
*/
public interface UserService {
public void addUser(String user);
}
接下来,编写具体实现的服务类 UserServiceImpl.java
/**
* 实际业务逻辑
*/
public class UserServiceImpl implements UserService{
@Override
public void addUser(String user) {
System.out.println(String.format("add user:%s success", user));
}
}
测试使用静态代理
/**
* 代理模式测试类
*/
public class ProxyTest {
/**
* 测试静态代理
*/
@Test
public void testStaticProxy(){
UserService userService = new UserServiceImpl();
StaticProxy staticProxy = new StaticProxy(userService);
staticProxy.addUser("xupeng.zhang");
}
}
运行上述代码输出结果:
begin to execute static proxy to add user......
add user:xupeng.zhang success
execute static proxy to add user end. cost 0 ms**
采用静态代理,我们需要在代理类StaticProxy中传入UserService的引用;
同时,代理类需要同时实现UserService的接口;只是简单的将请求转发给UserServiceImpl对象;
/**
* 静态代理类
*/
public class StaticProxy implements UserService{
private UserService userService;
public StaticProxy(UserService userService) {
this.userService = userService;
}
@Override
public void addUser(String user) {
//统计耗时
Long executeTime = System.currentTimeMillis();
System.out.println("begin to execute static proxy to add user......");
userService.addUser(user);
System.out.println(String.format("execute static proxy to add user end. cost %s ms", System.currentTimeMillis() - executeTime));
}
}
以上便是静态代理类的实现方式,比较简单;但是如果UserService新增一个接口,那么从UserService到Proxy,都要重新实现一下,而且统计耗时的代码逻辑每个方法都要写一份。
有没有什么办法可以减少这种重复性的工作呢?当然有,那就是接下来要讲的动态代理;
JDK动态代理
同样需要实现 UserService 接口,需要持有 UserService的引用;
所不同的是,需要JDK帮我们动态生成代理对象;
具体做法如下:
- 实现InvocationHandler接口,代理类对象的执行最后会转发到invoke方法;
- 通过JDK的动态代理获取UserService的代理对象;
/**
* 实际执行处理类
*/
public class RealInvocationHandler implements InvocationHandler{
private Object target;
public RealInvocationHandler(Object target) {
this.target = target;
}
/**
* 最终代理对象会将请求转发到invoke方法执行
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Long executeTime = System.currentTimeMillis();
System.out.println("begin to execute method in dynamic proxy...");
Object result = method.invoke(target, args);
System.out.println(String.format("execute method by dynamic proxy end, cost %s ms", System.currentTimeMillis() - executeTime));
return result;
}
/**
* 基于反射实现动态代理对象生成
* @param <T>
* @return
*/
public <T> T getProxy(){
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this);
}
}
测试使用JDK动态代理
/**
* 代理模式测试类
*/
public class ProxyTest {
/**
* 测试JDK动态代理
*/
@Test
public void testJDKDynamicProxy(){
UserService userService = new UserServiceImpl();
RealInvocationHandler realInvocationHandler = new RealInvocationHandler(userService);
UserService userProxy = realInvocationHandler.getProxy();
userProxy.addUser("xupeng.zhang");
}
}
运行上述代码输出结果:
begin to execute method in dynamic proxy...
add user:xupeng.zhang success
execute method by dynamic proxy end, cost 0 ms
Process finished with exit code 0**
上述代码中,我们并没有手写过代理类,JDK基于反射动态帮我们生成了代理类:
/**
* 基于反射实现动态代理对象生成
* @param <T>
* @return
*/
public <T> T getProxy(){
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this);//this即 RealInvocationHandler对象
}
如果进去newProxyInstance看源码的话,无非就以下三句关键代码:
1. Class<?> cl = getProxyClass0(loader, intfs); //获取代理类对象
2. final Constructor<?> cons = cl.getConstructor(constructorParams);//获取代理类的构造方法
3. return cons.newInstance(new Object[]{h});//构造方法生成代理对象并返回
其中:
loader,指定代理对象的类加载器;
intfs 即 interfaces,代理对象需要实现的接口,可以同时指定多个接口;
h 即 RealInvocationHandler对象,方法调用的实际处理者,代理对象的方法调用都会转发到invoke方法。
继续查看代理类的相关信息,你会发现JDK的动态代理居然如此奇妙:
/**
* 代理模式测试类
*/
public class ProxyTest {
@Test
public void testGetJDKProxyClassInfo(){
UserService userService = new UserServiceImpl();
RealInvocationHandler realInvocationHandler = new RealInvocationHandler(userService);
UserService userProxy = realInvocationHandler.getProxy();
System.out.println(userProxy.getClass().getName());
System.out.println(userProxy.getClass().getSuperclass());
System.out.println(userProxy.getClass().getInterfaces()[0].getName());
ProxyGeneratorUtils.saveProxyClass("D:/JdkProxy.class", userProxy.getClass());
}
}
运行上述代码输出结果:
com.sun.proxy.$Proxy2
class java.lang.reflect.Proxy
com.orig.design.proxy.UserService**
代理类的类型是com.sun.proxy.$Proxy2;且继承自Proxy.java;
此外还实现了UserService接口;
而我们将UserSercieProxy.class的字节码文件反编译会发现以下关键代码:
public final void addUser(String var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
最终addUser会将请求转发给 RealInvocationHandler的 invoke方法执行;invoke方法调用method.invoke执行服务类对应的方法;
CGLIB动态代理
JDK生成的代理类已经继承了Proxy类,Java不支持多重继承,因此无法实现继承式的动态代理;这个时候就可以采用Cglib动态代理来实现;
原理上跟JDK动态代理其实大同小异:
cglib会动态生成代理类,它只关注自己的父类,通过继承获得父类非final的接口;之后将请求转发给MethodInterceptor的intercept方法去做实际处理;
具体做法如下:
- 实现MethodInterceptor接口,intercept方法用于后续实际请求处理;
- 通过Cglib的Enhancer对象构造代理对象,需要告知enhance继承的父类;
具体实现代码如下:
/**
* cglib动态代理
* 和JDK动态代理不同
* 是基于继承实现
*/
public class RealInterceptor implements MethodInterceptor{
private Enhancer enhancer = new Enhancer();
/**
* 具体执行的回调方法
* @param o
* @param method
* @param args
* @param methodProxy
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Long currentTime = System.currentTimeMillis();
System.out.println("begin to execute cglib proxy to add user");
System.out.println(String.format("method: %s", method.getName()));
Object result = methodProxy.invokeSuper(o, args);//注意这里是invokeSuper
System.out.println(String.format("execute cglib proxy end, cost: %s ms", System.currentTimeMillis()-currentTime));
return result;
}
/**
* 设置代理的父类
* 设置回调方法
* @param tClass
* @param <T>
* @return
*/
public <T> T newProxyInstance(Class<T> tClass){
enhancer.setSuperclass(tClass);
enhancer.setCallback(this);
return (T) enhancer.create();
}
}
运行上述代码输出结果:
begin to execute cglib proxy to add user
method: addUser
add user:xupeng.zhang success
execute cglib proxy end, cost: 22 ms**
通过上述结果可以看出,Cglib性能上相对于JDK动态代理稍差;同样的代理的实现,Cglib的实现比JDK动态代理的实现要耗时;
同样的,我们针对代理类的相关信息进行挖掘:
/**
* 代理模式测试类
*/
public class ProxyTest {
/**
* 获取cglib代理类信息
*/
@Test
public void testGetCglibProxyClassInfo() throws Exception {
RealInterceptor realInterceptor = new RealInterceptor();
UserRoleService userRoleProxy = realInterceptor.newProxyInstance(UserRoleService.class);
System.out.println(userRoleProxy.getClass().getName());
System.out.println(userRoleProxy.getClass().getSuperclass());
System.out.println(userRoleProxy.getClass().getInterfaces()[0].getName());
ProxyGeneratorUtils.saveCglibProxyClass("D:/CglibProxy.class",realInterceptor.getEnhancer());
}
}
运行上述代码输出结果:
com.orig.design.proxy.UserServiceImpl$$EnhancerByCGLIB$$591ab16e
class com.orig.design.proxy.UserServiceImpl
net.sf.cglib.proxy.Factory**
Cglib生成的代理类关键代码如下:
public class UserRoleService$$EnhancerByCGLIB$$8ba328ed extends UserRoleService implements Factory {
.......省略部分代码
public final void addUserRole(String var1, String var2) {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$addUserRole$0$Method, new Object[]{var1, var2}, CGLIB$addUserRole$0$Proxy);
} else {
super.addUserRole(var1, var2);
}
}
}
Cglib动态代理类直接继承了UserRoleService,并重写了addUserRole方法;
此外,对原有的方法做了增强处理,如果Enhancer有设置设置回调方法,则会执行对应的回调方法interceptor;
/**
* 设置代理的父类
* 设置回调方法
* @param tClass
* @param <T>
* @return
*/
public <T> T newProxyInstance(Class<T> tClass){
enhancer.setSuperclass(tClass);//设置继承的父类
enhancer.setCallback(this);//设置回调函数,入参是Callback类型
return (T) enhancer.create();
}
/**回调方法**/
public interface MethodInterceptor extends Callback {
Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}
小结
- 动态代理原理:(1)动态生成字节码;(2)通过反射调用真实实现类方法
- JDK原生动态代理基于接口实现;Cglib动态代理基于继承方式实现;
- 动态代理相对静态代理的好处在于灵活,强大,不需要对每个接口进行代理逻辑的开发;
- 当然,增加了代理,无形中会增加调用链,意味着降低处理速度;实现代理也需要额外的开发工作,增加了实现成本。
本文相关的代码Demo已经上传到github上:github