AOP (Aspect Oriented Programming), which is aspect-oriented programming, is a traditional and maintained technology that realizes program functions through pre-compilation and dynamic proxy during runtime.
Function: During the running of the program, some methods can be functionally enhanced without modifying the source code
Advantages: Reduce duplicate code , improve development efficiency, and facilitate maintenance
jdk proxy: interface-based dynamic proxy technology
cglib Proxy: Dynamic proxy technology based on parent class
List item- Target (target object): proxy Target object
Proxy (proxy): After a class is woven into the enhancement by AOP, a resulting proxy class
Joinpoint (connection point ): Connection points are those points that are intercepted. In Spring, these points refer to methods, because Spring only supports method type connection points (methods that can be enhanced are called connection points)
PointCut (pointcut): pointcut It refers to the definition of which Joinpoints we want to intercept
Advice (notification/enhancement): Notification refers to what we need to do after intercepting the Joinpoint is to notify
Aspect (aspect): It is the combination of pointcuts and notifications
Weaving (weaving): The process of applying enhancements to the target object to create a new proxy object. Spring uses dynamic proxy weaving, while AspectJ uses compiler weaving and class loader weaving
I use annotation-based AOP development here .
Development steps
Add dependencies
<!--引入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>
Create the target interface and target class (with cut points inside)
Create aspect classes (with enhancement methods inside)
Leave the object creation rights of the target class and aspect class to Spirng
Use annotations to configure the weaving relationship in the aspect class
Enable component scanning and AOP in the configuration file Automatic proxy
#Because my project is a SpringBoot Web project, just enable annotations here.
The following code is the atomic counting class used.
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); } }
The following code is the global interface monitoring implemented.
I simply use Redis for caching in the project, and you can see all Redis-related operations.
Use @Before to configure pre-notification. Specifies that the enhanced method is executed before the pointcut method.
Use @ @AfterReturning to configure post notification. Specifies that the enhanced method is executed after the pointcut method.
Use @ @AfterThrowing to configure exception throwing notifications. Specifies that the enhanced method is executed when an exception occurs.
@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()); } }
The Controller layer code is given here.
@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); }
After the project has been running for a period of time, you can see the number of requests for the interface in Redis.
The final rendering of Web is as follows:
The above is the detailed content of How SpringBoot uses AOP to count the number of global interface visits. For more information, please follow other related articles on the PHP Chinese website!