Spring AOP 概念了解及@AspectJ支援
2012-02-24 10:56:35 | 分類: Java | 字号 訂閱
為了更好的了解Spring簡介一文http://quicker.iteye.com/blog/670056 中的概念,下面通過一些示例來加以說明。
首先要了解代理模式:有靜态代理和動态代理
有關代理模式相關文章:
http://quicker.iteye.com/blog/571494
http://quicker.iteye.com/blog/571493
下面先給出靜态代理的代碼。
Java代碼

- public interface UserManager {
- public void add(String name, String password);
- public void del(String id);
- public void modify( int id ,String name, String password);
- }
Java代碼

- public class UserManagerImpl implements UserManager {
- public void add(String name, String password) {
- }
- public void del(String id) {
- }
- public void modify( int id, String name, String password) {
- }
- }
Java代碼

- public class UserManagerProxy implements UserManager {
- private UserManager userManager ;
- public void add(String name, String password) {
- check();
- userManager.add(name, password);
- }
- public void del(String id) {
- check();
- userManager.del(id);
- }
- public void modify( int id, String name, String password) {
- check();
- userManager.modify(id, name, password);
- }
- public void check(){
- System.out.println("check security" );
- }
- public void setUserManager(UserManager userManager){
- this .userManager = userManager;
- }
- public static void main(String[] args) {
- UserManagerProxy proxy = new UserManagerProxy();
- proxy.setUserManager(new UserManagerImpl());
- proxy.add("name" , "pwd" );
- }
- }
上例中代理類控制在UserManagerImpl進行操作前對使用者進行檢查即check()方法。
那麼下面用Spring Aop來實作。
配置步驟:
Java代碼

- 通過示例,了解概念
- 一、建立普通JAVA項目,加入使用者自定義的包:
- 包裡有spring.jar,log4j-1.2 .15 .jar,commons-logging.jar
- 二、拷貝log4j.properties和applicationContext.xml到src目錄
- 三、建立代碼其中UserManager,UserManagerImpl類是使用者管理接口及實作類
- MySecurityManager,MySecurityManagerImpl類是包含安全檢查方法的接口及實作類。
- 四、要啟用@AspectJ 支援,@AspectJ 使用了Java 5 的注解,必須是Java 5 及後的才能使用。
- 在applicationContext.xml加入:<aop:aspectj-autoproxy/>啟用@AspectJ 支援。
- 并在我們的使用者自定義包中要加入aspectjrt.jar,aspectjweaver.jar,這兩個包可以spring釋出包
- 的lib\aspectj下找到。
- 五、聲明一個切面:
- 在類的定義前加入@Aspect ,并引入包 org.aspectj.lang.annotation.Aspect
- @Aspect 我們把用Aspect注解的類就叫切面
切面如:
Java代碼

- package com.lwf.aop;
- import org.aspectj.lang.annotation.Aspect;
- @Aspect
- public class MySecurityManagerImpl implements MySecurityManager {
- public void checkSecurity() {
- System.out.println("User security Check" );
- }
- }
Java代碼

- 六、聲明一個切入點(pointcut)
- 在前面我們提到,切入點決定了連接配接點關注的内容,使得我們可以控制通知什麼時候執行。
- Spring AOP隻支援Spring bean的方法執行連接配接點。是以你可以把切入點看做是Spring bean上方法執行的比對。
- 一個切入點聲明有兩個部分:一個包含名字和任意參數的簽名,還有一個切入點表達式,該表達式決定了我們關注那個方法的執行。
- 在@AspectJ 注解風格的AOP中,一個切入點簽名通過一個普通的方法定義來提供,
- 并且切入點表達式使用@Pointcut 注解來表示(作為切入點簽名的方法必須傳回 void 類型)。
如:
Java代碼

- package com.lwf.aop;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Pointcut;
- @Aspect
- public class MySecurityManagerImpl implements MySecurityManager {
- @Pointcut ( "execution (* add*(..))" )
- public void addAllMethod(){};
- public void checkSecurity() {
- System.out.println("User security Check" );
- }
- }
Java代碼

- 七、聲明通知
- 通知是跟一個切入點表達式關聯起來的,并且在切入點比對的方法執行之前或者之後或者前後運作。
- 切入點表達式可能是指向已命名的切入點的簡單引用或者是一個已經聲明過的切入點表達式。
- 通知有:前置通知,後置通知,異常通知,最終通知,環繞通知
如:我們聲明一個前置通知
Java代碼

