AOP(Aspect Oriented Programing)
面向切面編程采用橫向抽取機(jī)制,以取代傳統(tǒng)的縱向繼承體系的重復(fù)性代碼(如性能監(jiān)控/事務(wù)管理/安全檢查/緩存實(shí)現(xiàn)等)
Spring 實(shí)現(xiàn)AOP方式有cglib和Java動(dòng)態(tài)代理栅贴,默認(rèn)實(shí)現(xiàn)方式為Java動(dòng)態(tài)代理。
AOP相關(guān)術(shù)語
- Joinpoint:連接點(diǎn) 指那些被攔截到的點(diǎn).在Spring中,這些點(diǎn)指方法(因?yàn)镾pring只支持方法類型的連接點(diǎn)).
- Pointcut:切入點(diǎn) 指需要(配置)被增強(qiáng)的Joinpoint
- Advice:通知/增強(qiáng) 指攔截到Joinpoint后要做的操作.通知分為前置通知/后置通知/異常通知/最終通知/環(huán)繞通知等.
- Aspect:切面 切入點(diǎn)和通知的結(jié)合.
- Target:目標(biāo)對象 需要被代理(增強(qiáng))的對象.
- Proxy:代理對象 目標(biāo)對象被AOP 織入 增強(qiáng)/通知后,產(chǎn)生的對象.
- Weaving:織入 指把增強(qiáng)/通知應(yīng)用到目標(biāo)對象來創(chuàng)建代理對象的過程(Spring采用動(dòng)態(tài)代理織入,AspectJ采用編譯期織入和類裝載期織入).
- Introduction: 引介 一種特殊通知,在不修改類代碼的前提下,可以在運(yùn)行期為類動(dòng)態(tài)地添加一些Method/Field(不常用).
-
引入圖中的包
image.png - 在spring配置文件中配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd>
<!--和schema一起啟動(dòng)@Aspectj支持-->
<aop:aspectj-autoproxy/>
或者
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd>
<!--第二種方式啟動(dòng)@Aspectj支持-->
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/>
</beans>
- 使用@Aspect定義一個(gè)切面類
@Aspect
public class TestAspectModel {
...
}
- 定義一個(gè)需要增強(qiáng)的類
@Component(value = "firstaspect")
public class Firstaspect {
public void noReturn() {
System.out.println("執(zhí)行方法noReturn()");
}
public String hasReturn(String argument) {
System.out.println("執(zhí)行方法hasReturn()"+argument);
return " (hasReturn方法執(zhí)行返回值) "+argument;
}
public void throwException() {
System.out.println("執(zhí)行方法throwException()");
throw new IllegalArgumentException("i am a runtime exception");
}
}
- 定義增強(qiáng)方法
增強(qiáng)方式有:
@Before
@AfterReturning
@AfterThrowing
@After
@Around
5.1 @Before
@Aspect
public class TestAspectModel {
@Before("execution(* com.lq.play.model.*.*(..))")
public void before() {
System.out.println("Before!");
}
}
測試用例
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:config/spring/spring-servlet.xml"})
public class ApplicationTest {
@Autowired
private Firstaspect firstaspect;
@Test
public void test() throws Exception {
firstaspect.hasReturn("");
}
}
執(zhí)行結(jié)果:
Before!
執(zhí)行方法hasReturn()
@Before 增強(qiáng)只能在目標(biāo)方法執(zhí)行之前織入增強(qiáng)获高,如果需要阻止目標(biāo)方法執(zhí)行船响,可通過拋出一個(gè)異常來實(shí)現(xiàn)躬拢。
5.2 @AfterReturning
@AfterReturning增強(qiáng)方法將在目標(biāo)方法正常完成后被織入躲履。
@AfterReturning注解有兩個(gè)屬性:
- pointcut/value:兩個(gè)屬性作用相同,既可以是一個(gè)已有的切入點(diǎn)聊闯,也可以是一個(gè)切入點(diǎn)表達(dá)式工猜,當(dāng)指定了pointcut屬性之后,value值將被覆蓋菱蔬。
- returning:指定一個(gè)形參名篷帅,該形參可用于訪問方法返回值。在Advice方法中可以指定形參的類型拴泌,會(huì)限制目標(biāo)方法必須返回指定類型的值或者沒有返回值魏身。
在TestAspectModel 中增加一個(gè)織入方法:
@AfterReturning(returning = "returnValue",pointcut = "execution(* com.lq.play.model.*.*(..))")
//@AfterReturning(returning = "returnValue",pointcut = "myPointCut()")
public void afterReturning(JoinPoint joinPoint,Object returnValue) {
System.out.println("afterReturning!");
System.out.println("afterReturning!: 目標(biāo)方法返回值:"+returnValue);
System.out.println("afterReturning! 被織入增強(qiáng)處理的目標(biāo)方法為:"+joinPoint.getSignature().getName());
System.out.println("afterReturning! 被織入增強(qiáng)處理的目標(biāo)方法的參數(shù)為:"+joinPoint.getArgs());
System.out.println("afterReturning! 被織入增強(qiáng)處理的目標(biāo)對象為:" +joinPoint.getTarget());
}
執(zhí)行結(jié)果:
執(zhí)行方法hasReturn()
afterReturning!
afterReturning!: 目標(biāo)方法返回值: (hasReturn方法執(zhí)行返回值)
afterReturning! 被織入增強(qiáng)處理的目標(biāo)方法為:hasReturn
afterReturning! 被織入增強(qiáng)處理的目標(biāo)方法的參數(shù)為:[Ljava.lang.Object;@2a8448fa
afterReturning! 被織入增強(qiáng)處理的目標(biāo)對象為:com.lq.play.model.Firstaspect@6f204a1a
5.3 @AfterThrowing
@AfterThrowing增強(qiáng)方法將在目標(biāo)方法正常完成后被織入。
@AfterThrowing注解有兩個(gè)屬性:
- pointcut/value:兩個(gè)屬性作用相同弛针,既可以是一個(gè)已有的切入點(diǎn)叠骑,也可以是一個(gè)切入點(diǎn)表達(dá)式,當(dāng)指定了pointcut屬性之后削茁,value值將被覆蓋宙枷。
- throwing:指定一個(gè)形參名,該形參可用于訪問目標(biāo)方法拋出的異常茧跋。在Advice方法中可以指定形參的類型慰丛,會(huì)限制目標(biāo)方法必須拋出指定類型的異常。
向TestAspectModel增加織入方法:
@AfterThrowing(throwing = "ex",pointcut = "execution(* com.lq.play.model.*.*(..))")
public void afterThrowing(IllegalArgumentException ex) {
System.out.println("afterThrowing:"+ex);
}
向Firstaspect添加拋異常方法
public void throwException() {
System.out.println("執(zhí)行方法throwException()");
throw new IllegalArgumentException("i am a runtime exception");
}
測試用例
@ContextConfiguration(locations = {"classpath:config/spring/spring-servlet.xml"})
public class ApplicationTest {
@Autowired
private Firstaspect firstaspect;
@Test
public void test() throws Exception {
// firstaspect.hasReturn("");
firstaspect.throwException();
}
}
返回結(jié)果:
執(zhí)行方法throwException()
afterThrowing:java.lang.IllegalArgumentException: i am a runtime exception
java.lang.IllegalArgumentException: i am a runtime exceptionat com.lq.play.model.Firstaspect.throwException(Firstaspect.java:22)at com.lq.play.model.Firstaspect$$FastClassBySpringCGLIB$$89d307c5.invoke(<generated>)at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)...
5.4 @After
@AfterReturning增強(qiáng)處理在目標(biāo)方法成功執(zhí)行后才會(huì)被織入瘾杭。
@After增強(qiáng)處理不管目標(biāo)方法成功與否诅病,它都會(huì)被織入,和異常捕捉的finally關(guān)鍵字類似粥烁。
增強(qiáng)處理類:
```java
@Aspect
public class TestAspectModel {
@After("execution(* com.lq.play.model.*.*(..))")
public void after() {
System.out.println("After!");
}
@AfterReturning(returning = "returnValue", pointcut = "execution(* com.lq.play.model.*.*(..))")
public void afterReturning(JoinPoint joinPoint, Object returnValue) {
System.out.println("afterReturning!");
System.out.println("afterReturning!: 目標(biāo)方法返回值:" + returnValue);
System.out.println("afterReturning! 被織入增強(qiáng)處理的目標(biāo)方法為:" + joinPoint.getSignature().getName());
System.out.println("afterReturning! 被織入增強(qiáng)處理的目標(biāo)方法的參數(shù)為:" + joinPoint.getArgs());
System.out.println("afterReturning! 被織入增強(qiáng)處理的目標(biāo)對象為:" + joinPoint.getTarget());
}
}
測試用例
@ContextConfiguration(locations = {"classpath:config/spring/spring-servlet.xml"})
public class ApplicationTest {
@Autowired
private Firstaspect firstaspect;
@Test
public void test() throws Exception {
// firstaspect.hasReturn("");
firstaspect.throwException();
}
}
返回結(jié)果
執(zhí)行方法throwException()
After!
java.lang.IllegalArgumentException: i am a runtime exception
at com.lq.play.model.Firstaspect.throwException(Firstaspect.java:22)at com.lq.play.model.Firstaspect$$FastClassBySpringCGLIB$$89d307c5.invoke(<generated>)at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
執(zhí)行了@After增強(qiáng)贤笆,沒有執(zhí)行@AfterReturning
5.6 @Around
這個(gè)是功能最強(qiáng)大的增強(qiáng)處理,既可以在執(zhí)行方法前織入增強(qiáng)動(dòng)作讨阻,也可以在執(zhí)行目標(biāo)方法之后織入增強(qiáng)動(dòng)作芥永。
- @Around可以決定目標(biāo)方法在什么時(shí)候執(zhí)行,如何執(zhí)行钝吮,甚至可以組織目標(biāo)方法的執(zhí)行埋涧。
- @Around可以改變執(zhí)行目標(biāo)方法的參數(shù)值,也可以改標(biāo)執(zhí)行目標(biāo)方法之后的返回值奇瘦。
- @Around增強(qiáng)方法的第一個(gè)參數(shù)必須是ProceedingJoinPoint類型(至少包含一個(gè)形參)棘催,如果程序沒有調(diào)用ProceedingJoinPoint 參數(shù)的proceed方法,則目標(biāo)方法不會(huì)被執(zhí)行耳标。
- 調(diào)用ProceedingJoinPoint 參數(shù)的proceed方法時(shí)醇坝,可以傳入一個(gè)Object[]對象作為參數(shù),該數(shù)組中的值將被傳入目標(biāo)方法為執(zhí)行方法的實(shí)參次坡。
織入方法:
@Aspect
public class TestAspectModel {
@Around("execution(* com.lq.play.model.*.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("around!");
System.out.println("around! 被織入增強(qiáng)處理的目標(biāo)方法為:"+pjp.getSignature().getName());
System.out.println("around! 被織入增強(qiáng)處理的目標(biāo)方法的參數(shù)為:"+Arrays.toString(pjp.getArgs()));
System.out.println("around! 被織入增強(qiáng)處理的目標(biāo)對象為:" +pjp.getTarget());
System.out.println("around!");
Object[] args = pjp.getArgs();
if (args != null && args.length > 0) {
args[0]="around 增加的參數(shù)前綴"+args[0];
}
Object returnValue = pjp.proceed(args);
return "around 增加的執(zhí)行后前綴"+(String)returnValue;
}
}
目標(biāo)方法
public String hasReturn(String argument) {
System.out.println("執(zhí)行方法hasReturn()"+"傳入的參數(shù)值:"+argument);
return "(hasReturn方法執(zhí)行返回值)"+argument;
}
測試用例:
System.out.println(firstaspect.hasReturn(""));
執(zhí)行結(jié)果:
around!
around! 被織入增強(qiáng)處理的目標(biāo)方法為:hasReturn
around! 被織入增強(qiáng)處理的目標(biāo)方法的參數(shù)為:[Ljava.lang.Object;@533bda92
around! 被織入增強(qiáng)處理的目標(biāo)對象為:com.lq.play.model.Firstaspect@304bb45b
around!
執(zhí)行方法hasReturn()傳入的參數(shù)值:around 增加的參數(shù)前綴
around 增加的執(zhí)行后前綴(hasReturn方法執(zhí)行返回值)around 增加的參數(shù)前綴
五個(gè)增強(qiáng)都加入執(zhí)行結(jié)果
around!
around! 被織入增強(qiáng)處理的目標(biāo)方法為:hasReturn
around! 被織入增強(qiáng)處理的目標(biāo)方法的參數(shù)為:[Ljava.lang.Object;@7bd7d6d6
around! 被織入增強(qiáng)處理的目標(biāo)對象為:com.lq.play.model.Firstaspect@43f02ef2
around!
Before!
執(zhí)行方法hasReturn()傳入的參數(shù)值:around 增加的參數(shù)前綴
After!
afterReturning!
afterReturning!: 目標(biāo)方法返回值:around 增加的執(zhí)行后前綴(hasReturn方法執(zhí)行返回值)around 增加的參數(shù)前綴
afterReturning! 被織入增強(qiáng)處理的目標(biāo)方法為:hasReturn
afterReturning! 被織入增強(qiáng)處理的目標(biāo)方法的參數(shù)為:[Ljava.lang.Object;@5b7a7f33
afterReturning! 被織入增強(qiáng)處理的目標(biāo)對象為:com.lq.play.model.Firstaspect@43f02ef2
執(zhí)行了
- @Around
- @Before
- 目標(biāo)方法
- @After
- @AfterReturning
- 訪問目標(biāo)方法的參數(shù)
在增強(qiáng)方法里第一個(gè)參數(shù)定義為JoinPoint類型(ProceedingJoinPoint 即為JoinPoint子類)呼猪,JoinPoint代表了織入增強(qiáng)處理的連接點(diǎn)呀袱。JoinPoint包含如下幾個(gè)方法。
- Object[] getArgs():返回目標(biāo)方法執(zhí)行時(shí)的參數(shù)郑叠。
- Signature getSignature():返回被增強(qiáng)的方法的相關(guān)信息。
- Object getTarget():返回被織入增強(qiáng)處理的目標(biāo)對象明棍。
- Object getThis(): 返回AOP框架為目標(biāo)對象生成的代理對象乡革。
示例如@Around的增強(qiáng)方法:
@Around("execution(* com.lq.play.model.*.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around!");
System.out.println("around! 被織入增強(qiáng)處理的目標(biāo)方法為:" + pjp.getSignature().getName());
System.out.println("around! 被織入增強(qiáng)處理的目標(biāo)方法的參數(shù)為:" + Arrays.toString(pjp.getArgs()));
System.out.println("around! 被織入增強(qiáng)處理的目標(biāo)對象為:" + pjp.getTarget());
System.out.println("around!");
Object[] args = pjp.getArgs();
if (args != null && args.length > 0) {
args[0] = "around 增加的參數(shù)前綴" + args[0];
}
Object returnValue = pjp.proceed(args);
return "around 增加的執(zhí)行后前綴" + (String) returnValue;
}
可以指定織入優(yōu)先級
- 讓切面類實(shí)現(xiàn)org.springframework.core.Ordered接口,實(shí)現(xiàn)該接口值需要實(shí)現(xiàn)一個(gè)int getOrder()方法摊腋,該方法返回值越小沸版,則優(yōu)先級越高。
- 直接使用@Order注解來修飾一個(gè)切面類兴蒸,指定一個(gè)int類型的value屬性视粮,該方法返回值越小,則優(yōu)先級越高橙凳。
可以為表達(dá)式指定參數(shù),如下:
@AfterReturning(returning = "returnValue", pointcut = "execution(* com.lq.play.model.*.*(..))&&args(arg1)")
//@AfterReturning(returning = "returnValue",pointcut = "myPointCut()")
public void afterReturning(JoinPoint joinPoint, Object returnValue,String arg1) {}
- 定義切入點(diǎn)
當(dāng)多個(gè)表達(dá)式相同時(shí)蕾殴,可以定義一個(gè)
@Pointcut("execution(* com.lq.play.model.*.*(..))")
public void myPointCut() {
}
然后其它引入即可
@AfterReturning(returning = "returnValue",pointcut = "myPointCut()")
- 也可以使用xml配置
<bean name="testAspectModel" class="com.lq.play.aspect.TestAspectModel" />
<!-- 進(jìn)行 aop 的配置 -->
<aop:config><!-- 配置切入點(diǎn)表達(dá)式 :哪些類的方法需要進(jìn)行增強(qiáng) 哪些類的方法需要進(jìn)行增強(qiáng) -->
<aop:pointcut expression="execution(* com.lq.play.model.*.*(..))" id="pointcut1"/>
<aop:pointcut expression="execution(* com.lq.play.model.*.*(..))" id="pointcut2"/>
<!-- 配置切面 -->
<aop:aspect ref="testAspectModel">
<aop:before method="before" pointcut-ref ="pointcut1" />
<aop:after-returning method ="afterReturning" pointcut-ref ="pointcut1" returning="returnValue" />
<aop:around method="around" pointcut-ref ="pointcut1" />
<aop:after-throwing method ="afterThrowing" pointcut-ref ="pointcut1" throwing="ex"/>
<aop:after method="after" pointcut-ref ="pointcut1" />
</aop:aspect>
</aop:config>
<aop:aspect .../>有如下三個(gè)屬性:
id定義改切面的標(biāo)識名
ref用于將ref屬性所引用的普通Bean轉(zhuǎn)換為切面Bean.
order:指定該切面Bean的優(yōu)先級,值越小岛啸,優(yōu)先級越高钓觉。
<aop:before.../>
<aop:after-returning.../>
<aop:before.../>
<aop:after-throwing .../>
<aop:after.../>
這幾個(gè)元素都不支持子元素,但有如下屬性:
- pointcut 織入點(diǎn)表達(dá)式
- pointcut-ref 和pointcut只能有一個(gè)坚踩,指定一個(gè)pointcut
- method 指定將該方法轉(zhuǎn)為增強(qiáng)處理方法
- throwing 指定拋出的異常參數(shù)
- returning 目標(biāo)方法返回值