javascript
aop统计请求数量_Spring-Boot+AOP+统计单次请求方法的执行次数和耗时情况-Go语言中文社区...
本篇結合aop(面向切面編程)的特性,對spring-boot項目下后端開發人員所關心的java代碼的性能做了一次簡單的統計,比如,前端發了一個post請求(一連串數據的保存),到了后端,首先是指定Controller的某個方法做接收,本篇稱之為接入點方法;其次就是以接入點方法為線索,繼續向下執行剩余模塊(包)里面的方法,而這些方法就是我們所關心的數據業務的實現部分。
如果利用aop技術對這些方法進行相應的切點操作的話,我們會清晰的看到,每個方法所在的包+類名,每個方法的名稱,每個方法的參數類型以及每個方法的執行耗時情況等等,就如單元測試驗證項目的可行性一樣,我們拿到了這些信息后,就可以利用給出的信息去定位優化,雖然起決定性因素的還是整個項目的架構和數據庫層面上的優化,顯然本篇還是小試牛刀,就當做是自我娛樂吧。
一、Spring-Boot 添加 aop依賴
org.springframework.boot
spring-boot-starter-aop
二、Spring-Boot項目樹
(1)
(2)添加Controller級別的攔截器(Class)
ControllerInterceptor.java
package com.appleyk.interceptor;
import java.util.HashMap;
import java.util.Map;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* 攔截器
*/
@Aspect
@Component
public class ControllerInterceptor {
static Logger logger = LoggerFactory.getLogger(ControllerInterceptor.class);
//ThreadLocal 維護變量 避免同步
//ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。
ThreadLocal startTime = new ThreadLocal<>();// 開始時間
/**
* map1存放方法被調用的次數O
*/
ThreadLocal> map1 = new ThreadLocal<>();
/**
* map2存放方法總耗時
*/
ThreadLocal> map2 = new ThreadLocal<>();
/**
* 定義一個切入點. 解釋下:
*
* ~ 第一個 * 代表任意修飾符及任意返回值. ~ 第二個 * 定義在web包或者子包 ~ 第三個 * 任意方法 ~ .. 匹配任意數量的參數.
*/
static final String pCutStr = "execution(* com.appleyk.*..*(..))";
@Pointcut(value = pCutStr)
public void logPointcut() {
}
@Around("logPointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
//初始化 一次
if(map1.get() ==null ){
map1.set(new HashMap<>());
}
if(map2.get() == null){
map2.set(new HashMap<>());
}
long start = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
if(result==null){
//如果切到了 沒有返回類型的void方法,這里直接返回
return null;
}
long end = System.currentTimeMillis();
logger.info("===================");
String tragetClassName = joinPoint.getSignature().getDeclaringTypeName();
String MethodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();// 參數
int argsSize = args.length;
String argsTypes = "";
String typeStr = joinPoint.getSignature().getDeclaringType().toString().split(" ")[0];
String returnType = joinPoint.getSignature().toString().split(" ")[0];
logger.info("類/接口:" + tragetClassName + "(" + typeStr + ")");
logger.info("方法:" + MethodName);
logger.info("參數個數:" + argsSize);
logger.info("返回類型:" + returnType);
if (argsSize > 0) {
// 拿到參數的類型
for (Object object : args) {
argsTypes += object.getClass().getTypeName().toString() + " ";
}
logger.info("參數類型:" + argsTypes);
}
Long total = end - start;
logger.info("耗時: " + total + " ms!");
if(map1.get().containsKey(MethodName)){
Long count = map1.get().get(MethodName);
map1.get().remove(MethodName);//先移除,在增加
map1.get().put(MethodName, count+1);
count = map2.get().get(MethodName);
map2.get().remove(MethodName);
map2.get().put(MethodName, count+total);
}else{
map1.get().put(MethodName, 1L);
map2.get().put(MethodName, total);
}
return result;
} catch (Throwable e) {
long end = System.currentTimeMillis();
logger.info("====around " + joinPoint + "tUse time : " + (end - start) + " ms with exception : "
+ e.getMessage());
throw e;
}
}
//對Controller下面的方法執行前進行切入,初始化開始時間
@Before(value = "execution(* com.appleyk.controller.*.*(..))")
public void beforMehhod(JoinPoint jp) {
startTime.set(System.currentTimeMillis());
}
//對Controller下面的方法執行后進行切入,統計方法執行的次數和耗時情況
//注意,這里的執行方法統計的數據不止包含Controller下面的方法,也包括環繞切入的所有方法的統計信息
@AfterReturning(value = "execution(* com.appleyk.controller.*.*(..))")
public void afterMehhod(JoinPoint jp) {
long end = System.currentTimeMillis();
long total = end - startTime.get();
String methodName = jp.getSignature().getName();
logger.info("連接點方法為:" + methodName + ",執行總耗時為:" +total+"ms");
//重新new一個map
Map map = new HashMap<>();
//從map2中將最后的 連接點方法給移除了,替換成最終的,避免連接點方法多次進行疊加計算
//由于map2受ThreadLocal的保護,這里不支持remove,因此,需要單開一個map進行數據交接
for(Map.Entry entry:map2.get().entrySet()){
if(entry.getKey().equals(methodName)){
map.put(methodName, total);
}else{
map.put(entry.getKey(), entry.getValue());
}
}
for (Map.Entry entry :map1.get().entrySet()) {
for(Map.Entry entry2 :map.entrySet()){
if(entry.getKey().equals(entry2.getKey())){
System.err.println(entry.getKey()+",被調用次數:"+entry.getValue()+",綜合耗時:"+entry2.getValue()+"ms");
}
}
}
}
}
(3)說明
A.
B.
C.
D.
E.
D.其他見代碼注釋,還有一個地方需要注意
三、本次請求(保存players)測試的入口(Controller)
(1)對應Controller
(2)再往下走一層,見證一下對應的ServiceImpl的實現部分
四、請求測試
(1)數據準備
(2)mysql數據庫 查看數據(暫時無)
(3) 清空項目中的Console控制臺輸出信息
(4)Send 測試數據進行 post請求
(5)查看項目控制臺輸出內容
(6)post請求數據(存儲)驗證
(7)再來個大數據量的post數據請求(存儲)
(8)本地日志查看
本篇涉及兩個知識點,一個就是spring-boot日志的配置,一個就是aop的配置,前者在我的博文里有,后者的配置也很簡單,總得來說,spring-boot作為spring的孩子,其在配置方面,真的是簡化了不少,基本上就是零xml配置。
總結
以上是生活随笔為你收集整理的aop统计请求数量_Spring-Boot+AOP+统计单次请求方法的执行次数和耗时情况-Go语言中文社区...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python有趣的简单代码_简单几步,1
- 下一篇: 企业需求的Java程序员是什么样子的