天天看點

spring之面向切面程式設計(AOP)

什麼是AOP?

AOP(Aspect Oriented programming),又叫面向切面程式設計,在原有縱向執行流程中添加橫切面(也稱為前置通知和後置通知)。正常程式執行流程都是縱向執行流程(Demo1→Demo2→Demo3)。在程式原有縱向執行流程中,針對某一個或某一些方法添加通知,形成橫切面的過程就叫做面向切面程式設計(Demo1→前置通知→Demo2→後置通知→Demo3)。如下圖:

spring之面向切面程式設計(AOP)

通俗來講,就是在某個方法執行前,先執行一些操作;方法執行後再執行一些操作。一般用來攔截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