基于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环绕进行切面调用数据入库处理
直接上代码
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;
}
}