Spring AOP拦截抽象类(父类)中方法失效问题
背景
最近工作中需要对组内各个系统依赖的第三方接口进行监控报警,对于下游出现问题的接口能够及时感知.首先我们写了一个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.
- 1
异常信息非常明确,找不到当前的代理,需要在暴露出代理,我们看下AopContext这个类的源码,看看到底哪里出错了,看到了我们输出错误信息的地方.
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,设置当前代理,而通过调试这个字段为false
if (this.advised.exposeProxy) { oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; }
- 1
- 2
- 3
- 4
那么我们就在需要通知的地方,即你需要拦截方法的类上加上如下注解.
@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关键字时同样会出现这个问题,同样采用强制使用代理对象即可.

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
DolphinDB与InfluxDB对比测试报告
DolphinDB Database 是一款分析型的分布式时序数据库,内置处理流式数据处理引擎,具有内置的并行和分布式计算的功能,并提供分布式文件系统,支持集群扩展。DolphinDB以C++编写,响应速度极快。提供类似于Python的脚本语言对数据进行操作,支持类标准SQL的语法。提供其它常用编程语言的API,方便与已有应用程序集成。在金融领域中的历史数据分析建模与实时流数据处理,以及物联网领域中的海量传感器数据处理与实时分析等场景中表现出色。 InfluxDB是一款开源的时序数据库,由Go语言实现。它的核心是一款定制的存储引擎TSM Tree,对时间序列数据做了优化,优先考虑插入和查询数据的性能。InfluxDB使用类SQL的查询语言InfluxQL,并提供开箱即用的时间序列数学和统计函数。适用于监控、实时分析、物联网、传感器数据等应用场景。是目前最为流行的时间序列数据库。 本文将会对DolphinDB和InfluxDB进行性能测试对比。 在本次测试中,硬件配置如下: 设备:DellXPS 8920(07DC) CPU:Inter® Core™ i7-7700 CPU @ 3.60...
- 下一篇
我在阿里巴巴做 Serverless 云研发平台
简介: Serverless 云研发平台经过这半年多的蜕变,已经从简单的解决工程链路的平台演进成一个面向研发、上线、运维的全生命周期研发平台,后续要解决的命题会集中在用户低门槛上。 作者 | 林昱(苏河) 技术的成熟度源自大规模的实践,在 Java 领域,阿里将自身的实践源源不断的反哺给微服务技术体系;在 Node.js 领域,阿里正掀起了前所未有的前端革命浪潮,将实践反哺给 Serverless 技术体系,并逐渐拓展到其他多语言体系和后端 BaaS上。 Serverless 云研发平台作为阿里巴巴集团前端委员会发起的一体化云研发平台,底层基于函数计算 FC,是整个 Node Serverless 体系中的研发入口,承接了淘宝、飞猪、ICBU、考拉、高德、文娱等研发、交付和运维工作。目前,集团已经有上千位前端和客户端的工程师使用 Serverless 云研发平台进行业务的开发工作,包括但不限于营销导购、中后台、行业前台等规模化场景。 从今年双 11 整体的大盘数据来看, 仅淘系 Node Serverless 的支撑流量就已经从去年的 2K QPS 峰值增加到今年的 30K QPS ...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- SpringBoot2整合Redis,开启缓存,提高访问速度
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS7设置SWAP分区,小内存服务器的救世主
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7安装Docker,走上虚拟化容器引擎之路
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS关闭SELinux安全模块