若依日志打印入参,返回参数,日志切面打印接口调用,出现一个接口多次调用的情况且,多次调用返回值为null,initBinder的原因。

使用若依Springboot项目,Controller均继承BaseController。
这里若依原日志切面不会打印接口请求参数,返回参数等信息,这里我做了修改。

问题描述

修改后日志打印出现一个接口调用4次,5次的情况

15:08:58.342 [http-nio-8082-exec-4] INFO  c.s.f.a.LogAspect - [doBefore,103] -
=== 开始请求 ===
request-url:http://localhost:8082/xxx/xxx/xxx
request-desc:
request-method:POST
request-ip:
class-method:com.syt.api.controller.BaseController.initBinder
request-param:{"sign":["00001111111dddddddddddd"]}

=== 返回值 ===
null
==== time cost ====

=== 开始请求 ===
request-url:http://localhost:8082/xxx/xxx/xxx
request-desc:
request-method:POST
request-ip:
class-method:com.syt.api.controller.BaseController.initBinder
request-param:{"sign":["00001111111dddddddddddd"]}

=== 返回值 ===
null
==== time cost ====

........

这里出现问题就是BaseController.initBinder方法重复调用了好几次,且返回值均为null。
BaseController.initBinder这个方法使用注解 @InitBinder
源代码为:

    /**
     * 将前台传递过来的日期格式的字符串,自动转化为Date类型
     */
    @InitBinder
    public void initBinder(WebDataBinder binder)
    {
        // Date 类型转换
        binder.registerCustomEditor(Date.class, new PropertyEditorSupport()
        {
            @Override
            public void setAsText(String text)
            {
                setValue(DateUtils.parseDate(text));
            }
        });
    }

@InitBinder从字面意思可以看出这个的作用是给Binder做初始化的,@InitBinder主要用在@Controller中标注于方法上(@RestController也算),表示初始化当前控制器的数据绑定器(或者属性绑定器),只对当前的Controller有效。@InitBinder标注的方法必须有一个参数WebDataBinder。所谓的属性编辑器可以理解就是帮助我们完成参数绑定,然后是在请求到达controller要执行方法前执行!
链接: 原文地址

解决方案

为避免在日志重复显示接口调用,需要处理日志切面LogAspect.java,在@Before注解下的方法添加下面代码

    @Before("pointcut()")
    public void doBefore(JoinPoint joinPoint) throws Exception {
    
        *****
        
        Signature signature1 = joinPoint.getSignature();
        MethodSignature signature = (MethodSignature) signature1;
        //如果获取的方法名为initBinder,则直接return
        if (signature.getMethod().getName().contains("initBinder")) {
            return;
        }
        
        ******
    }

@AfterReturning注解的方法需要修改

    @AfterReturning(returning = "ret", pointcut = "pointcut()")
    public void doAfterReturning(Object ret) {
        if (!isDoReturning) {
            return;
        }
        //这里需要添加是否为空判断,不然会报错
        if (StringUtils.isNotNull(ret)) {
            log.info("\n=== 返回值 ===\n" + JSON.toJSONString(ret));
            log.info("\n==== time cost" + ((System.currentTimeMillis() - threadLocal.get())) + "ms" + " ====\n");
        }
        return;
    }

源文件

finish:修改后文件如下所示LogAspect.java

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

import com.alibaba.fastjson.JSON;
import io.swagger.annotations.ApiOperation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import com.syt.common.utils.StringUtils;

/**
 * 操作日志记录处理
 * 
 * @author syt
 */
@Aspect
@Component
public class LogAspect
{
    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

    @Autowired
    private Environment env;
    private static boolean isDoBefore = false;
    private static boolean isDoAfter = false;
    private static boolean isDoReturning = false;
    private static boolean isDoThrowing = false;

    /**
     * 保证每个线程都有一个单独的实例
     */
    private ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    private static String[] params;

    static {
        params = new String[]{"aspect.logger.spring-application-name", "aspect.logger.request-url", "aspect.logger.request-uri",
                "aspect.logger.request-desc", "aspect.logger.session", "aspect.logger.cookie",
                "aspect.logger.content-type", "aspect.logger.request-method", "aspect.logger.request-ip",
                "aspect.logger.request-user-agent", "aspect.logger.class-method", "aspect.logger.request-param"};
    }

    @PostConstruct
    public void init() {
        isDoBefore = env.getProperty("aspect.do-before") == null ? false : env.getProperty("aspect.do-before", Boolean.class);
        isDoAfter = env.getProperty("aspect.do-after") == null ? false : env.getProperty("aspect.do-after", Boolean.class);
        isDoReturning = env.getProperty("aspect.do-returning") == null ? false : env.getProperty("aspect.do-returning", Boolean.class);
        isDoThrowing = env.getProperty("aspect.do-throwing") == null ? false : env.getProperty("aspect.do-throwing", Boolean.class);
    }

    @Pointcut("execution(* com.syt.*.controller..*(..))")
    public void pointcut() {
    }

