经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
解决Spring?AOP拦截抽象类(父类)中方法失效问题
来源:jb51  时间:2021/11/24 15:08:43  对本文有异议

背景

最近工作中需要对组内各个系统依赖的第三方接口进行监控报警,对于下游出现问题的接口能够及时感知.首先我们写了一个Spring AOP注解,用于收集调用第三方时返回的信息.而我们调用第三方的类抽象出一个父类.并在父类的方法中加入我们的自定义注解用于监控日志并打印日志.

父类

很多子类继承了这个父类并使用父类中的方法.如:

子类

当调用子类的doSomething方法时问题出现了,发现Spring AOP没有拦截doPost()方法.而将注解加在子类方法上时,Spring AOP可以拦截子类的方法,但这不是我们想要的结果.而当我们将父类通过@Autowired方式注入到子类中代替使用继承的方式调用父类中方法时Spring AOP可以拦截父类中的方法.至此发现问题出现在继承上面.

原因分析

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不能使用代理对象调用这些父类的方法.

解决方案

知道了问题原因,解决问题就比较容易了,由于我们使用的super关键字调用父类的方法行不通,那么我们就强制使用代理对象调用父类方法.

代理对象

好了,我们运行程序,发现不但没有拦截方法而且还报错了.

java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.

异常信息非常明确,找不到当前的代理,需要在暴露出代理,我们看下AopContext这个类的源码,看看到底哪里出错了,看到了我们输出错误信息的地方.

  1. package org.springframework.aop.framework;
  2. import org.springframework.core.NamedThreadLocal;
  3. import org.springframework.lang.Nullable;
  4. public final class AopContext {
  5. private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal("Current AOP proxy");
  6. private AopContext() {
  7. }
  8. public static Object currentProxy() throws IllegalStateException {
  9. Object proxy = currentProxy.get();
  10. if (proxy == null) {
  11. throw new IllegalStateException("Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.");
  12. } else {
  13. return proxy;
  14. }
  15. }
  16. @Nullable
  17. static Object setCurrentProxy(@Nullable Object proxy) {
  18. Object old = currentProxy.get();
  19. if (proxy != null) {
  20. currentProxy.set(proxy);
  21. } else {
  22. currentProxy.remove();
  23. }
  24. return old;
  25. }
  26. }

说名setCurrentProxy方法没有被调用,通过查找发现有两个类调用了该方法,分别为CglibAopProxyJdkDynamicAopProxy,是不是很熟悉,这两个就是Spring aop的代理方式,由于我们讨论的目标对象不是基于接口的,因此本文使用的代理都是基于CglibAopProxy,我们找到该类中调用setCurrentProxy方法的地方,程序中判断this.advised.exposeProxy是否为true,如果为true,设置当前代理,而通过调试这个字段为false

  1. if (this.advised.exposeProxy) {
  2. oldProxy = AopContext.setCurrentProxy(proxy);
  3. setProxyContext = true;
  4. }

那么我们就在需要通知的地方,即你需要拦截方法的类上加上如下注解.

  1. @EnableAspectJAutoProxy(exposeProxy = true)

这次在调用,发现已经可以拦截注解标注的方法了.

后记

解决这个问题的方式有很多,可以子类不继承父类,而是改为@Autowired方式,然后每一个调用的地方需要加上父类的对象.

最终我们在程序中的方案是加了一个父类的代理类,用于强制使用代理对象调用父类的方法.而原来父类的子类继承代理类即可.

  1. import org.springframework.aop.framework.AopContext;
  2. import org.springframework.stereotype.Component;
  3. import java.util.Map;
  4. @Component
  5. public class ProxyAgent extends BaseAgent{
  6. private BaseAgent getProxyObject() {
  7. return ((BaseAgent)AopContext.currentProxy());
  8. }
  9. protected String doGet(String url, Map<String, Object> headers, Map<String, Object> params, Object... uriVariables) {
  10. return getProxyObject().doGetBase(url, headers, params, uriVariables);
  11. }
  12. protected String doPost(String url, Map<String, Object> headers, Object body) {
  13. return getProxyObject().doPostBase(url, headers, body);
  14. }
  15. protected String doPostForm(String url, Map<String, Object> params) {
  16. return doPostForm(url, null, params);
  17. }
  18. protected String doPostForm(String url, Map<String, Object> headers, Map<String, Object> params) {
  19. return getProxyObject().doPostFormBase(url, headers, params);
  20. }
  21. protected String doPostFormWithContentHeader(String url, Map<String, Object> headers,
  22. Map<String, Object> params, byte[] boundary) {
  23. return getProxyObject().doPostFormWithContentHeaderBase(url, headers, params, boundary);
  24. }
  25. protected String doPostFormUpload(String url, Map<String, Object> headers, Map<String, Object> params) {
  26. return getProxyObject().doPostFormUploadBase(url, headers, params);
  27. }
  28. }

同理,调用内部方法使用this关键字时同样会出现这个问题,同样采用强制使用代理对象即可.

以上为个人经验,希望能给大家一个参考,也希望大家多多支持w3xue。

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号