1. Spring容器解決了什么問題
優(yōu)點:
- 讓代碼架構(gòu)自上到下實現(xiàn)了低耦合.Spring支持接口注入,變更代碼邏輯時,只需要變更實現(xiàn)類即可做到無縫的切換.
- OOP將業(yè)務(wù)程序分解成各個層次的對象,通過對象聯(lián)動完成業(yè)務(wù)
缺點:
- 無法處理分散在各個業(yè)務(wù)之間的通用系統(tǒng)需求.例如:代碼日志、業(yè)務(wù)功能權(quán)限校驗、緩存、事務(wù)管理...容易發(fā)生代碼入侵,增加耦合度和維護成本.
2. 面向切面編程
Aspect Oriented Programming,面向切面編程,即我們常說的AOP.
它提供了一種在編譯期間和運行期間對某個功能進行增強的技術(shù),它是OOP的一種補充.在OOP中每個單元模塊為Class,而AOP關(guān)注的單元模塊為切面(Aspect).
舉個例子:@Transactional這個關(guān)于事務(wù)管理的注解,在方法上進行標記,那么在這么方法的開始和結(jié)束都會進行一些事務(wù)的操作.

從圖上可以看到,當addUser加上@Transactional,其中涉及數(shù)據(jù)庫的操作就會被事務(wù)管理的類負責代理,進而實現(xiàn)功能上的增強.
Spring基于AspectJ開發(fā)了Spring AOP的框架,提供基于schema方式或者@AspectJ注解風格的切面編程能力.
其中,框架內(nèi)部提供了一系列聲明式的切面服務(wù):例如@Transactional、@Async、@Cacheable...
另外,用戶如果想自定義自己的切面,也可以使用Spring AOP框架進行編程.
注意,AOP是一種思想,并不是Spring所獨有的.
2.1 切面編程中的概念
要了解AOP,需要先了解一下AOP中常說的概念:
-
Aspect: 切面,切面是AOP中封裝切面邏輯的模塊化單元.在
Spring AOP中,切面往往以類或者XML的形式存在。
打個比方,如果你需要對系統(tǒng)日志做一個切面處理,那么你就需要編寫一個類來描述其中的邏輯,那么這個類就是Aspect. -
Join point: 連接點,指程序執(zhí)行中的一點。在
Spring AOP中,連接點始終代表方法的執(zhí)行. -
Advice: 通知,即在特定的連接點切面執(zhí)行的時機.
Spring AOP提供了Around、Before、After等切面邏輯執(zhí)行的時機. -
Pointcut: 切入點,連接點表示的是所有可以進行切面邏輯的地方,那么切入點則對應(yīng)切面邏輯需要切入的地方,
Spring AOP支持使用AspectJ的切入點表達式來指定切入點.
這里可能會有點繞,這里舉一個例子:假設(shè)一個類中有3個方法,那么這三個方法都可以作為切面的連接點,此時如果只需要對其中的一個方法做切面邏輯,那么這個方法就是我們的切入點了。 -
Introduction: 在某個被切面邏輯環(huán)繞的對象中引入新的接口.在
Spring AOP中,可以通過introduction來將一個新的接口"引入"到被切入的類中. -
Target object: 被切面邏輯環(huán)繞的對象。在
Spring AOP中,被切面邏輯環(huán)繞的類通常是被代理的,也可以稱之為被代理的對象。 -
AOP proxy: 由AOP框架創(chuàng)建出的對象,為了去實現(xiàn)AOP邏輯.在
Spring AOP中,支持兩種代理JDK動態(tài)代理和CGLIB代理 -
Weaving: 將切面與被代理對象進行鏈接.
Spring AOP在運行時執(zhí)行織入邏輯.
2.1.1 Spring AOP中的Advice
| Advice(通知) | 描述 |
|---|---|
| Before advice | 前置通知,在連接點之前執(zhí)行,不會影響整體的執(zhí)行流程,除非拋出異常. |
| After returning advice | 后置通知,在連接點正常返回之后執(zhí)行(如果拋出了異常就不會執(zhí)行此通知). |
| After throwing advice | 在某個連接點拋出異常后執(zhí)行 |
| After (finally) advice | 無論連接點是否正常執(zhí)行,均會執(zhí)行此通知(相當于try finally中的finally). |
| Around advice | 環(huán)繞通知可以做到上述通知可以做到的事情.想象一下被代理的方法為a(),那么環(huán)繞可以對a()做try catch finally,環(huán)繞通知是最常用的一種通知. |
既然
Around advice是覆蓋所有advice的,那么為什么Spring AOP還需要聲明這么多advice,官方的說法是建議使用最小的advice級別來滿足你的需求.
打個比方:如果僅僅需要記錄每個方法的入?yún)⒆鲆粋€log操作,那么使用before advice就已經(jīng)可以滿足了,而不需要使用到around advice.
2.1.2 advice執(zhí)行順序
先說結(jié)論:Spring從5.2.7后對
@After的執(zhí)行順序進行了調(diào)整.如圖所示:
按官方的說法是跟隨AspectJ的語義.
點我前往

