Spring AOP的應用

http://blog.csdn.net/ganglong99/archive/2009/02/27/3942726.aspx

 

在實際的應用程序開發中,經常需要在一個服務流程中插入一些與業務邏輯無關的系統服務邏輯(最常見的就是記錄日誌,權限檢查等),如果把所有這些與業務邏輯無關的服務與業務邏輯編織在一起,就會使業務邏輯對象的負擔加重,因爲它不但要具有業務邏輯的功能,還帶有例如記錄日誌等其他功能,這樣就容易產生對象的職責混淆。
爲了避免對象職責的混淆,我們在設計中就需要將與業務邏輯無關的服務邏輯從業務邏輯中剝離出來,獨立設計爲一個模塊或對象,而在希望需要使用這些對象的時候插入進來,不希望使用的時候去掉即可,這種設計模式就稱爲AOP。
Spring AOP是實現AOP的一種技術。Spring AOP最常用的就是採用XML配置文件的方式。Spring AOP只支持在目標對象的某個方法的前後調用服務代碼。
下面是使用Spring AOP的幾個簡單的例子:
1.在目標對象的方法執行之前調用(Before Adivce)
首先需要定義目標對象必須實現的接口:
package com.test.spring.aop;

public interface IHello {

 public void sayHello(String name);
}
接着定義一個IHello的實現類:
package com.test.spring.aop;

public class HelloSpeaker implements IHello {

 public void sayHello(String name) {
  System.out.println("Hello," + name);
 }
}
現在我們想要在調用對象HelloSpeaker的sayHello()方法前打印一下方法開始執行的日誌,一般情況下我們會直接在sayHello()的方法中直接加上日誌的代碼,這樣就把日誌和業務邏輯的代碼融在了一起,對於要記錄大量日誌的程序來說,無疑會加重目標對象的負擔。
我們把打印日誌的代碼獨立出來成爲一個專門用於日誌記錄的對象,這裏需要實現Spring的MethodBeforeAdvice接口:
package com.test.spring.aop;

import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.springframework.aop.MethodBeforeAdvice;

public class LogBeforeAdvice implements MethodBeforeAdvice {

 private Logger logger = Logger.getLogger(this.getClass().getName());

 public void before(Method method, Object[] args, Object target) throws Throwable {
  logger.log(Level.INFO, "method start..." + method);
  System.out.println("LogBeforeAdvice: method start... " + method);
 }
}
在before()方法的實現中,加入了一些記錄日誌的代碼,LogBeforeAdive被設計成一個獨立的服務,可以提供給需要這個服務的對象,在下面的配置中,我們把這個服務提供給了前面的HelloSpeaker對象:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans.xsd">
 
 <bean id="logBeforeAdvice" class="com.test.spring.aop.LogBeforeAdvice"></bean>
 
 <bean id="helloSpeaker" class="com.test.spring.aop.HelloSpeaker"></bean>
 
 <bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
  <property name="proxyInterfaces" value="com.test.spring.aop.IHello"></property>
  <property name="target" ref="helloSpeaker"></property>
  <property name="interceptorNames">
   <list>
    <value>logBeforeAdvice</value>
   </list>
  </property>
 </bean>
</beans>
在上面的配置文件中,屬性target指定了要被服務的目標對象爲helloSpeaker,屬性interceptorNames指定了使用的服務對象是logBeforeAdvice。這樣就建立了目標對象與服務對象之間的聯繫,下面是測試代碼:
package com.test.spring.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class BeforeAdviceTest {

 public static void main(String[] args) {
  ApplicationContext context = new ClassPathXmlApplicationContext(
    "com/test/spring/aop/beforeAfterAdvice.xml");

  IHello helloProxy = (IHello) context.getBean("helloProxy");
  helloProxy.sayHello("world");
 }
}
可以看到,測試代碼中,取的對象是helloProxy代理對象,而不是helloSpeaker目標對象,取到的代理對象必須轉換爲IHello接口,這樣當調用代理對象的sayHello()方法時,就可以在調用目標對象的sayHello()方法體中的代碼前打印日誌,實際上流程是先進入LogBeforeAdvice對象執行before()方法,執行完畢後再進入helloSpeaker的sayHello()方法,這一切的流程控制都是有Spring完成的,而我們僅僅需要做的工作就是定義目標對象和服務對象,並編寫XML配置文件建立服務對象與目標對象的聯繫。動手試一下吧,看看執行結果再說。
2.在目標對象的方法執行之後調用(After Advice)
這裏就不在定義目標對象的接口及實現類了,依然使用前面定義好的目標對象helloSpeaker。
要在目標對象的方法執行之後進行一些操作,這裏依然是打印日誌,Spring AOP要求我們實現AfterReturningAdvice接口:
package com.test.spring.aop;

import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.springframework.aop.AfterReturningAdvice;

public class LogAfterAdvice implements AfterReturningAdvice {

 private Logger logger = Logger.getLogger(this.getClass().getName());

 public void afterReturning(Object object, Method method, Object[] args, Object target)
   throws Throwable {
  logger.log(Level.INFO, "method end... " + method);
  System.out.println("method end... " + method);
 }
}
可以看到,這個服務對象的目的是在執行目標對象的方法之後執行afterReturning()方法打印方法執行完畢的日誌。
下一步就是配置XML文件了,把這個服務提供給前面helloSpeaker目標對象:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans.xsd">
 
 <bean id="logAfterAdvice" class="com.test.spring.aop.LogAfterAdvice"></bean>
 
 <bean id="helloSpeaker" class="com.test.spring.aop.HelloSpeaker"></bean>
 
 <bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
  <property name="proxyInterfaces" value="com.test.spring.aop.IHello"></property>
  <property name="target" ref="helloSpeaker"></property>
  <property name="interceptorNames">
   <list>
    <value>logAfterAdvice</value>
   </list>
  </property>
 </bean>
</beans>
可以看到,與前面的配置Before Advice非常相似。
測試代碼與Before Advice的完全一樣,這裏不再重複列出。動手試一下,看看是否在執行目標對象的sayHello()方法之後調用了打印結束日誌。
當然,還可以把Before Advice和After Advice一起提供給目標對象,這樣,就可以在執行目標對象的方法的前、後都打印出日誌了,配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans.xsd">

 <bean id="logBeforeAdvice" class="com.test.spring.aop.LogBeforeAdvice"></bean>
 
 <bean id="logAfterAdvice" class="com.test.spring.aop.LogAfterAdvice"></bean>
 
 <bean id="helloSpeaker" class="com.test.spring.aop.HelloSpeaker"></bean>
 
 <bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
  <property name="proxyInterfaces" value="com.test.spring.aop.IHello"></property>
  <property name="target" ref="helloSpeaker"></property>
  <property name="interceptorNames">
   <list>
    <value>logBeforeAdvice</value>
    <value>logAfterAdvice</value>
   </list>
  </property>
 </bean>
</beans>
可以看到,在interceptorNames屬性中,把logBeforeAdvice和logAfterAdvice兩個服務對象都提供給了目標對象,實際上我們可以配置任意多個。
測試代碼依然與前面的一樣,不再列出,執行一下,看看結果是否是在執行sayHello()方法之前和之後分別打印了日誌。
3.在目標對象的方法執行前後調用(Around Advice)
在前面的兩種模式中,有一個缺陷,就是無論如何,目標對象的方法總是會被執行,我們不能控制目標對象的方法是否執行,且執行的時機。
有時候我們需要自行決定是否要執行目標對象的方法,有時需要在不同的時機才執行目標對象的方法,比如權限檢查,當權限檢查通過時,才繼續執行目標對象的方法,如果權限檢查不通過,則進入別的流程中。前面的兩種模式是做不到這一點的。
在Around Advice這種模式中,我們就可以自行決定是否執行目標對象的方法及其執行的時機了。
這種模式需要實現MethodInterceptor接口:
package com.test.spring.aop;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class LogInterceptor implements MethodInterceptor {

 public Object invoke(MethodInvocation methodInvocation) throws Throwable {
  System.out.println("Log start...");
  Object result = null;
  result = methodInvocation.proceed();
  System.out.println("Log end...");
  return result;
 }
}
這個服務實現的方法是invoke()方法,在這個方法中,調用了proceed()方法,當調用了這個方法時,就表示需要執行目標對象的方法,下面是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"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans.xsd">

 <bean id="logInterceptor" class="com.test.spring.aop.LogInterceptor"></bean>
 
 <bean id="helloSpeaker" class="com.test.spring.aop.HelloSpeaker"></bean>
 
 <bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
  <property name="proxyInterfaces" value="com.test.spring.aop.IHello"></property>
  <property name="target" ref="helloSpeaker"></property>
  <property name="interceptorNames">
   <list>
    <value>logInterceptor</value>
   </list>
  </property>
 </bean>
