最近跳槽了,新公司使用了 AOP 相關的技術,因而查點資料,複習一下。以前,多少知道點,但沒怎麼在實際項目中使用過~java
package com.cap.aop;
public interface ICalculator {
public double add(double num1, double num2) throws Exception;
public double sub(double num1, double num2) throws Exception;
public double div(double num1, double num2) throws Exception;
public double mul(double num1, double num2) throws Exception;
}
package com.cap.aop;
/**
* 加減乘除
* */
public class Calculator implements ICalculator {
@Override
public double add(double num1, double num2) {
double result = num1 + num2;
return result;
}
@Override
public double sub(double num1, double num2) {
double result = num1 - num2;
return result;
}
@Override
public double div(double num1, double num2) {
double result = num1 / num2;
return result;
}
@Override
public double mul(double num1, double num2) {
double result = num1 * num2;
return result;
}
}
定義 ICalculator 接口和 Calculator 類,而且 Calculator 也繼承 ICalculator。正則表達式
若要爲這個類添加「日誌」功能該如何作?日誌在實際項目中頗有必要,好比數據庫日誌,業務日誌等等,經過日誌就能知道數據庫和業務存在的問題,這要比調試程序容易多了,此外還有性能統計,安全控制,事務處理,異常處理等等都是相似問題。數據庫
咱們最可能想到的是,在類的每一個方法內都寫日誌相關的代碼,或是在該類的基類中寫,在其子類中繼承。編程
package com.cap.aop;
/**
* 加減乘除,原始方式
* */
public class CalculatorOriginalWay implements ICalculator {
@Override
public double add(double num1, double num2) {
System.out.println("the method [add()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = num1 + num2;
System.out.println("the method [add()]" + "end with result (" + result
+ ")");
return result;
}
@Override
public double sub(double num1, double num2) {
System.out.println("the method [sub()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = num1 - num2;
System.out.println("the method [sub()]" + "end with result (" + result
+ ")");
return result;
}
@Override
public double div(double num1, double num2) {
System.out.println("the method [div()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = num1 / num2;
System.out.println("the method [div()]" + "end with result (" + result
+ ")");
return result;
}
@Override
public double mul(double num1, double num2) {
System.out.println("the method [mul()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = num1 * num2;
System.out.println("the method [mul()]" + "end with result (" + result
+ ")");
return result;
}
}
這樣作的缺點顯而易見,重複代碼太多,耦合也很差。要是該類只針對正數運算呢,還要對變量作檢查,代碼以下所示:設計模式
package com.cap.aop;
/**
* 加減乘除,只對正數
* */
public class CalculatorForPositiveNumber implements ICalculator {
@Override
public double add(double num1, double num2) throws Exception {
this.argsValidatior(num1);
this.argsValidatior(num2);
System.out.println("the method [add()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = num1 + num2;
System.out.println("the method [add()]" + "end with result (" + result
+ ")");
return result;
}
@Override
public double sub(double num1, double num2) throws Exception {
this.argsValidatior(num1);
this.argsValidatior(num2);
System.out.println("the method [sub()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = num1 - num2;
System.out.println("the method [sub()]" + "end with result (" + result
+ ")");
return result;
}
@Override
public double div(double num1, double num2) throws Exception {
this.argsValidatior(num1);
this.argsValidatior(num2);
System.out.println("the method [div()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = num1 / num2;
System.out.println("the method [div()]" + "end with result (" + result
+ ")");
return result;
}
@Override
public double mul(double num1, double num2) throws Exception {
this.argsValidatior(num1);
this.argsValidatior(num2);
System.out.println("the method [mul()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = num1 * num2;
System.out.println("the method [mul()]" + "end with result (" + result
+ ")");
return result;
}
private void argsValidatior(double arg) throws Exception {
if (arg < 0)
throw new Exception("參數不能爲負數");
}
}
這也僅僅是一個類而已,實際項目中那麼多類,要是都這麼幹,顯然不現實,那麼如何改進?——設計模式「裝飾者模式」,在沒必要改變原類文件和繼承的狀況下,動態地擴展一個對象的功能。安全
package com.cap.aop;
/**
* 加減乘除,裝飾者模式
*/
public class CalculatorDecorator implements ICalculator {
private ICalculator cal;
public CalculatorDecorator(ICalculator iCalculator) {
cal = iCalculator;
}
@Override
public double add(double num1, double num2) throws Exception {
System.out.println("the method [add()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = cal.add(num1, num2);
System.out.println("the method [add()]" + "end with result (" + result
+ ")");
return result;
}
@Override
public double sub(double num1, double num2) throws Exception {
System.out.println("the method [sub()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = cal.sub(num1, num2);
System.out.println("the method [sub()]" + "end with result (" + result
+ ")");
return result;
}
@Override
public double div(double num1, double num2) throws Exception {
System.out.println("the method [div()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = cal.div(num1, num2);
System.out.println("the method [div()]" + "end with result (" + result
+ ")");
return result;
}
@Override
public double mul(double num1, double num2) throws Exception {
System.out.println("the method [mul()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = cal.mul(num1, num2);
System.out.println("the method [mul()]" + "end with result (" + result
+ ")");
return result;
}
}
這個方法比上面的強點,用原來的類 Calculator 建立一個新類 CalculatorDecorator,也繼承 ICalculator 接口,這樣,在對原來的類不作任何修改的狀況下,在新類中添加日誌功能。但該方法也有弱點,須要爲每一個類都應用「裝飾者模式」,工做量也不小。如何解決?——代理。服務器
JDK 從 1.3 版本開始,引入了動態代理。JDK 動態代理很是簡單,但動態代理的對象必須是一個或多個接口。網絡
若想代理類,就須要使用 cglib 包。cglib 是一個強大的、高性能的代碼生成包,cglib 包的底層是經過使用一個小而快的字節碼處理框架 ASM,來轉換字節碼並生成新的類,cglib 被許多 AOP 框架使用,例如 Spring 的 AOP;Hibernate 使用 cglib 來代理單端 single-ended(多對一和一對一)關聯;EasyMock 經過使用模仿(moke)對象來測試 java 包……它們都經過 cglib 來爲那些沒有接口的類建立模仿(moke)對象。app
package com.cap.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* 加減乘除,JDK 代理<br/>
* 只能代理接口,不能代理類
*
* */
public class CalculatorInvocationHandler implements InvocationHandler {
// 動態代理只有在運行時才知道代理誰,因此定義爲Object類型
private Object target = null;
/**
* 經過構造函數傳入原對象
*
* @param target
* 要代理的對象
*/
public CalculatorInvocationHandler(Object target) {
this.target = target;
}
/**
* InvocationHandler 接口的 invoke 方法,調用代理的方法
*
* @param proxy在其上調用方法的代理實例
* @param method攔截的方法
* @param args攔截的參數
* */
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("JDK proxy...");
// 日誌開始
System.out.println("the method [" + method.getName() + "]"
+ "begin with args (" + Arrays.toString(args) + ")");
Object result = method.invoke(this.target, args);
// 日誌結束
System.out.println("the method [" + method.getName() + "]"
+ "end with result (" + result + ")");
return result;
}
/**
* 獲取代理類
*/
public Object getProxy() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new CalculatorInvocationHandler(target));
}
}
package com.cap.aop;
import java.lang.reflect.Method;
import java.util.Arrays;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* 加減乘除,cglib 代理<br/>
* 能代理接口和類,不能代理final類
*/
public class CalculatorInterceptor implements MethodInterceptor {
private Object target;
public CalculatorInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy invocation) throws Throwable {
System.out.println("cglib proxy...");
// 日誌開始
System.out.println("the method [" + method.getName() + "]"
+ "begin with args (" + Arrays.toString(args) + ")");
Object result = invocation.invoke(target, args);
// 日誌結束
System.out.println("the method [" + method.getName() + "]"
+ "end with result (" + result + ")");
return result;
}
public Object proxy() {
return Enhancer.create(target.getClass(), new CalculatorInterceptor(
target));
}
}
package com.cap.aop;
public class Client {
public static void main(String[] args) throws Exception {
ICalculator calculatorProxy = (ICalculator) new CalculatorInvocationHandler(
new Calculator()).getProxy();
calculatorProxy.add(10, 10);
Calculator calculator = (Calculator) new CalculatorInterceptor(
new Calculator()).proxy();
calculator.add(20, 20);
}
}
運行結果:框架
JDK proxy...
the method [add]begin with args ([10.0, 10.0])
the method [add]end with result (20.0)
cglib proxy...
the method [add]begin with args ([20.0, 20.0])
the method [add]end with result (40.0)
利用 AOP 框架,你只須要利用註釋和 Aspect 就能夠完成上面的全部工做。
AOP,Aspect Oriented Programming,稱爲「面向切面編程」,AOP 是 OOD/OOP 和 GoF 的延續,GoF 孜孜不倦的追求是調用者和被調用者之間的解耦,提升代碼的靈活性和可擴展性,AOP 的目標也是同樣。
AOP 是一種經過預編譯和運行時動態代理實如今不修改源代碼的狀況下給程序動態統一添加功能的技術。利用 AOP 能夠對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度下降,提升程序的可重用性,同時提升了開發效率。在 Spring 中,經過分離應用的業務邏輯與系統級服務,應用對象只完成業務邏輯,不負責(甚至是意識)其它的系統級關注點,例如日誌、事務、審計等等。
本文最開始的「日誌功能」,就是一個切面。
AOP 主要應用在日誌記錄,性能統計,安全控制,事務處理,異常處理等等,將它們從業務邏輯代碼中分離,將它們獨立到非業務邏輯的方法中,進而在改變這些行爲時不影響業務邏輯的代碼。
AOP(面向切面編程)與 OOP(面向對象編程)字面上雖然相似,但倒是面向不一樣領域的兩種設計思想。
OOP 針對業務中的實體及其屬性和行爲進行抽象封裝,以便劃分邏輯單元。而 AOP 則是針對業務中的「切面」進行提取,它面對的是處理過程當中的某個步驟或階段,以得到各部分之間低耦合性的隔離效果。所以,這兩種設計思想有着本質的差別。
簡單來講,對「僱員」這個業務實體進行封裝,是 OOP 的任務,咱們能夠創建一個「Employee」類,並將「僱員」相關的屬性和行爲封裝其中。而用 AOP 對「僱員」進行封裝將無從談起;權限檢查也是如此,它是 AOP 的領域。
換而言之,OOD/OOP 面向名詞領域,AOP 面向動詞領域。
不少人在初次接觸 AOP 的時候可能會說,AOP 能作到的,一個定義良好的 OOP 接口也能,這個觀點是值得商榷。AOP 和定義良好的 OOP 能夠說都是用來解決而且實現需求中的橫切問題。但對於 OOP 中的接口來講,它仍然須要咱們在相應的模塊中去調用該接口中相關的方法,這是 OOP 所沒法迴避的,而且一旦接口不得不進行修改的時候,全部事情會變得一團糟;AOP 則不會,你只須要修改相應的 Aspect,再從新 weave(編織)便可。 AOP 也絕對不會取代 OOP。核心的需求仍然會由 OOP 來加以實現,而 AOP 將會和 OOP 整合起來,揚長避短。
下面概念在 AspectWerkz 的註釋中都有所體現。