基于spring boot的controller日志切面处理

问题需求:项目中需要在日志中记录接口传入值,接口调用ip,接口调用时间,并且入库

处理方式:在controller层加切面处理

1.需要的包,springboot 中aop切面,两个工具类,hutool和jackson

pom如下(版本可以更换):

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-core</artifactId>
            <version>4.5.18</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.9.0</version>
        </dependency>

2.代码编写:

/**
 * <dl>
 * <dt>ControllerAspect</dt>
 * <dd>Description:</dd>
 * <dd>Copyright: Copyright (C) 2019</dd>
 * <dd>Company:</dd>
 * <dd>CreateDate: 2019/7/26</dd>
 * </dl>
 *
 * @author hht
 */
@Component
@Aspect
public class ControllerAspect {
    private Logger logger = LoggerFactory.getLogger(ControllerAspect.class);
    // 定义切点Pointcut
    @Pointcut( "execution(* com.test.test.controller..*(..))")
    public void executeController() {
    }
    @Before("executeController()")
    public void before(JoinPoint joinPoint) throws Exception{
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = servletRequestAttributes.getRequest();
        //这一步获取到的方法有可能是代理方法也有可能是真实方法
        Method m = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //判断代理对象本身是否是连接点所在的目标对象,不是的话就要通过反射重新获取真实方法
        if (joinPoint.getThis().getClass() != joinPoint.getTarget().getClass()) {
            m = ReflectUtil.getMethod(joinPoint.getTarget().getClass(), m.getName(), m.getParameterTypes());
        }
        //通过真实方法获取该方法的参数名称
        LocalVariableTableParameterNameDiscoverer paramNames = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = paramNames.getParameterNames(m);
        //获取连接点方法运行时的入参列表
        Object[] args = joinPoint.getArgs();
        //将参数名称与入参值一一对应起来
        Map<String, Object> params = new HashMap<>();
        //自己写的一个判空类方法
        if (!TextUtil.isEmpty(parameterNames)){
            for (int i = 0; i < parameterNames.length; i++) {
            //这里加一个判断,如果使用requestParam接受参数,加了require=false,这里会存现不存在的现象
            if (TextUtil.isEmpty(args[i])){
                    continue;
                }
                //通过所在类转换,获取值,包含各种封装类都可以
                ObjectMapper objectMapper = new ObjectMapper();
                objectMapper.convertValue(args[i],args[i].getClass());
                params.put(parameterNames[i],JSON.toJSON(objectMapper.convertValue(args[i],args[i].getClass())));
            }
        }
        logger.info("before start:-------------------------------");
        logger.info("URL : " + request.getRequestURL().toString());
        logger.info("HTTP_METHOD : " + request.getMethod());
        logger.info("IP : " + request.getRemoteAddr());
        logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName()+ "." + joinPoint.getSignature().getName());
        //这里经过处理,就可以获得参数名字与值一一对应
        logger.info("ARGS-JSON : " + params);
        //这个就是纯粹拿到参数,值需要自己匹配
        logger.info("ARGS : "+ Arrays.toString(joinPoint.getArgs()));
        logger.info("before end:-------------------------------");
    }
    @AfterReturning(value = "executeController()",returning = "rtv")
    public void after(JoinPoint joinPoint, Object rtv){
        logger.info("before return+++++++++++++++++++++++++++");
        logger.info("responseBody:"+JSON.toJSONString(rtv,SerializerFeature.WriteMapNullValue));
        logger.info("end return++++++++++++++++++++++++++++++++");
    }
}

以上 只要切点修改就可以直接使用,可以更直观的看日志数据,对了,没加异常处理切面。

利用aop环绕进行切面调用数据入库处理

直接上代码:

ControllerAspect 控制器切面

public class ControllerAspect {
    @Autowired
    AsyncTask asyncTask;
    private Logger logger = LoggerFactory.getLogger(ControllerAspect.class);
    // 定义切点Pointcut
    @Pointcut( "execution(* com.test.web..*.*(..))")
    public void executeController() {
    }
    @Before("executeController()")
    public void before(JoinPoint joinPoint) throws Exception{
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = servletRequestAttributes.getRequest();
        logger.info("before start:-------------------------------");
        logger.info("URL : " + request.getRequestURL().toString());
        logger.info("HTTP_METHOD : " + request.getMethod());
        logger.info("IP : " + request.getRemoteAddr());
        logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName()+ "." + joinPoint.getSignature().getName());
        logger.info("ARGS-JSON : " + CommonAop.getAopParam(joinPoint));
        logger.info("ARGS : "+ Arrays.toString(joinPoint.getArgs()));
        logger.info("before end:-------------------------------");
    }
    /**
     * 环绕切面数据入库
    */
    @Around("executeController()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
    //注意!!!!!这里pjp.proceed()这个方法得到返回值,不能多次调用,多次调用整个接口会运行多次,类似hasnext()和next()
        Object object = pjp.proceed();
        try {
            //环绕通知处理方法
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
            HttpServletRequest request = servletRequestAttributes.getRequest();
            //异步入库,这里调用一个异步类方法,进行数据入库
            //CommonAop.getAopParam(pjp)这个方法,参考上面获取参数ProceedingJoinPoint和JoinPoint是类似的
            asyncTask.doInsertLog(request.getRequestURL().toString(),request.getRemoteAddr(), JSON.toJSONString(CommonAop.getAopParam(pjp),SerializerFeature.WriteMapNullValue),request.getMethod(),JSON.toJSONString(object,SerializerFeature.WriteMapNullValue));
        }catch (Exception e){
            e.printStackTrace();
        }
        return object;
    }
    @AfterReturning(value = "executeController()",returning = "rtv")
    public void after(JoinPoint joinPoint, Object rtv){
        logger.info("before return+++++++++++++++++++++++++++");
        logger.info("responseBody:"+JSON.toJSONString(rtv,SerializerFeature.WriteMapNullValue));
        logger.info("end return++++++++++++++++++++++++++++++++");
    }
}

CommonAop

public class CommonAop {
    public static Map<String,Object> getAopParam(JoinPoint joinPoint){
        //这一步获取到的方法有可能是代理方法也有可能是真实方法
        Method m = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //判断代理对象本身是否是连接点所在的目标对象,不是的话就要通过反射重新获取真实方法
        if (joinPoint.getThis().getClass() != joinPoint.getTarget().getClass()) {
            m = ReflectUtil.getMethod(joinPoint.getTarget().getClass(), m.getName(), m.getParameterTypes());
        }
        //获取连接点方法运行时的入参列表
        Object[] args = joinPoint.getArgs();
        return CommonAop.getParam(m,args);
    }
    public static Map<String,Object> getParam(Method m,Object[] args){
        //通过真实方法获取该方法的参数名称
        LocalVariableTableParameterNameDiscoverer paramNames = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = paramNames.getParameterNames(m);
        //将参数名称与入参值一一对应起来
        Map<String, Object> params = new HashMap<>();
        if (!TextUtil.isEmpty(parameterNames)){
            for (int i = 0; i < parameterNames.length; i++) {
                if (TextUtil.isEmpty(args[i])){
                    continue;
                }
                //通过所在类转换,获取值,包含各种封装类都可以
                ObjectMapper objectMapper = new ObjectMapper();
                objectMapper.convertValue(args[i],args[i].getClass());
                params.put(parameterNames[i], JSON.toJSON(objectMapper.convertValue(args[i],args[i].getClass())));
            }
        }
        return params;
    }
}


大家都在看: