AOP的全稱是Aspect Oriented Programming,面向切面編程。
切面是什么呢,就是一個Java類。之所以叫切面,就是說在調用其他一些類的方法的某個時候(之前,之后,拋異常等),調用這個類里的某個方法,因為要對很多類的很多方法進行相同的作用,就像一刀切一樣。
aop涉及到三個要素:將哪個類的哪個方法,在什么時候,切入到哪個類的哪個方法上。依次對應了切面組件Aspect、通知Advice、切入點pointcut
看下面的示例,一個Controller組件,一個Service組件,兩個Aspect組件,一個測試類,代碼如下:
//Controller組件
package net.sonng.controller;
import javax.annotation.Resource;
import net.sonng.service.TestService;
import org.springframework.stereotype.Controller;
@Controller
public class TestController {
@Resource
private TestService ts;
public void testA(){
System.out.println("Controller的testA方法開始執行");
ts.testA();
ts.testB();
}
public void testB(){
System.out.println("Controller的testB方法開始執行");
}
}
//Service組件
package net.sonng.service;
import org.springframework.stereotype.Service;
@Service
public class TestService {
public void testA (){
System.out.println("TestService的TestA方法開始執行");
}
public void testB(){
System.out.println("TestService的TestB方法開始執行");
}
}
//切面A
package net.sonng.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component
public class TestLogA {
public void log(){
System.out.println("TestLogA是切面組件,切入點是Controller的方法前面");
}
}
//切面B
package net.sonng.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class TestLogB {
@Before("execution(* net.sonng.service.TestService.testA(..))") //方法限定表達式
public void log(){
System.out.println("TestLogB是切面組件,在調用Service方法前調用");
}
}
//測試類
package net.sonng.test;
import net.sonng.controller.TestController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args){
ApplicationContext ac=new ClassPathXmlApplicationContext("ac.xml");
TestController tc=ac.getBean("testController",TestController.class);
tc.testA();
tc.testB();
}
}
采用xml配置,TestLogA的log方法在Controller組件的所有方法調用前調用
ac.xml配置如下:
<mvc:annotation-driven /> <!-- 開啟注解掃描 -->
<context:component-scan base-package="net.sonng" /> <!-- 開啟組件掃描 -->
<aop:aspectj-autoproxy /> <!-- 開啟AOP注解掃描 -->
<bean id="log" class="net.sonng.aspect.TestLogA"></bean> <!-- 聲明切面組件 -->
<aop:config>
<aop:aspect ref="log" > <!-- 以log為切面組件 -->
<aop:before method="log" pointcut="within(net.sonng.controller.TestController)"/>
</aop:aspect> <!-- aop:before是前置通知;method表示要調用的切面的方法;pointcut表示切入點,可以用類型/方法限定表達式 -->
</aop:config>
<!-- 這個xml配置表示:在net.sonng.controller.TestController下的所有方法被調用前,先調用net.sonng.aspect.TestLogA的log方法 -->
用注解配置,TestLogB的log方法在TestService的testA()方法調用前調用
注解配置見代碼,要點如下:
1. 用@Aspect聲明TestLogB是一個切面組件
2. 在log方法前添加@Before注解,這是前置通知
3. 在@Before注解的屬性中指定切入點,這里用的是方法限定表達式
測試類輸出如下:
TestLogA是切面組件,切入點是Controller的方法前面 //TestLogA的log方法在Controller的testA()方法前調用
Controller的testA方法開始執行
TestLogB是切面組件,在調用Service方法前調用 //TestLogB的log方法在Service的testA()方法前調用
TestService的TestA方法開始執行
TestService的TestB方法開始執行 //因為TestLogB的log方法指定了只在testA前調用,在testB前不調用
TestLogA是切面組件,切入點是Controller的方法前面 //TestLogA的log方法在Controller的testB()方法前調用
Controller的testB方法開始執行
切面組件Aspect
切面組件一般是登錄檢查、日志記錄、事務管理等
xml配置中先聲明bean,再在aop:config的aop:aspect中指定為切面
或者用@Aspect注解指定
通知advice
通知就是:在調用目標方法的什么時候調用切面組件的方法,可以是:
前置通知:@Before,方法調用之前
后置通知:@AfterReturning,方法正常返回
異常通知:@AfterThrowing,方法調用過程中出現異常
最終通知:@After,不管方法是正常返回還是出現異常,這個通知都會調用,因此必須這兩種可能的情況
環繞通知:@Around,
切入點
切入點就是要在哪些類的哪些方法上進行切入,可以限定一些類下的所有方法,或者不同類下的一些方法,或者指定類下的指定方法
方法限定表達式:
基本模式:execution([修飾符] 返回值類型 方法名(參數) [異常類型])。具體見Spring Reference 4.3.6版第223頁
execution(public * * (..)):所有 的public方法
execution(* set(..)):以set打頭的任意方法
execution( com.xyz.service.SomeService.(..):SomeService類下的任意方法
execution( com.xyz.service..(..)):service包下的任意類的任意方法
類型限定表達式:
within(com.xyz.service.):service包下的任意類下的任意方法,不包含子包
within(com.xyz.service..):包含子包