SpringBoot使用AOP實現(xiàn)統(tǒng)計全局接口訪問次數(shù)詳解
AOP是什么
AOP(Aspect Oriented Programming),也就是面向切面編程,是通過預編譯方式和運行期間動態(tài)代理實現(xiàn)程序功能的傳統(tǒng)已維護的一種技術。
AOP的作用和優(yōu)勢
作用:在程序運行期間,在不修改源代碼的情況下對某些方法進行功能增強
優(yōu)勢:減少重復代碼,提高開發(fā)效率,并且便于維護
常見的動態(tài)代理技術
jdk代理:基于接口的動態(tài)代理技術

cglib代理:基于父類的動態(tài)代理技術

AOP相關概念
- List item- Target(目標對象):代理的目標對象
- Proxy(代理):一個類被AOP織入增強后,就產生一個結果代理類
- Joinpoint(連接點):連接點是指那些被攔截到的點。在Spring中,這些點指的是方法,因為Spring只支持方法類型的連接點(可以被增強的方法叫連接點)
- PointCut(切入點):切入點是指我們要對哪些Joinpoint進行攔截的定義
- Advice(通知/增強):通知是指攔截到Joinpoint之后所要做的事情就是通知
- Aspect(切面):是切入點和通知的結合
- Weaving(織入):把增強應用到目標對象來創(chuàng)建新的代理對象的過程。Spring采用動態(tài)代理織入,而AspectJ采用編譯器織入和類裝載器織入
實現(xiàn)
我在這里采用基于注解形式的的AOP開發(fā)。
開發(fā)步驟
加入依賴
<!--引入AOP依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--AOP-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.12</version>
</dependency>
創(chuàng)建目標接口和目標類(內部有切點)
創(chuàng)建切面類(內部有增強方法)
將目標類和切面類的對象創(chuàng)建權交給Spirng
在切面類中使用注解配置織入關系
在配置文件中開啟組件掃描和AOP自動代理

因為我的項目是SpringBoot Web項目,在這里開啟注解就好了。
下面的代碼為使用到的原子計數(shù)類。
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounter {
private static final AtomicCounter atomicCounter = new AtomicCounter();
/**
* 單例,不允許外界主動實例化
*/
private AtomicCounter() {
}
public static AtomicCounter getInstance() {
return atomicCounter;
}
private static AtomicInteger counter = new AtomicInteger();
public int getValue() {
return counter.get();
}
public int increase() {
return counter.incrementAndGet();
}
public int decrease() {
return counter.decrementAndGet();
}
// 清零
public void toZero(){
counter.set(0);
}
}下面的代碼為實現(xiàn)的全局接口監(jiān)控。
我在項目中簡單使用了Redis作緩存,所有你可以看到有Redis相關的操作。
使用 @Before 用于配置前置通知。指定增強的方法在切入點方法之前執(zhí)行。
使用@ @AfterReturning 用于配置后置通知。指定增強的方法在切入點方法之后執(zhí)行。
使用@ @AfterThrowing 用于配置異常拋出通知。指定增強的方法在出現(xiàn)異常時執(zhí)行。
@Component
@Aspect
public class GlobalActuator {
private static final Logger log = LoggerFactory.getLogger(GlobalActuator.class);
@Resource
private StringRedisTemplate stringRedisTemplate;
ThreadLocal<Long> startTime = new ThreadLocal<>();
ConcurrentHashMap<Object, Object> countMap = new ConcurrentHashMap<Object, Object>();
/**
* 匹配控制層層通知 這里監(jiān)控controller下的所有接口
*/
@Pointcut("execution(* com.sf.controller.*Controller.*(..))")
private void controllerPt() {
}
/**
* 在接口原有的方法執(zhí)行前,將會首先執(zhí)行此處的代碼
*/
@Before("com.sf.actuator.GlobalActuator.controllerPt()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
startTime.set(System.currentTimeMillis());
//獲取傳入目標方法的參數(shù)
Object[] args = joinPoint.getArgs();
}
/**
* 只有正常返回才會執(zhí)行此方法
* 如果程序執(zhí)行失敗,則不執(zhí)行此方法
*/
@AfterReturning(returning = "returnVal", pointcut = "com.sf.actuator.GlobalActuator.controllerPt()")
public void doAfterReturning(JoinPoint joinPoint, Object returnVal) throws Throwable {
Signature signature = joinPoint.getSignature();
String declaringName = signature.getDeclaringTypeName();
String methodName = signature.getName();
String mapKey = declaringName + methodName;
// 執(zhí)行成功則計數(shù)加一
int increase = AtomicCounter.getInstance().increase();
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
synchronized (this) {
//在項目啟動時,需要在Redis中讀取原有的接口請求次數(shù)
if (countMap.size() == 0) {
JSONObject jsonObject = RedisUtils.objFromRedis(StringConst.INTERFACE_ACTUATOR);
if (jsonObject != null) {
Set<String> strings = jsonObject.keySet();
for (String string : strings) {
Object o = jsonObject.get(string);
countMap.putIfAbsent(string, o);
}
}
}
}
// 如果此次訪問的接口不在countMap,放入countMap
countMap.putIfAbsent(mapKey, 0);
countMap.compute(mapKey, (key, value) -> (Integer) value + 1);
synchronized (this) {
// 內存計數(shù)達到30 更新redis
if (increase == 30) {
RedisUtils.objToRedis(StringConst.INTERFACE_ACTUATOR, countMap, Constants.AVA_REDIS_TIMEOUT);
//刪除過期時間
stringRedisTemplate.persist(StringConst.INTERFACE_ACTUATOR);
//計數(shù)器置為0
AtomicCounter.getInstance().toZero();
}
}
//log.info("方法執(zhí)行次數(shù):" + mapKey + "------>" + countMap.get(mapKey));
//log.info("URI:[{}], 耗費時間:[{}] ms", request.getRequestURI(), System.currentTimeMillis() - startTime.get());
}
/**
* 當接口報錯時執(zhí)行此方法
*/
@AfterThrowing(pointcut = "com.sf.actuator.GlobalActuator.controllerPt()")
public void doAfterThrowing(JoinPoint joinPoint) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
log.info("接口訪問失敗,URI:[{}], 耗費時間:[{}] ms", request.getRequestURI(), System.currentTimeMillis() - startTime.get());
}
}這里再給出Controller層代碼。
@GetMapping("/interface/{intCount}")
@ApiOperation(value = "查找接口成功訪問次數(shù)(默認倒序)")
public Result<List<InterfaceDto>> findInterfaceCount(
@ApiParam(name = "intCount", value = "需要的接口數(shù)") @PathVariable Integer intCount
) {
HashMap<String, Integer> hashMap = new HashMap<>();
JSONObject jsonObject = RedisUtils.objFromRedis(StringConst.INTERFACE_ACTUATOR);
if (jsonObject != null) {
Set<String> strings = jsonObject.keySet();
for (String string : strings) {
Integer o = (Integer) jsonObject.get(string);
hashMap.putIfAbsent(string, o);
}
}
//根據(jù)value倒序
Map<String, Integer> sortedMap = hashMap.entrySet().stream().sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
//返回列表
List<InterfaceDto> resultList = new ArrayList<>();
//排序后中的map中所有的key
Object[] objects = sortedMap.keySet().toArray();
for (int i = 0; i < intCount; i++) {
InterfaceDto interfaceDto = new InterfaceDto();
interfaceDto.setName((String) objects[i]);
interfaceDto.setCount(sortedMap.get((String) objects[i]));
resultList.add(interfaceDto);
}
return Result.success(resultList);
}項目運行一段時間后,在Redis中可以看到接口的請求次數(shù)。

Web最終效果圖如下:

到此這篇關于SpringBoot使用AOP實現(xiàn)統(tǒng)計全局接口訪問次數(shù)詳解的文章就介紹到這了,更多相關SpringBoot AOP內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Spring?Boot項目集成Knife4j接口文檔的實例代碼
Knife4j就相當于是swagger的升級版,對于我來說,它比swagger要好用得多<BR>,這篇文章主要介紹了Spring?Boot項目集成Knife4j接口文檔的示例代碼,需要的朋友可以參考下2021-12-12
spring-boot-maven-plugin:打包時排除provided依賴問題
這篇文章主要介紹了spring-boot-maven-plugin:打包時排除provided依賴問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04
Java基于SpringBoot和tk.mybatis實現(xiàn)事務讀寫分離代碼實例
這篇文章主要介紹了Java基于SpringBoot和tk.mybatis實現(xiàn)事務讀寫分離代碼實例,讀寫分離,基本的原理是讓主數(shù)據(jù)庫處理事務性增、改、刪操作,而從數(shù)據(jù)庫處理SELECT查詢操作,數(shù)據(jù)庫復制被用來把事務性操作導致的變更同步到集群中的從數(shù)據(jù)庫,需要的朋友可以參考下2023-10-10

