写在前面

最近在分析Spring源码时,在同一个类中写了嵌套的AOP方法,测试时出现:Spring AOP在同一个类里自身方法相互调用时无法拦截。哎,怎么办?还能怎么办呢?继续分析Spring源码,解决问题呗。于是乎,有了本文。

项目工程源码已经提交到GitHub:https://github.com/sunshinelyz/spring-annotation

问题阐述

Spring AOP在同一个类里自身方法相互调用时无法拦截。比如下面的代码:

public class SomeServiceImpl implements SomeService  {
public void someMethod() {
someInnerMethod();
}
public void someInnerMethod(){
}
}

两个方法经过AOP代理,执行时都实现系统日志记录。单独使用someInnerMethod时,没有任何问题。但someMethod就有问题了。someMethod里调用的someInnerMethod方法是原始的,未经过AOP增强的。我们期望调用一次someMethod会记录下两条系统日志,分别是someInnerMethod和someMethod的,但实际上只能记录下someMethod的日志,也就是只有一条。在配置事务时也可能会出现问题,比如someMethod方法是REQUIRED,someInnerMethod方法是REQUIRES_NEW,someInnerMethod的配置将不起作用,与someMethod方法会使用同一个事务,不会按照所配置的打开新事务。

问题分析

由于java这个静态类型语言限制,最后想到个曲线救国的办法,出现这种特殊情况时,不要直接调用自身方法,而通过AOP代理后的对象。在实现里保留一个AOP代理对象的引用,调用时通过这个代理即可。例如下面的代码。

//从beanFactory取得AOP代理后的对象
SomeService someServiceProxy = (SomeService)beanFactory.getBean("someService"); //把AOP代理后的对象设置进去
someServiceProxy.setSelf(someServiceProxy); //在someMethod里面调用self的someInnerMethod,这样就正确了
someServiceProxy.someMethod();

但这个代理对象还要我们手动set进来。有没有更好的方式解决呢?

问题解决

幸好SpringBeanFactory有BeanPostProcessor扩展,在bean初始化前后会统一传递给BeanPostProcess处理,繁琐的事情就可以交给程序了,代码如下,首先定义一个BeanSelfAware接口,实现了此接口的程序表明需要注入代理后的对象到自身。

public class SomeServiceImpl implements SomeService,BeanSelfAware{
//AOP增强后的代理对象
private SomeService self;
//实现BeanSelfAware接口
public void setSelf(Object proxyBean){
this.self = (SomeService)proxyBean
}
public void someMethod(){
//注意这句,通过self这个对象,而不是直接调用的
someInnerMethod();
}
public void someInnerMethod(){
}
}

再定义一个BeanPostProcessor,beanFactory中的每个Bean初始化完毕后,调用所有BeanSelfAware的setSelf方法,把自身的代理对象注入自身。

public class InjectBeanSelfProcessor implements BeanPostProcessor  {
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException{
if(bean instanceof BeanSelfAware){
System.out.println("inject proxy:" + bean.getClass());
BeanSelfAware myBean = (BeanSelfAware)bean;
myBean.setSelf(bean);
return myBean;
}
return bean;
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException{
return bean;
}
}

最后,在BeanFactory配置中组合起来,只需要把BeanPostProcesser加进去就可以了,比平常多一行配置而已。

<!-- 注入代理后的bean到bean自身的BeanPostProcessor... -->
<bean class=" org.mypackage.InjectBeanSelfProcessor"></bean> <bean id="someServiceTarget" class="org.mypackage.SomeServiceImpl" /> <bean id="someService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref local="someServiceTarget" />
</property>
<property name="interceptorNames">
<list>
<value>someAdvisor</value>
</list>
</property>
</bean>
<!-- 调用spring的DebugInterceptor记录日志,以确定方法是否被AOP增强 -->
<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor" /> <bean id="someAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="debugInterceptor" />
</property>
<property name="patterns">
<list>
<value>.*someMethod</value>
<value>.*someInnerMethod</value>
</list>
</property>
</bean>

这里的someService#someInnerMethod就表现出预期的行为了,无论怎样,它都是经过AOP代理的,执行时都会输出日志信息。

注意事项

用XmlBeanFactory进行测试需要注意,所有的BeanPostProcessor并不会自动生效,需要执行以下代码:

XmlBeanFactory factory = new XmlBeanFactory(...);
InjectBeanSelfProcessor postProcessor = new InjectBeanSelfProcessor();
factory.addBeanPostProcessor(postProcessor);

好了,咱们今天就聊到这儿吧!别忘了给个在看和转发,让更多的人看到,一起学习一起进步!!

项目工程源码已经提交到GitHub:https://github.com/sunshinelyz/spring-annotation

写在最后

如果觉得文章对你有点帮助,请微信搜索并关注「 冰河技术 」微信公众号,跟冰河学习Spring注解驱动开发。公众号回复“spring注解”关键字,领取Spring注解驱动开发核心知识图,让Spring注解驱动开发不再迷茫。

参考:iteye.com/blog/fyting-109236

【String注解驱动开发】困扰了我很久的AOP嵌套调用终于解决了!的更多相关文章

  1. 【String注解驱动开发】如何按照条件向Spring容器中注册bean?这次我懂了!!

    写在前面 当bean是单实例,并且没有设置懒加载时,Spring容器启动时,就会实例化bean,并将bean注册到IOC容器中,以后每次从IOC容器中获取bean时,直接返回IOC容器中的bean,不 ...

