如上图所示,代理类继承于目标类,每次调用代理类的方法都会在拦截器中进行拦截,拦截器中再会调用目标类的方法。
下面我们通过一个示例来演示一下 CGLIB 动态代理的使用
首先导入 CGLIB 相关 jar 包,我们使用的是 MAVEN 的方式
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.5</version> </dependency>然后我们新创建一个 UserService 类,为了和上面的 UserDao 和 UserDaoImpl 进行区分。
public class UserService { public void saveUser(){ System.out.println("---- 保存用户 ----"); } }之后我们创建一个自定义方法拦截器,这个自定义方法拦截器实现了拦截器类
public class AutoMethodInterceptor implements MethodInterceptor { public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("---- 方法拦截 ----"); Object object = methodProxy.invokeSuper(obj, args); return object; } }这里解释一下这几个参数都是什么含义
Object obj: obj 是 CGLIB 动态生成代理类实例
Method method: Method 为实体类所调用的被代理的方法引用
Objectp[] args: 这个就是方法的参数列表
MethodProxy methodProxy : 这个就是生成的代理类对方法的引用。
对于 methodProxy 参数调用的方法,在其内部有两种选择:invoke() 和 invokeSuper() ,二者的区别不在本文展开说明,感兴趣的读者可以参考本篇文章:Cglib源码分析 invoke和invokeSuper的差别
然后我们创建一个测试类进行测试
public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserService.class); enhancer.setCallback(new AutoMethodInterceptor()); UserService userService = (UserService)enhancer.create(); userService.saveUser(); }测试类主要涉及 Enhancer 的使用,Enhancer 是一个非常重要的类,它允许为非接口类型创建一个 Java 代理,Enhancer 动态的创建给定类的子类并且拦截代理类的所有的方法,和 JDK 动态代理不一样的是不管是接口还是类它都能正常工作。
JDK 动态代理与 CGLIB 动态代理都是将真实对象隐藏在代理对象的后面,以达到 代理 的效果。与 JDK 动态代理所不同的是 CGLIB 动态代理使用 Enhancer 来创建代理对象,而 JDK 动态代理使用的是 Proxy.newProxyInstance 来创建代理对象;还有一点是 CGLIB 可以代理大部分类,而 JDK 动态代理只能代理实现了接口的类。
Javassist 代理Javassist是在 Java 中编辑字节码的类库;它使 Java 程序能够在运行时定义一个新类, 并在 JVM 加载时修改类文件。我们使用最频繁的动态特性就是 反射,而且反射也是动态代理的基础,我们之所以没有提反射对动态代理的作用是因为我想在后面详聊,反射可以在运行时查找对象属性、方法,修改作用域,通过方法名称调用方法等。实时应用不会频繁使用反射来创建,因为反射开销比较大,另外,还有一种具有和反射一样功能强大的特性那就是 Javaassist。
我们先通过一个简单的示例来演示一下 Javaassist ,以及 Javaassist 如何创建动态代理。
我们仍旧使用上面提到的 UserDao 和 UserDaoImpl 作为基类。
我们新创建一个 AssistByteCode 类,它里面有一个 createByteCode 方法,这个方法主要做的事情就是通过字节码生成 UserDaoImpl 实现类。我们下面来看一下它的代码
public class AssistByteCode { public static void createByteCode() throws Exception{ ClassPool classPool = ClassPool.getDefault(); CtClass cc = classPool.makeClass("com.cxuan.proxypattern.UserDaoImpl"); // 设置接口 CtClass ctClass = classPool.get("com.cxuan.proxypattern.UserDao"); cc.setInterfaces(new CtClass[] {ctClass}); // 创建方法 CtMethod saveUser = CtMethod.make("public void saveUser(){}", cc); saveUser.setBody("System.out.println(\"---- 插入用户 ----\");"); cc.addMethod(saveUser); Class c = cc.toClass(); cc.writeFile("/Users/mr.l/cxuan-justdoit"); } }由于本文并不是一个具体研究 Javaassist 的文章,所以我们不会过多研究细节问题,只专注于这个框架一些比较重要的类
ClassPool:ClassPool 就是一个 CtClass 的容器,而一个 CtClass 对象就是一个 class 对象的实例,这个实例和 class 对象一样,包含属性、方法等。
那么上面代码主要做了哪些事儿呢?通过 ClassPool 来获取 CtClass 所需要的接口、抽象类的 CtClass 实例,然后通过 CtClass 实例添加自己的属性和方法,并通过它的 writeFile 把二进制流输出到当前项目的根目录路径下。writeFile 其内部是使用了 DataOutputStream 进行输出的。
流写完后,我们打开这个 .class 文件如下所示