2014-03-11 Spring的學習(3)------面向切面編程(AOP)

1. AOP概念

首先讓咱們從一些重要的AOP概念和術語開始。這些術語不是Spring特有的。不過AOP術語並非特別的直觀,若是Spring使用本身的術語,將會變得更加使人困惑。 java

切面(Aspect):一個關注點的模塊化,這個關注點可能會橫切多個對象。事務管理是J2EE應用中一個關於橫切關注點的很好的例子。在Spring AOP中,切面可使用基於模式)或者基於@Aspect註解的方式來實現。 node

鏈接點(Joinpoint):在程序執行過程當中某個特定的點,好比某方法調用的時候或者處理異常的時候。在Spring AOP中,一個鏈接點老是表示一個方法的執行。spring

通知(Advice):在切面的某個特定的鏈接點上執行的動做。其中包括了「around」、「before」和「after」等不一樣類型的通知(通知的類型將在後面部分進行討論)。許多AOP框架(包括Spring)都是以攔截器作通知模型,並維護一個以鏈接點爲中心的攔截器鏈。 express

切入點(Pointcut):匹配鏈接點的斷言。通知和一個切入點表達式關聯,並在知足這個切入點的鏈接點上運行(例如,當執行某個特定名稱的方法時)。切入點表達式如何和鏈接點匹配是AOP的核心:Spring缺省使用AspectJ切入點語法。 編程

引入(Introduction):用來給一個類型聲明額外的方法或屬性(也被稱爲鏈接類型聲明(inter-type declaration))。Spring容許引入新的接口(以及一個對應的實現)到任何被代理的對象。例如,你可使用引入來使一個bean實現IsModified接口,以便簡化緩存機制。 緩存

目標對象(Target Object): 被一個或者多個切面所通知的對象。也被稱作被通知(advised)對象。 既然Spring AOP是經過運行時代理實現的,這個對象永遠是一個被代理(proxied)對象。 app

AOP代理(AOP Proxy):AOP框架建立的對象,用來實現切面契約(例如通知方法執行等等)。在Spring中,AOP代理能夠是JDK動態代理或者CGLIB代理。 框架

織入(Weaving):把切面鏈接到其它的應用程序類型或者對象上,並建立一個被通知的對象。這些能夠在編譯時(例如使用AspectJ編譯器),類加載時和運行時完成。Spring和其餘純Java AOP框架同樣,在運行時完成織入。 dom

通知類型:eclipse

前置通知(Before advice):在某鏈接點以前執行的通知,但這個通知不能阻止鏈接點以前的執行流程(除非它拋          出一個異常)。 

後置通知(After returning advice):在某鏈接點正常完成後執行的通知:例如,一個方法沒有拋出任何異常,正       常返回。 

 異常通知(After throwing advice):在方法拋出異常退出時執行的通知。 

最終通知(After (finally) advice):當某鏈接點退出的時候執行的通知(不管是正常返回仍是異常退出)。 

環繞通知(Around Advice):包圍一個鏈接點的通知,如方法調用。這是最強大的一種通知類型。環繞通知能夠在     方法調用先後完成自定義的行爲。它也會選擇是否繼續執行鏈接點或直接返回它本身的返回值或拋出異常來結束執       行。 

      環繞通知是最經常使用的通知類型。和AspectJ同樣,Spring提供全部類型的通知,咱們推薦你使用盡量簡單的通知類型來實現須要的功能。例如,若是你只是須要一個方法的返回值來更新緩存,最好使用後置通知而不是環繞通知,儘管環繞通知也能完成一樣的事情。用最合適的通知類型可使得編程模型變得簡單,而且可以避免不少潛在的錯誤。好比,你不須要在JoinPoint上調用用於環繞通知的proceed()方法,就不會有調用的問題。 

2. AOP代理

Spring缺省使用J2SE 動態代理(dynamic proxies)來做爲AOP的代理。 這樣任何接口(或者接口集)均可以被代理。

package cn.itcast.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JDKServiceProxy implements InvocationHandler {
	/**目標對象**/
	private Object targetObject;
	/**建立代理對象**/
	public Object createProxyObject(Object targetObject){
		this.targetObject = targetObject;
		return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(), 
				this.targetObject.getClass().getInterfaces(),
				this);
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		// TODO Auto-generated method stub
		return method.invoke(targetObject, args);
	}
}


/**J2SE實現動態代理,目標類必須實現接口**/
package cn.itcast.service;
public interface PersonService {
	public abstract String getName();
	public abstract void save();
	public abstract void update();
}

package cn.itcast.service.impl;
import cn.itcast.service.PersonService;
public class PersonServiceImpl implements PersonService {
	/* (non-Javadoc)
	 * @see cn.itcast.service.impl.PersonService#getName()
	 */
	@Override
	public String getName(){
		return "xxx";
	}
	/* (non-Javadoc)
	 * @see cn.itcast.service.impl.PersonService#save()
	 */
	@Override
	public void save(){
		System.out.println("This is a save");
	}
	/* (non-Javadoc)
	 * @see cn.itcast.service.impl.PersonService#update()
	 */
	@Override
	public void update(){
//		throw new RuntimeException();
		System.out.println("This is a update");
	}
}

測試類:JDKAopTest.java
package junit.test;
import org.junit.Test;
import cn.itcast.proxy.JDKServiceProxy;
import cn.itcast.service.PersonService;
import cn.itcast.service.impl.PersonServiceImpl;
public class JDKAopTest {
	@Test
	public void test() {
		JDKServiceProxy proxy = new JDKServiceProxy();
		PersonService service = (PersonService) proxy.createProxyObject(new PersonServiceImpl());
	    service.save();
	}
}
//output:
This is a save
//~

Spring也可使用CGLIB代理. 對於須要代理類而不是代理接口的時候CGLIB代理是頗有必要的。若是一個業務對象並無實現一個接口,默認就會使用CGLIB。做爲面向接口編程的最佳實踐,業務對象一般都會實現一個或多個接口。但也有可能會強制使用CGLIB,在這種狀況(但願不常有)下,你可能須要通知一個沒有在接口中聲明的方法,或者須要傳入一個代理對象給方法做爲具體類型.須要在開發工程裏導入cglib-nodep-2.1_3.jar架包。

3. 基於@AspectJ(註解)進行AOP開發

  @AspectJ使用了Java 5的註解,能夠將切面聲明爲普通的Java類。@AspectJ樣式在AspectJ 5發佈的AspectJ project部分中被引入。Spring 2.0使用了和AspectJ 5同樣的註解,並使用AspectJ來作切入點解析和匹配。可是,AOP在運行時仍舊是純的Spring AOP,並不依賴於AspectJ的編譯器或者織入器(weaver)。 須要在你的應用程序的classpath中引入兩個AspectJ庫:aspectjweaver.jaraspectjrt.jar

Beans.xml

<?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:context="http://www.springframework.org/schema/context"
       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/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
        <aop:aspectj-autoproxy/>   
        <context:component-scan base-package="cn.itcast"/>
</beans>

MyInterceptor.java

package cn.itcast.interceptor;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;


@Aspect @Component
public class MyInterceptor {
	// the pointcut expression
	@Pointcut ("execution(* cn.itcast.service.impl.PersonServiceImpl.*(..))")
	private void anyMethod(){}
	@Before ("anyMethod()")
	public void doBefore() { 
		System.out.println("前置通知");
	}
	@AfterReturning ("anyMethod()")
	public void doAfterReturning() {
		System.out.println("後置通知");
	}
	@After ("anyMethod()")
	public void doAfter(){
		System.out.println("最終通知");
	}
	@AfterThrowing ("anyMethod()")
	public void doAfterThrowing(){
		System.out.println("異常通知");
	}
	@Around ("anyMethod()")
	public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
		 // start stopwatch
		System.out.println("開始方法");
	    Object retVal = pjp.proceed();
	    // stop stopwatch
	    System.out.println("退出方法");
	    return retVal;

	}

}

測試代碼:SpringAopTest.java

package junit.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.itcast.service.PersonService;
public class SpringAopTest {
	@Test
	public void test(){
		ApplicationContext context= new ClassPathXmlApplicationContext("beans.xml");
		PersonService service = (PersonService) context.getBean("personServiceImpl");
		service.update();
	}
}
測試結果://output:
前置通知
開始方法
This is a update
後置通知
最終通知
退出方法
//~

開發步驟以及詳細配置:請參考Spring FrameWork開發手冊(例如切入點的表達式,通知參數等)

4. 基 於Schema(XML文件)進行AOP開發

若是你沒法使用Java 5,或者你比較喜歡使用XML格式,Spring2.0也提供了使用新的"aop"命名空間來定義一個切面。 和使用@AspectJ風格徹底同樣,切入點表達式和通知類型一樣獲得了支持。

使用本章所介紹的aop命名空間標籤,你須要引入附錄 A, XML Schema-based configuration中說起的spring-aop schema。 

在Spring的配置文件中,全部的切面和通知都必須定義在<aop:config>元素內部。 (一個application context能夠包含多個 <aop:config>)。 一個<aop:config>能夠包含pointcut,advisor和aspect元素 (注意這三個元素必須按照這個順序進行聲明)。 

警告

<aop:config>風格的配置使得Spring auto-proxying機制的使用變得很笨重。若是你已經經過 BeanNameAutoProxyCreator或相似的東西顯式使用auto-proxying,它可能會致使問題 (例如通知沒有被織入)。 推薦的使用模式是僅僅使用<aop:config>風格, 或者僅僅使用AutoProxyCreator風格。

