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