SpringBoot使用AOP实现统计全局接口访问次数详解

目录

AOP是什么

AOP的作用和优势

常见的动态代理技术

AOP相关概念

实现

AOP是什么

AOP(Aspect Oriented Programming),也就是面向切面编程,是通过预编译方式和运行期间动态代理实现程序功能的传统已维护的一种技术。

AOP的作用和优势

作用:在程序运行期间,在不修改源代码的情况下对某些方法进行功能增强

优势:减少重复代码,提高开发效率,并且便于维护

常见的动态代理技术

jdk代理:基于接口的动态代理技术

cglib代理:基于父类的动态代理技术

AOP相关概念

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实现统计全局接口访问次数详解的文章就介绍到这了,更多相关SpringBoot AOP内容请搜索易知道(ezd.cc)以前的文章或继续浏览下面的相关文章希望大家以后多多支持易知道(ezd.cc)!

推荐阅读

    公共CPU接口类型的详细描述

    公共CPU接口类型的详细描述,,我们知道CPU是电脑的大脑, CPU的处理速度直接决定电脑的性能, 那你知道CPU发展到现在, 都那些CPU接口类型吗.

    EXCEL如何统计个数?

    EXCEL如何统计个数?,个数,统计,如何,关于计数,最常用的就是Cout系列函数和Sumproduct函数。一、Count。功能:统计指定范围中数值类型值的个数

    FM1和AM3接口将在今年年底前淘汰

    FM1和AM3接口将在今年年底前淘汰,,据来自主板制造商的消息,AMD将开始逐步淘汰Socket FM1和Socket AM3接口从本月开始的处理器,最后消失在第

    Windows8开发版系统高清系统接口图

    Windows8开发版系统高清系统接口图,,今天,微软发布,目前由开发者体验版Windows 8操作系统为整个英文系统,Windows 8带来了一系列新功能,没有折

    「显卡上的接口」显卡上的接口有几种

    「显卡上的接口」显卡上的接口有几种,显卡上的接口,本篇文章给大家谈谈显卡上的接口,以及显卡上的接口有几种对应的知识点,希望对各位有所帮

    支持DDR3!AM3接口790gx主板的评价

    支持DDR3!AM3接口790gx主板的评价,,二月,在主板市场,各种各样的AM3主板将推出。对Phenom II处理器的刺激下,每一个主板制造商不能忽视这样一个

    常识硬件的计算机日常维护

    常识硬件的计算机日常维护,,硬件(防尘、防高温、防磁、防潮、防静电、防震) 应将电脑放在一个干净的房间,避免灰尘太多造成的不利影响,对各种