跟我學Spring3(6.3):基于Schema的AOP

Roc95S 8年前發布 | 11K 次閱讀 AOP Spring JEE框架

來自: http://www.importnew.com/17795.html

6.3 基于Schema的AOP

基于Schema的AOP從Spring2.0之后通過“aop”命名空間來定義切面、切入點及聲明通知。

在Spring配置文件中,所以AOP相關定義必須放在<aop:config>標簽下,該標簽下可以有<aop:pointcut>、<aop:advisor>、<aop:aspect>標簽,配置順序不可變。

<aop:pointcut>:用來定義切入點,該切入點可以重用;

<aop:advisor>:用來定義只有一個通知和一個切入點的切面;

<aop:aspect>:用來定義切面,該切面可以包含多個切入點和通知,而且標簽內部的通知和切入點定義是無序的;和advisor的區別就在此,advisor只包含一個通知和一個切入點。

</div>

6.3.1 聲明切面

切面就是包含切入點和通知的對象,在Spring容器中將被定義為一個Bean,Schema方式的切面需要一個切面支持Bean,該支持Bean的字段和方法提供了切面的狀態和行為信息,并通過配置方式來指定切入點和通知實現。

切面使用<aop:aspect>標簽指定,ref屬性用來引用切面支持Bean。

切面支持Bean“aspectSupportBean”跟普通Bean完全一樣使用,切面使用“ref”屬性引用它。

6.3.2 聲明切入點

切入點在Spring中也是一個Bean,Bean定義方式可以有很三種方式:

1)在<aop:config>標簽下使用<aop:pointcut>聲明一個切入點Bean,該切入點可以被多個切面使用,對于需要共享使用的切入點最好使用該方式,該切入點使用id屬性指定Bean名字,在通知定義時使用pointcut-ref屬性通過該id引用切入點,expression屬性指定切入點表達式:

java代碼:

<aop:config> 
 <aop:pointcut id="pointcut" expression="execution(* cn.javass..*.*(..))"/> 
 <aop:aspect ref="aspectSupportBean"> 
 <aop:before pointcut-ref="pointcut" method="before"/> 
 </aop:aspect> 
</aop:config>

2)在<aop:aspect>標簽下使用<aop:pointcut>聲明一個切入點Bean,該切入點可以被多個切面使用,但一般該切入點只被該切面使用,當然也可以被其他切面使用,但最好不要那樣使用,該切入點使用id屬性指定Bean名字,在通知定義時使用pointcut-ref屬性通過該id引用切入點,expression屬性指定切入點表達式:

java代碼:

<aop:config> 
 <aop:aspect ref="aspectSupportBean"> 
 <aop:pointcut id=" pointcut" expression="execution(* cn.javass..*.*(..))"/> 
 <aop:before pointcut-ref="pointcut" method="before"/> 
 </aop:aspect> 
</aop:config>

3)匿名切入點Bean,可以在聲明通知時通過pointcut屬性指定切入點表達式,該切入點是匿名切入點,只被該通知使用:

java代碼:

<aop:config> 
 <aop:aspect ref="aspectSupportBean"> 
 <aop:after pointcut="execution(* cn.javass..*.*(..))" method="afterFinallyAdvice"/> 
 </aop:aspect> 
</aop:config>

6.3.3 聲明通知

基于Schema方式支持前邊介紹的5中通知類型:

一、前置通知:在切入點選擇的方法之前執行,通過<aop:aspect>標簽下的<aop:before>標簽聲明:

java代碼:

<aop:before pointcut="切入點表達式" pointcut-ref="切入點Bean引用" 
method="前置通知實現方法名" 
arg-names="前置通知實現方法參數列表參數名字"/>

pointcut和pointcut-ref:二者選一,指定切入點;

method:指定前置通知實現方法名,如果是多態需要加上參數類型,多個用“,”隔開,如beforeAdvice(java.lang.String);

arg-names:指定通知實現方法的參數名字,多個用“,”分隔,可選,類似于【3.1.2 構造器注入】中的參數名注入限制:在class文件中沒生成變量調試信息是獲取不到方法參數名字的,因此只有在類沒生成變量調試信息時才需要使用arg-names屬性來指定參數名,如arg-names=”param”表示通知實現方法的參數列表的第一個參數名字為“param”。

</div>

首先在cn.javass.spring.chapter6.service.IhelloWorldService定義一個測試方法:

java代碼:

public void sayBefore(String param);

其次在cn.javass.spring.chapter6.service.impl. HelloWorldService定義實現

java代碼:

@Override 
public void sayBefore(String param) { 
 System.out.println("============say " + param); 
}

