Sping-AOP

Spring AOP        

        在Spring Framework簡介中介紹了spring的整體框架,在Spring IOC中介紹了spring的核心功能,那麼spring framework中,另外一個比較重要的模塊就是Spring AOP。那麼,什麼是AOP呢?AOP是Aspect-Oriented Programming的縮寫,是一種不同與面向對象(Object-Oriented Programming (OOP))的編程方式。其主要的設計目的是:將日誌記錄,性能統計,安全控制,事務處理,異常處理等代碼從業務邏輯代碼中劃分出來,通過對這些行爲的分離,我們希望可以將它們獨立到非指導業務邏輯的方法中,進而改變這些行爲的時候不影響業務邏輯的代碼,也就是無入侵式的編程。

AOP概念

        要理解AOP,就必須要對一些關鍵詞非常瞭解,下面列表是AOP中常用的一些關鍵詞:
  • Aspect,切面,可以包含多個類,多個方法組成,由Advice,Pointcut等元素組成。在常見的事務管理中,我們通常會使用如下的配置:
    <aop:config>
        <aop:pointcut id="service" expression="execution(* com.ckg.luohong.biz.*.*.service.impl.*.*(..))" />
	<aop:advisor pointcut-ref="service" advice-ref="advice" />
    </aop:config>
  • Join Point,切入點,通常指的是一個方法,也就是在調用時,匹配Pointcut表達式指定的方法。
  • Advice,增強點,通常是在執行某個方法時,根據需要來執行某種操作。比如在發送郵件前,先要記錄下日誌,這種就是屬於前置增強。發送郵件後發送通知給管理員,這種就屬於後置增強點。
  • Pointcut,切入點集合,通常是一個表達式,比如:execution(* com.ckg.luohong.biz.*.*.service.impl.*.*(..))。
  • Introduction:爲目標對象聲明一些字段或者方法,通常是聲明接口。
  • Target Object:目標對象,需要使用AOP增強的目標類
  • AOP proxy:代理對象,由spring framework動態創建
  • Wearving:根據配置信息,在編譯、加載類、運行時,動態的代碼組織起來。

Advice的類型

       在AOP的規定中,Advice的類型主要有:Before Advice,After Advice,Around Advice,After returning Advice, After throwing advice。如下圖所示:

        通過上面的圖片,我們可以看出,這些定義都是圍繞着代碼的執行時間點來命名的,比如Before advice就是在執行Join point前插入一段執行代碼,After advice就是在執行成功後插入一段代碼,After throwing advice就是在拋出異常後執行一段代碼,Around advice就是把Before advice、After advice同時都執行。

AOP的實現原理

       介紹完AOP的一些基本概念後,下面我們來探討下AOP的實現。在設計模式中,有一種設計模式叫做代理設計模式。那麼什麼是代理設計模式呢?在生活中,該設計模式也隨處可見。比如我們經常在淘寶上海淘,通常會去某家海淘店鋪購買物品,然後店主幫我們去購買,發貨給我們。如下圖:

       在這裏面,我們就是購買者,換成java角度而言,就是調用者;海淘店主就是代理對象(Proxy);實際貨品提供者就是目標對象(Target object)。通過代理模式,即可爲Target object提供一些而外的功能,也就是上文所述的Advice。在這裏面,可能大家都很疑惑,Advice是在代碼實現層是如何動態加入進去的呢?這個其實也不難,在JDK中,對於動態代理模式提供的相應的API,可以動態的生成代理對象。如下代碼所示:
<span style="font-size:18px;">/**
 * 接口PersonService,Target Object的接口定義,因爲jdk只能生成接口的動態代理對象
 * */
public interface PersonService{
    public void save();

    public void add();

    public void update();
}

/**
 * 要被代理的對象,也就是Target object
 * */
public class PersonServiceImpl implements PersonService{
    private String user;

    public PersonServiceImpl(String user){
        this.user = user;
    }

    //給外部提供接口
    public String getUser(){
        return user;
    }

    public void add(){
        System.out.println("I am the PersonServiceImpl add() method");
    }
    public void update(){
    }

    public void save(){
    }
}

