什麼是AOP?
AOP(Aspect Oriented programming),又叫面向切面程式設計,在原有縱向執行流程中添加橫切面(也稱為前置通知和後置通知)。正常程式執行流程都是縱向執行流程(Demo1→Demo2→Demo3)。在程式原有縱向執行流程中,針對某一個或某一些方法添加通知,形成橫切面的過程就叫做面向切面程式設計(Demo1→前置通知→Demo2→後置通知→Demo3)。如下圖:
通俗來講,就是在某個方法執行前,先執行一些操作;方法執行後再執行一些操作。一般用來攔截service方法,service方法一般不進行try和catch,否則spring捕獲不到異常,在後面進行聲明式事務時,就不能進行事務復原。
AOP常用概念
- 切點(PointCut):原有功能,反映在上圖就是Demo2,即要在Demo2處切入;
- 前置通知(Before Advice):在切點之前執行的功能;
- 後置通知(After Advice): 在切點之後執行的功能;
- 如果切點在執行過程中出現異常,會出發異常通知(Throw Advice);
- 所有功能總稱叫做切面;
- 織入:把切面嵌入到原有功能的過程叫做織入;
spring實作AOP的兩種方式
- schema-based:每個通知都需要實作接口或類,配置spring配置檔案時在
配置;<aop:config>
- aspectJ:每個通知不需要實作接口或類,配置spring配置檔案時在
的子标簽<aop:config>
中配置<aop:aspect>
AOP導入的包
①bean、context、core、expression、aop、aspect、tx(spring環境包)
②日志列印:commons-logging
③aop依賴的jar包:aopalliance、aspectjweaver(aop是依賴這兩個包寫的,不是自己寫的)
AOP實作前置和後置通知(schema-based)
- Demo類(在demo2處設定切面):
public class Demo{
public void demo1(){
System.out.println("demo1");
}
public void demo2(){
System.out.println("demo2");
}
public void demo3(){
System.out.println("demo3");
}
}
- 建立前置通知類
A:arg0:表示切點方法對象method對象
B:arg1:切點方法的參數
C:arg2:切點在哪個對象中
public class MyBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("執行前置通知");
}
}
- 建立後置通知類
A:arg0 切點方法傳回值
B:arg1表示切點方法對象method對象
C:arg2切點方法的參數
D:arg3切點方法所在類的對象
public class MyAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
System.out.println("執行後置通知");
}
}
- spring配置檔案ApplicationContext.xml中引入aop命名空間:
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
- 在配置檔案中配置切面
注意:
①excution(* 切點方法的全路徑),這個是固定表達式,“ * ”表示聲明出通配符;
例如:excution(* xxx.xxx.test.Demo.demo2());
②如果方法有參數:
public void demo2(String name,int id){}
這樣寫:excution(* xxx.xxx.test.Demo.demo2(String,int)) and args(name,id)
③通配符的使用:
*為通配符,可以比對任意方法,任意類和任意一級包;
比對任意無參數方法:excution(* xxx.xxx.test.Demo.*());
比對任意參數方法:excution(* xxx.xxx.test.Demo.*(. .));
比對任意類的任意參數方法:excution(* xxx.xxx.test.*.*(. .));
以此類推。
<!-- 配置通知類的對象 -->
<bean id="mybefore" class="xxx.xxx.advice.MyBeforeAdvice"></bean>
<bean id="myafter" class="xxx.xxx.advice.MyAfterAdvice"></bean>
<!-- 配置切面 -->
<aop:config>
<!-- 配置切點 -->
<aop:pointcut expression="execution(* xxx.xxx.test.Demo.demo2())" id="mypoint"/>
<!-- 通知 -->
<aop:advisor advice-ref="mybefore" pointcut-ref="mypoint"/>
<aop:advisor advice-ref="myafter" pointcut-ref="mypoint"/>
</aop:config>
<!-- 配置demo類,測試使用 -->
<bean id="demo" class="Demo"></bean>
- 測試:
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Demo demo = ac.getBean("demo",Demo.class);
demo.demo1();
demo.demo2();
demo.demo3();
- 測試結果:
demo1
執行前置通知
demo2
執行後置通知
demo3
AOP實作通知(AspectJ方式)
- 通知類
public class MyAdvice {
public void before() {
System.out.println("前置");
}
public void after() {
System.out.println("後置");
}
public void aftering() {
System.out.println("後置returning");
}
public void throwing() {
System.out.println("異常");
}
public Object arround(ProceedingJoinPoint p) throws Throwable {
System.out.println("執行環繞");
System.out.println("環繞-前置");
Object result = p.proceed();
System.out.println("環繞-後置");
return result;
}
}
- 配置通知
<bean id="myadvice" class="xxx.xxx.advice.MyAdvice"></bean>
<aop:config>
<aop:aspect ref="myadvice">
<aop:pointcut expression="execution(* xxx.xxx.test.Demo.demo1())" id="mypoint"/>
<aop:before method="before" pointcut-ref="mypoint"/>
<aop:after method="after" pointcut-ref="mypoint"/>
<aop:after-returning method="aftering" pointcut-ref="mypoint"/>
<aop:around method="arround" pointcut-ref="mypoint"/>
<aop:after-throwing method="throwing" pointcut-ref="mypoint"/>
</aop:aspect>
</aop:config>
<bean id="demo" class="xxx.xxx.test.Demo"></bean>
<aop:after>:不管出不出異常都執行
<aop:after-returning>:不出異常才執行
<aop:fater-throwing>:執行順序和配置順序一緻
- 擷取參數
public class Demo {
public void demo1(String name, int age) {
System.out.println("demo1:"+name+" "+age);
}
}
public class MyAdvice {
public void before(String name1, int age1) {
System.out.println("前置:"+name+" "+age);
}
}
<bean id="myadvice" class="MyAdvice"></bean>
<aop:config>
<aop:aspect ref="myadvice">
<aop:pointcut expression="execution(* Demo.demo1(String, int)) and args(name1,age1)" id="mypoint"/>
<aop:before method="before" pointcut-ref="mypoint" arg-names="name1,age1"/>
<!--這裡的name1等必須和advice的參數一緻-->
</aop:aspect>
</aop:config>
<bean id="demo" class="test.Demo"></bean>
<aop:before> 中arg-names =”名稱”,名稱來源于
expression=“”中的args(),名稱必須一緻;
AOP配置異常通知(schema-based)
- 隻有當切點報異常才能觸發異常通知;
- 建立一個類,實作
接口,必須自己寫方法,且必須叫ThrowsAdvice
。afterThrowing
public class MyThrowAdvice implements ThrowsAdvice{
public void afterThrowing(Exception ex) throws Throwable {
System.out.println("執行異常通知");
}
}
- 配置檔案
<!--異常通知類-->
<bean id="mythrow" class="xxx.xxx.advice.MyThrowAdvice"></bean>
<!--配置切面-->
<aop:config>
<!--配置切點-->
<aop:pointcut expression="execution(* xxx.xxx.test.Demo.demo1())" id="mypoint"/>
<!--配置異常通知方法到切點-->
<aop:advisor advice-ref="mythrow" pointcut-ref="mypoint"/>
</aop:config>
<!--測試-->
<bean id="demo" class="xxx.xxx.test.Demo"></bean>
AOP配置異常通知(AspectJ方式)
- 隻有當切點報異常才能觸發異常通知。在spring中有AspectJ方式提供了異常通知的辦法,如果希望通過schema-base實作,需要按照特定的要求自己編寫方法;這種配置方式對切點要求低,但對配置檔案要求高。
- Demo類
public class Demo throws Exception{
public void demo1(){
int i = 5/0;
System.out.println("demo1");
}
}
- 建立異常通知類
public class MyThrowAdvice {
public void mythrow(Exception e) {
System.out.println("執行異常通知,異常資訊:"+e.getMessage());
}
}
- 在spring配置檔案中配置
<aop:aspect>的ref屬性表示:方法在哪個類中
<aop:XXX>表示什麼通知
method:通知時調用什麼方法
throwing:異常對象名,必須和通知中方法參數名相同(可以不再通知中聲明異常對象)
<!--異常通知類-->
<bean id="throw" class="xxx.xxx.advice.MyThrowAdvice"></bean>
<!--配置切面-->
<aop:config>
<!--引用異常通知類-->
<aop:aspect ref="throw">
<!--配置切點-->
<aop:pointcut expression="execution(* xxx.xxx.test.Demo.demo1())" id="mypoint"/>
<!--配置異常通知方法到切點-->
<aop:after-throwing method="mythrow" pointcut-ref="mypoint" throwing="e"/>
</aop:aspect>
</aop:config>
<!--測試-->
<bean id="demo" class="xxx.xxx.test.Demo"></bean>
AOP配置環繞通知(schema-based)
- 把前置通知和後置通知都寫到一個通知中,組成了環繞通知;
- 建立環繞通知類
public class MyArround implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation arg0) throws Throwable {
System.out.println("環繞-前置");
Object proceed = arg0.proceed(); //放行,調用切點方法
System.out.println("環繞-後置");
return proceed;
}
}
- 配置環繞通知
<bean id="myarround" class="xxx.xxx.advice.MyArround"></bean>
<aop:config>
<aop:pointcut expression="execution(* xxx.xxx.test.Demo.demo1())" id="mypoint"/>
<aop:advisor advice-ref="myarround" pointcut-ref="mypoint"/>
</aop:config>
<bean id="demo" class="xxx.xxx.test.Demo"></bean>
使用注解配置AOP