什么是动态代理?
先说说什么是代理模式,代理模式就是为某个对象提供一个代理对象,并且由代理对象控制对原对象的访问。代理模式通俗来讲就是我们生活中常见的中介。
而动态代理就是,在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作。
动态代理有什么用?
其实无论是日志框架或 Spring 框架,它们都包含了动态代理的实现代码,Java动态代理的优势是实现无侵入式的代码扩展,也就是方法的增强,可以在不用修改源码的情况下,增强一些方法,增强就是在方法的前后做我们任何想做的事情,甚至不去执行这个方法。
我们知道大名鼎鼎的AOP的思想是:不去动原来的代码,而是基于原来代码产生代理对象,通过代理的方法,去包装原来的方法,完成对以前方法的增强。换句话说,AOP的底层原理就是动态代理的实现。
动态代理如何实现?
实现动态代理有两种办法,JDK自带的Proxy类和CGLib框架。
另外在Spring 框架中同时使用了两种动态代理 JDK Proxy 和 CGLib,当 Bean 实现了接口时,Spring 就会使用 JDK Proxy,在没有实现接口时就会使用 CGLib,我们也可以在配置中指定强制使用 CGLib
JDK Proxy和CGLib有什么区别?
- JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现;
- Java 对 JDK Proxy 提供了稳定的支持,并且会持续的升级和更新 JDK Proxy,例如 java 8 版本中的 JDK Proxy 性能相比于之前版本提升了很多;
- JDK Proxy 是通过拦截器加反射的方式实现的;
- JDK Proxy 只能代理继承接口的类;
- JDK Proxy 实现和调用起来比较简单;
- CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高;
- CGLib 无需通过接口来实现,它是通过实现子类的方式来完成调用的。
什么是ASM
ASM是一个通用的Java字节码操作和分析框架。 它可以用于修改现有类或直接以二进制形式动态生成类。 ASM提供了一些常见的字节码转换和分析算法,可以从中构建自定义复杂转换和代码分析工具。 ASM提供与其他Java字节码框架类似的功能,但专注于性能。 因为它的设计和实现尽可能小而且快,所以它非常适合在动态系统中使用。
JDK Proxy实现
public interface IAnimal {
public void eat();
}
public class Dog implements IAnimal {
@Override
public void eat() {
System.out.println("Dog吃饭");
}
}
public class Main {
public static void main(String[] args) {
IAnimal dog = (IAnimal) Proxy.newProxyInstance(Dog.class.getClassLoader(), Dog.class.getInterfaces(), new DogInvocationHandler(new Dog()));
dog.eat();
}
static class DogInvocationHandler implements InvocationHandler {
private Object object;
public DogInvocationHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Dog 说谢谢");
Object invoke = method.invoke(object, args);
System.out.println("狗吃完了");
return invoke;
}
}
}
原先Dog只会吃饭,现在Dog会在吃饭前说谢谢了。
可以看出 JDK Proxy 实现动态代理的核心是实现 Invocation 接口,至于JDK怎么实现,可以查看以前的博客。
JDK动态代理的代理类字节码在创建时,需要业务实现类所实现的接口作为参数。如果业务实现类是没有实现接口而是直接定义业务方法的话,就无法使用JDK动态代理了。
CGLib 的实现
加入依赖
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
CGLib 在初始化被代理类时,是通过 Enhancer 对象把代理对象设置为被代理类的子类来实现动态代理的。因此被代理类不能被关键字 final 修饰,如果被 final 修饰,再使用 Enhancer 设置父类时会报错,动态代理的构建会失败。
public class Dog {
public void eat() {
System.out.println("Dog吃饭");
}
}
public class Main {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Dog.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> {
System.out.println("DOg说谢谢");
Object result = proxy.invokeSuper(obj, args1);
System.out.println("Dog吃完了");
return result;
});
Dog dog = (Dog) enhancer.create();
dog.eat();
}
}
我们可以通过设置调试class输出地址,来查看生成的代理类。
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/home/HouXinLin/cglib");
CGlib创建代理的速度比较慢,但创建代理之后运行的速度却非常快,而JDK动态代理刚好相反。如果在运行的时候不断地用CGlib去创建代理,系统的性能会大打折扣,所以建议一般在系统初始化的时候用CGlib去创建代理。