什么是AOP
AOP(Aspect-Oriented-Programming,面向切面編程)是面向物件編程的一種補充,可以通過預編譯方式和運行期動態代理實作在不修改源代碼的情況下給程式動態統一添加功能的一種技術,它的主要實作技術有spring aop和aspectJ,
解釋看起來比較抽象,我們先通過傳統的方式模擬實作日志記錄功能
傳統方式
/*計算介面*/
public interface Operation {
double calculate(double num1,double num2);
}
/*實作計算介面的加法類,還有減乘除三個方法這里不再贅述*/
public class Add implements Operation {
public double calculate(double num1, double num2) {
System.out.println("正在寫計算之前日志");
double rtn=-1;
try {
rtn=num1+num2;//業務功能代碼
System.out.println("正在寫計算之后日志");
} catch (Exception e) {
System.out.println("正在寫出現例外時的日志");
}
return rtn;
}
}
/*測驗類*/
public class Main {
public static void main(String[] args) {
Operation operation=new Add();
double rtn=operation.calculate(8,9 );
System.out.println(rtn);
}
}
可以看出,日志的寫入需要分別在各實作類中重復寫入日志記錄的代碼,如果日志需求發生變化,必須修改所有的模塊,但是實際的業務功能代碼只有一句,這就導致核心業務邏輯變得復雜,
普通代理模式
我們可以通過一種設計模式——代理模式,解決代碼冗余,
/*計算介面*/
public interface Operation {
double calculate(double num1,double num2);
}
/*實作計算介面的加法類*/
public class Add implements Operation {
public double calculate(double num1, double num2) {
return num1+num2;
}
}
/*代理類*/
public class OperationProxy implements Operation {
Operation operation=null;
public OperationProxy(Operation operation) {
this.operation=operation;
}
@Override
public double calculate(double num1, double num2) {
System.out.println("正在寫計算之前日志");
double num=-1;
try {
num=operation.calculate(num1, num2);//業務代碼
System.out.println("正在寫計算之后日志");
} catch (Exception e) {
System.out.println("正在寫出現例外時的日志");
}
return num;
}
}
/*測驗類*/
public class Main {
public static void main(String[] args) {
Operation add=new Add();
OperationProxy proxy=new OperationProxy(add);
double rtn=proxy.calculate(8, 9);
System.out.println(rtn);
}
}
但是這種方式要求代理類必須和它的代理目標有共同的介面,意思是這個代理類不能代理其它的,,,
動態代理
/*計算介面*/
public interface Operation {
double calculate(double num1,double num2);
}
/*實作計算介面的加法類*/
public class Add implements Operation {
public double calculate(double num1, double num2) {
return num1+num2;
}
}
/**
* 動態代理
* 需要繼承InvocationHandler介面
* @author yang
*
*/
public class DynamicProxy implements InvocationHandler {
private Object target;//被代理的物件,Operation
public DynamicProxy(Object target) {
this.target=target;
}
/*
* 反射呼叫該介面
* proxy:代理物件
* method:需要執行的方法
* args:需要執行的方法引數
* 回傳方法的執行結果
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("正在寫計算之前日志......");
Object rtn=-1;
try {
rtn=method.invoke(target, args);
System.out.println("正在寫計算之后日志......");
} catch (Exception e) {
System.out.println("正在寫出現例外時的日志......");
}
return rtn;
}
/*獲取代理物件*/
public static Object createProxy(Object target) {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),new DynamicProxy(target));
}
}
/*測驗類*/
public class Main {
public static void main(String[] args) {
Operation add=new Add();
Operation op=(Operation) DynamicProxy.createProxy(add);
double num=op.calculate(8, 9);
System.out.println(num);
}
}
將完成業務功能的代碼與輔助功能(記錄日志)分開,業務邏輯更加清晰
AOP方式(JDK動態代理)
依賴AspectJ類別庫(pom.xml中)
spring-aspect(自動依賴aspectjweaver)和aopalliance
spring組態檔參考命名空間aop

