JDK动态代理
-
需要导入的包
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; -
利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理;JDK代理只能对实现接口的类生成代理
/**
* 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()!");
}
}
- JDK代理使用的是反射机制实现aop的动态代理,jdk动态代理的方式创建代理对象效率较高,执行效率较低;JDK动态代理机制是委托机制,具体说就是动态实现接口类,在动态生成的实现类里面委托handler去调用原始实现类方法
CGLIB动态代理(ASM框架)
- 利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
/**
* CGlib动态代理类
*/
public class CGLibProxy implements MethodInterceptor {
// CGlib需要代理的目标对象
private Object targetObject;
public Object createProxyObject(Object obj) {
//createProxyObject传入了要继承的实现类,通过setSuperClass继承
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()!");
}
}
- CGLIB代理使用字节码处理框架asm(ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能),通过修改字节码生成子类,所以cglib创建效率较低,执行效率高;CGLIB则使用的继承机制(createProxyObject传入了要继承的实现类,通过setSuperClass继承),具体说就是被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口
面试必备技能:JDK动态代理给Spring事务埋下的坑!
两个注解为事务的方法,在A方法内调用B方法,B方法的事务失效;我们知道Spring事务管理是通过JDK动态代理的方式进行实现的(另一种是使用CGLib动态代理实现的),也正是因为动态代理的特性造成了上述A()方法调用B()方法的时候造成了B()方法中的事务失效!简单的来说,在A()方法调用B()方法的时候,B()方法的事务是不起作用的,此时的child()方法像一个没有加事务的普通方法,代码的作用就好像把B()方法的代码粘贴到了A()方法中,并没有事务的加持。
于把B()的方法体放入到A()中,也就是内部方法,同样的不管你嵌套了多少层,只有代理对象proxy 直接调用的那一个方法才是真正的走代理的
如何解决这个坑?
上文的分析中我们已经了解了为什么在特定场景下使用Spring事务的时候造成事务无法回滚的问题,下边我们谈一下几种解决的方法:
1、我们可以选择逃避这个问题!
我们可以不使用以上这种事务嵌套的方式来解决问题,最简单的方法就是把问题提到Service或者是更靠前的逻辑中去解决,使用service.xxxtransaction是不会出现这种问题的。
2、通过AopProxy上下文获取代理对象:AopContext.currentProxy():
(1)SpringBoot配置方式:注解开启 exposeProxy = true,暴露代理对象 (否则AopContext.currentProxy() 会抛出异常)。
添加依赖:
添加注解:
修改原有代码的执行方式为:
- 注意的是在parent调用child的时候是通过try/catch捕获了异常的!
3、通过ApplicationContext上下文进行解决: