spring之為什麼要使用AOP(面向切片程式設計)?
需求1-日誌:在程式執行期間追蹤正在發生的活動;
需求2-驗證:希望計算器只處理正數的運算;
一、普通方法實現
Calculator.java
package com.gong.spring.aop.helloworld; public interface Calculator { int add(int i, int j); int sub(int i, int j); int mul(int i, int j); int div(int i, int j); }
CalculatorImpl.java
package com.gong.spring.aop.helloworld; public class CalculatorImpl implements Calculator{ @Override public int add(int i, int j) { System.out.println("add begin"); // TODO Auto-generated method stub int result = i+j; System.out.println("add end"); return result; } @Override public int sub(int i, int j) { System.out.println("sub begin"); // TODO Auto-generated method stub int result = i - j; System.out.println("sub end"); return result; } @Override public int mul(int i, int j) { System.out.println("mul begin"); // TODO Auto-generated method stub int result = i * j; System.out.println("mul end"); return result; } @Override public int div(int i, int j) { System.out.println("div begin"); // TODO Auto-generated method stub int result = i / j; System.out.println("div end"); return result; } }
Main.java
package com.gong.spring.aop.helloworld; public class Main { public static void main(String[] args) { Calculator calculator = new CalculatorImpl(); int i = 2; int j = 1; int res = calculator.add(i, j); System.out.println(res); res = calculator.sub(i, j); System.out.println(res); res = calculator.mul(i, j); System.out.println(res); res = calculator.div(i, j); System.out.println(res); } }
輸出:
存在兩個問題:
(1)程式碼混亂:每個方法在處理邏輯核心問題時還要關注其它問題,比如日誌和計算。
(2)程式碼分散:如果日誌發生變化,則需要修改所有方法的日誌。
二、第一種解決方法:使用動態代理。
動態代理原理:使用一個代理將物件包裝起來,然後呼叫該代理物件取代原始物件,任何對原始物件的呼叫都要通過代理。代理物件決定是否以及何時將方法轉到原始的物件上。
CalculatorLoggingImpl.java
package com.gong.spring.aop.helloworld; public class CalculatorLoggingImpl implements Calculator{ @Override public int add(int i, int j) { // TODO Auto-generated method stub int result = i+j; return result; } @Override public int sub(int i, int j) { // TODO Auto-generated method stub int result = i - j; return result; } @Override public int mul(int i, int j) { // TODO Auto-generated method stub int result = i * j; return result; } @Override public int div(int i, int j) { // TODO Auto-generated method stub int result = i / j; return result; } }
package com.gong.spring.aop.helloworld; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class CalculatorLoggingProxy { public CalculatorLoggingProxy(Calculator target) { this.target = target; } //要代理的物件 private Calculator target; public Calculator getLoggingProxy() { Calculator proxy = null; //代理物件由哪一個類載入器進行載入 ClassLoader loader = target.getClass().getClassLoader(); //代理物件的型別,即其中有哪些方法 Class [] interfaces = new Class[]{Calculator.class}; //呼叫代理物件其中的方法時,該執行的程式碼 InvocationHandler h = new InvocationHandler() { /*proxy:正在返回的代理物件 *method::正在被呼叫的方法 *args:呼叫方法時傳入的引數 * */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub String methodName = method.getName(); //日誌 System.out.println(methodName+" begin"); //執行方法 Object result = method.invoke(target, args); //日誌 System.out.println(methodName+" end"); return result; } }; proxy = (Calculator) Proxy.newProxyInstance(loader, interfaces, h); return proxy; } }
Main.java
package com.gong.spring.aop.helloworld; public class Main { public static void main(String[] args) { Calculator calculator = new CalculatorLoggingImpl(); Calculator proxy = new CalculatorLoggingProxy(calculator).getLoggingProxy(); int i = 2; int j = 1; int result =proxy.add(i, j); System.out.println(result); result = proxy.sub(i, j); System.out.println(result); result = proxy.mul(i, j); System.out.println(result); result = proxy.div(i, j); System.out.println(result); } }
輸出:
可以達到同樣的效果,但是我們的日誌模組只需要關注在代理中如何修改,進而可以影響到普通實現的所有方法。
三、第二種方式:使用AOP
AOP是對傳統OOP(面向物件程式設計)的一種補充。其主要程式設計物件是切面,而切面模組化橫切關注點。在應用AOP時,仍然需要定義公共功能,但可以明確定義這個功能在哪裡,以什麼方式應用。並且不必修改受影響的類,這樣一來橫切關注點就被模組化到特殊物件(切面)裡。
新建一個包,目錄結構如下:
需要加入到build path中的包:
說明:註解中的value屬性標識了為哪些方法加上通知。
Calculator.java
package com.gong.spring.aop.impl; public interface Calculator { int add(int i, int j); int sub(int i, int j); int mul(int i, int j); int div(int i, int j); }
CalculatorImpl.java
package com.gong.spring.aop.impl; import org.springframework.stereotype.Component; @Component public class CalculatorImpl implements Calculator{ @Override public int add(int i, int j) { // TODO Auto-generated method stub int result = i+j; return result; } @Override public int sub(int i, int j) { // TODO Auto-generated method stub int result = i - j; return result; } @Override public int mul(int i, int j) { // TODO Auto-generated method stub int result = i * j; return result; } @Override public int div(int i, int j) { // TODO Auto-generated method stub int result = i / j; return result; } }
applicationContext.java
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <context:component-scan base-package="com.gong.spring.aop.impl"></context:component-scan> <!-- 使AspectJ註解起作用:自動為匹配的類生成代理物件 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
在LoggingAspect.java中先只定義這麼一個類,並且加上註解:
package com.gong.spring.aop.impl; import java.util.Arrays; import java.util.List; import javax.management.RuntimeErrorException; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; //把這個類宣告為一個切面:需要把該類放入到IOC容器中,再宣告為一個切面 @Aspect @Component public class LoggingAspect { }
Main.java(這裡只測試兩個)
package com.gong.spring.aop.impl; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); //從IOC容器中獲取bean的例項 Calculator calculator = (Calculator) ctx.getBean(Calculator.class); int res = calculator.add(2, 1); System.out.println("在主函式中加法計算的結果="+res); res = calculator.div(2, 1); System.out.println("在主函式中除法計算的結果="+res); } }
1.前置通知
在applicationContext.java中先加入:
//宣告該方法為一個前置通知,在目標方法之前執行 @Before("execution(public int com.gong.spring.aop.impl.Calculator.*(int, int))") public void beforeMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println(methodName+" begin with "+args); }
輸出:
即前置通知是在目標函式呼叫前執行。
2.後置通知
再加入:
//後置通知:在目標方法執行後,無論是否發生異常,執行的通知 //在後置通知中不能訪問目標的執行結果 @After("execution(public int com.gong.spring.aop.impl.Calculator.*(int, int))") public void afterMethod(JoinPoint joinPoint) { //獲取名字 String methodName = joinPoint.getSignature().getName(); //獲取引數 List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println(methodName+" end with "+args); }
輸出:
後置通知在目標函式執行後執行,並且無論目標函式是否出現異常,都會執行。 j假設將除法分母設定為0,那麼結果為:
3.帶返回值的後置通知
註釋掉 afterMethod方法,再加上:
//在方法正常結束後執行的程式碼 //返回通知是可以訪問到方法的返回值的 @AfterReturning(value="execution(public int com.gong.spring.aop.impl.Calculator.*(..))", returning="result") public void afterReturning(JoinPoint joinPoint,Object result) { String methodName = joinPoint.getSignature().getName(); List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println("在afterReturning得到返回值:"+ result); System.out.println(methodName+" end with "+args); }
輸出:
可以在通知函式裡面獲得計算的值。
4.當發生異常時才會執行的通知
還有一種當只有發生異常時才會執行該通知:將除法分母變為0
@AfterThrowing(value="execution(public int com.gong.spring.aop.impl.Calculator.*(..))", throwing="ex") public void afterThrowing(JoinPoint joinPoint,Exception ex) { String methodName = joinPoint.getSignature().getName(); System.out.println(methodName+" occurs exception:"+ex); }
輸出:
5.環繞通知
@Around(value="execution(public int com.gong.spring.aop.impl.Calculator.*(..))") public Object aroundMethod(ProceedingJoinPoint pjd) { Object result = null; String methodName = pjd.getSignature().getName(); //執行目標方法 try { //前置通知 System.out.println(methodName+" begin with "+Arrays.asList(pjd.getArgs())); //執行目標方法 result = pjd.proceed(); //後置通知 System.out.println("在aroundMethod中得到值:"+result); } catch (Throwable e) { // TODO Auto-generated catch block //異常通知 System.out.println("the method occurs exception:" + e); throw new RuntimeException(e); } //後置通知 System.out.println(methodName+" end with "+Arrays.asList(pjd.getArgs())); return result; }
輸出:
當然,在環繞通知中也能狗處理異常。
AOP的好處:
(1)每個事物邏輯位於一個位置,程式碼不分散,便於維護和升級;
(2)業務模組更簡潔,只包含核心業務代