在5.2.7之前,Spring AOP按照
@Around,@Before,After,AfterReturning,AfterThrowing的順序執(zhí)行.

2.2 開始簡單的AOP編程
- 建立一個簡單的service作為被代理對象
package com.xjm.service.impl;
import com.xjm.service.HelloService;
import org.springframework.stereotype.Service;
/**
* @author jaymin
* 2020/11/26 17:04
*/
@Service
public class HelloServiceImpl implements HelloService {
@Override
public String hello() {
return "Hello,Spring Framework!";
}
}
- 編寫切面類
package com.xjm.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.logging.Logger;
/**
* @author jaymin. <br>
* 系統(tǒng)基礎(chǔ)切面,主要用于研究AOP.
* 2021/2/12 20:56
*/
@Aspect
@Component
public class SystemServiceAspect {
private static final Logger log = Logger.getGlobal();
/**
* 使用常量統(tǒng)一管理切入點
*/
public static final String SYSTEM_SERVICE_POINT_CUT = "systemServicePointCut()";
/**
* <p>pointcut可以使用表達式來指定切入點.
* <p>execution中的內(nèi)容即為表達式.</p>
* <p>* com.xjm..service..*.*(..)</p>
* <p>表示,攔截com.xjm包下的service包中的所有類的所有方法(包括任意參數(shù))
*/
@Pointcut("execution(* com.xjm..service..*.*(..))")
public void systemServicePointCut() {
}
/**
* 在方法執(zhí)行前進行切入
* @param joinPoint
*/
@Before(SYSTEM_SERVICE_POINT_CUT)
public void before(JoinPoint joinPoint) {
log.info("before method execute ");
}
/**
* 環(huán)繞整個方法的執(zhí)行
* @param joinPoint
* @return
*/
@Around(SYSTEM_SERVICE_POINT_CUT)
public Object around(JoinPoint joinPoint) {
LocalDateTime startTime = LocalDateTime.now();
log.info("around method starts ");
Object result;
try {
result = ((ProceedingJoinPoint) joinPoint).proceed();
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
} finally {
LocalDateTime endTime = LocalDateTime.now();
long executeTime = Duration.between(startTime, endTime).toMillis();
log.info("method end.");
}
return result;
}
/**
* 在方法返回值后進行切入
* @param joinPoint
* @param result
*/
@AfterReturning(pointcut = SYSTEM_SERVICE_POINT_CUT, returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
log.info("method return");
}
/**
* 在方法拋出異常后進行切入
* @param joinPoint
* @param exception
*/
@AfterThrowing(pointcut = SYSTEM_SERVICE_POINT_CUT, throwing = "exception")
public void afterThrowing(JoinPoint joinPoint, Exception exception) {
log.info("current method throw an exception,message");
}
/**
* 獲取連接點方法名
* @param joinPoint
* @return
*/
public String getMethodName(JoinPoint joinPoint) {
return ((MethodSignature) joinPoint.getSignature()).getMethod().getName();
}
/**
* 在方法執(zhí)行后進行切入
* @param joinPoint
*/
@After(SYSTEM_SERVICE_POINT_CUT)
public void after(JoinPoint joinPoint) {
log.info("after method execute");
}
}
- 執(zhí)行hello方法
注意,Spring是默認不開啟AOP的,如果使用的是SpringBoot,需要在啟動類上加上
@EnableAspectJAutoProxy.
Result:
二月 13, 2021 10:42:38 下午 com.xjm.aop.SystemServiceAspect around
信息: around method starts
二月 13, 2021 10:42:38 下午 com.xjm.aop.SystemServiceAspect before
信息: before method execute
二月 13, 2021 10:42:38 下午 com.xjm.aop.SystemServiceAspect around
信息: method end.
二月 13, 2021 10:42:38 下午 com.xjm.aop.SystemServiceAspect after
信息: after method execute
二月 13, 2021 10:42:38 下午 com.xjm.aop.SystemServiceAspect afterReturning
信息: method return
筆者使用的是Spring 5.1.x版本
總結(jié)
- Spring AOP以可插拔的形式提供了面向切面編程的框架能力,以便對OOP做進一步的增強.
- AOP是另一種編程思想,它擁有成熟的一套體系和自己的術(shù)語.
- AOP可以通過預(yù)編譯方式或者運行期動態(tài)代理的方式對程序功能進行織入.
- Spring AOP僅支持方法執(zhí)行連接點,內(nèi)部使用了JDK動態(tài)代理和CGLIB代理對AOP進行支持.