基于SpringBoot使用AOP开发接口的访问日志信息

本文详细介绍了SpringBoot中AOP(面向切面编程)的原理,包括基于JDK动态代理和CGLIB动态代理的方式,以及如何在方法执行前后添加日志记录,以实现接口访问监控和数据收集功能。
摘要由CSDN通过智能技术生成

SpringBoot的AOP原理

Spring Boot的AOP(面向切面编程)原理是基于动态代理实现的。

在Spring Boot中,AOP通过代理模式对目标对象进行包装,实现在目标对象的方法执行前后增加额外的逻辑。AOP可以在不修改目标对象的情况下,通过代理对象对目标对象进行方法的增强。

Spring Boot中的AOP主要使用了两种代理方式:JDK动态代理和CGLIB动态代理。

  • JDK动态代理:JDK动态代理是基于接口的代理,目标对象必须实现至少一个接口。Spring Boot使用JDK动态代理机制生成代理对象,代理对象实现了目标对象所实现的接口,并且将方法调用转发给目标对象。JDK动态代理通过Proxy类和InvocationHandler接口实现。
  • CGLIB动态代理:CGLIB动态代理是基于类的代理,不需要目标对象实现接口。Spring Boot使用CGLIB动态代理机制通过生成目标对象的子类来实现代理,代理对象继承了目标对象的所有方法,并且可以重写其中的方法来实现增强逻辑。

当目标对象的方法被调用时,AOP框架会根据切面定义和通知定义来决定是否需要对目标对象的方法进行增强。如果需要增强,AOP框架会通过代理对象来调用目标对象的方法,并在调用前后执行通知中定义的增强逻辑。

背景功能说明

在开发应用系统的时候,我需要了解什么接口是什么IP在访问,用时多少,用什么参数来请求,请求之后的结果返回的事情全部记录下来,用来帮助自己分析本系统的热点数据、接口访问频率,访问地址等等信息,可以根据这些信息做系统的改进策略。

代码开发

创建数据表