</beans>
如上例中,我們在調用proceed()方法的前後打印了日誌,則表示在執行helloSpeaker目標對象的sayHello()方法的前後都打印日誌。
可以看到,關鍵就在於proceed()方法的調用,因此,在這種模式中,我們可以在調用proceed()方法的前、後的任何地方插入任何處理邏輯,當然,我們也可以不調用proceed()方法,這樣,目標對象的方法就不能執行了。
測試代碼與前面的完全一樣,這裏不再列出。
對於前面的三種方式,我們可以看出,這幾種方式都需要在使用時建立代理對象,然後獲取代理對象並把類型轉換爲目標對象的接口,這需要我們手動創建代理對象,即每一個目標對象都要建立一個相應的代理對象,對於有很多目標對象的應用程序,配置XML文件無疑會是一件痛苦的事。
Spring AOP框架中提供了一種自動創建代理的工具。
4.自動代理(BeanNameAutoProxyCreator)
要使用Spring AOP的自動代理,需要使用org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator類來完成。
這裏,目標對象依然使用前面的helloSpeaker,服務對象依然使用前面的LogInterceptor,代碼不再列出,下面是配置自動代理的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"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans.xsd">
 
 <bean id="logInterceptor" class="com.test.spring.aop.LogInterceptor"></bean>

 <bean id="helloSpeaker" class="com.test.spring.aop.HelloSpeaker"></bean>
 
 <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
  <property name="beanNames">
   <list>
    <value>helloSpeaker</value>
   </list>
  </property>
  <property name="interceptorNames" value="logInterceptor"></property>
 </bean>
</beans>
在上面的配置中,指定了org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator類的兩個屬性,beanNames和interceptorNames,beanNames指定了要進行自動代理的目標對象,interceptorNames與前面的幾種模式中的這個屬性含義相同,指定了提供服務的對象。
測試代碼爲:
package com.test.spring.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class BeforeAdviceTest {

 public static void main(String[] args) {
  ApplicationContext context = new ClassPathXmlApplicationContext(
    "com/test/spring/aop/beforeAfterAdvice.xml");
  IHello helloSpeaker = (IHello) context.getBean("helloSpeaker");
  helloSpeaker.sayHello("longyg");
 }
}
看,這次我們不再是獲取的代理對象,而是直接獲得helloSpeaker目標對象了。由於我們在XML配置文件中指定了爲helloSpeaker目標對象進行自動代理,因此當我們獲取目標對象時,Spring會自動爲其創建代理對象,並根據配置的服務對象提供相應的服務(logInterceptor)。
基於這種方式,我們可以把一個服務對象用於多個目標對象,下面我另外定義了一個helloSpeaker2目標對象,它也實現了IHello接口(當然我們也可以實現任務 其他接口):
package com.test.spring.aop;

public class HelloSpeaker2 implements IHello {

 public void sayHello(String name) {
  System.out.println("Welcome to world," + name);
 }
}
下面在XML文件中增加對helloSpeaker2目標對象提供服務:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans.xsd">
 
 <bean id="logInterceptor" class="com.test.spring.aop.LogInterceptor"></bean>

 <bean id="helloSpeaker" class="com.test.spring.aop.HelloSpeaker"></bean>

 <bean id="helloSpeaker2" class="com.test.spring.aop.HelloSpeaker2"></bean>
 
 <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
  <property name="beanNames">
   <list>
    <value>helloSpeaker</value>
    <value>helloSpeaker2</value>
   </list>
  </property>
  <property name="interceptorNames" value="logInterceptor"></property>
 </bean>
</beans>
在beanNames屬性中增加了helloSpeaker2目標對象,因此logInterceptor也對它提供服務。我們還可以增加任務多的目標對象。
同樣,我們也可以爲目標對象提供多個服務對象,下面我又定義了一個agumentInterceptor服務對象:
package com.test.spring.aop;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class AgumentInterceptor implements MethodInterceptor {

 public Object invoke(MethodInvocation methodInvocation) throws Throwable {
  String name = null;
  Object[] args = methodInvocation.getArguments();
  for (int i = 0; i < args.length; i++) {
   if (args[i] instanceof String) {
    name = (String) args[i];
   }
  }
  System.out.println("the argument is : " + name);  
  Object result = methodInvocation.proceed(); 
  System.out.println("the argument is proceed"); 
  return result;
 }
}
下面在XML文件中增加agumentInterceptor服務對象:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans.xsd">
 
 <bean id="logInterceptor" class="com.test.spring.aop.LogInterceptor"></bean>

 <bean id="agumentInterceptor" class="com.test.spring.aop.AgumentInterceptor"></bean>

 <bean id="helloSpeaker" class="com.test.spring.aop.HelloSpeaker"></bean>

 <bean id="helloSpeaker2" class="com.test.spring.aop.HelloSpeaker2"></bean>
 
 <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
  <property name="beanNames">
   <list>
    <value>helloSpeaker</value>
    <value>helloSpeaker2</value>
   </list>
  </property>
  <property name="interceptorNames">
   <list>
    <value>logInterceptor</value>
    <value>agumentInterceptor</value>
   </list>
  </property>
 </bean>
</beans>
在interceptorNames屬性中增加了agumentInterceptor服務對象,這樣,agumentInterceptor服務就可以在目標對象上被應用了。
5.結束語
以上就是Spring AOP提供的幾種比較簡單且常用的模式,當然,Spring還提供了很多其他的方式,這裏不再一一列出。一般情況下,自動代理的模式是我們經常使用的,它的配置很靈活,我認爲初學只要能掌握並使用這種模式就可以了。

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