  2. 【String注解驱动开发】面试官让我说说:如何使用FactoryBean向Spring容器中注册bean?

    写在前面 在前面的文章中,我们知道可以通过多种方式向Spring容器中注册bean.可以使用@Configuration结合@Bean向Spring容器中注册bean:可以按照条件向Spring容器中 ...

  3. 【String注解驱动开发】你了解@PostConstruct注解和@PreDestroy注解吗?

    写在前面 在之前的文章中,我们介绍了如何使用@Bean注解指定初始化和销毁的方法,小伙伴们可以参见<[Spring注解驱动开发]如何使用@Bean注解指定初始化和销毁的方法?看这一篇就够了!!& ...

  4. 【Spring注解驱动开发】如何使用@Bean注解指定初始化和销毁的方法?看这一篇就够了!!

    写在前面 在[String注解驱动开发专题]中,前面的文章我们主要讲了有关于如何向Spring容器中注册bean的知识,大家可以到[String注解驱动开发专题]中系统学习.接下来,我们继续肝Spri ...

  5. 【Spring注解驱动开发】BeanPostProcessor在Spring底层是如何使用的?看完这篇我懂了!!

    写在前面 在<[String注解驱动开发]面试官再问你BeanPostProcessor的执行流程,就把这篇文章甩给他!>一文中,我们详细的介绍了BeanPostProcessor的执行流 ...

  6. 【Spring注解驱动开发】如何实现方法、构造器位置的自动装配?我这样回答让面试官很满意!

    在 冰河技术 微信公众号前面的文章中,我们介绍了如何使用注解来自动装配Spring组件.之前将的都是在来的字段上添加注解,那有没有什么方法可以实现方法.构造器位置的自动装配吗?今天我们就一起来探讨下如 ...

  7. 【Spring注解驱动开发】使用@Scope注解设置组件的作用域

    写在前面 Spring容器中的组件默认是单例的,在Spring启动时就会实例化并初始化这些对象,将其放到Spring容器中,之后,每次获取对象时,直接从Spring容器中获取,而不再创建对象.如果每次 ...

  8. 【Spring注解驱动开发】面试官:如何将Service注入到Servlet中?朋友又栽了!!

    写在前面 最近,一位读者出去面试前准备了很久,信心满满的去面试.没想到面试官的一个问题把他难住了.面试官的问题是这样的:如何使用Spring将Service注入到Servlet中呢?这位读者平时也是很 ...

  9. 【Spring注解驱动开发】关于BeanPostProcessor后置处理器,你了解多少?

    写在前面 有些小伙伴问我,学习Spring是不是不用学习到这么细节的程度啊?感觉这些细节的部分在实际工作中使用不到啊,我到底需不需要学习到这么细节的程度呢?我的答案是:有必要学习到这么细节的程度,而且 ...

随机推荐

  1. Java实现 蓝桥杯 算法训练 p1103

    算法训练 P1103 时间限制:1.0s 内存限制:256.0MB 编程实现两个复数的运算.设有两个复数 和 ,则他们的运算公式为: 要求:(1)定义一个结构体类型来描述复数. (2)复数之间的加法. ...

  2. Java实现 蓝桥杯VIP 算法提高 开灯游戏

    算法提高 开灯游戏 时间限制:1.0s 内存限制:256.0MB 问题描述 有9盏灯与9个开关,编号都是1~9. 每个开关能控制若干盏灯,按下一次会改变其控制的灯的状态(亮的变成不亮,不亮变成亮的). ...

  3. Java实现 LeetCode 57 插入区间

    57. 插入区间 给出一个无重叠的 ,按照区间起始端点排序的区间列表. 在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠(如果有必要的话,可以合并区间). 示例 1: 输入: inte ...

  4. java实现第八届蓝桥杯平方十位数

    平方十位数 题目描述 由0~9这10个数字不重复.不遗漏,可以组成很多10位数字. 这其中也有很多恰好是平方数(是某个数的平方). 比如:1026753849,就是其中最小的一个平方数. 请你找出其中 ...

  5. java代码(8) ---guava字符串工具

    guava字符串工具 一.Joiner 根据指定的分隔符把字符串连接在一起,MapJoiner执行相同的操作,但是针对Map的key和value 分析源码可知:该类构造方法被private修饰,无法直 ...

  6. 解读三组容易混淆的Dockerfile指令

    长话短说,今天分享三组容易混淆的Dockerfile指令, 帮助大家编写更优雅的Dockfile文件.构建更纯净的Docker镜像. COPY vs ADD COPY.ADD主体功能类似:从指定位置拷 ...

  7. Python大神编程常用4大工具,你用过几个?

    摘要:Python是一种跨平台的编程语言,能够在所有主要的操作系统上,运行你编写的任何Python程序.今天介绍几款常见的工具:Python自带的解释器.文本编辑器(Geany.Sublime Tex ...

  8. Cypress系列(22)- 可操作类型的命令 之 select()

    如果想从头学起Cypress,可以看下面的系列文章哦 https://www.cnblogs.com/poloyy/category/1768839.html .select() 在 <sele ...

  9. MySQL LIMIT:限制查询结果的记录条数

    基本的语法格式如下: <LIMIT> [<位置偏移量>,] <行数> LIMIT 接受一个或两个数字参数.参数必须是一个整数常量.如果给定两个参数,第一个参数指定第 ...

  10. 基于JQuery的简单富文本编辑器

    利用jQuery实现最简单的编辑器 我试了很多种方法,目前最快捷能够实现及其简单的编辑可以使用 document.execCommand("ForeColor", "fa ...