GoF代理模式
代理模式概述
代理模式:通过代理对象完成对目标对象的访问,向客户端隐藏目标对象的存在。
在GoF23种设计模式中,属于结构性设计模式。
代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不应该看到的内容和服务或者添加客户需要的额外服务。 通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。

代理模式中的角色:
代理类(代理主题)
代理类中的方法即代理方法。
目标类(真实主题)
目标类中的方法即目标方法。
代理类和目标类的公共接口(抽象主题):客户端面向公共接口,故是由代理类就像是在使用目标类一样。

代理模式的使用场景:
- 在程序中,对象A和对象B无法直接交互时:不允许或者不能够直接交互等,需要一个“中介”来完成对目标的访问。
- 在程序中,功能需要增强时:代理对象在执行代理方法时,在访问目标对象执行目标方法的前后,可以插入一些代码以达到功能增强,而对于客户端来说,是察觉不到的。
- 在程序中,目标需要被保护时:当需要向客户端隐藏真实主题时,可以使用代理模式向客户端隐藏这部分的存在,即客户端不知道最终是哪个目标对象在执行。
简单的能体现代理模式思想的即getter方法。在获取属性前后,可以方便地添加计数、输出日志等功能增强。
代理模式的实现方式:
- 静态代理
- 动态代理
静态代理
编写 .java 文件,手动给出代理类的实现,即静态代理。
可见,静态代理一旦大范围覆盖时,容易类爆炸,且不好维护,代码复用程度低。
基于继承的实现
基于继承,在代理方法中使用 super 调用父类的实现即可,甚至不需要目标对象,只需要父类型引用指向子类对象(多态)即可。
public class OrderServiceImplSub extends OrderServiceImpl{
@Override
public void generate() {
// 前增强
long begin = System.currentTimeMillis();
super.generate(); // 目标方法
// 后增强
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
}
}缺点:类爆炸,而且是子类爆炸,耦合度非常长(继承是一种耦合度非常高的类之间的关系)。
基于接口的实现
面向代理类和目标类的公共接口,在代理类中提供目标类的一个引用(目标对象作为属性),在代理方法中通过目标对象调用目标方法即可。
// 代理对象
public class OrderServiceProxy implements OrderService{
// ⽬标对象,作为属性
private OrderService orderService;
// 通过构造⽅法将⽬标对象传递给代理对象
public OrderServiceProxy(OrderService orderService) {
this.orderService = orderService;
}
@Override
// 代理方法
public void generate() {
// 前增强
long begin = System.currentTimeMillis();
// 执⾏⽬标对象的⽬标⽅法
orderService.generate();
// 后增强
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
}
}代理模式代码结构上很像装饰模式(使用对象属性),装饰模式用以组织一系列的方法流,而代理模式更关注于对方法的增强。
优点:符合OCP开闭原则(想换一种增强只需要增加代理类即可);同时采⽤的是关联关系,所以程序的耦合度较低。
客户端面向接口,接口指向代理类对象即可:
public class Client {
public static void main(String[] args) {
// 创建⽬标对象
OrderService target = new OrderServiceImpl();
// 创建代理对象
OrderService proxy = new OrderServiceProxy(target);
// 调⽤代理对象的代理⽅法
proxy.generate();
proxy.modify();
proxy.detail();
}
}动态代理
采用技术手段在程序运行过程中动态生成代理类,即动态代理。
因为代理模式有着一个公共接口(接口、继承等),故动态代理不会添加 .java 文件(面向公共接口+目标对象动态生成),不会类爆炸(类在内存中动态生成,硬盘上眼不见为净了)。
动态代理的优点;
- 解决类爆炸
- 代码复用高
- 耦合度无需关心:即使Cglib采用继承的方式实现代理类,但生成在内存中,耦合度再高也无所谓。
动态代理的实现,关键是在内存当中动态生成类的技术,常见有:
JDK 动态代理技术:只能代理接口。JDK 提供的一套规范。
CGLIB动态代理技术:可以代理接口、类,通过继承实现。
CGLIB(Code Generation Library)是⼀个开源项⽬。是⼀个强⼤的,⾼性能,⾼质量的Code⽣成类库,它可以在运⾏期扩展Java类与实现Java接⼝。它既可以代理接⼝,⼜可以代理类,底层是通过继承的⽅式实现的。性能⽐JDK动态代理要好。(底层有⼀个⼩⽽快的字节码处理框架ASM。)
Javassist动态代理技术
Javassist是⼀个开源的分析、编辑和创建Java字节码的类库。是由东京⼯业⼤学的数学和计算机科学系的 Shigeru Chiba (千叶滋)所创建的。它已加⼊了开放源代码 JBoss 应⽤服务器项⽬,通过使⽤Javassist对字节码操作为JBoss实现动态"AOP"框架。
JDK 动态代理
JDK 提供了 Proxy 类来生成动态生成目标对象的代理类。
/*
代理对象 = Proxy.newProxyInstance(
与目标对象相同的类加载器, // 使用 target.get
Class().getClassLoader() 得到
与目标对象相同的接口, // target.getClass().getInterfaces() 得到,或手动指定
调⽤处理器对象 // 要自己写一个实现类,在其中给出代理对象的代理方法的实现算法
);
*/- 参数较为固定,可以封装工具类简化代码。
public class JDKProxyUtil {
private JDKProxyUtil(){}
// 使用泛型,不用二次强转
public static <T> T getProxyObject(T target, InvocationHandler invocationHandler){
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
invocationHandler
);
}
}调用处理器:InvocationHandler,是一个接口。
一个调用处理器是可以生成多个代理类的,故一种增强即对应一个调用处理器,如果需要给某个类增强这些功能,生成代理时指定对应的代理类即可。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimerInvocationHandler implements InvocationHandler {
private Object target; // 目标对象
// 使用构造方法指定目标对象
public TimerInvocationHandler(Object target) {
this.target = target;
}
// 接口中只给了代理对象和方法(这个方法是接口中定义的,目标对象和代理对象都可以调用),目标对象需要通过别的手段获取,如构造方法。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前增强
System.out.println("proxy method ...");
// 返回值为 void,则返回 null
// return method.invoke(proxy,args); // 调用代理对象的方法(代理方法),会无限递归直至溢栈
Object rev = method.invoke(target, args); // 调用目标对象的方法(目标方法)
// 后增强
return rev;
}
}代理方法伪码:
private InvocationHandler invocationHandler;
@Override
... void | ... 方法名(参数表){
// 获取这个方法
Method method = ...;
// 封装参数表为数组
Object[] args = ...;
if(返回值类型是 void) {
invocationHandler.invoke(this, method, args);
return;
} else {
return invocationHandler.invoke(this, method, args);
}
}CGLIB动态代理
CGLIB既可以代理接⼝,⼜可以代理类。底层采⽤继承的⽅式实现,所以被代理的⽬标类不能使用 final 修饰。
CGLIB 依赖;
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>java9 以上需要添加 JVM 参数:
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/sun.net.util=ALL-UNNAMED- 好像只添加
--add-opens java.base/java.lang=ALL-UNNAMED也可。
使用 CGLIB 生成带代理类:
// 创建字节码增强器
Enhancer enhancer = new Enhancer();
// 设置父类:告诉cglib要继承哪个类
enhancer.setSuperclass(UserService.class);
// 设置增强:设置回调接⼝
enhancer.setCallback(⽅法拦截器对象);
// ⽣成源码,编译class,加载到JVM,并创建代理对象
UserService userServiceProxy = (UserService)enhancer.create();方法拦截器对象:和JDK动态代理类似,这里需要提供 net.sf.cglib.proxy.MethodInterceptor 接口的实现类。
public class TimerMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
// 前增强
long begin = System.currentTimeMillis();
// 通过代理方法对象完成调用
Object retValue = methodProxy.invokeSuper(target, args);
// 后增强
long end = System.currentTimeMillis();
System.out.println("耗时" + (end - begin) + "毫秒");
// ⼀定要返回
return retValue;
}
}参数说明:目标对象、目标方法、实参、代理方法
- 方法参数中有目标对象,故可以直接使用无参构造实例化。
- 使用
methodProxy.invokeSuper方法完成对目标对象的调用。
同样的,也可以封装工具类:
public class CglibProxyUtil {
private CglibProxyUtil(){}
// 使用泛型,不用二次强转了
public static <T> T getProxyObject(T target, MethodInterceptor methodInterceptor){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(methodInterceptor);
return (T) enhancer.create();
}
}