- package com.lwf.aop;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Before;
- import org.aspectj.lang.annotation.Pointcut;
- @Aspect
- public class MySecurityManagerImpl implements MySecurityManager {
- @Pointcut ( "execution (* add*(..))" )
- public void addAllMethod(){};
- @Before ( "addAllMethod()" )
- public void checkSecurity() {
- System.out.println("User security Check" );
- }
- }
上面是分步的配置,下面我把整個配置好的項目代碼列出來:
Java代碼

- package com.lwf.aop;
- public interface UserManager {
- public void add(String name, String password);
- public void del(String id);
- public void modify( int id ,String name, String password);
- }
Java代碼

- package com.lwf.aop;
- public class UserManagerImpl implements UserManager {
- public void add(String name, String password) {
- System.out.println("add method" );
- }
- public void del(String id) {
- System.out.println("del method" );
- }
- public void modify( int id, String name, String password) {
- System.out.println("modify method" );
- }
- }
Java代碼

- package com.lwf.aop;
- public interface MySecurityManager {
- public void checkSecurity();
- }
Java代碼

- package com.lwf.aop;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Before;
- import org.aspectj.lang.annotation.Pointcut;
- @Aspect
- public class MySecurityManagerImpl implements MySecurityManager {
- @Pointcut ( "execution(* add*(..))" )
- public void addAllMethod(){};
- @Before ( "addAllMethod()" )
- public void checkSecurity() {
- System.out.println("User security Check" );
- }
- }
配置檔案:
Java代碼

- <?xml version= "1.0" encoding= "UTF-8" ?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xmlns:tx="http://www.springframework.org/schema/tx"
- xsi:schemaLocation="
- http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"
- default -autowire= "byType"
- >
- <aop:aspectj-autoproxy/>
- <bean id="userManager" class = "com.lwf.aop.UserManagerImpl" ></bean>
- <bean id="mySecurityManager" class = "com.lwf.aop.MySecurityManagerImpl" ></bean>
- </beans>
下面我們建立一個測試類:
Java代碼

- package com.lwf.aop;
- import junit.framework.TestCase;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- public class Client extends TestCase{
- public void testAop(){
- ApplicationContext ac = new ClassPathXmlApplicationContext( "applicationContext.xml" );
- UserManager userManager = (UserManager)ac.getBean("userManager" );
- userManager.add("zhangshang" , "123456" );
- }
- }
好了,上面的測試類,應該輸出什麼呢?
按照我們的靜态代理,在調用add之前要先調用check方法,這裡我們是先調用 checkSecurity()方法。
看下面的輸出結果:
Java代碼

- 2010 - 05 - 20 17 : 08 : 44 , 461 INFO [org.springframework.context.support.ClassPathXmlApplicationContext] - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext @affc70 : display name [org.springframework.context.support.ClassPathXmlApplicationContext @affc70 ]; startup date [Thu May 20 17 : 08 : 44 CST 2010 ]; root of context hierarchy
- 2010 - 05 - 20 17 : 08 : 44 , 633 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - Loading XML bean definitions from class path resource [applicationContext.xml]
- 2010 - 05 - 20 17 : 08 : 45 , 055 INFO [org.springframework.context.support.ClassPathXmlApplicationContext] - Bean factory for application context [org.springframework.context.support.ClassPathXmlApplicationContext @affc70 ]: org.springframework.beans.factory.support.DefaultListableBeanFactory @1c9a690
- 2010 - 05 - 20 17 : 08 : 45 , 243 INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory @1c9a690 : defining beans [org.springframework.aop.config.internalAutoProxyCreator,userManager,mySecurityManager]; root of factory hierarchy
- User security Check
- add method
顯然調用了 checkSecurity()方法。
需要注意的是在切入點:@Pointcut ("execution(* add*(..))")這個地方一定要寫對
是execution而不是execute,還有我們設定的是所有以add字元串開頭的方法,注意前面的*與add之間要有空隔。因為最前面的*号代表所有的傳回類型,而add*(..)中的*表示所有以add開頭的方法名。
上面我們隻定義了在add開頭的方法前執行檢查,那麼我們也可以在del之前執行檢查,使用||操作符,如下:
Java代碼

