项目中,本身基于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
在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事务