</span>
<span style="font-size:18px;">import java.lang.reflect.*; 
/*在這裏,直接讓工廠實現了InvocationHandler,所以在調用createProxy()的時候,給Proxy.netProxyInstance()的第三個參數傳進去了一個this。如果要把工廠和InvocationHandler解耦,可以重新頂一個實現InvocationHandler的類*/
public class JDKProxyFactory implements InvocationHandler{
     
    //要代理的目標對象
    private Object target;

    public JDKProxyFactory(Object obj){
        target = obj;
    }
    
    //生產一個代理對象
    public Object createProxy(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    
    //當客戶端調用目標對象進行工作的時候,就會被這個代理對象攔截到    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        //將目標對象向下轉型
        PersonServiceImpl bean = (PersonServiceImpl)target;
        //這裏進行攔截
        if(bean.getUser() != null && !"".equals(bean.getUser())){
            //調用目標對象的方法
            Object result = method.invoke(target, args);
            return result;
        }else{
            System.out.println("sorry, you have no limitation to access this resource!!!");
            return null;
        }
    }
}</span>
       在代碼中,我們發現創建一個代理對象並不難,主要步驟如下:
     1、通過實現InvocationHandler接口創建自己的調用處理器 IvocationHandler handler = new InvocationHandlerImpl(...);
     2、通過爲Proxy類指定ClassLoader對象和一組interface創建動態代理類
Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});
     3、通過反射機制獲取動態代理類的構造函數,其參數類型是調用處理器接口類型
Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
     4、通過構造函數創建代理類實例,此時需將調用處理器對象作爲參數被傳入
Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
爲了簡化對象創建過程,Proxy類中的newInstance方法封裝了2~4,只需兩步即可完成代理對象的創建。
生成的ProxySubject繼承Proxy類實現Subject接口,實現的Subject的方法實際調用處理器的invoke方法,而invoke方法利用反射調用的是被代理對象的的方法(Object result=method.invoke(proxied,args))
     但是凡事都有弊端,java提供生成代理對象的API其實是不支持爲類生成代理對象,只能給接口生成代理對象。所以在這裏面,Spring AOP藉助於Aspectj代理框架來完成該任務,實現類的動態代理對象的生成。有了代理模式,動態代理對象的生成,那麼面向切面大功告成了。
      

AOP的應用

      在spring framework中,AOP通常和IOC模塊一同使用,藉助AOP來實現聲明式的事務。下面我們通過配置一個聲明式的事務,通過觀察日誌來了解AOP的工作原理。首先我們在IOC中聲明如下幾個bean:
<span style="font-size:18px;">        <!-- 對數據源進行事務管理 -->
	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
		p:dataSource-ref="dataSource" />

	<!-- 配置事務傳播特性 -->
	<tx:advice id="advice" transaction-manager="transactionManager">
		<tx:attributes>
			<tx:method name="save*" propagation="REQUIRED" />
			<tx:method name="del*" propagation="REQUIRED" />
			<tx:method name="update*" propagation="REQUIRED" />
			<tx:method name="add*" propagation="REQUIRED" />
			<tx:method name="find*" propagation="REQUIRED" />
			<tx:method name="get*" propagation="REQUIRED" />
			<tx:method name="insert*" propagation="REQUIRED" />
			<tx:method name="apply*" propagation="REQUIRED" />
			<tx:method name="list*" propagation="REQUIRED" />
		</tx:attributes>
	</tx:advice>
        <!--配置一個AOP切面,同時指定advice、pointcut,那麼一個最簡單的AOP就配置完畢。-->
	<aop:config>
		<aop:pointcut id="service"
			expression="execution(* com.ckg.luohong.biz.*.*.service.impl.*.*(..))" />
		<aop:advisor pointcut-ref="service" advice-ref="advice" />
	</aop:config></span>
        單元測試類,這裏面使用spring + junit的方式來運行。
<span style="font-size:18px;">package com.skg.luohong.biz.ou.system;


import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

import com.skg.luohong.biz.ou.system.service.IDaysUserService;

/**
 *
 * @author 駱宏
 * @date 2015-08-29 10:27:09
 * @author [email protected]
 * @author 15013336884
 * @blog http://blog.csdn.net/u010469003
 * */