第三在cn.javass.spring.chapter6.aop. HelloWorldAspect定義通知實現:

java代碼:

public void beforeAdvice(String param) { 
 System.out.println("===========before advice param:" + param); 
}

最后在chapter6/advice.xml配置文件中進行如下配置:

java代碼:

<bean id="helloWorldService" class="cn.javass.spring.chapter6.service.impl.HelloWorldService"/> 
<bean id="aspect" class="cn.javass.spring.chapter6.aop.HelloWorldAspect"/> 
<aop:config> 
 <aop:aspect ref="aspect"> 
 <aop:before pointcut="execution(* cn.javass..*.sayBefore(..)) and args(param)" 
 method="beforeAdvice(java.lang.String)" 
 arg-names="param"/> 
 </aop:aspect> 
</aop:config>

測試代碼cn.javass.spring.chapter6.AopTest:

java代碼:

@Test 
public void testSchemaBeforeAdvice(){ 
 System.out.println("======================================"); 
 ApplicationContext ctx = new ClassPathXmlApplicationContext("chapter6/advice.xml"); 
 IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class); 
 helloworldService.sayBefore("before"); 
 System.out.println("======================================"); 
}

將輸入:

==========================================
===========before advice param:before
============say before
==========================================

分析一下吧:

1)切入點匹配:在配置中使用“execution(* cn.javass..*.sayBefore(..)) ”匹配目標方法sayBefore,且使用“args(param)”匹配目標方法只有一個參數且傳入的參數類型為通知實現方法中同名的參數類型;

2)目標方法定義:使用method=” beforeAdvice(java.lang.String) “指定前置通知實現方法,且該通知有一個參數類型為java.lang.String參數;

3)目標方法參數命名:其中使用arg-names=” param “指定通知實現方法參數名為“param”,切入點中使用“args(param)”匹配的目標方法參數將自動傳遞給通知實現方法同名參數。

</div>

二、后置返回通知:在切入點選擇的方法正常返回時執行,通過<aop:aspect>標簽下的<aop:after-returning>標簽聲明:

java代碼:

<aop:after-returning pointcut="切入點表達式" pointcut-ref="切入點Bean引用" 
 method="后置返回通知實現方法名" 
 arg-names="后置返回通知實現方法參數列表參數名字" 
 returning="返回值對應的后置返回通知實現方法參數名" 
/>

pointcut和pointcut-ref:同前置通知同義;

method:同前置通知同義;

arg-names:同前置通知同義;

returning:定義一個名字,該名字用于匹配通知實現方法的一個參數名,當目標方法執行正常返回后,將把目標方法返回值傳給通知方法;returning限定了只有目標方法返回值匹配與通知方法相應參數類型時才能執行后置返回通知,否則不執行,對于returning對應的通知方法參數為Object類型將匹配任何目標返回值。

</div>

首先在cn.javass.spring.chapter6.service.IhelloWorldService定義一個測試方法:

java代碼:

public boolean sayAfterReturning();

其次在cn.javass.spring.chapter6.service.impl. HelloWorldService定義實現

java代碼:

@Override 
public boolean sayAfterReturning() { 
 System.out.println("============after returning"); 
 return true; 
}

第三在cn.javass.spring.chapter6.aop. HelloWorldAspect定義通知實現:

java代碼:

public void afterReturningAdvice(Object retVal) { 
 System.out.println("===========after returning advice retVal:" + retVal); 
}

最后在chapter6/advice.xml配置文件中接著前置通知配置的例子添加如下配置:

java代碼:

<aop:after-returning pointcut="execution(* cn.javass..*.sayAfterReturning(..))" 
 method="afterReturningAdvice" 
 arg-names="retVal" 
 returning="retVal"/>

測試代碼cn.javass.spring.chapter6.AopTest:

java代碼:

@Test 
public void testSchemaAfterReturningAdvice() { 
 System.out.println("======================================"); 
 ApplicationContext ctx = new ClassPathXmlApplicationContext("chapter6/advice.xml"); 
 IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class); 
 helloworldService.sayAfterReturning(); 
 System.out.println("======================================"); 
}

將輸入:

======================================
============after returning
===========after returning advice retVal:true
=====================================

分析一下吧:

1)切入點匹配:在配置中使用“execution(* cn.javass..*.sayAfterReturning(..)) ”匹配目標方法sayAfterReturning,該方法返回true;

2)目標方法定義:使用method=”afterReturningAdvice”指定后置返回通知實現方法;

3)目標方法參數命名:其中使用arg-names=”retVal”指定通知實現方法參數名為“retVal”;

