您好,登錄后才能下訂單哦!
本篇內容主要講解“SpringBoot如何使用AOP實現統計全局接口訪問次數”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“SpringBoot如何使用AOP實現統計全局接口訪問次數”吧!
AOP(Aspect Oriented Programming),也就是面向切面編程,是通過預編譯方式和運行期間動態代理實現程序功能的傳統已維護的一種技術。
作用:在程序運行期間,在不修改源代碼的情況下對某些方法進行功能增強
優勢:減少重復代碼,提高開發效率,并且便于維護
jdk代理:基于接口的動態代理技術
cglib代理:基于父類的動態代理技術
List item- Target(目標對象):代理的目標對象
Proxy(代理):一個類被AOP織入增強后,就產生一個結果代理類
Joinpoint(連接點):連接點是指那些被攔截到的點。在Spring中,這些點指的是方法,因為Spring只支持方法類型的連接點(可以被增強的方法叫連接點)
PointCut(切入點):切入點是指我們要對哪些Joinpoint進行攔截的定義
Advice(通知/增強):通知是指攔截到Joinpoint之后所要做的事情就是通知
Aspect(切面):是切入點和通知的結合
Weaving(織入):把增強應用到目標對象來創建新的代理對象的過程。Spring采用動態代理織入,而AspectJ采用編譯器織入和類裝載器織入
我在這里采用基于注解形式的的AOP開發。
開發步驟
加入依賴
<!--引入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>
創建目標接口和目標類(內部有切點)
創建切面類(內部有增強方法)
將目標類和切面類的對象創建權交給Spirng
在切面類中使用注解配置織入關系
在配置文件中開啟組件掃描和AOP自動代理
因為我的項目是SpringBoot Web項目,在這里開啟注解就好了。
下面的代碼為使用到的原子計數類。
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); } }
下面的代碼為實現的全局接口監控。
我在項目中簡單使用了Redis作緩存,所有你可以看到有Redis相關的操作。
使用 @Before 用于配置前置通知。指定增強的方法在切入點方法之前執行。
使用@ @AfterReturning 用于配置后置通知。指定增強的方法在切入點方法之后執行。
使用@ @AfterThrowing 用于配置異常拋出通知。指定增強的方法在出現異常時執行。
@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>(); /** * 匹配控制層層通知 這里監控controller下的所有接口 */ @Pointcut("execution(* com.sf.controller.*Controller.*(..))") private void controllerPt() { } /** * 在接口原有的方法執行前,將會首先執行此處的代碼 */ @Before("com.sf.actuator.GlobalActuator.controllerPt()") public void doBefore(JoinPoint joinPoint) throws Throwable { startTime.set(System.currentTimeMillis()); //獲取傳入目標方法的參數 Object[] args = joinPoint.getArgs(); } /** * 只有正常返回才會執行此方法 * 如果程序執行失敗,則不執行此方法 */ @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; // 執行成功則計數加一 int increase = AtomicCounter.getInstance().increase(); HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); synchronized (this) { //在項目啟動時,需要在Redis中讀取原有的接口請求次數 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) { // 內存計數達到30 更新redis if (increase == 30) { RedisUtils.objToRedis(StringConst.INTERFACE_ACTUATOR, countMap, Constants.AVA_REDIS_TIMEOUT); //刪除過期時間 stringRedisTemplate.persist(StringConst.INTERFACE_ACTUATOR); //計數器置為0 AtomicCounter.getInstance().toZero(); } } //log.info("方法執行次數:" + mapKey + "------>" + countMap.get(mapKey)); //log.info("URI:[{}], 耗費時間:[{}] ms", request.getRequestURI(), System.currentTimeMillis() - startTime.get()); } /** * 當接口報錯時執行此方法 */ @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 = "查找接口成功訪問次數(默認倒序)") public Result<List<InterfaceDto>> findInterfaceCount( @ApiParam(name = "intCount", value = "需要的接口數") @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); } } //根據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中可以看到接口的請求次數。
Web最終效果圖如下:
到此,相信大家對“SpringBoot如何使用AOP實現統計全局接口訪問次數”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。