spring 注解生效和java代理

spring使用时,在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解是不会生效的。

解决方法

方法一: 把这两个方法分开到不同的类中;

方法二: 把注解加上移,加到类入口处;

方法三:同一个类中调用—特殊方法

缺点:较复杂,不建议使用,特殊情况例外
步骤为:

  1. 修改代理配置


@EnableAspectJAutoProxy(exposeProxy = true)

  1. 方法调用改为
  //原为:book(paramHeader)
 ((AbstractTemplete) AopContext.currentProxy()).book(paramHeader);

注意:

  1. 方法调用者的方法不能为final,不然报:Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.
  2. 被调用者的调用的方法必须为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);

  1. h为之前在IHello iHello = (IHello) constructor.newInstance(new HWInvocationHandler(new Hello())); 中设置的new HWInvocationHandler(new Hello())
  2. this为$Proxy0
  3. 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博客

你可能感兴趣的:(spring 注解生效和java代理)