代理模式¶
介绍¶
代理模式的三大角色:
- 目标对象
- 代理对象
- 目标对和代理对象的公共接口(相同的行为)
如果使用代理模式,客户端程序是无法察觉的。客户端使用代理对象时就像在使用目标对象
可以使用代理模式的场景:
- 程序中的两个对象无法直接交互
- 程序中目标对象需要被保护
- 对象的功能需要被增强
静态代理¶
场景:需要统计所有业务接口中每个业务方法的耗时情况
- 不能违背 OCP ,不能违背开闭原则
一种不违背 OCP 的方案:使用继承。分别写一个类去继承各个业务接口的实现类,重写每个业务方法,加上计时逻辑。这种方案使用继承,耦合度过高。
静态代理实现:
- 目标类和代理类都实现公共接口(此处是某个业务接口)。
- 将目标对象作为代理类的一个属性。这是一种关联关系,相比继承关系有更低的耦合度。
- 在代理类的方法中通过目标对象调用目标对象的方法,并且可以据此来进行增强。
缺点:类太多,不好维护。解决方案:动态代理
动态代理¶
使用字节码生成技术,在程序运行的过程中,在内存中动态地生成字节码形式的代理类
- JDK 动态代理:只能代理接口
- CGLIB 动态代理:开源项目。可以代理接口或代理类。底层通过继承的方式实现。在内存中生成一个目标类的子类
JDK 动态代理¶
OrderService target = new OrderServiceImpl();
// 在内存中创建一个代理对象
OrderService proxyObj = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), TimerInvocationHandler(target));
proxyObj.generateOrder();
newProxyInstance()
方法的三个重要的参数:
-
第一个参数:ClassLoader loader
JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个。
-
第二个参数:Class<?>[] interfaces
代理类和目标类要实现同一个接口或同一些接口。在内存中生成代理类的时候,这个代理类是需要你告诉它实现哪些接口的。
-
第三个参数:InvocationHandler
调用处理器。是一个接口。在调用处理器接口的实现类中编写增强代码。
注意:代理对象和目标对象实现的接口一样,所以可以向下转型为接口类型。
invoke()
方法由 JDK 自动调用,其参数如下:
- Object proxy
- Method method:在 invoke 方法执行的过程中调用的目标对象上的目标方法(要执行的目标方法)
- Object[] args:目标方法上的实参
invoke()
方法当代理对象调用代理方法的时候,注册在 InvocationHandler 中的 invoke 方法被调用
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 {
long begin = System.currentTimeMillis();
Object retValue = method.invoke(target, args);
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
// 注意这个invoke方法的返回值,如果代理对象调用代理方法之后,需要返回结果的话,invoke方法必须将目标对象的目标方法执行结果继续返回。
return retValue;
}
}