并在組態檔中啟用aspectJ注解支持
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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">
<!-- 基于注解的aop -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<context:component-scan base-package="day0915.cal"></context:component-scan>
</beans>
在實作類中加入注解 ,將其的bean放入ioc容器中
@Component
public class Add implements Operation {
public double calculate(double num1, double num2) {
return num1+num2;
}
}
撰寫切面類
/**
* 切面類
* Aspect注解表示該類為切面類
* @author yang
*
*/
@Component
@Aspect
public class OperationAspect {
//前置通知
@Before("execution(* *.calculate(..))")
public void before(){
System.out.println("業務執行之前執行的方法")
}
}
/*測驗類*/
public class Main {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("applictioncontext4.xml");
/*
* 這里使用了切面類,創建容器時new Add()還需要經過AOP
* 進入ioc容器的是實體類的動態代理類,而不是實體類
* 所以最好還是用id來獲取
*/
// Operation operation=ac.getBean(Add.class);
Operation operation=(Operation) ac.getBean("add");//動態獲取代理
double num=operation.calculate(8, 9);
System.out.println(num);
}
}
可見每個事務邏輯位于一個位置,代碼不分散,便于維護和升級,并且對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率,
spring AOP術語
(1)通知(Advice):切面必須要實作的功能(比如上面例子的日志記錄功能),切面類中加入了通知注解的方法,
AspectJ支持的五種通知注解:
1. @Before 前置通知,在業務代碼執行前執行
2. @After 后置通知,在業務代碼執行之后執行,不管業務代碼是否正常執行(類似例外捕獲時的finally,不管try中的方法是否例外都會執行)
3. @AfterRetruning 回傳通知,業務代碼執行成功之后才能執行
4. @AfterThrowing 例外通知,業務邏輯拋出例外之后執行
5. @Around 環繞通知,圍繞著方法執行,包圍一個連接點的通知,如方法呼叫,這是最強大的一種通知型別,環繞通知可以在方法呼叫前后完成自定義的行為,它也會選擇是否繼續執行連接點或直接回傳它自己的回傳值或拋出例外來結束執行,
(2)連接點(Joinpoint):spring允許通知的地方,方法前后、或者拋出例外時,spring只支持方法連接點
(3)切點(pointcut):一個類中有很多方法,每個方法都有幾個連接點,但是你并不希望有的方法都被通知,此時需要使用切入點運算式來匹配你希望被通知的方法,aop就是通過切點來定位到想要的連接點
(4)切面(Aspect):通知和切入點的結合,是加上注解@aspect的類,簡單來說,通知就是說明要做什么,什么時候做,切點就是在哪里做,這就是一個切面的完整定義
(5)目標(target):要被通知的物件,也就是真正的業務邏輯,如果想只用這個切面通知的類,使用JoinPoint.getTarget()方法獲取
(6)代理(proxy):向目標物件應用通知之后創建的物件
AOP原理
spring用代理類將切面包裹住,然后將它們織入到ioc容器的bean中,通過動態代理的方式將代理類偽裝成目標類(就是上面例子中的add類),代理類會截取對目標類中方法的呼叫,然后先執行切面,然后再把呼叫轉發給目標類的bean,
要實作這個偽裝還要躲避JVM的檢查,有兩種方式:
- spring使用JDK的java.lang…reflect.Proxy類,動態生成一個新的類來實作和目標類相同的介面,織入通知,并且把這些介面的任何呼叫都轉發給目標類,但是這方法有個局限,如果目標類沒有實作某個介面,就不能用這方法了
- spring使用CGLIB庫生成目標類的一個子類,織入通知,這種方法是在呼叫這個子類的方法,一些無法繼承的方法就不能用了,
相比之下,還是第一種方法好些,他能更好的實作低耦合,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/89192.html
標籤:其他
下一篇:八種經典排序演算法總結
