JDK动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
CGlib动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
区别:JDK代理只能对实现接口的类生成代理;CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。
强制使用CGlib
<!-- proxy-target-class="false"默认使用JDK动态代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<aop-config proxy-target-class="true">
<!-- 切面详细配置 -->
</aop-config>
具体代码示例:
/**
* 目标接口类
*/
public interface UserManager {
public void addUser(String id, String password);
public void delUser(String id);
}
/**
* 接口实现类
*/
public class UserManagerImpl implements UserManager {
@Override
public void addUser(String id, String password) {
System.out.println("调用了UserManagerImpl.addUser()方法!");
}
@Override
public void delUser(String id) {
System.out.println("调用了UserManagerImpl.delUser()方法!");
}
}
/**
* JDK动态代理类
*/
public class JDKProxy implements InvocationHandler {
// 需要代理的目标对象
private Object targetObject;
public Object newProxy(Object targetObject) {
// 将目标对象传入进行代理
this.targetObject = targetObject;
// 返回代理对象
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
}
// invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 进行逻辑处理的函数
checkPopedom();
Object ret = null;
// 调用invoke方法
ret = method.invoke(targetObject, args);
return ret;
}
private void checkPopedom() {
// 模拟检查权限
System.out.println("检查权限:checkPopedom()!");
}
}
/**
* CGlib动态代理类
*/
public class CGLibProxy implements MethodInterceptor {
// CGlib需要代理的目标对象
private Object targetObject;
public Object createProxyObject(Object obj) {
this.targetObject = obj;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());
enhancer.setCallback(this);
Object proxyObj = enhancer.create();
return proxyObj;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object obj = null;
// 过滤方法
if ("addUser".equals(method.getName())) {
// 检查权限
checkPopedom();
}
obj = method.invoke(targetObject, args);
return obj;
}
private void checkPopedom() {
System.out.println("检查权限:checkPopedom()!");
}
}
/**
* 测试类
*/
public class ProxyTest {
public static void main(String[] args) {
UserManager userManager = (UserManager)new CGLibProxy().createProxyObject(new UserManagerImpl());
System.out.println("CGLibProxy:");
userManager.addUser("tom", "root");
System.out.println("JDKProxy:");
JDKProxy jdkProxy = new JDKProxy();
UserManager userManagerJDK = (UserManager)jdkProxy.newProxy(new UserManagerImpl());
userManagerJDK.addUser("tom", "root");
}
}
// 运行结果
CGLibProxy:
检查权限checkPopedom()!
调用了UserManagerImpl.addUser()方法!
JDKProxy:
检查权限checkPopedom()!
掉用了UserManagerImpl.addUser()方法!
总结:
1.JDK代理使用的是反射机制实现aop的动态代理,CGLIB代理使用字节码处理框架asm,通过修改字节码生成子类。所以jdk动态代理的方式创建代理对象效率较高,执行效率较低,cglib创建效率较低,执行效率高;
2.JDK动态代理机制是委托机制,具体说动态实现接口类,在动态生成的实现类里面委托hanlder去调用原始实现类方法,CGLIB则使用的继承机制,具体说被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口。
JDK动态代理依赖一个类和一个接口,分别是什么?
答:Proxy类和InvocationHandler接口。
调用Proxy类中的newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法可以创建一个动态代理对象,但是这个方法需要3个参数,前两个参数是固定的,但第三个参数比较麻烦,需要我们创建一个类MyInvocationHandler来实现InvocationHandler接口,这个类里面要重写invoke()方法。
JDK动态代理和cglib动态代理有什么区别?
答:JDK动态代理智能对实现了接口的类生成代理对象;
cglib可以对任意类生成代理对象,它的原理是对目标对象进行继承代理,如果目标对象被final修饰,那么该类无法被cglib代理。
Spring框架的一大特点就是AOP,SpringAOP的本质就是动态代理,那么Spring到底使用的是JDK代理,还是cglib代理呢?
答:混合使用。如果被代理对象实现了接口,就优先使用JDK代理,如果没有实现接口,就用用cglib代理。
动态代理的应用
AOP(Aspect-OrientedProgramming,面向切面编程),AOP包括切面(aspect)、通知(advice)、连接点(joinpoint),实现方式就是通过对目标对象的代理在连接点前后加入通知,完成统一的切面操作。
实现AOP的技术,主要分为两大类:
一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;
二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
Spring提供了两种方式来生成代理对象: JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。
默认的策略是如果目标类是接口,则使用JDK动态代理技术,如果目标对象没有实现接口,则默认会采用CGLIB代理。
如果目标对象实现了接口,可以强制使用CGLIB实现代理(添加CGLIB库,并在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)。
JDK动态代理
1、因为利用JDKProxy生成的代理类实现了接口,所以目标类中所有的方法在代理类中都有。
2、生成的代理类的所有的方法都拦截了目标类的所有的方法。而拦截器中invoke方法的内容正好就是代理类的各个方法的组成体。
3、利用JDKProxy方式必须有接口的存在。
4、invoke方法中的三个参数可以访问目标类的被调用方法的API、被调用方法的参数、被调用方法的返回类型。
cglib动态代理
1、 CGlib是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。
2、 用CGlib生成代理类是目标类的子类。
3、 用CGlib生成代理类不需要接口。
4、 用CGLib生成的代理类重写了父类的各个方法。
5、 拦截器中的intercept方法内容正好就是代理类中的方法体。
spring两种代理方式
1. 若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
优点:因为有接口,所以使系统更加松耦合
缺点:为每一个目标类创建接口
2. 若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
优点:因为代理类与目标类是继承关系,所以不需要有接口的存在。
缺点:因为没有使用接口,所以系统的耦合性没有使用JDK的动态代理好。