Spring AOP通知實例 – Advice
Spring AOP(面向方面編程)框架,用於在模塊化方面的橫切關注點。簡單得說,它只是一個攔截器攔截一些過程,例如,當一個方法執行,Spring AOP 可以劫持一個執行的方法,在方法執行之前或之後添加額外的功能。
在Spring AOP中,有 4 種類型通知(advices)的支持:
通知(Advice)之前 - 該方法執行前運行
通知(Advice)返回之後 – 運行後,該方法返回一個結果
通知(Advice)拋出之後 – 運行方法拋出異常後,
環繞通知 – 環繞方法執行運行,結合以上這三個通知。
下面的例子顯示Spring AOP 通知如何工作。
簡單的 Spring 例子
創建一個簡單的客戶服務類及一個print方法作爲演示。
package org.1ju.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="org.1ju.customer.services.CustomerService">
<property name="name" value="Mook Kim" />
<property name="url" value="https://www.1ju.org" />
</bean>
</beans>
執行它
package org.1ju.common;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.1ju.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 : Mook Kim ************************* Customer website : https://www.1ju.org *************************
一個簡單的Spring項目用來注入(DI)bean和輸出一些字符串。
Spring AOP 通知
現在,附加 Spring AOP 建議到上述的客戶服務。
1. 之前通知
它會在方法執行之前執行。創建一個實現 MethodBeforeAdvice 接口的類。
package org.1ju.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="org.1ju.customer.services.CustomerService">
<property name="name" value="Mook Kim" />
<property name="url" value="https://www.1ju.org" />
</bean>
<bean id="hijackBeforeMethodBean" class="org.1ju.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 org.1ju.common;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.1ju.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 : Mook Kim ************************* HijackBeforeMethod : Before method hijacked! Customer website : https://www.1ju.org ************************* HijackBeforeMethod : Before method hijacked!
它將運行 HijackBeforeMethod 的 before() 方法,在每個 CustomerService 的方法之前執行。
2.返回後通知
該方法返回一個結果之後它將執行。創建一個實現AfterReturningAdvice接口的類。
package org.1ju.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="org.1ju.customer.services.CustomerService">
<property name="name" value="Mook Kim" />
<property name="url" value="https://www.1ju.org" />
</bean>
<bean id="hijackAfterMethodBean" class="org.1ju.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 : Mook Kim HijackAfterMethod : After method hijacked! ************************* Customer website : https://www.1ju.org HijackAfterMethod : After method hijacked! *************************
它將運行 HijackAfterMethod 的 afterReturning()方法,在每次 CustomerService 方法返回結果之後。
3.拋出後通知
它將在執行方法拋出一個異常後。創建一個實現ThrowsAdvice接口的類,並創建一個afterThrowing方法攔截拋出:IllegalArgumentException
異常。
package org.1ju.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="org.1ju.customer.services.CustomerService">
<property name="name" value="Mook Kim" />
<property name="url" value="https://www.1ju.org" />
</bean>
<bean id="hijackThrowExceptionBean" class="org.1ju.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 : Mook Kim ************************* Customer website : http://www.1ju.org ************************* HijackThrowException : Throw exception hijacked!
它將運行 HijackThrowException 的 afterThrowing()方法,如果 CustomerService 的方法拋出異常。
4.環繞通知
它結合了上面的三個通知,在方法執行過程中執行。創建一個實現了MethodInterceptor接口的類。必須調用「methodInvocation.proceed();
」 繼續在原來的方法執行,否則原來的方法將不會執行。
package org.1ju.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="org.1ju.customer.services.CustomerService">
<property name="name" value="Yong Mook Kim" />
<property name="url" value="https://www.1ju.org" />
</bean>
<bean id="hijackAroundMethodBean" class="org.1ju.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 : Mook Kim HijackAroundMethod : Before after hijacked! ************************* Method name : printURL Method arguments : [] HijackAroundMethod : Before method hijacked! Customer website : https://www.1ju.org HijackAroundMethod : Before after hijacked! ************************* Method name : printThrowException Method arguments : [] HijackAroundMethod : Before method hijacked! HijackAroundMethod : Throw exception hijacked!
它將運行HijackAroundMethod 的 invoke()方法,在每一個 CustomerService 方法執行後。
總結
大部分的 Spring 開發者都只是實現了「環繞通知」,因爲它可以對所有通知類型,但更好的做法應該是選擇最合適的通知類型來滿足要求。
切入點
在這個例子中,在一客戶服務類中的所有方法都自動攔截(通知)。但在大多數情況下,可能需要使用切入點和Advisor通過它的方法名攔截它的方法。