SpringBoot如何使用AOP实现统计全局接口访问次数
导读:本文共4624字符,通常情况下阅读需要15分钟。同时您也可以点击右侧朗读,来听本文内容。按键盘←(左) →(右) 方向键可以翻页。
摘要: AOP是什么AOP(Aspect Oriented Programming),也就是面向切面编程,是通过预编译方式和运行期间动态代理实现程序功能的传统已维护的一种技术。AOP的作用和优势作用:在程序运行期间,在不修改源代码的情况下对某些方法进行功能增强优势:减少重复代码,提高开发效率,并且便于维护常见的动态代理技术jdk代理:基于接口的动态代理技术cglib代理... ...
目录
(为您整理了一些要点),点击可以直达。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项目,在这里开启注解就好了。
下面的代码为使用到的原子计数类。
importjava.util.concurrent.atomic.AtomicInteger;publicclassAtomicCounter{privatestaticfinalAtomicCounteratomicCounter=newAtomicCounter();/***单例,不允许外界主动实例化*/privateAtomicCounter(){}publicstaticAtomicCountergetInstance(){returnatomicCounter;}privatestaticAtomicIntegercounter=newAtomicInteger();publicintgetValue(){returncounter.get();}publicintincrease(){returncounter.incrementAndGet();}publicintdecrease(){returncounter.decrementAndGet();}//清零publicvoidtoZero(){counter.set(0);}}
下面的代码为实现的全局接口监控。
我在项目中简单使用了Redis作缓存,所有你可以看到有Redis相关的操作。
使用 @Before 用于配置前置通知。指定增强的方法在切入点方法之前执行。
使用@ @AfterReturning 用于配置后置通知。指定增强的方法在切入点方法之后执行。
使用@ @AfterThrowing 用于配置异常抛出通知。指定增强的方法在出现异常时执行。
@Component@AspectpublicclassGlobalActuator{privatestaticfinalLoggerlog=LoggerFactory.getLogger(GlobalActuator.class);@ResourceprivateStringRedisTemplatestringRedisTemplate;ThreadLocal<Long>startTime=newThreadLocal<>();ConcurrentHashMap<Object,Object>countMap=newConcurrentHashMap<Object,Object>();/***匹配控制层层通知这里监控controller下的所有接口*/@Pointcut("execution(*com.sf.controller.*Controller.*(..))")privatevoidcontrollerPt(){}/***在接口原有的方法执行前,将会首先执行此处的代码*/@Before("com.sf.actuator.GlobalActuator.controllerPt()")publicvoiddoBefore(JoinPointjoinPoint)throwsThrowable{startTime.set(System.currentTimeMillis());//获取传入目标方法的参数Object[]args=joinPoint.getArgs();}/***只有正常返回才会执行此方法*如果程序执行失败,则不执行此方法*/@AfterReturning(returning="returnVal",pointcut="com.sf.actuator.GlobalActuator.controllerPt()")publicvoiddoAfterReturning(JoinPointjoinPoint,ObjectreturnVal)throwsThrowable{Signaturesignature=joinPoint.getSignature();StringdeclaringName=signature.getDeclaringTypeName();StringmethodName=signature.getName();StringmapKey=declaringName+methodName;//执行成功则计数加一intincrease=AtomicCounter.getInstance().increase();HttpServletRequestrequest=((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();synchronized(this){//在项目启动时,需要在Redis中读取原有的接口请求次数if(countMap.size()==0){JSONObjectjsonObject=RedisUtils.objFromRedis(StringConst.INTERFACE_ACTUATOR);if(jsonObject!=null){Set<String>strings=jsonObject.keySet();for(Stringstring:strings){Objecto=jsonObject.get(string);countMap.putIfAbsent(string,o);}}}}//如果此次访问的接口不在countMap,放入countMapcountMap.putIfAbsent(mapKey,0);countMap.compute(mapKey,(key,value)->(Integer)value+1);synchronized(this){//内存计数达到30更新redisif(increase==30){RedisUtils.objToRedis(StringConst.INTERFACE_ACTUATOR,countMap,Constants.AVA_REDIS_TIMEOUT);//删除过期时间stringRedisTemplate.persist(StringConst.INTERFACE_ACTUATOR);//计数器置为0AtomicCounter.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()")publicvoiddoAfterThrowing(JoinPointjoinPoint){HttpServletRequestrequest=((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();log.info("接口访问失败,URI:[{}],耗费时间:[{}]ms",request.getRequestURI(),System.currentTimeMillis()-startTime.get());}}
这里再给出Controller层代码。
@GetMapping("/interface/{intCount}")@ApiOperation(value="查找接口成功访问次数(默认倒序)")publicResult<List<InterfaceDto>>findInterfaceCount(@ApiParam(name="intCount",value="需要的接口数")@PathVariableIntegerintCount){HashMap<String,Integer>hashMap=newHashMap<>();JSONObjectjsonObject=RedisUtils.objFromRedis(StringConst.INTERFACE_ACTUATOR);if(jsonObject!=null){Set<String>strings=jsonObject.keySet();for(Stringstring:strings){Integero=(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=newArrayList<>();//排序后中的map中所有的keyObject[]objects=sortedMap.keySet().toArray();for(inti=0;i<intCount;i++){InterfaceDtointerfaceDto=newInterfaceDto();interfaceDto.setName((String)objects[i]);interfaceDto.setCount(sortedMap.get((String)objects[i]));resultList.add(interfaceDto);}returnResult.success(resultList);}
项目运行一段时间后,在Redis中可以看到接口的请求次数。
Web最终效果图如下:
</div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
SpringBoot如何使用AOP实现统计全局接口访问次数的详细内容,希望对您有所帮助,信息来源于网络。