遇到過 方法A 內(nèi)調(diào)用Aop修飾的方法B 失效、方法A 內(nèi)調(diào)用@Async修飾的方法C 失效,百度谷歌都沒看到一個好的解決,這里分享一個我的解決方案。
為什么失效
這個百度上很多解答,分析的也很好,其實(shí)就是Spring代理機(jī)制造成的。
簡單的說,就是通過spring容器獲取的類對象,很多情況下并不是原類,而是被spring修飾過了的代理類。
例如你執(zhí)行 A類對象的方法A.invoke()
,而spring對A類做了修飾:
proxyAbean.invoke():
before
invoke(bean,A)
after
實(shí)際你運(yùn)行的是spring修飾過的代理類proxyAbean.invoke()
方法。
這樣就會造成一個問題,如果你在invoke()
中調(diào)用A類的其余方法invoke2()
,此時invoke2()
是直接調(diào)用的原類的 A.invoke2()
,而不是代理類proxyAbean.invoke2()
,spring對方法做的修飾增強(qiáng)(@Async
、@Transational
、AOP
)全部不會實(shí)現(xiàn)。
如何解決
百度上都講,將調(diào)用方法放入另外一個類就行了,這種方法其實(shí)走了彎路。
既然是因?yàn)闆]有調(diào)用到代理類的方法造成的,那我們重新獲取一遍代理類,調(diào)用方法不就行了嗎?
public class A{
public void aMethod() {
System.out.println("method a start");
// bMethod(); //直接調(diào)用方法b,@Async不會生效
A a = context.getBean(A.class); //從spring容器中重新獲取A的代理對象,再調(diào)用b方法注解即生效
a.bMethod();
System.out.println("method a end");
}
@Async
public void bMethod() {
Thread.sleep(1000);
System.out.println("我是異步方法!");
}
}
代理類的獲取很簡單,通過spring容器context.getBean()
即可。一般的spring項(xiàng)目都會全局保持一個context:
/**
* 持有spring上下文的工具類,一個系統(tǒng)只能有一個SpringContextHolder。
* <p>該工具類主要用于: 通過spring上下文獲取bean</p>
*/
public class SpringContextHolder implements ApplicationContextAware,DisposableBean{
protected static final Log log = LogFactory.getLog(SpringContextHolder.class);
private static ApplicationContext applicationContext;
/**
* 將spring容器上下文:applicationContext注入
*/
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
if(applicationContext != null) throw new IllegalStateException("ApplicationContextHolder already holded 'applicationContext'.");
log.info("Injecting 'applicationContext' to " + SpringContextHolder.class.getSimpleName() + ", applicationContext=" + context);
applicationContext = context;
}
private static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 本類SpringContextHolder被銷毀時,將spring上下文置空
*/
@Override
public void destroy() throws Exception {
applicationContext = null;
}
/**
* 根據(jù)class獲取spring容器中的bean
*/
public static <T> T getBean(Class<T> c){
return applicationContext.getBean(c);
}
/**
* 根據(jù)class名稱獲取spring中的bean
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String beanName){
return (T)getApplicationContext().getBean(beanName);
}
}
補(bǔ)充:spring官方文檔上的解決
spring的官方文檔上針對AOP方法內(nèi)部調(diào)用還提供了一種解決方案:
- 在配置文件加入如下配置,使代理類暴露給線程。注意該配置要spring3.0以上:
<aop:aspectj-autoproxy expose-proxy="true"/>
- 手動調(diào)用代理類運(yùn)行方法B:
if (null != AopContext.currentProxy()) {
rs=((Bean)AopContext.currentProxy()).method(...);
} else {
rs=method(...);
}