CREATE TABLE `api_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `ip_addr` varchar(100) DEFAULT NULL COMMENT '访问IP地址',
  `uri` varchar(500) DEFAULT NULL COMMENT '请求接口地址',
  `method_name` varchar(255) DEFAULT NULL COMMENT '请求接口方法名',
  `description` varchar(255) DEFAULT NULL COMMENT '请求接口描述',
  `duration` bigint(20) DEFAULT NULL COMMENT '请求接口用时',
  `start_time` datetime DEFAULT NULL COMMENT '请求开始时间',
  `end_time` datetime DEFAULT NULL COMMENT '请求结束时间',
  `params` text COMMENT '请求接口参数',
  `log_result` text COMMENT '请求接口返回结果',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=84110 DEFAULT CHARSET=utf8;

注解类

此类用于注解在控制器的方法类上。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {

    /**
     * 日志信息描述
     * @return
     */
    String value() default "";


    /**
     * 是否保存到数据库
     * @return
     */
    boolean save() default true;

    /**
     * 是否输出到控制台
     * @return
     */
    boolean console() default true;

}

使用AOP保存接口信息

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.net.NetUtil;
import com.alibaba.fastjson.JSON;
import com.api.annotation.Loggable;
import com.api.entity.ApiLog;
import com.api.service.ApiLogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

@Aspect
@Component
@Slf4j
public class LogAspect {

    /**
     * 操作开始时间
     */
    private Long startTime = 0L;

    @Resource
    private ApiLogService apiLogService;

    /**
     * Controller层切点
     */
    @Pointcut("execution(* com.api.controller..*.*(..))")
    public void controllerAspect() {
    }

    /**
     * 前置通知 在方法前使用
     */
    @Before("controllerAspect()")
    public void beforeControllerLog(JoinPoint joinpoint){

    }

    @Around("controllerAspect()")
    public Object aroundControllerLog(ProceedingJoinPoint point) throws Throwable{
        //获取Request
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);

        //获取目标方法
        Method method = ((MethodSignature) point.getSignature()).getMethod();

        //判断是否有@Loggable注解
        if(!method.isAnnotationPresent(Loggable.class)){
            //如果没有直接返回结果
            return point.proceed();
        }

        //记录日志信息
        ApiLog logMessage = new ApiLog();

        /**
         * 获取方法上的注解实例
         */
        Loggable loggable = method.getAnnotation(Loggable.class);

        //处理接口请求日志
        handleRequestLog(point, loggable, request, logMessage);

        //执行目标方法内容,获取执行结果
        Object result = point.proceed();

        //处理接口响应日志
        handleResponseLog(logMessage, loggable, result);
        return result;
    }

    private void handleRequestLog(ProceedingJoinPoint point, Loggable loggable, HttpServletRequest request, ApiLog logMessage) {
        //类名
        String targetName = point.getTarget().getClass().toString();
        //方法名称
        String methodName = point.getSignature().getName();

        Map<String, Object> params = getMethodParamNames(point);

        //判断是否输出日志
        if(loggable.console()){
            log.info("targetName:{},methodName:{},params:{}",targetName,methodName,params);
        }

        startTime = System.currentTimeMillis();
        logMessage.setStartTime(DateUtil.date());
        logMessage.setDescription(loggable.value());
        logMessage.setMethodName(methodName);
        logMessage.setAppKey(request.getParameter("appKey"));
        logMessage.setUri(request.getRequestURI());
        logMessage.setIpAddr(getIpAddress(request)==null?request.getRemoteAddr():getIpAddress(request));
        logMessage.setParams(JSON.toJSONString(params));
    }


    private void handleResponseLog(ApiLog logMessage, Loggable loggable, Object result) {
        /**
         * 操作结束时间
         */
        Long endTime = System.currentTimeMillis();
        Long duration = endTime - startTime;
        logMessage.setDuration(duration);
        logMessage.setEndTime(DateUtil.date());
        logMessage.setLogResult(JSON.toJSONString(result));
        if(loggable.save()){
            //这里考虑用异步操作进行优化
            apiLogService.save(logMessage);
        }
        if(loggable.console()){
            log.info("方法执行所需时间 : {} , 输出的结果 : {}",duration,result);
        }
    }
 
     //获取请求方法的参数
    private static Map<String, Object> getMethodParamNames(ProceedingJoinPoint point) {
        Map<String, Object> param = new HashMap<>();
        Object[] paramValues = point.getArgs();
        String[] paramNames = ((CodeSignature) point.getSignature()).getParameterNames();
        for (int i = 0; i < paramNames.length; i++) {
            param.put(paramNames[i], paramValues[i]);
        }
        return param;
    }

    // 获取代理前的IP
    private String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        if (ip.contains(",")) {
            return ip.split(",")[0];
        } else {
            return ip;
        }
    }
}

上述代码即可实现保存接口访问的信息。

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用SpringBoot AOP的步骤: 1. 首先,在pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> ``` 2. 创建一个切面类,使用@Aspect注解标记该类,并在该类中定义切点和通知。 ```java @Aspect @Component public class MyAspect { @Pointcut("execution(* com.example.demo.service.*.*(..))") public void pointcut() {} @Before("pointcut()") public void before(JoinPoint joinPoint) { System.out.println("Before method: " + joinPoint.getSignature().getName()); } @After("pointcut()") public void after(JoinPoint joinPoint) { System.out.println("After method: " + joinPoint.getSignature().getName()); } } ``` 3. 在应用程序主类上添加@EnableAspectJAutoProxy注解,启用自动代理。 ```java @SpringBootApplication @EnableAspectJAutoProxy public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } ``` 4. 在需要使用AOP的类或方法上添加自定义注解,并在切面类中使用@Around注解来拦截该注解。 ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MyAnnotation {} @Around("@annotation(com.example.demo.aspect.MyAnnotation)") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); System.out.println("After method: " + joinPoint.getSignature().getName()); return result; } @Service public class MyService { @MyAnnotation public void doSomething() { System.out.println("Doing something..."); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值