Spring自定义注解不生效缘由解析及解决方法

自定义注解不生效缘由解析及解决方法

背景:

项目中,本身基于spring AOP实现了一套java缓存注解。可是最近出现一种状况:缓存居然没有生效,大量请求被击穿到db层,致使db压力过大。如今咱们看一下具体代码情形(代码为伪代码,只是为了说明一下具体状况)。java

interface A {
    int method1(..);
    int method2(..);
    ... ...
}

class AImpl implements A {
    @Override
    @CacheMM(second=600)      //这里的@CacheMM就是我实现的自定义缓存注解
    public int method1(..) {
        ... ...
        method2(..);
        ... ...
    }
    
    @Override
    @CacheMM(second=600)
    public int method2(..) {
        ... ...
    }
}

如上代码,当调用method1时,发现method2注解并无生效。spring

分析:

这是为何呢?别急,咱们带着这个问题去看了一下注解的实现类。(这里就不贴缓存注解的实现代码了)个人自定义注解是直接extends AbstractBeanFactoryPointcutAdvisor类而后实现其中的getPointcut() 和 getAdvice() 实现的。(其实这里能够直接使用aop环绕通知的,原理都差很少,我是为了熟悉源码才这样写的)。 缓存

接下来,咱们继续往下分析,咱们都知道基于spring aop实现的注解,在spring 中,若是有aop实现,那么容器注入的是该类的代理类,这里的代理类是aop 动态代理生成的代理类。Spring aop 的动态代理有两种:一种是jdk的动态代理,一种是基于CGLIB的。这两个的区别我就很少说了,若是你的业务类是基于接口实现的,则使用jdk动态代理,不然使用CGLIB动态代理。 我这里使用的是接口实现,因此咱们就顺着思路去看一下jdk动态代理的具体实现。 ide

上边的业务代码类我已经贴出。而须要生成代理对象(proxy),分红两步:this

  1. 生成代理对象须要创建代理对象(proxy)和真实对象(AImpl)的代理关系
  2. 实现代理方法

在JDK动态代理中须要实现接口:java.lang.reflect.InvocationHandler..net

import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;   
import java.lang.reflect.Proxy;   
  
public class AProxy implements InvocationHandler   
{   
    private Object target;   
      
    /**  
     * 生成代理对象,并和真实服务对象绑定.  
     * @param target 真实服务对象  
     * @return 代理对象 */   
    public Object bind(Object target)   
    {   
        this.target = target;   
          
        //生成代理对象,并绑定.   
        Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), //类的加载器   
                              target.getClass().getInterfaces(), //对象的接口,明确代理对象挂在哪些接口下   
                              this);//指明代理类,this表明用当前类对象,那么就要求其实现InvocationHandler接口的invoke方法   
          
        return proxy;   
    }   
          
    /**  
     * 当生成代理对象时,第三个指定使用AProxy进行代理时,代理对象调用的方法就会进入这个方法。  
     * @param proxy 代理对象  
     * @param method  被调用的方法  
     * @param args 方法参数  
     * @return 代理方法返回。  
     * @throws Throwable  异常处理 */   
     @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable   
     {   
        System.err.println("反射真实对象方法前");   
        Object obj = method.invoke(target, args);//至关于AImpl类中对应方法调用.   
        System.err.println("反射真实对象方法后");   
          
        return obj;   
    }  
}

代码中,Object obj = method.invoke(target,args) 经过反射调度真实对象的方法,这个很重要。咱们知道其实虽然aop是经过代理对象去实现一些附加的操做的,可是真正的类方法调用仍是经过反射调用真实对象的。这个时候,咱们回头看一下问题,咱们AImpl中有两个方法,其中method2是在method1内部调用的。当调用method1时,spring内部其实调用的是代理类AProxy类的invoke,这个时候在执行真实对象方法钱去执行method1中的一些附加操做。而后,在经过反射进入对应AImpl类中调用method1方法。注意,这个时候,已经不在代理对象中操做了,因为method2的调用是在method1内部调用的,因此在这里实际调用method2的是真实对象,并非代理对象。 因此,就致使method2上的缓存注解没有生效。代理

解决:

好了,如今知道问题的缘由后(动态代理的坑啊,内部调用不走代理类,因此实现的附加操做确定不会执行了),咱们来针对性的解决。咱们如今知道这个实际上是由于实际执行的不是代理类而致使的,那咱们解决的思路就想办法让method2的调用走代理类就能够了。(就是这么简单) code

AProxy类咱们是能够在spring容器中获得的。下面是修改后的解决方案:对象

method1(..) {
    ... ...
     // 若是但愿调用的内部方法也被拦截,那么必须用过上下文获取代理对象执行调用,而不能直接内部调用,不然没法拦截  
        if(null != AopContext.currentProxy()){  
            AopContext.currentProxy().method2();  
        }else{  
            method2();  
        }      

}

这里的AopContext.currentProxy() 拿到的实际就是代理对象了,这样经过代理对象去调用method2确定就没有问题了。 blog

还有一种解决方法就是不使用 动态代理织入,使用aspectJ织入,aspectJ直接在源类上进行字节码的插入,而不是以代理的方式进行。

这里能够参考一下
AspectJ 编译时织入(Compile Time Weaving, CTW)

由于这样改动比较大,因此目前我仍是采用第一种方案解决问题了。至此,问题获得解决。

总结:

结合Spring aop动态代理的实现原理,提供两种动态代理:JDK代理和CGLIB代理

JDK代理只能对实现了接口的类生成代理,而不能针对类;
CGLIB是针对类实现代理的,主要对指定的类生成一个子类,并覆盖其中的方法,
由于是继承,因此不能使用final来修饰类或方法。因此该类或方法最好不要声明成final

更加详细的解释能够参考这篇博文 哪些方法不能实施Spring AOP事务

相关文章
相关标签/搜索