背景
最近工做中须要对组内各个系统依赖的第三方接口进行监控报警,对于下游出现问题的接口可以及时感知.首先咱们写了一个Spring AOP注解,用于收集调用第三方时返回的信息.而咱们调用第三方的类抽象出一个父类.并在父类的方法中加入咱们的自定义注解用于监控日志并打印日志.
不少子类继承了这个父类并使用父类中的方法.如:
当调用子类的doSomething方法时问题出现了,发现Spring AOP没有拦截doPost()方法.而将注解加在子类方法上时,Spring AOP能够拦截子类的方法,但这不是咱们想要的结果.而当咱们将父类经过@Autowired方式注入到子类中代替使用继承的方式调用父类中方法时Spring AOP能够拦截父类中的方法.至此发现问题出如今继承上面.java
缘由分析
Spring AOP拦截器的实现原理就是利用动态代理技术实现面向切面编程,Spring 的代理实现有两种:一是基于 JDK Dynamic Proxy 技术而实现的;二是基于 CGLIB 技术而实现的。若是目标对象实现了接口,在默认状况下Spring会采用JDK的动态代理实现AOP,在本例目标对象没有实现接口,所以使用的CGLIB实现动态代理对SuperClass对象进行代理,而后加强doPost()方法.下面的代码展现了为何Spring AOP没有加强doPost()方法.
图2等价于图3,即便用super关键字调用doPost()方法,这就代表咱们使用的SuperClass的实例调用的doPost()方法,在咱们在使用Spring AOP的时候,咱们从IOC容器中获取的Bean对象其实都是代理对象,而不是那些Bean对象自己.所以AOP不能使用代理对象调用这些父类的方法.spring
解决方案
知道了问题缘由,解决问题就比较容易了,因为咱们使用的super关键字调用父类的方法行不通,那么咱们就强制使用代理对象调用父类方法.
好了,咱们运行程序,发现不但没有拦截方法并且还报错了.编程
java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.
- 1
异常信息很是明确,找不到当前的代理,须要在暴露出代理,咱们看下AopContext这个类的源码,看看到底哪里出错了,看到了咱们输出错误信息的地方.this
package org.springframework.aop.framework; import org.springframework.core.NamedThreadLocal; import org.springframework.lang.Nullable; public final class AopContext { private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal("Current AOP proxy"); private AopContext() { } public static Object currentProxy() throws IllegalStateException { Object proxy = currentProxy.get(); if (proxy == null) { throw new IllegalStateException("Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available."); } else { return proxy; } } @Nullable static Object setCurrentProxy(@Nullable Object proxy) { Object old = currentProxy.get(); if (proxy != null) { currentProxy.set(proxy); } else { currentProxy.remove(); } return old; } }
说名setCurrentProxy方法没有被调用,经过查找发现有两个类调用了该方法,分别为CglibAopProxy和JdkDynamicAopProxy,是否是很熟悉,这两个就是Spring aop的代理方式,因为咱们讨论的目标对象不是基于接口的,所以本文使用的代理都是基于CglibAopProxy,咱们找到该类中调用setCurrentProxy方法的地方,程序中判断this.advised.exposeProxy是否为true,若是为true,设置当前代理,而经过调试这个字段为falseurl
if (this.advised.exposeProxy) { oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; }
- 1
- 2
- 3
- 4
那么咱们就在须要通知的地方,即你须要拦截方法的类上加上以下注解.spa
@EnableAspectJAutoProxy(exposeProxy = true)
- 1
此次在调用,发现已经能够拦截注解标注的方法了.代理
后记
解决这个问题的方式有不少,能够子类不继承父类,而是改成@Autowired方式,而后每个调用的地方须要加上父类的对象.
最终咱们在程序中的方案是加了一个父类的代理类,用于强制使用代理对象调用父类的方法.而原来父类的子类继承代理类便可.
的调试
import org.springframework.aop.framework.AopContext; import org.springframework.stereotype.Component; import java.util.Map; @Component public class ProxyAgent extends BaseAgent{ private BaseAgent getProxyObject() { return ((BaseAgent)AopContext.currentProxy()); } protected String doGet(String url, Map<String, Object> headers, Map<String, Object> params, Object... uriVariables) { return getProxyObject().doGetBase(url, headers, params, uriVariables); } protected String doPost(String url, Map<String, Object> headers, Object body) { return getProxyObject().doPostBase(url, headers, body); } protected String doPostForm(String url, Map<String, Object> params) { return doPostForm(url, null, params); } protected String doPostForm(String url, Map<String, Object> headers, Map<String, Object> params) { return getProxyObject().doPostFormBase(url, headers, params); } protected String doPostFormWithContentHeader(String url, Map<String, Object> headers, Map<String, Object> params, byte[] boundary) { return getProxyObject().doPostFormWithContentHeaderBase(url, headers, params, boundary); } protected String doPostFormUpload(String url, Map<String, Object> headers, Map<String, Object> params) { return getProxyObject().doPostFormUploadBase(url, headers, params); } }
同理,调用内部方法使用this关键字时一样会出现这个问题,一样采用强制使用代理对象便可.日志