4)返回值命名:returning=”retVal”用于將目標返回值賦值給通知實現方法參數名為“retVal”的參數上。

</div>

三、后置異常通知:在切入點選擇的方法拋出異常時執行,通過<aop:aspect>標簽下的<aop:after-throwing>標簽聲明:

java代碼:

<aop:after-throwing pointcut="切入點表達式" pointcut-ref="切入點Bean引用" 
 method="后置異常通知實現方法名" 
 arg-names="后置異常通知實現方法參數列表參數名字" 
 throwing="將拋出的異常賦值給的通知實現方法參數名"/>

pointcut和pointcut-ref:同前置通知同義;

method:同前置通知同義;

arg-names:同前置通知同義;

throwing:定義一個名字,該名字用于匹配通知實現方法的一個參數名,當目標方法拋出異常返回后,將把目標方法拋出的異常傳給通知方法;throwing限定了只有目標方法拋出的異常匹配與通知方法相應參數異常類型時才能執行后置異常通知,否則不執行,對于throwing對應的通知方法參數為Throwable類型將匹配任何異常。

</div>

首先在cn.javass.spring.chapter6.service.IhelloWorldService定義一個測試方法:

java代碼:

public void sayAfterThrowing();

其次在cn.javass.spring.chapter6.service.impl. HelloWorldService定義實現

java代碼:

@Override 
public void sayAfterThrowing() { 
 System.out.println("============before throwing"); 
 throw new RuntimeException(); 
}

第三在cn.javass.spring.chapter6.aop. HelloWorldAspect定義通知實現:

java代碼:

public void afterThrowingAdvice(Exception exception) { 
 System.out.println("===========after throwing advice exception:" + exception); 
}

最后在chapter6/advice.xml配置文件中接著前置通知配置的例子添加如下配置:

java代碼:

<aop:after-throwing pointcut="execution(* cn.javass..*.sayAfterThrowing(..))" 
 method="afterThrowingAdvice" 
 arg-names="exception" 
 throwing="exception"/>

測試代碼cn.javass.spring.chapter6.AopTest:

java代碼:

@Test(expected = RuntimeException.class) 
public void testSchemaAfterThrowingAdvice() { 
 System.out.println("======================================"); 
 ApplicationContext ctx = new ClassPathXmlApplicationContext("chapter6/advice.xml"); 
 IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class); 
 helloworldService.sayAfterThrowing(); 
 System.out.println("======================================"); 
}

將輸入:

======================================
============before throwing
===========after throwing advice exception:java.lang.RuntimeException
======================================

分析一下吧:

1)切入點匹配:在配置中使用“execution(* cn.javass..*.sayAfterThrowing(..))”匹配目標方法sayAfterThrowing,該方法將拋出RuntimeException異常;

2)目標方法定義:使用method=”afterThrowingAdvice”指定后置異常通知實現方法;

3)目標方法參數命名:其中使用arg-names=”exception”指定通知實現方法參數名為“exception”;

4)異常命名:returning=”exception”用于將目標方法拋出的異常賦值給通知實現方法參數名為“exception”的參數上。

</div>

四、后置最終通知:在切入點選擇的方法返回時執行,不管是正常返回還是拋出異常都執行,通過<aop:aspect>標簽下的<aop:after >標簽聲明:

java代碼:

<aop:after pointcut="切入點表達式" pointcut-ref="切入點Bean引用" 
 method="后置最終通知實現方法名" 
 arg-names="后置最終通知實現方法參數列表參數名字"/>

pointcut和pointcut-ref:同前置通知同義;

method:同前置通知同義;

arg-names:同前置通知同義;

</div>

首先在cn.javass.spring.chapter6.service.IhelloWorldService定義一個測試方法:

java代碼:

public boolean sayAfterFinally();

其次在cn.javass.spring.chapter6.service.impl. HelloWorldService定義實現

java代碼:

@Override 
public boolean sayAfterFinally() { 
 System.out.println("============before finally"); 
 throw new RuntimeException(); 
}

第三在cn.javass.spring.chapter6.aop. HelloWorldAspect定義通知實現:

java代碼:

public void afterFinallyAdvice() { 
 System.out.println("===========after finally advice"); 
}

最后在chapter6/advice.xml配置文件中接著前置通知配置的例子添加如下配置:

java代碼:

<aop:after pointcut="execution(* cn.javass..*.sayAfterFinally(..))" 
 method="afterFinallyAdvice"/>

測試代碼cn.javass.spring.chapter6.AopTest:

java代碼:

