假設(shè)一個(gè)接口里面有兩個(gè)方法:
package demo.long;
public interface CustomerService {
public void doSomething1();
public void doSomething2();
}
接口實(shí)現(xiàn)類如下:
package demo.long.impl;
import demo.long.CustomerService;
public class CustomerServiceImpl implements CustomerService {
public void doSomething1() {
System.out.println("CustomerServiceImpl.doSomething1()");
doSomething2();
}
public void doSomething2() {
System.out.println("CustomerServiceImpl.doSomething2()");
}
}
現(xiàn)在我需要在CustomerService接口的每個(gè)方法被調(diào)用時(shí)都在方法前執(zhí)行一些邏輯脆粥,所以需要配置一個(gè)攔截器:
package demo.long;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class CustomerServiceInterceptor {
@Before("execution(* demo.long..*.*(..))")
public void doBefore() {
System.out.println("do some important things before...");
}
}
把Bean加到Spring配置中
<aop:aspectj-autoproxy />
<bean id="customerService" class="demo.long.impl.CustomerServiceImpl" />
<bean id="customerServiceInterceptor" class="demo.long.CustomerServiceInterceptor" />
如果現(xiàn)在外部對(duì)象調(diào)用CustomerService的doSomething1()方法的時(shí)候,會(huì)發(fā)現(xiàn)只有doSomething1()方法執(zhí)行前打印了“do some important things before...”,而doSomething1()內(nèi)部調(diào)用doSomething2()時(shí)并沒(méi)有打印上述內(nèi)容;外部對(duì)象單獨(dú)調(diào)用doSomething2()時(shí)會(huì)打印上述內(nèi)容。
public class CustomerServiceTest {
@Autowired
ICustomerService customerService;
@Test
public void testAOP() {
customerService.doSomething1();
}
}
原因分析
攔截器的實(shí)現(xiàn)原理就是動(dòng)態(tài)代理包个,實(shí)現(xiàn)AOP機(jī)制。Spring 的代理實(shí)現(xiàn)有兩種:一是基于 JDK Dynamic Proxy 技術(shù)而實(shí)現(xiàn)的冤留;二是基于 CGLIB 技術(shù)而實(shí)現(xiàn)的碧囊。如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,在默認(rèn)情況下Spring會(huì)采用JDK的動(dòng)態(tài)代理實(shí)現(xiàn)AOP纤怒,CustomerServerImpl正是這種情況糯而。
JDK動(dòng)態(tài)代理生成的CustomerServiceImpl的代理類大致如下:
public class CustomerServiceProxy implements CustomerService {
private CustomerService customerService;
public void setCustomerService(CustomerService customerService) {
this.customerService = customerService;
}
public void doSomething1() {
doBefore();
customerService.doSomething1();
}
public void doSomething2() {
doBefore();
customerService.doSomething2();
}
private void doBefore() {
// 例如,可以在此處開(kāi)啟事務(wù)或記錄日志
System.out.println("do some important things before...");
}
}
客戶端程序使用代理類對(duì)象去調(diào)用業(yè)務(wù)邏輯:
public class TestProxy {
public static void main(String[] args) {
// 創(chuàng)建代理目標(biāo)對(duì)象
// 對(duì)于Spring來(lái)說(shuō)泊窘,這一工作是由Spring容器完成的熄驼。
CustomerService serviceProxyTarget = new CustomerServiceImpl();
// 創(chuàng)建代理對(duì)象
// 對(duì)于Spring來(lái)說(shuō),這一工作也是由Spring容器完成的烘豹。
CustomerServiceProxy serviceProxy = new CustomerServiceProxy();
serviceProxy.setCustomerService(serviceProxyTarget);
CustomerService serviceBean = (CustomerService) serviceProxy;
// 調(diào)用業(yè)務(wù)邏輯操作
serviceBean.doSomething1();
}
}
執(zhí)行main方法瓜贾,發(fā)現(xiàn)doSomething1()中調(diào)用doSomething2()方法的時(shí)候并未去執(zhí)行CustomerServiceProxy類的doBefore()方法。其實(shí)doSomething2()等同于this.doSomething2()携悯,在CustomerServiceImpl類中this關(guān)鍵字表示的是當(dāng)前這個(gè)CustomerServiceImpl類的實(shí)例祭芦,所以程序會(huì)去執(zhí)行CustomerServiceImpl對(duì)象中的doSomething2()方法,而不會(huì)去執(zhí)行CustomerServiceProxy類對(duì)象中的 doSomething2()方法憔鬼。
在使用Spring AOP的時(shí)候龟劲,我們從IOC容器中獲取的Bean對(duì)象其實(shí)都是代理對(duì)象胃夏,而不是那些Bean對(duì)象本身,由于this關(guān)鍵字引用的并不是該Service Bean對(duì)象的代理對(duì)象昌跌,而是其本身仰禀,因此Spring AOP是不能攔截到這些被嵌套調(diào)用的方法的。
解決方案
- 修改類蚕愤,不要出現(xiàn)“自調(diào)用”的情況:這是Spring文檔中推薦的“最佳”方案悼瘾;
- 若一定要使用“自調(diào)用”,那么this.doSomething2()替換為:((CustomerService) AopContext.currentProxy()).doSomething2()审胸;此時(shí)需要修改spring的aop配置:
<aop:aspectj-autoproxy expose-proxy="true" />