@ContextConfiguration({"classpath:conf/spring-mybatis.xml", 
	"classpath:conf/spring.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
@Transactional
public class DaysUserTest{
    
	@Autowired
	private IDaysUserService service;
	
	@Test
	public void testCrud(){
	    System.out.println(service.countAll());
		System.out.println(service.findAll());
	}
}
</span>
        日誌輸出,下面爲IOC容器初始化bean的過程,下面的日誌爲IOC創建Pointcut, Advice,TransactionManager bean的過程:
<span style="font-size:18px;">2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Finished creating instance of bean 'sqlSessionFactory'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'org.mybatis.spring.mapper.MapperScannerConfigurer#0'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Creating shared instance of singleton bean 'transactionManager'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Creating instance of bean 'transactionManager'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Eagerly caching bean 'transactionManager' to allow for resolving potential circular references
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'dataSource'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Invoking afterPropertiesSet() on bean with name 'transactionManager'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Finished creating instance of bean 'transactionManager'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Creating shared instance of singleton bean 'advice'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Creating instance of bean 'advice'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Eagerly caching bean 'advice' to allow for resolving potential circular references
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'transactionManager'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Creating instance of bean '(inner bean)'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
2015-08-29 10:39:01 [org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource]-[DEBUG] Adding transactional method [save*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
2015-08-29 10:39:01 [org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource]-[DEBUG] Adding transactional method [del*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
2015-08-29 10:39:01 [org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource]-[DEBUG] Adding transactional method [update*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
2015-08-29 10:39:01 [org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource]-[DEBUG] Adding transactional method [add*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
2015-08-29 10:39:01 [org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource]-[DEBUG] Adding transactional method [countAll*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
2015-08-29 10:39:01 [org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource]-[DEBUG] Adding transactional method [find*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
2015-08-29 10:39:01 [org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource]-[DEBUG] Adding transactional method [get*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
2015-08-29 10:39:01 [org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource]-[DEBUG] Adding transactional method [insert*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
2015-08-29 10:39:01 [org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource]-[DEBUG] Adding transactional method [apply*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
2015-08-29 10:39:01 [org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource]-[DEBUG] Adding transactional method [list*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Finished creating instance of bean '(inner bean)'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Invoking afterPropertiesSet() on bean with name 'advice'</span>
       下面的日誌展示了AOP對事物的攔截:
<span style="font-size:18px;">2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'transactionManager'
2015-08-29 10:39:01 [org.springframework.jdbc.datasource.DataSourceTransactionManager]-[DEBUG] Creating new transaction with name [testCrud]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2015-08-29 10:39:01 [org.springframework.jdbc.datasource.DataSourceTransactionManager]-[DEBUG] Acquired Connection [com.mysql.jdbc.JDBC4Connection@3ff1b8db] for JDBC transaction
2015-08-29 10:39:01 [org.springframework.jdbc.datasource.DataSourceTransactionManager]-[DEBUG] Switching JDBC Connection [com.mysql.jdbc.JDBC4Connection@3ff1b8db] to manual commit
2015-08-29 10:39:01 [org.springframework.test.context.transaction.TransactionalTestExecutionListener]-[DEBUG] No method-level @Rollback override: using default rollback [true] for test context [TestContext@7475b962 testClass = DaysUserTest, testInstance = com.skg.luohong.biz.ou.system.DaysUserTest@5cde0ca9, testMethod = testCrud@DaysUserTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@76115ae0 testClass = DaysUserTest, locations = '{classpath:conf/spring-mybatis.xml, classpath:conf/spring.xml}', classes = '{}', contextInitializerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]]
2015-08-29 10:39:01 [org.springframework.test.context.transaction.TransactionalTestExecutionListener]-[INFO] Began transaction (1): transaction manager [org.springframework.jdbc.datasource.DataSourceTransactionManager@19dde4d9]; rollback [true]</span>

總結

        AOP是一種不同於OOP的編程風格,在事務管理,日誌管理等需要橫跨多個類的地方,有着非常廣泛的用途。AOP本身的概念其實並不難,如果對代理設計模式掌握的較好,那麼AOP就更加不在話下。最近這段時間,工作較爲充實,抽空把Spring AOP的知識整理了一番,受益匪淺。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章