Java动态代理 深度详解(二)
如何使用动态代理?
参照上面的例子,我们可以知道要实现动态代理需要做两方面的工作。
- 首先需要新建一个类,并且这个类必须实现 InvocationHandler 接口。
//杏仁动态代理
public class ApricotHandler implements InvocationHandler{
private Object object;
public ApricotHandler(Object object) {
this.object = object;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(object, args); //调用真正的蛋糕机做蛋糕
System.out.println("adding apricot...");
return result;
}
}
- 在调用的时候使用 Proxy.newProxyInstance() 方法生成代理类。
public class CakeShop {
public static void main(String[] args) {
//水果蛋糕撒一层杏仁
CakeMachine fruitCakeMachine = new FruitCakeMachine();
ApricotHandler fruitCakeApricotHandler = new ApricotHandler(fruitCakeMachine);
CakeMachine fruitCakeProxy = (CakeMachine) Proxy.newProxyInstance(fruitCakeMachine.getClass().getClassLoader(),
fruitCakeMachine.getClass().getInterfaces(), fruitCakeApricotHandler);
fruitCakeProxy.makeCake();
}
- 最后直接使用生成的代理类调用相关的方法即可。
动态代理的几种实现方式
动态代理其实指的是一种设计模式概念,指的是通过代理来做一些通用的事情,常见的应用有权限系统、日志系统等,都用到了动态代理。
而
Java 动态代理只是动态代理的一种实现方式而已,动态代理还有另外一种实现方式,即
CGLib(Code Generation Library)。
Java 动态代理只能针对实现了接口的类进行拓展,所以细心的朋友会发现我们的代码里有一个叫 MachineCake 的接口。而 CGLib 则没有这个限制,因为 CGLib 是使用继承原有类的方式来实现代理的。
我们还是举个例子来说明
CGLib 是如何实现动态代理的吧。还是前面的例子:我们要做杏仁水果蛋糕、巧克力水果蛋糕、五仁巧克力蛋糕,这时候用代码描述是这样的。
首先我们需要写一个杏仁拦截器类,这个拦截器可以给做好的蛋糕加上杏仁。
public class ApricotInterceptor implements MethodInterceptor {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
methodProxy.invokeSuper(o, objects);
System.out.println("adding apricot...");
return o;
}
}
接着直接让蛋糕店使用 CGLib 提供的工具类做杏仁水果蛋糕:
public class CakeShop {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(FruitCakeMachine.class);
enhancer.setCallback(new ApricotInterceptor());
FruitCakeMachine fruitCakeMachine = (FruitCakeMachine) enhancer.create();
fruitCakeMachine.makeCake();
}
}
上面的 enhancer.setSuperClass() 设置需要增强的类,而 enhancer.setCallback() 则设置需要回调的拦截器,即实现了 MethodInterceptor 接口的类。最后最后使用 enhancer.create() 生成了对应的增强类,最后输出结果为:
making a Fruit Cake...
adding apricot...
和我们预期的一样。如果要做一个杏仁巧克力蛋糕,那么直接让蛋糕店利用ApricotHandler 再做一个就可以了,它们的区别只是传入的增强类不同。
public class CakeShop {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ChocolateCakeMachine.class);
enhancer.setCallback(new ApricotInterceptor());
ChocolateCakeMachine chocolateCakeMachine = (ChocolateCakeMachine) enhancer.create();
chocolateCakeMachine.makeCake();
}
}
可以看到,这里传入的增强类是 ChocolateCakeMachine,而不是之前的 FruitCakeMachine。
对比 Java 动态代理和 CGLib 动态代理两种实现方式,你会发现
Java 动态代理适合于那些有接口抽象的类代理,而 CGLib 则适合那些没有接口抽象的类代理。
Java动态代理的原理
从上面的例子我们可以知道,Java 动态代理的入口是从 Proxy.newInstance() 方法中开始的,那么我们就从这个方法开始边剖析源码边理解其原理。
image其实通过这个方法,Java 替我们生成了一个继承了指定接口(CakeMachine)的代理类(ApricotHandler)实例。从
Proxy.newInstance()
的源码我们可以看到首先调用了 getProxyClass0 方法,该方法返回了一个 Class 实例对象,该实例对象其实就是 ApricotHandler 的 Class 对象。接着获取其构造方法对象,最后生成该 Class 对象的实例。其实这里最主要的是 getProxyClass0() 方法,这里面动态生成了 ApricotHandler 的 Class 对象。下面我们就深入到 getProxyClass0() 方法中去了解这里面做了什么操作。
imagegetProxyClass0() 方法首先是做了一些参数校验,之后从 proxyClassCache 参数中取出 Class 对象。其实 proxyClassCache 是一个 Map 对象,缓存了所有动态创建的 Class 对象。从源码中的注释可以知道,如果从 Map 中取出的对象为空,那么其会调用
ProxyClassFactory
生成对应的 Class 对象。
image在 ProxyClassFactory 类的源码中,最终是调用了
ProxyGenerator.genrateProxyClass()
方法生成了对应的 class 字节码文件。
到这里,我们已经把动态代理的 Java 源代码都解析完了,现在思路就很清晰了。Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
方法简单来说执行了以下操作:
- 1、生成一个实现了参数 interfaces 里所有接口且继承了 Proxy 的代理类的字节码,然后用参数里的 classLoader 加载这个代理类。
- 2、使用代理类父类的构造函数 Proxy(InvocationHandler h) 来创造一个代理类的实例,将我们自定义的 InvocationHandler 的子类传入。
- 3、返回这个代理类实例,因为我们构造的代理类实现了 interfaces(也就是我们程序中传入的 fruitCakeMachine.getClass().getInterfaces() 里的所有接口,因此返回的代理类可以强转成 MachineCake 类型来调用接口中定义的方法。
CGLib动态代理的原理
因为 JVM 并不允许在运行时修改原有类,所以所有的动态性都是通过新建类来实现的,上面说到的 Java 动态代理也不例外。所以对于 CGLib 动态代理的原理,其实也是通过动态生成代理类,最后由代理类来完成操作实现的。
对于 CGLib 动态代理的实现,我并没有深入到源码中,而是通过查阅资料了解了其大概的实现原理。
- 首先,我们在使用的时候通过 enhancer.setSuperclass(FruitCakeMachine.class) 传入了需要增加的类,CGLib 便会生成一个继承了改类的代理类。
- 接着,我们通过 enhancer.setCallback(new ApricotInterceptor()) 传入了代理类对象,CGLib 通过组装两个类的结构实现一个静态代理,从而达到具体的目的。
而在 CGLib 生成新类的过程中,其使用的是一个名为 ASM 的东西,它对 Java 的 class 文件进行操作、生成新的 class 文件。如果你对 CGLib 的原理感兴趣,不妨看看这篇文章:从兄弟到父子:动态代理在民间是怎么玩的?
动态代理的应用
动态代理在代码界可是有非常重要的意义,我们开发用到的许多框架都使用到了这个概念。我所知道的就有:Spring AOP、Hibernate、Struts 使用到了动态代理。
- Spring AOP。Spring 最重要的一个特性是 AOP(Aspect Oriented Programming 面向切面编程),利用 Spring AOP 可以快速地实现权限校验、安全校验等公用操作。而 Spring AOP 的原理则是通过动态代理实现的,默认情况下 Spring AOP 会采用 Java 动态代理实现,而当该类没有对应接口时才会使用 CGLib 动态代理实现。
- Hibernate。Hibernate 是一个常用的 ORM 层框架,在获取数据时常用的操作有:get() 和 load() 方法,它们的区别是:get() 方法会直接获取数据,而 load() 方法则会延迟加载,等到用户真的去取数据的时候才利用代理类去读数据库。
- Struts。Struts 现在虽然因为其太多 bug 已经被抛弃,但是曾经用过 Struts 的人都知道 Struts 中的拦截器。拦截器有非常强的 AOP 特性,仔细了解之后你会发现 Struts 拦截器其实也是用动态代理实现的。
总结
我们通过蛋糕店的不同业务场景介绍了静态代理和动态代理的应用,接着重点介绍了动态代理两种实现方式(Java 动态代理、CGLib 动态代理)的使用方法及其实现原理,其中还针对 Java 动态代理的源码进行了简单的分析。最后,我们介绍了动态代理在实际上编程中的应用(Spring AOP、Hibernate、Struts)。