- @Pointcut ( "execution(* add(..)) || execution(* del(..))" )
還要注意通知@Before("addAllMethod()"),不要寫成@Before("addAllMethod")
還應該注意到切入點addAllMethod()這個方法是不會被執行的,隻是起到一個标志作用。
現 在來總結一下:從靜态代理到spring aop我們都實作了在操作使用者之前調用方法進行使用者檢查。靜态代理我們看成是OOP的處理,它需要代理類通過繼承,是樹型結構,要實作就要改變原來的樹 型,即是有侵入性的。而spring aop則是橫向的切入。沒有改變原來的結構,是沒有侵入性的。
AOP的沒有侵入性的特性,是對OOP的一個補充。
對于常用的切入點表達式有:
Java代碼

- 使用的最頻繁的傳回類型模式是*,它代表了比對任意的傳回類型。
- 一個全限定的類型名将隻會比對傳回給定類型的方法。名字模式比對的是方法名。 你可以使用*通配符作為所有或者部分命名模式。
- 參數模式稍微有點複雜:()比對了一個不接受任何參數的方法, 而(..)比對了一個接受任意數量參數的方法(零或者更多)。
- 模式(*)比對了一個接受一個任何類型的參數的方法。 模式(*,String)比對了一個接受兩個參數的方法,第一個可以是任意類型, 第二個則必須是String類型。
- 任意公共方法的執行:
execution(public * *(..))
- 任何一個名字以“set”開始的方法的執行:
execution(* set*(..))
-
接口定義的任意方法的執行:AccountService
execution(* com.xyz.service.AccountService.*(..))
- 在service包中定義的任意方法的執行:
execution(* com.xyz.service.*.*(..))
- 在service包或其子包中定義的任意方法的執行:
execution(* com.xyz.service..*.*(..))
- 在service包中的任意連接配接點(在Spring AOP中隻是方法執行):
within(com.xyz.service.*)
- 在service包或其子包中的任意連接配接點(在Spring AOP中隻是方法執行):
within(com.xyz.service..*)
- 實作了
接口的代理對象的任意連接配接點 (在Spring AOP中隻是方法執行):AccountService
'this'在綁定表單中更加常用:- 請參見後面的通知一節中了解如何使得代理對象在通知體内可用。this(com.xyz.service.AccountService)
- 實作
接口的目标對象的任意連接配接點 (在Spring AOP中隻是方法執行):AccountService
'target'在綁定表單中更加常用:- 請參見後面的通知一節中了解如何使得目标對象在通知體内可用。target(com.xyz.service.AccountService)
- 任何一個隻接受一個參數,并且運作時所傳入的參數是
接口的連接配接點(在Spring AOP中隻是方法執行)Serializable
args(java.io.Serializable)
'args'在綁定表單中更加常用:- 請參見後面的通知一節中了解如何使得方法參數在通知體内可用。
請注意在例子中給出的切入點不同于
: args版本隻有在動态運作時候傳入參數是Serializable時才比對,而execution版本在方法簽名中聲明隻有一個execution(* *(java.io.Serializable))
類型的參數時候比對。Serializable
- 目标對象中有一個
注解的任意連接配接點 (在Spring AOP中隻是方法執行)@Transactional
'@target'在綁定表單中更加常用:- 請參見後面的通知一節中了解如何使得注解對象在通知體内可用。@target(org.springframework.transaction.annotation.Transactional)
- 任何一個目标對象聲明的類型有一個
注解的連接配接點 (在Spring AOP中隻是方法執行):@Transactional
'@within'在綁定表單中更加常用:- 請參見後面的通知一節中了解如何使得注解對象在通知體内可用。@within(org.springframework.transaction.annotation.Transactional)
- 任何一個執行的方法有一個
注解的連接配接點 (在Spring AOP中隻是方法執行)@Transactional
'@annotation'在綁定表單中更加常用:- 請參見後面的通知一節中了解如何使得注解對象在通知體内可用。@annotation(org.springframework.transaction.annotation.Transactional)
- 任何一個隻接受一個參數,并且運作時所傳入的參數類型具有
注解的連接配接點(在Spring AOP中隻是方法執行)@Classified
'@args'在綁定表單中更加常用:- 請參見後面的通知一節中了解如何使得注解對象在通知體内可用。@args(com.xyz.security.Classified)
- 任何一個在名為'
'的Spring bean之上的連接配接點 (在Spring AOP中隻是方法執行):tradeService
bean(tradeService)
- 任何一個在名字比對通配符表達式'
'的Spring bean之上的連接配接點 (在Spring AOP中隻是方法執行):*Service
bean(*Service)