@Test(expected = RuntimeException.class) 
public void testSchemaAfterFinallyAdvice() { 
 System.out.println("======================================"); 
 ApplicationContext ctx = new ClassPathXmlApplicationContext("chapter6/advice.xml"); 
 IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class); 
 helloworldService.sayAfterFinally(); 
 System.out.println("======================================"); 
}

將輸入:

======================================
============before finally
===========after finally advice
======================================

分析一下吧:

1)切入點匹配:在配置中使用“execution(* cn.javass..*.sayAfterFinally(..))”匹配目標方法sayAfterFinally,該方法將拋出RuntimeException異常;

2)目標方法定義:使用method=” afterFinallyAdvice “指定后置最終通知實現方法。

</div>

五、環繞通知:環繞著在切入點選擇的連接點處的方法所執行的通知,環繞通知非常強大,可以決定目標方法是否執行,什么時候執行,執行時是否需要替換方法參數,執行完畢是否需要替換返回值,可通過<aop:aspect>標簽下的<aop:around >標簽聲明:

java代碼:

<aop:around pointcut="切入點表達式" pointcut-ref="切入點Bean引用" 
 method="后置最終通知實現方法名" 
 arg-names="后置最終通知實現方法參數列表參數名字"/>

pointcut和pointcut-ref:同前置通知同義;

method:同前置通知同義;

arg-names:同前置通知同義;

</div>

環繞通知第一個參數必須是org.aspectj.lang.ProceedingJoinPoint類型,在通知實現方法內部使用ProceedingJoinPoint的proceed()方法使目標方法執行,proceed 方法可以傳入可選的Object[]數組,該數組的值將被作為目標方法執行時的參數。

首先在cn.javass.spring.chapter6.service.IhelloWorldService定義一個測試方法:

java代碼:

public void sayAround(String param);

其次在cn.javass.spring.chapter6.service.impl. HelloWorldService定義實現

java代碼:

@Override 
public void sayAround(String param) { 
 System.out.println("============around param:" + param); 
}

第三在cn.javass.spring.chapter6.aop. HelloWorldAspect定義通知實現:

java代碼:

public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable { 
 System.out.println("===========around before advice"); 
 Object retVal = pjp.proceed(new Object[] {"replace"}); 
 System.out.println("===========around after advice"); 
 return retVal; 
}

最后在chapter6/advice.xml配置文件中接著前置通知配置的例子添加如下配置:

java代碼:

<aop:around pointcut="execution(* cn.javass..*.sayAround(..))" 
 method="aroundAdvice"/>

測試代碼cn.javass.spring.chapter6.AopTest:

java代碼:

@Test 
public void testSchemaAroundAdvice() { 
 System.out.println("======================================"); 
 ApplicationContext ctx = new ClassPathXmlApplicationContext("chapter6/advice.xml"); 
 IHelloWorldService helloworldService = 
 ctx.getBean("helloWorldService", IHelloWorldService.class); 
 helloworldService.sayAround("haha"); 
 System.out.println("======================================"); 
}

將輸入:

======================================
===========around before advice
============around param:replace
===========around after advice
======================================

分析一下吧:

1)切入點匹配:在配置中使用“execution(* cn.javass..*.sayAround(..))”匹配目標方法sayAround;

2)目標方法定義:使用method=”aroundAdvice”指定環繞通知實現方法,在該實現中,第一個方法參數為pjp,類型為ProceedingJoinPoint,其中“Object retVal = pjp.proceed(new Object[] {“replace”});”,用于執行目標方法,且目標方法參數被“new Object[] {“replace”}”替換,最后返回“retVal ”返回值。

3)測試:我們使用“helloworldService.sayAround(“haha”);”傳入參數為“haha”,但最終輸出為“replace”,說明參數被替換了。

</div>

6.3.4 引入

Spring引入允許為目標對象引入新的接口,通過在< aop:aspect>標簽內使用< aop:declare-parents>標簽進行引入,定義方式如下:

java代碼:

<aop:declare-parents 
 types-matching="AspectJ語法類型表達式" 
 implement-interface=引入的接口" 
 default-impl="引入接口的默認實現" 
 delegate-ref="引入接口的默認實現Bean引用"/>

types-matching:匹配需要引入接口的目標對象的AspectJ語法類型表達式;

implement-interface:定義需要引入的接口;

default-impl和delegate-ref:定義引入接口的默認實現,二者選一,default-impl是接口的默認實現類全限定名,而delegate-ref是默認的實現的委托Bean名;

</div>

接下來讓我們練習一下吧:首先定義引入的接口及默認實現:

java代碼:

package cn.javass.spring.chapter6.service; 
public interface IIntroductionService { 
 public void induct(); 
}

java代碼:

package cn.javass.spring.chapter6.service.impl; 
import cn.javass.spring.chapter6.service.IIntroductionService; 
public class IntroductiondService implements IIntroductionService { 
 @Override 
 public void induct() { 
 System.out.println("=========introduction"); 
 } 
}

其次在chapter6/advice.xml配置文件中接著前置通知配置的例子添加如下配置:

java代碼:

<aop:declare-parents 
 types-matching="cn.javass..*.IHelloWorldService+" 
 implement-interface="cn.javass.spring.chapter6.service.IIntroductionService" 
 default-impl="cn.javass.spring.chapter6.service.impl.IntroductiondService"/>

最后測試一下吧,測試代碼cn.javass.spring.chapter6.AopTest:

java代碼:

@Test 
public void testSchemaIntroduction() { 
 System.out.println("======================================"); 
 ApplicationContext ctx = new ClassPathXmlApplicationContext("chapter6/advice.xml"); 
 IIntroductionService introductionService = 
 ctx.getBean("helloWorldService", IIntroductionService.class); 
 introductionService.induct(); 
 System.out.println("======================================"); 
}

將輸入:

======================================
=========introduction
======================================

分析一下吧:

1)目標對象類型匹配:使用types-matching=”cn.javass..*.IHelloWorldService+”匹配IHelloWorldService接口的子類型,如HelloWorldService實現;

2)引入接口定義:通過implement-interface屬性表示引入的接口,如“cn.javass.spring.chapter6.service.IIntroductionService”。

3)引入接口的實現:通過default-impl屬性指定,如“cn.javass.spring.chapter6.service.impl.IntroductiondService”,也可以使用“delegate-ref”來指定實現的Bean。

4)獲取引入接口:如使用“ctx.getBean(“helloWorldService”, IIntroductionService.class);”可直接獲取到引入的接口。

</div>

6.3.5 Advisor

Advisor表示只有一個通知和一個切入點的切面,由于Spring AOP都是基于AOP聯盟的攔截器模型的環繞通知的,所以引入Advisor來支持各種通知類型(如前置通知等5種),Advisor概念來自于Spring1.2對AOP的支持,在AspectJ中沒有相應的概念對應。

Advisor可以使用<aop:config>標簽下的<aop:advisor>標簽定義:

java代碼:

<aop:advisor pointcut="切入點表達式" pointcut-ref="切入點Bean引用" 
 advice-ref="通知API實現引用"/>

pointcut和pointcut-ref:二者選一,指定切入點表達式;advice-ref:引用通知API實現Bean,如前置通知接口為MethodBeforeAdvice;

接下來讓我們看一下示例吧:首先在cn.javass.spring.chapter6.service.IhelloWorldService定義一個測試方法:

java代碼:

public void sayAdvisorBefore(String param)

其次在cn.javass.spring.chapter6.service.impl. HelloWorldService定義實現

java代碼:

@Override 
public void sayAdvisorBefore(String param) { 
 System.out.println("============say " + param); 
}

第三定義前置通知API實現:

java代碼:

package cn.javass.spring.chapter6.aop; 
import java.lang.reflect.Method; 
import org.springframework.aop.MethodBeforeAdvice; 
public class BeforeAdviceImpl implements MethodBeforeAdvice { 
 @Override 
 public void before(Method method, Object[] args, Object target) throws Throwable { 
 System.out.println("===========before advice"); 
 } 
}

在chapter6/advice.xml配置文件中先添加通知實現Bean定義:

java代碼:

<bean id="beforeAdvice" class="cn.javass.spring.chapter6.aop.BeforeAdviceImpl"/>

然后在<aop:config>標簽下,添加Advisor定義,添加時注意順序:

java代碼:

<aop:advisor pointcut="execution(* cn.javass..*.sayAdvisorBefore(..))" 
 advice-ref="beforeAdvice"/>

測試代碼cn.javass.spring.chapter6.AopTest:

java代碼:

@Test 
public void testSchemaAdvisor() { 
 System.out.println("======================================"); 
 ApplicationContext ctx = new ClassPathXmlApplicationContext("chapter6/advice.xml"); 
 IHelloWorldService helloworldService = 
 ctx.getBean("helloWorldService", IHelloWorldService.class); 
 helloworldService.sayAdvisorBefore("haha"); 
 System.out.println("======================================"); 
}

將輸入:

======================================
===========before advice
============say haha
======================================

在此我們只介紹了前置通知API,其他類型的在后邊章節介紹。不推薦使用Advisor,除了在進行事務控制的情況下,其他情況一般不推薦使用該方式,該方式屬于侵入式設計,必須實現通知API。

本系列:

 本文由用戶 Roc95S 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!