背景
最近手头一个项目进入转测阶段,测试组长提出要求把所有http请求的入参以及相应的json报文详细输出到日志文件,为了方便测试组小伙伴对每一个接口的验证。虽说不是功能性的需求,但是这样的要求确实有道理,让我无法抗拒啊。
步骤详解
自定义注解
- Logc.java
import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** *自定义日志记录注解 * * @author xubin * @version 1.0 * @taskId * @CreateDate 2019/4/12 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Logc { /** * Description: * * @author xubin * @taskId * @return */ String value() default "";}
使用AOP
- pom.xml 引入依赖
org.springframework.boot spring-boot-starter-aop ch.qos.logback logback-classic
- MainLogAspect.java
import javax.servlet.http.HttpServletRequest;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.Signature;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.AfterThrowing;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import lombok.extern.slf4j.Slf4j;/** *日志切面 * * @author xubin * @version 1.0 * @taskId * @CreateDate 2019/4/12 */@Aspect@Slf4j@Componentpublic class MainLogAspect { /** * Description: 自定义切点 * * @author xubin * @taskId */ @Pointcut("@annotation(com.minicore.salmon.aop.Logc)") public void pointCut() { } /** * Description: 前置通知-记录请求信息 * * @author xubin * @taskId * @param joinPoint */ @Before("pointCut()") public void doBeforeAdvice(JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = sra.getRequest(); // 获取目标方法的参数信息 Object[] obj = joinPoint.getArgs(); log.info("[MainLogAspect]-request url:{}, class: {}, method: {}, param: {}", request.getRequestURI(), signature .getDeclaringTypeName(), signature.getName(), obj[0].toString()); } /** * Description: 后置通知-记录返回信息 * * @author xubin * @taskId * @param joinPoint * @param result */ @AfterReturning(returning = "result", pointcut = "pointCut()") public void doAfterReturningAdvice(JoinPoint joinPoint, Object result) { Signature signature = joinPoint.getSignature(); ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = sra.getRequest(); log.info("[MainLogAspect]-response url:{}, class: {}, method: {}, param: {}", request.getRequestURI(), signature .getDeclaringTypeName(), signature.getName(), result.toString()); } /** * Description: 后置异常通知-记录返回出现异常 * * @author xubin * @taskId * @param joinPoint * @param exception */ @AfterThrowing(value = "pointCut()", throwing = "exception") public void doAfterThrowingAdvice(JoinPoint joinPoint, Throwable exception) { Signature signature = joinPoint.getSignature(); ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = sra.getRequest(); log.info("[MainLogAspect]-response exception url:{}, class: {}, method: {}", request.getRequestURI(), signature .getDeclaringTypeName(), signature.getName()); }}
使用示例
- 在web层需要输出日志的方法上加上注解@Logc
/** * Description: 分页查询设备列表-无层级 * * @author xubin * @taskId * @param queryDeviceParamReq 查询条件 * @param request HttpServletRequest * @return */ @Logc @PostMapping(value = "/queryDeviceListByPage") public String queryDeviceListByPage(@RequestBody QueryDeviceInfoReq queryDeviceParamReq, HttpServletRequest request) { try { ValidateUtil.validate(queryDeviceParamReq); } catch (Exception e) { log.error("[DeviceApi]-queryDeviceList fail, param : {}, exception : {}", queryDeviceParamReq.toString(), e.getMessage()); return JsonUtil.getErrorJson(WebConstant.PARAM_EXCEPTION); } DeviceInfoBO deviceInfoBO = new DeviceInfoBO(); BeanUtils.copyProperties(queryDeviceParamReq, deviceInfoBO); deviceInfoBO.setAppKey(request.getHeader(CommonConstant.APP_KEY)); PageInfopage = deviceInfoService.queryDeviceListByPage(deviceInfoBO, Optional.ofNullable(queryDeviceParamReq.getPageNum()).orElse(CommonConstant.DEFAULT_PAGE_NUM), Optional.ofNullable(queryDeviceParamReq.getPageSize()).orElse(CommonConstant.DEFAULT_PAGE_SIZE)); return JsonUtil.getSucc4data(Optional.ofNullable(page).orElse(new PageInfo ())); }
- 日志输出
2019-05-29 14:40:52.223 INFO http-nio-55001-exec-5 [com.minicore.salmon.aop.MainLogAspect:56]-[MainLogAspect]-request url:/salmon_device/deviceInfoApi/queryDeviceListByPage, class: com.minicore.salmon.rest.api.DeviceApi, method: queryDeviceListByPage, param: QueryDeviceInfoReq(pageNum=1, pageSize=20, deviceName=null, deviceCode=1559068230635, deviceType=null, deviceState=null, deviceMac=null, isOnline=null, isActivated=null)2019-05-29 14:40:52.263 DEBUG http-nio-55001-exec-5 [com.minicore.salmon.mapper.DeviceInfoEntityMapper.queryDeviceInfoList_COUNT:159]-==> Preparing: SELECT count(0) FROM t_device_info d LEFT JOIN t_device_state ds ON d.device_state = ds.id LEFT JOIN t_device_type dt ON d.device_type = dt.id WHERE d.is_delete = 0 AND d.device_code = ? 2019-05-29 14:40:52.264 DEBUG http-nio-55001-exec-5 [com.minicore.salmon.mapper.DeviceInfoEntityMapper.queryDeviceInfoList_COUNT:159]-==> Parameters: 1559068230635(String)2019-05-29 14:40:52.265 DEBUG http-nio-55001-exec-5 [com.minicore.salmon.mapper.DeviceInfoEntityMapper.queryDeviceInfoList_COUNT:159]-<== Total: 12019-05-29 14:40:52.266 DEBUG http-nio-55001-exec-5 [com.minicore.salmon.mapper.DeviceInfoEntityMapper.queryDeviceInfoList:159]-==> Preparing: SELECT d.id, d.app_key, d.device_name, d.device_code, d.device_desc, d.device_type, dt.type_name AS device_type_name, dt.type_code AS device_type_code, d.is_online, d.is_online_upd_time, d.is_activated, d.activate_time, d.device_state, d.version, ds.state_name AS device_state_name, ds.state_code AS device_state_code, d.device_ipv4, d.device_mac, d.p_id, d.is_delete, d.create_time, d.creator, d.creator_id, d.update_time, d.modifier, d.modifier_id FROM t_device_info d LEFT JOIN t_device_state ds ON d.device_state = ds.id LEFT JOIN t_device_type dt ON d.device_type = dt.id WHERE d.is_delete = 0 AND d.device_code = ? ORDER BY d.create_time DESC LIMIT 20 2019-05-29 14:40:52.267 DEBUG http-nio-55001-exec-5 [com.minicore.salmon.mapper.DeviceInfoEntityMapper.queryDeviceInfoList:159]-==> Parameters: 1559068230635(String)2019-05-29 14:40:52.270 DEBUG http-nio-55001-exec-5 [com.minicore.salmon.mapper.DeviceInfoEntityMapper.queryDeviceInfoList:159]-<== Total: 12019-05-29 14:40:52.272 INFO http-nio-55001-exec-5 [com.minicore.salmon.aop.MainLogAspect:74]-[MainLogAspect]-response url:/salmon_device/deviceInfoApi/queryDeviceListByPage, class: com.minicore.salmon.rest.api.DeviceApi, method: queryDeviceListByPage, param: {"resCode":1,"resData":{"pageNum":1,"pageSize":20,"rows":[{"activateTime":null,"appKey":"7bbf10d0bdca560edb30dabb1254964d","createTime":"2019-05-29 02:30:31","creator":"","creatorId":"","deviceCode":"1559068230635","deviceDesc":"","deviceIpv4":"","deviceMac":"","deviceName":"1559068230635","deviceState":"","deviceStateCode":"","deviceStateName":"","deviceType":"6188028fb7f24f90a91e4c6ccd06798f","deviceTypeCode":"box","deviceTypeName":"anfang-pad","id":"0e26a36fd614467d8c3d7c8e9e99402b","isActivated":"1","isDelete":"0","isOnline":"1","isOnlineUpdTime":1559068230676,"modifier":"","modifierId":"","pId":"-1","subDeviceList":[],"updateTime":"2019-05-29 02:30:30","version":""}],"total":1},"resMsg":[]}
上面的日志会输出控制台,如何输出到指定的日志文件,可以参考另一篇log4j2配置示例
总结
aop的灵活应用确实可以为我们省下很多重复性的工作,注解的使用让代码更加规范、简洁,这种代码动手撸一把真的可以让自己很放松???