    @Before("pointcut()")
    public void doBefore(JoinPoint joinPoint) throws Exception {
        if (!isDoBefore) {
            return;
        }
        Signature signature1 = joinPoint.getSignature();
        MethodSignature signature = (MethodSignature) signature1;
        if (signature.getMethod().getName().contains("initBinder")) {
            return;
        }
        threadLocal.set(System.currentTimeMillis());
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        StringBuilder stringBuilder = new StringBuilder()
                .append("\n=== 开始请求 ===\n");

        // 记录请求的内容
        this.logHandle(joinPoint, params, request, stringBuilder);
        log.info(stringBuilder.toString());
        return;
    }

    @After("pointcut()")
    public void doAfter(JoinPoint joinPoint) {
        if (!isDoAfter) {
            return;
        }
        log.info("\n==== doAfter ===\n" + joinPoint.toString());
    }

    /**
     * 返回值信息
     *
     * @param ret
     */
    @AfterReturning(returning = "ret", pointcut = "pointcut()")
    public void doAfterReturning(Object ret) {
        if (!isDoReturning) {
            return;
        }
        if (StringUtils.isNotNull(ret)) {
            log.info("\n=== 返回值 ===\n" + JSON.toJSONString(ret));
            log.info("\n==== time cost" + ((System.currentTimeMillis() - threadLocal.get())) + "ms" + " ====\n");
        }
        return;
    }

    @AfterThrowing(throwing = "ex",pointcut = "pointcut()")
    public void doThrowing(Throwable ex){
        if (!isDoThrowing) {
            return;
        }
        log.error("\n=== 异常 ===\n" + ex);
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param joinPoint 切点
     * @return 方法描述
     * @throws Exception
     */
    private String getControllerMethodDescription(JoinPoint joinPoint) throws Exception {
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        String description = "";
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    Object temp = method.getAnnotation(ApiOperation.class);
                    if (temp != null) {
                        description = method.getAnnotation(ApiOperation.class).value();
                    }
                    break;
                }
            }
        }
        return description;
    }

    /**
     * 处理请求参数输出
     *
     * @param joinPoint
     * @param requestParams
     * @param request
     * @param stringBuilder
     * @throws Exception
     */
    public void logHandle(JoinPoint joinPoint, String[] requestParams, HttpServletRequest request, StringBuilder stringBuilder) throws Exception {
        Map<String, Object> paramMap = new HashMap<>(16);
        String contentType = request.getContentType();
        paramMap.put("session", request.getSession());
        paramMap.put("cookie", request.getCookies());
        paramMap.put("spring-application-name", env.getProperty("spring.application.name"));
        paramMap.put("request-url", request.getRequestURL());
        paramMap.put("request-uri", request.getRequestURI());
        paramMap.put("request-param", JSON.toJSONString(request.getParameterMap()));
        paramMap.put("request-desc", getControllerMethodDescription(joinPoint));
        paramMap.put("request-method", request.getMethod());
        paramMap.put("content-type", contentType);
        paramMap.put("class-method", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        paramMap.put("request-ip", request.getRemoteAddr());
        paramMap.put("request-user-agent", request.getHeader("User-Agent"));

        String reqParam = null;
        Object[] o = joinPoint.getArgs();
        if (contentType != null && contentType.contains("multipart/form-data")) {
            MultipartFile file = (MultipartFile) o[0];
            reqParam = file.getOriginalFilename();
        } else {
            if (o != null && o.length > 0) {
                reqParam = o[0].toString();
            }
        }
        paramMap.put("aspect.logger.request-param", reqParam);

        // 按配置输出
        for (String param : requestParams) {
            Boolean property = env.getProperty(param, Boolean.class);
            String p = param.replace("aspect.logger.", "");
            if (property != null && property && paramMap.containsKey(p)) {
                stringBuilder.append(p + ":" + paramMap.get(p) + "\n");
            }
        }
    }
}

配置文件applicaton-test.yml添加下面配置,这里aspect顶格

# 日志切面处理
aspect:
  logger:
    spring-application-name: false
    request-url: true
    request-uri: false
    class-method: true
    request-method: true
    request-param: true
    request-desc: true
    request-ip: true
    request-user-agent: false
    content-type: false
    session: false
    cookie: false
  do-before: true
  do-after: false
  do-returning: true
  do-throwing: true

项目测试中日志切面出现的部分问题:

  1. 日志切面出现空指针异常
    报错代码及修改
if (contentType != null && contentType.contains("multipart/form-data")) {
    MultipartFile file = (MultipartFile) o[0];
    reqParam = file.getOriginalFilename();
} else {
    if (o != null && o.length > 0) {
        if (o[0] != null) { //在这里添加空指针判断
            reqParam = o[0].toString(); //这里报空指针异常
        }
    }
}
  1. 文件上传时报无法强制类型转换异常
    报错代码及修改
if (contentType != null && contentType.contains("multipart/form-data")) {
    StandardMultipartHttpServletRequest request1 = new StandardMultipartHttpServletRequest(request);
    Map<String, MultipartFile> fileMap = request1.getFileMap();
    MultipartFile file = fileMap.get("file");
//            MultipartFile file = (MultipartFile) o[0];
    reqParam = file.getOriginalFilename();
} else {
    if (o != null && o.length > 0) {
        if (o[0] != null) {
            reqParam = o[0].toString();
        }
    }
}

你可能感兴趣的:(springboot,java,服务器,java,spring,spring,boot)