Spring有兩大核心,IOC和AOP。IOC在spring項目中基本無處不在,而AOP則用的基本比較少。 AOP(Aspect Oriented Programming),即面向切面編程。
AOP有一些復(fù)雜的概念:
- 切面(aspect):用來切插業(yè)務(wù)方法的類。
- 連接點(joinpoint):是切面類和業(yè)務(wù)類的連接點,其實就是封裝了業(yè)務(wù)方法的一些基本屬性,作為通知的參數(shù)來解析。
- 通知(advice):在切面類中,聲明對業(yè)務(wù)方法做額外處理的方法。
- 切入點(pointcut):業(yè)務(wù)類中指定的方法,作為切面切入的點。其實就是指定某個方法作為切面切的地方。
- 目標對象(target object):被代理對象。
- AOP代理(aop proxy):代理對象。
通知:- 前置通知(before advice):在切入點之前執(zhí)行。
- 后置通知(after returning advice):在切入點執(zhí)行完成后,執(zhí)行通知。
- 環(huán)繞通知(around advice):包圍切入點,調(diào)用方法前后完成自定義行為。
- 異常通知(after throwing advice):在切入點拋出異常后,執(zhí)行通知。
初學的時候這些概念很難理解,可以先把它理解成一個高級的代理模式:
那么我們首先看一下什么是代理模式
代理模式即它可以在不改變原始類(或叫被代理類)代碼的情況下,通過引入代理類來給原始類附加功能。
代理模式常用在業(yè)務(wù)系統(tǒng)中開發(fā)一些非功能性需求,比如:監(jiān)控、統(tǒng)計、鑒權(quán)、限流、事務(wù)、冪等、日志。我們將這些附加功能與業(yè)務(wù)功能解耦,放到>代理類統(tǒng)一處理,讓程序員只需要關(guān)注業(yè)務(wù)方面的開發(fā)。除此之外,代理模式還可以用在 RPC、緩存等應(yīng)用場景中。
比如有一個類,我們想不改變這個類代碼的情況下統(tǒng)計這個類下面的所有方法執(zhí)行時間。
public class UserController {
public String login(String username, String password) {
//do something
try {
Thread.sleep(50);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("執(zhí)行用戶密碼登錄");
return "登錄成功";
}
public String register(String username, String password) {
//do something
try {
Thread.sleep(50);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("執(zhí)行用戶密碼注冊");
return "注冊成功";
}
}
先看一下靜態(tài)代理實現(xiàn)
很簡單,就是創(chuàng)建一個代理類來繼承原始類(還有一種方法:可以通過實現(xiàn)相同的接口來創(chuàng)建代理類)
public class UserControllerProxy extends UserController {
public String login(String username, String password) {
long startTimestamp = System.currentTimeMillis();
//執(zhí)行原始方法
String res = super.login(username, password);
long endTimeStamp = System.currentTimeMillis();
long executeTime = endTimeStamp - startTimestamp;
System.out.println("登錄方法執(zhí)行時間:" + executeTime);
return res;
}
public String register(String username, String password) {
long startTimestamp = System.currentTimeMillis();
//執(zhí)行原始方法
String res = super.register(username, password);
long endTimeStamp = System.currentTimeMillis();
long executeTime = endTimeStamp - startTimestamp;
System.out.println("注冊方法執(zhí)行時間:" + executeTime);
return res;
}
}
執(zhí)行直接用代理類來執(zhí)行
@Test
public void test(){
UserController userControllerProxy = new UserControllerProxy();
userControllerProxy.login("hello","123456");
}
//輸出:
//執(zhí)行用戶密碼登錄
//登錄方法執(zhí)行時間:50
這樣的代碼有點問題。一方面,我們需要在代理類中,將原始類中的所有的方法,都重新實現(xiàn)一遍,并且為每個方法都附加相似的代碼邏輯。另一方面,如果要添加的附加功能的類有不止一個,我們需要針對每個類都創(chuàng)建一個代理類。
動態(tài)代理實現(xiàn)
動態(tài)代理不需要給每一類創(chuàng)建代理類,只需要一個攔截方法類就可以。java有兩種常用的代理實現(xiàn),一種是基于反射的JDK動態(tài)代理實現(xiàn),一種是基于字節(jié)碼的CGLIB實現(xiàn)。這點主要介紹一些CGLIB的實現(xiàn)。
先引入cglib包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.1</version>
</dependency>
創(chuàng)建CGLIB代理類
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLibProxy implements MethodInterceptor {
public <T> T getProxy(Class<T> cls) {
return (T) Enhancer.create(cls, this);
}
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
long startTimestamp = System.currentTimeMillis();
//代理類中的所有方法
Object result = proxy.invokeSuper(obj, args);
long endTimeStamp = System.currentTimeMillis();
long executeTime = endTimeStamp - startTimestamp;
System.out.println("方法執(zhí)行時間是:" + executeTime);
return result;
}
}
@Test
public void test1(){
CGLibProxy cgLibProxy = new CGLibProxy();
UserController userControllerProxy = cgLibProxy.getProxy(UserController.class);
userControllerProxy.login("hello","123456");
//輸出:
//執(zhí)行用戶密碼登錄
//方法執(zhí)行時間是:62
}
再回頭看一下springAOP
aop其實就像代理模式一樣可以幫助我們增強方法,但它比動態(tài)代理方法更為細致??梢詳r截到方法執(zhí)行之前,方法執(zhí)行過程,以及方法執(zhí)行之后,通過這一些面對方法進行增強。
首先我們可以先定義一個注解,用于標識那些方法需要增強
假如我們要做一個業(yè)務(wù)監(jiān)控的功能,這個功能需要和業(yè)務(wù)代碼解耦,用AOP來做就非常適合了。
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Monitor {
String value();
}
首先我們先定義一個AOP的切面
切面的作用就是攔截方法的,通過切面可以對方法進行增強。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
@Aspect
public class AopAspect {
//定義切入點,指定那些方法可以被切面到,這里定義的是 被@Monitor注解定義的方法會被切面
@Pointcut("@annotation(com.dofun.aopdemo.aop.Monitor)")
public void pointcut() {
System.out.println("定義 pointcut");
}
//定義方法前置攔截,方法在執(zhí)行之前可以做一些事情
@Before("pointcut()")
public void before(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
//獲取方法上的注解
Monitor monitor = method.getDeclaredAnnotation(Monitor.class);
if (monitor != null) {
Object[] paramValues = joinPoint.getArgs();
String[] paramNames = methodSignature.getParameterNames();
System.out.print(monitor.value()+"參數(shù):");
for (int i = 0; i < paramNames.length; i++) {
System.out.print(paramNames[i] + ":" + paramValues[i]+";");
}
System.out.println("");
}
}
//定義方法后置攔截,方法在執(zhí)行之后可以做一些什么事情
@After("pointcut()")
public void after(JoinPoint joinPoint) {
System.out.println(""+joinPoint.getSignature().getName()+"方法結(jié)束。。。發(fā)送事件");
}
//定義方法環(huán)繞攔截,可以在方法執(zhí)行過程中做一些什么事情
@Around("pointcut()")
public void around(ProceedingJoinPoint joinPoint) {
long startTimestamp = System.currentTimeMillis();
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
long endTimeStamp = System.currentTimeMillis();
long executeTime = endTimeStamp - startTimestamp;
System.out.println("方法執(zhí)行時間是:" + executeTime);
}
}
添加一個測試方法
@SpringBootTest
public class ProxyTest {
@Autowired
private AdminService adminService;
@Test
public void test2(){
adminService.updatePassword("1","張三");
}
//修改管理員密碼參數(shù):adminId:1;adminName:張三;
//執(zhí)行方法本身,do something
//updatePassword方法結(jié)束。。。發(fā)送事件
//方法執(zhí)行時間是:13
}
整個AOP實現(xiàn)就完成了。然后我們可以打個斷點看一下,adminService其實已經(jīng)不是我們當初定義那個AdminService類型了,而是一個代理類的對象。

這其實就是spring AOP的原理了,spring容器在掃描這個類的過程中發(fā)現(xiàn)這個類有被切面“切入”到,就會往容器里面放入代理類而不是原始類。