AOP的那些事以及在SpringBoot的應用
前言
最近讀了《Spring原始碼深度解析》這本書,感覺到Spring設計的巧妙和嚴謹,讀到了AOP這塊發現自己還有不少問題沒有理解清楚,特此進行AOP相關技術的集中調研和總結,記錄在此希望可以幫到大家。
什麼是AOP
面向切面程式設計(Aspect Oriented Programming),通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。
AOP其實只是OOP的補充而已。OOP從橫向上區分出一個個的類來,而AOP則從縱向上向物件中加入特定的程式碼。有了AOP,OOP變得立體了。如果加上時間維度,AOP使OOP由原來的二維變為三維了,由平面變成立體了
AOP對系統過程的公共行為抽取
把大象放在冰箱裡的故事
比如我們做了一個專案是要求把大象放在冰箱裡。
場景一
(開發把大象放到冰箱模組)
開啟冰箱
把大象放到冰箱
關閉冰箱
場景二
(複用改模組並擴充套件到其他動物上)
把馬放到冰箱裡
把長頸鹿放到冰箱裡
把其他大象放到冰箱裡
目前已經把這個過長應用到1000多種動物
場景三
(增加需求)
為了進一步優化,需要計算每次的時間
怎麼辦
為每個過程單獨計時?
- 耗時大
- 重複工作
錯誤可能性高
還可以怎麼做
1.將大象放到冰箱中這個過程封裝成標準的過程
2.為標準過程的開始通知開始計時
3.為標準過程的結束通知結束計時
4.輸出計時結果
上面的思路就是AOP的思路
即通過一定手段將不同服務的公共的縱向處理邏輯提取出來,統一實現。
AOP的優勢
- 避免修改業務程式碼
- 避免引入重複程式碼
- 可以對業務邏輯的各個部分進行隔離,降低邏輯的耦合度
AOP的典型應用場景
- 快取
- 日誌
- 安全驗證
- 審計
- 效能監控
- 事物處理
- 異常處理
AOP的理論
1.橫切關注點
對哪些方法進行攔截,攔截後怎麼處理,這些關注點稱之為橫切關注點
2.切面(aspect)
類是對物體特徵的抽象,切面就是對橫切關注點的抽象
3.連線點(joinpoint)
被攔截到的點因為Spring只支援方法型別的連線點,所以在Spring中連線點指的就是被攔截到的方法,實際上連線點還可以是欄位或者構造器
4.切入點(pointcut)
對連線點進行攔截的定義
5.通知(advice)
所謂通知指的就是指攔截到連線點之後要執行的程式碼,通知分為前置、後置、異常、最終、環繞通知五類
目標物件,代理的目標物件
6.織入(weave)
將切面應用到目標物件並導致代理物件建立的過程
7.引入(introduction)
在不修改程式碼的前提下,引入可以在執行期為類動態地新增一些方法或欄位
Spring Boot中如何使用
環境
Springboot(1.5.7)
Maven依賴(3.3.9)
Intellij IDEA
基本流程
1、定義普通業務元件
2、定義切入點,一個切入點可能橫切多個業務元件
3、定義增強處理,增強處理就是在AOP框架為普通業務元件織入的處理動作
示例程式碼
目錄結構
依賴
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zw.se2.research</groupId>
<artifactId>demo-aop</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo-aop</name>
<description>Aop Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.2.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
入口
package com.zw.se2.research;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoAopApplication {
public static void main(String[] args) {
SpringApplication.run(DemoAopApplication.class, args);
}
}
Controller
package com.zw.se2.research.ctrl;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Created by zhaoenwei on 2017/10/17.
*/
@RestController
public class Controller {
@ApiOperation(value="測試", notes="")
@RequestMapping("/hello")
public String sayHello(){
return "hello aop";
}
}
切面處理
package com.zw.se2.research.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Date;
/**
* Created by zhaoenwei on 2017/10/17.
*/
@Aspect
@Component
public class CtrlAspect {
private static final Logger logger= LoggerFactory.getLogger(CtrlAspect.class);
@Pointcut("execution(* com.zw.se2.research.ctrl..*.*(..)) && @annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void controllerMethodPointcut(){}
@Around("controllerMethodPointcut()")
public void Interceptor(ProceedingJoinPoint pjp){
long beginTime = System.currentTimeMillis();
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod(); //獲取被攔截的方法
String methodName = method.getName(); //獲取被攔截的方法名
logger.info("請求開始,方法:{}", methodName);
Date date=new Date(beginTime);
logger.info("請求開始,時間:{}",date);
try {
pjp.proceed();
} catch (Throwable throwable) {
logger.info("異常,{}", throwable.getMessage());
}
logger.info("請求結束,耗時:{}", System.currentTimeMillis()-beginTime);
}
}
Spring AOP原理
Spring中AOP代理由Spring的IOC容器負責生成、管理,其依賴關係也由IOC容器負責管理。因此,AOP代理可以直接使用容器中的其它bean例項作為目標,這種關係可由IOC容器的依賴注入提供。Spring建立代理的規則為:
1、預設使用Java動態代理來建立AOP代理,這樣就可以為任何介面例項建立代理了
2、當需要代理的類不是代理介面的時候,Spring會切換為使用CGLIB代理,也可強制使用CGLIB
Spirng 為什麼能支援AOP
容器的統一封裝
責任鏈
代理-反射
AOP的常見坑
1.濫用
將業務程式碼放到AOP中
業務邏輯不集中且難以理解
2.誤用
沒有正確呼叫
直接執行相應方法無法觸發AOP,必須通過Spring的容器的方式呼叫才能出發AOP