一、是什么?
Java动态代理是什么?
动态代理是23种设计模式中代理模式中的一种设计模式,它与静态代理不同的区别在于动态代理是在运行时创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。,是AOP编程的重要实现途径之一。
- 正常创建类的过程
- 动态代理创建代理对象过程
二、为什么?
为什么要使用动态代理?
相信了解了动态代理是什么后我们不禁有疑问,为什么要用动态代理?他有什么优点?
比如现在有这么几个需求,你需要打印每个业务方法的日志信息,你需要有一个统一的全局异常处理,或者多个业务方法都需要进行事务操作等功能,如果我们不用动态代理来实现aop编程也有很多方式,但如果使用原始的方式无疑会造成代码的冗余,管理起来差等问题。那么此时动态代理的作用也就体现出来了。并且Java中还有静态代理的概念,与动态代理区别主要在于静态上,它其实是硬编码到代码中,功能在运行时之前就有;而动态代理,不是硬编码,必须在运行时才能生成。
特点:字节码随用随创建,随用随加载
作用:不修改源码的基础上对方法增强
三、怎么做?
目前Java中实现动态代理的方式主要有两种:jdk动态代理、cglib动态代理
jdk动态代理
jdk动态代理主要是实现InvocationHandler接口,并重写invoke()方法
- 测试结构
- TargetInterface
public interface TargetInterface {
String sayHello(String name);
String sayTanks(String name);
}
- TargetInterfaceImpl
public class TargetInterfaceImpl implements TargetInterface {
@Override
public String sayHello(String name) {
return "Hello, " + name;
}
@Override
public String sayTanks(String name) {
return "Thanks, " + name;
}
}
- TargetProxy (代理代理对象的类)
public class TargetProxy implements InvocationHandler {
/**
* 持有目标接口类的引用
*/
private Object target;
public TargetProxy(Object target) {
this.target = target;
}
@SuppressWarnings("unchecked")
public <T> T getProxy(Class clazz) {
return (T) Proxy.newProxyInstance(
clazz.getClassLoader(),
new Class<?>[] {clazz},
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + "数据缓存......");
return method.invoke(target, args);
}
}
- Test (测试类)
public class Test {
public static void main(String[] args) {
TargetProxy targetProxy = new TargetProxy(new TargetInterfaceImpl());
TargetInterface targetInterface = targetProxy.getProxy(TargetInterface.class);
System.out.println(targetInterface.sayHello("zhangsan"));
}
}
- 测试结果
cglib动态代理
cglib动态代理底层是依靠的ASM字节码操作框架进行实现的
- 依赖 (注:cglib-nodep已继承asm依赖)
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
-
测试结构
-
TargetInterface
public interface TargetInterface {
String sayHello(String name);
String sayTanks(String name);
}
- TargetInterfaceImpl
public class TargetInterfaceImpl implements TargetInterface {
@Override
public String sayHello(String name) {
return "Hello, " + name;
}
@Override
public String sayTanks(String name) {
return "Thanks, " + name;
}
}
- TargetProxy
public class TargetProxy implements MethodInterceptor {
/**
* 获取真正的代理类
* @param clazz
* @param <T>
* @return
*/
public <T> T getProxy(Class<T> clazz) {
// 字节码增强的一个类
Enhancer enhancer = new Enhancer();
// 注意:cglib实现动态代理其实是写一个子类实现覆盖父类的方法
// 设置父类
enhancer.setSuperclass(clazz);
// 设置回调对象(这里因为回调类就是当前类所以用this)
enhancer.setCallback(this);
// 创建代理类
return (T) enhancer.create();
}
/**
* 既可以拦截sayHello()也可以拦截sayThanks()
* @param o
* @param method
* @param objects
* @param methodProxy
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("数据缓存......");
// 这里是调用目标方法(调用接口实现类的方法)
return methodProxy.invokeSuper(o, objects);
// 获取一个接口的代理,那么需要在此对接口进行实现
// String hello = "Hello, " + Arrays.toString(objects);
// System.out.println(hello);
// return hello;
}
- UserService
public class UserService {
public void thinking() {
System.out.println("thinking......");
}
}
- Test (测试类)
public class Test {
public static void main(String[] args) {
TargetProxy targetProxy = new TargetProxy();
// 拿到目标类的代理类
// 对类进行代理
TargetInterfaceImpl targetInterfaceImpl = targetProxy.getProxy(TargetInterfaceImpl.class);
System.out.println(targetInterfaceImpl.sayHello("zhangsan"));
// 对接口进行代理
// TargetInterface TargetInterface = targetProxy.getProxy(TargetInterface.class);
// System.out.println(TargetInterface.sayHello("zhangsan"));
UserService userService = targetProxy.getProxy(UserService.class);
userService.thinking();
}
}
补:
注意:生成的代理对象实在内存中的,正常情况下我们是无法查看的,但可以将class文件输出到磁盘上。(方法很多,此处就不做过多阐述)
总结
原理区别:
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。核心是实现InvocationHandler接口,使用invoke()方法进行面向切面的处理,调用相应的通知。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。核心是实现MethodInterceptor接口,使用intercept()方法进行面向切面的处理,调用相应的通知。
1、如果目标对象实现了接口,默认情况下spring会采用JDK的动态代理实现AOP;
2、如果目标对象实现了接口,spring也可以强制使用CGLIB实现AOP;
3、如果目标对象没有实现接口,必须采用CGLIB实现动态代理,当然spring可以在JDK动态代理和CGLIB动态代理之间转换;
性能区别
1、CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在jdk6之前比使用Java反射效率要搞。唯一需要注意的是,CGLib不能对声明为final类和final方法进行代理因为CGLib原理是动态生成被代理类的子类。
2、在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理。
各自局限
1、JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理。
2、cglib它的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行处理。
各自优势
- JDK Proxy 的优势:
最小化依赖关系,减少依赖意味着简化开发和维护,JDK本身的支持,可能比cglib更可靠。
平滑进行JDK版本升级,而字节码类库通常需要进行更新以保证在新版Java上能够使用,代码实现更简单。 - cglib 的优势
从某种角度看,限定调用者实现接口是有些侵入性的实践,类似cglib动态代理就没有这种限制。只操作我们关心的类,而不必为其他相关类增加工作量。另外高性能。