spring使用时,在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解是不会生效的。
解决方法
方法一: 把这两个方法分开到不同的类中;
方法二: 把注解加上移,加到类入口处;
方法三:同一个类中调用—特殊方法
缺点:较复杂,不建议使用,特殊情况例外
步骤为:
- 修改代理配置
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
或
@EnableAspectJAutoProxy(exposeProxy = true)
- 方法调用改为
//原为:book(paramHeader)
((AbstractTemplete) AopContext.currentProxy()).book(paramHeader);
注意:
- 方法调用者的方法不能为final,不然报:Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.
- 被调用者的调用的方法必须为public
原因分析
要了解注解不生效的原因,首先要了解代码的实现,我们以JDK代理为例。
JDK代理原理
一句话概括:
同一个类中的方法调用,调用时,调用的是原对象的方法。
以下通过代码示例进行分析:
java代码
public interface IHello {
void sayHello1();
void sayHello2();
}
public class Hello implements IHello {
public void sayHello1() {
System.out.println("Hello world 1!!");
sayHello2();
}
public void sayHello2() {
System.out.println("Hello world 2!!");
}
}
//自定义InvocationHandler
public class HWInvocationHandler implements InvocationHandler {
//目标对象å
private Object target;
public HWInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("------插入前置通知代码-------------");
//执行相应的目标方法
Object rs = method.invoke(target, args);
System.out.println("------插入后置处理代码-------------");
return rs;
}
}
public class Test {
public static void main(String[] args) throws Exception {
//生成$Proxy0的class文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//获取动态代理类
Class proxyClazz = Proxy.getProxyClass(IHello.class.getClassLoader(), IHello.class);
//获得代理类的构造函数,并传入参数类型InvocationHandler.class
Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
//通过构造函数来创建动态代理对象,将自定义的InvocationHandler实例传入
IHello iHello = (IHello) constructor.newInstance(new HWInvocationHandler(new Hello()));
//通过代理对象调用目标方法
iHello.sayHello1();
}
}
注意:saveGeneratedFiles用于生成class文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
执行结果:
------插入前置通知代码-------------
Hello world 1!!
Hello world 2!!
------插入后置处理代码-------------
从执行结果可以看出,前置和后置代码只执行了一次,没有对sayHello2
为何sayHello2前后没有通知代码?
先看代理类实现:
package com.sun.proxy;
import blog.aop.proxy.cglib.MyProxy.IHello;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public final class $Proxy0 extends Proxy implements IHello {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
private static Method m4;
static {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("blog.aop.proxy.cglib.MyProxy$IHello").getMethod("sayHello1");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m4 = Class.forName("blog.aop.proxy.cglib.MyProxy$IHello").getMethod("sayHello2");
}
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final void sayHello1() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void sayHello2() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}
主要看下sayHello1()方法的实现:
super.h.invoke(this, m3, (Object[])null);
- h为之前在
IHello iHello = (IHello) constructor.newInstance(new HWInvocationHandler(new Hello()));
中设置的new HWInvocationHandler(new Hello())
- this为
$Proxy0
类 - m3为:
blog.aop.proxy.cglib.MyProxy$IHello.sayHello1
执行时会执行HWInvocationHandler 类的invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("------插入前置通知代码-------------");
//执行相应的目标方法
Object rs = method.invoke(target, args);
System.out.println("------插入后置处理代码-------------");
return rs;
}
执行Object rs = method.invoke(target, args);
方法时,其实就是执行new Hello()类的sayHello1方法
target: new Hello()
method: sayHello1
可以看出:执行代理对象的sayHello1方法就是执行HWInvocationHandler的invoke方法,invoke方法中执行的是原类的sayHello1方法。
最终执行的是Hello 类的sayHello1,执行sayHello1方法的对象也还是是Hello对象,代理只一次,不会对sayHello1中的方法再次代理。
@ Transactional代理实现
@ Transactional注解也是通过生成代理类来管理事务,具体可以参考@Transactional原理 - 简书。
了解了JDK中的代理,我们就很容易了解spring 中@Transational不生效的原因了。
@Service
class A{
method a(){
b();
}
@Transactinal
method b(){...}
}
生成的代理类如下,
class proxy$A{
A objectA = new A();
method a(){
objectA.a();
}
method b(){
startTransaction();
try{
objectA.b();
}catch (Throwable ex) {
rollbackTransaction();
}finally{
commitTransaction();
}
}
当执行代理类的a()方法时,实际执行的是原类A的a()方法。
引用
深度剖析JDK动态代理机制 - MOBIN - 博客园
JAVA设计模式-动态代理(Proxy)源码分析 - 张橙子 - 博客园
spring aop 通过获取代理对象实现事务切换 - CSDN博客
网友评论