Spring-Boot+AOP+統計單次請求方法的執行次數和耗時情況
阿新 • • 發佈:2019-02-02
package com.yanshu.aspect;
import java.util.*;
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;
/**
* 攔截器
* @author Administrator
*
*/
//@Aspect
//@Component
public class ControllerInterceptor {
//定義一個日誌的全域性的靜態
private static Logger logger =LoggerFactory.getLogger(ControllerInterceptor.class);
//ThreadLocal 維護變數 避免同步
//ThreadLocal為每個使用該變數的執行緒提供獨立的變數副本,所以每一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒所對應的副本。
ThreadLocal<Long> startTime = new ThreadLocal<>();// 開始時間
//ThreadLocal 維護變數 避免同步 ThreadLocal是一個關於建立執行緒區域性變數的類、
//通常情況下,我們建立的變數是可以被任何一個執行緒訪問並且修改的。而使用ThreadLocal建立的變數只能被當前執行緒訪問,其他執行緒無法訪問和修改。
//ThreadLocal為每個使用該變數的執行緒提供獨立的變數副本,所以每一個執行緒都可以獨以地改變自己的副本,而不會影響其它執行緒所對應的副本
ThreadLocal<Long> startTimes=new ThreadLocal<>();//開始時間
/**
* map1存放方法被呼叫的次數O
*/
ThreadLocal<Map<String, Long >> map1 = new ThreadLocal<>();
/**
* map2存放方法總耗時
*/
ThreadLocal<Map<String, Long >> map2 = new ThreadLocal<>();
/**
* 定義一個切入點. 解釋下:
*
* ~ 第一個 * 代表任意修飾符及任意返回值. ~ 第二個 * 定義在web包或者子包 ~ 第三個 * 任意方法 ~ .. 匹配任意數量的引數.
*/
/// static final String pCutStr = "execution(* com.appleyk.*..*(..))";
// static final String pCutStr = "execution(* com.yanshu.controller..*..*(..))";
static final String pCutStr = "execution(* com.yanshu.*..*(..))";
@Pointcut(value = pCutStr)
public void logPointcut() {
}
/**
* Aop:環繞通知 切整個包下面的所有涉及到呼叫的方法的資訊
* @param joinPoint
* @return
* @throws Throwable
*/
@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.yanshu.controller.*.*(..))")
public void beforMehhod(JoinPoint jp) {
startTime.set(System.currentTimeMillis());
}
//對Controller下面的方法執行後進行切入,統計方法執行的次數和耗時情況
//注意,這裡的執行方法統計的資料不止包含Controller下面的方法,也包括環繞切入的所有方法的統計資訊
@AfterReturning(value = "execution(* com.yanshu.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<String, Long> map = new HashMap<>();
//從map2中將最後的 連線點方法給移除了,替換成最終的,避免連線點方法多次進行疊加計算
//由於map2受ThreadLocal的保護,這裡不支援remove,因此,需要單開一個map進行資料交接
for(Map.Entry<String, Long> entry:map2.get().entrySet()){
if(entry.getKey().equals(methodName)){
map.put(methodName, total);
}else{
map.put(entry.getKey(), entry.getValue());
}
}
for (Map.Entry<String, Long> entry :map1.get().entrySet()) {
for(Map.Entry<String, Long> entry2 :map.entrySet()){
if(entry.getKey().equals(entry2.getKey())){
System.err.println(entry.getKey()+",被呼叫次數:"+entry.getValue()+",綜合耗時:"+entry2.getValue()+"ms");
}
}
}
}
}
import java.util.*;
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;
/**
* 攔截器
* @author Administrator
*
*/
//@Aspect
//@Component
public class ControllerInterceptor {
//定義一個日誌的全域性的靜態
private static Logger logger =LoggerFactory.getLogger(ControllerInterceptor.class);
//ThreadLocal 維護變數 避免同步
//ThreadLocal為每個使用該變數的執行緒提供獨立的變數副本,所以每一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒所對應的副本。
ThreadLocal<Long> startTime = new ThreadLocal<>();// 開始時間
//ThreadLocal 維護變數 避免同步 ThreadLocal是一個關於建立執行緒區域性變數的類、
//通常情況下,我們建立的變數是可以被任何一個執行緒訪問並且修改的。而使用ThreadLocal建立的變數只能被當前執行緒訪問,其他執行緒無法訪問和修改。
//ThreadLocal為每個使用該變數的執行緒提供獨立的變數副本,所以每一個執行緒都可以獨以地改變自己的副本,而不會影響其它執行緒所對應的副本
ThreadLocal<Long> startTimes=new ThreadLocal<>();//開始時間
/**
* map1存放方法被呼叫的次數O
*/
ThreadLocal<Map<String, Long >> map1 = new ThreadLocal<>();
/**
* map2存放方法總耗時
*/
ThreadLocal<Map<String, Long >> map2 = new ThreadLocal<>();
/**
* 定義一個切入點. 解釋下:
*
* ~ 第一個 * 代表任意修飾符及任意返回值. ~ 第二個 * 定義在web包或者子包 ~ 第三個 * 任意方法 ~ .. 匹配任意數量的引數.
*/
/// static final String pCutStr = "execution(* com.appleyk.*..*(..))";
// static final String pCutStr = "execution(* com.yanshu.controller..*..*(..))";
static final String pCutStr = "execution(* com.yanshu.*..*(..))";
@Pointcut(value = pCutStr)
public void logPointcut() {
}
/**
* Aop:環繞通知 切整個包下面的所有涉及到呼叫的方法的資訊
* @param joinPoint
* @return
* @throws Throwable
*/
@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.yanshu.controller.*.*(..))")
public void beforMehhod(JoinPoint jp) {
startTime.set(System.currentTimeMillis());
}
//對Controller下面的方法執行後進行切入,統計方法執行的次數和耗時情況
//注意,這裡的執行方法統計的資料不止包含Controller下面的方法,也包括環繞切入的所有方法的統計資訊
@AfterReturning(value = "execution(* com.yanshu.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<String, Long> map = new HashMap<>();
//從map2中將最後的 連線點方法給移除了,替換成最終的,避免連線點方法多次進行疊加計算
//由於map2受ThreadLocal的保護,這裡不支援remove,因此,需要單開一個map進行資料交接
for(Map.Entry<String, Long> entry:map2.get().entrySet()){
if(entry.getKey().equals(methodName)){
map.put(methodName, total);
}else{
map.put(entry.getKey(), entry.getValue());
}
}
for (Map.Entry<String, Long> entry :map1.get().entrySet()) {
for(Map.Entry<String, Long> entry2 :map.entrySet()){
if(entry.getKey().equals(entry2.getKey())){
System.err.println(entry.getKey()+",被呼叫次數:"+entry.getValue()+",綜合耗時:"+entry2.getValue()+"ms");
}
}
}
}
}