动态代理可以在不改动原代码的前提下,为其添加验权、事务控制、日志打印等功能。
例如,统计某方法运行所消耗的时间。通常做法如下:
// 记录此方法的开始时间(动态代理模式下,这部分不在此方法中实现)
// 处理业务
// 记录此方法的结束时间并打印(动态代理模式下,这部分不在此方法中实现)
在动态代理里,时间统计代码写在了代理模块里。将业务代码和时间统计代码完全解耦了。
动态代理有两种实现方式,一种是调用 JDK。另一种是使用 Cglib,两者的区别是Cglib是可以不需要接口。
就上面伪代码的例子。如果用代理模式,应该如何做呢?下面就是我用代码完成的一个示例及相关说明。
用调用JDK的方式实现动态代理,有这么几个类:
1、JDK 实现的 Proxy 类。此类的 newProxyInstance 方法用来生成代理对象。需要 InvocationHandler 的实例和目标代码类的字节码作为参数。
2、实现 InvocationHandler 接口的类,这个类通过实现InvocationHandler 接口的 invoke 方法,将「代理的代码」注入到「目标代码」中。而 createProxyIntance 方法用目标对象字节码和 handler(此类)以反射的方式生成代理对象。
public class RemindHandler implements InvocationHandler {
/** 要注入的目标对象 */
private Object target;
public Object createProxyIntance(Object target) {
this.target = target;
Class<?> clazz = this.target.getClass();// 目标对象的字节码
// 通过目标对象字节码和 handler(此类)以反射的方式生成代理对象。
Object object = Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
return object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("学习之前,提醒自己做好学习计划。");
method.invoke(target);
System.out.println("学习之后,提醒自己及时总结,做笔记,自测。");
return null;
}
}
3、目标代码类,对目标代码的具体事务进行实现。
public class StudyEnglish implements Study{
@Override
public void startReading() {
System.out.println("阅读英语教材中...");
}
}
4、目标代码类的接口类。
public interface Study {
public void startReading();
}
下面是对以上代码进行测试的代码
public static void main(String args[]){
StudyEnglish study = new StudyEnglish();
RemindHandler handler = new RemindHandler();
Study s = (Study) handler.createProxyIntance(study);
s.startReading();
}
}
一句话总结就是,动态代理通过被代理类的字节码和注入的代码(日志,权限,事务),以反射的方式生成一个代理的对象。
备注1:代理模式中,日志、验权、事务等代码是可复用的,不变的代码,在项目中大部分属于技术代码。而被代理的代码,大部分属于变化的业务代码。
备注2:动态代理需要注意的一个点
Spring事务处理时自我调用的解决方案及一些实现方式的风险
网友评论