<?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:context="http://www.springframework.org/schema/context" 
       xmlns:aop="http://www.springframework.org/schema/aop"      
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
        <aop:aspectj-autoproxy/> 
        <bean id="personService" class="cn.itcast.service.impl.PersonServiceBean"></bean>
        <bean id="aspetbean" class="cn.itcast.service.MyInterceptor"/>
        <aop:config>
          <aop:aspect id="asp" ref="aspetbean">
        	<aop:pointcut id="mycut" expression="execution(* cn.itcast.service..*.*(..))"/>
        	<aop:before pointcut-ref="mycut" method="doAccessCheck"/>
        	<aop:after-returning pointcut-ref="mycut" method="doAfterReturning"/>
		<aop:after-throwing pointcut-ref="mycut" method="doAfterThrowing"/>
		<aop:after pointcut-ref="mycut" method="doAfter"/>
		<aop:around pointcut-ref="mycut" method="doBasicProfiling"/>
           </aop:aspect>
        </aop:config>
</beans>

5. AOP聲明風格的選擇

當你肯定切面是實現一個給定需求的最佳方法時,你如何選擇是使用Spring AOP仍是AspectJ,以及選擇 Aspect語言(代碼)風格、@AspectJ聲明風格或XML風格?這個決定會受到多個因素的影響,包括應用的需求、 開發工具和小組對AOP的精通程度。 

    5.1 Spring AOP仍是徹底用AspectJ?

作能起做用的最簡單的事。Spring AOP比徹底使用AspectJ更加簡單, 由於它不須要引入AspectJ的編譯器/織入器到你開發和構建過程當中。 若是你僅僅須要在Spring bean上通知執行操做,那麼Spring AOP是合適的選擇。 若是你須要通知domain對象或其它沒有在Spring容器中管理的任意對象,那麼你須要使用AspectJ。 若是你想通知除了簡單的方法執行以外的鏈接點(如:調用鏈接點、字段get或set的鏈接點等等), 也須要使用AspectJ。

當使用AspectJ時,你能夠選擇使用AspectJ語言(也稱爲「代碼風格」)或@AspectJ註解風格。 很顯然,若是你用的不是Java 5+那麼結論是你只能使用代碼風格。 若是切面在你的設計中扮演一個很大的角色,而且你能在Eclipse中使用AspectJ Development Tools (AJDT), 那麼首選AspectJ語言 :- 由於該語言專門被設計用來編寫切面,因此會更清晰、更簡單。若是你沒有使用 Eclipse,或者在你的應用中只有不多的切面並無做爲一個主要的角色,你或許應該考慮使用@AspectJ風格 並在你的IDE中附加一個普通的Java編輯器,而且在你的構建腳本中增長切面織入(連接)的段落。 

    5.2 Spring AOP中使用@AspectJ仍是XML?

若是你選擇使用Spring AOP,那麼你能夠選擇@AspectJ或者XML風格。顯然若是你不是運行 在Java 5上,XML風格是最佳選擇。對於使用Java 5的項目,須要考慮多方面的折衷。

XML風格對現有的Spring用戶來講更加習慣。它可使用在任何Java級別中 (參考鏈接點表達式內部的命名鏈接點,雖然它也須要Java 5+) 而且經過純粹的POJO來支持。當使用AOP做爲工具來配置企業服務時XML會是一個很好的選擇。 (一個好的例子是當你認爲鏈接點表達式是你的配置中的一部分時,你可能想單獨更改它) 對於XML風格,從你的配置中能夠清晰的代表在系統中存在那些切面。

XML風格有兩個缺點。第一是它不能徹底將需求實現的地方封裝到一個位置。 DRY原則中說系統中的每一項知識都必須具備單1、無歧義、權威的表示。 當使用XML風格時,如何實現一個需求的知識被分割到支撐類的聲明中以及XML配置文件中。 當使用@AspectJ風格時就只有一個單獨的模塊 -切面- 信息被封裝了起來。 第二是XML風格同@AspectJ風格所能表達的內容相比有更多的限制:僅僅支持"singleton"切面實例模型, 而且不能在XML中組合命名鏈接點的聲明。例如,在@AspectJ風格中咱們能夠編寫以下的內容:

@Pointcut(execution(* get*()))
                public void propertyAccess() {}
                @Pointcut(execution(org.xyz.Account+ *(..))
                public void operationReturningAnAccount() {}
                
                @Pointcut(propertyAccess() && operationReturningAnAccount())
            public void accountPropertyAccess() {}

在XML風格中能聲明開頭的兩個鏈接點:

  <aop:pointcut id="propertyAccess"
                expression="execution(* get*())"/>
                
                <aop:pointcut id="operationReturningAnAccount"
            expression="execution(org.xyz.Account+ *(..))"/>

可是不能經過組合這些來定義accountPropertyAccess鏈接點

@AspectJ風格支持其它的實例模型以及更豐富的鏈接點組合。它具備將切面保持爲一個模塊單元的優勢。 還有一個優勢就是@AspectJ切面能被Spring AOP和AspectJ二者都理解 - 因此若是稍後你認爲你須要AspectJ的能力去實現附加的需求,那麼你很是容易遷移到基於AspectJ的途徑。 總而言之,咱們更喜歡@AspectJ風格只要你有切面去作超出簡單的「配置」企業服務以外的事情。 

結束.....

相關文章
相關標籤/搜索