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

网友投稿 403 2022-07-22


目录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开发。

开发步骤

加入依赖

org.springframework.boot

spring-boot-starter-aop

org.aspectj

aspectjrt

1.9.4

org.aspectj

aspectjweaver

1.9.4

cglib

3.2.12

创建目标接口和目标类(内部有切点)

创建切面类(内部有增强方法)

将目标类和切面类的对象创建权交给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 toZehttp://ro(){

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 startTime = new ThreadLocal<>();

ConcurrentHashMap countMap = new ConcurrentHashMap();

/**

* 匹配控制层层通知 这里监控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 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> findInterfaceCount(

@ApiParam(name = "intCount", value = "需要的接口数") @PathVariable Integer intCount

) {

HashMap hashMap = new HashMap<>();

JSONObject jsonObject = RedisUtils.objFromRedis(StringConst.INTERFACE_ACTUATOR);

if (jsonObject != null) {

Set strings = jsonObject.keySet();

for (String string : strings) {

Integer o = (Integer) jsonObject.get(string);

hashMap.putIfAbsent(string, o);

}

}

//根据value倒序

Map 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 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最终效果图如下:


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:springboot集成redis存对象乱码的问题及解决
下一篇:Java中的Kotlin 内部类原理
相关文章

 发表评论

暂时没有评论,来抢沙发吧~