自定义注解+AOP,优雅的打印方法接受和返回的参数内容

Posted 默辨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义注解+AOP,优雅的打印方法接受和返回的参数内容相关的知识,希望对你有一定的参考价值。



写在前面

经历了近两个月的时间,我终于完成了一个项目点十多个接口的开发到现场SIT。在这个过程中我深刻的意识到了添加日志的重要性(以前的自己从来不会想着添加日志,这次是真的给我上了一课)。

举一个授信占用接口的例子。在我们的系统中,前台对一笔交易进行提交审批的时候,最终会通过一个接口发送对应的数据给与之对应的信贷系统(两个系统的交互,其实还有涉及中转平台)。在调用授信接口前,我统会对那一笔业务进行相应的授信数据处理(业务流程也十分复杂),最终会把接口需要的参数(对方信贷系统需要的参数)添加到指定的对象中,然后接口再组装成指定格式的报文数据,发送给对方。

在这个过程中有一个问题,我如何能够确保我接受到的参数是正确的?即当我按照对方信贷系统需要的参数去对象中get值得时候,拿到了null,我如何去定位是因为前台授信处理后给我传的对象中的值为null(授信后没有传),还是我拿到值以后,由于我进行了相关的逻辑操作导致变成了null?



授信开发人员往往会在调用相关接口的地方,将发送给的对象打印到日志中,以便于日中定位是他们传值的问题还是接口这边的逻辑问题。

只能说这个解决办法是,授信功能的开发人员定位问题的方法,但现场出现了生产问题,第一个找的人却往往是接口的开发人员(我就被找了几次),此时我由于不了解授信相关的业务逻辑,更不可能知道他们会在什么地方打印传给接口的对象,所以我需要自己去对接受到的参数对象进行打印。

可这人呀,总是会忘记,只有每次出现了问题,我才会想到,为什么我最开始没有在这个位置打印一下结果呢?



总结一句话:我要打印方法传入的参数的内容



我能想到的打印方式有三种,有其他更好的方法,欢迎评论给出

第一种打印方式

在方法开始的位置,直接调用相关的log.info(xxx)方法

1、定义一个logger变量

private static Logger logger = LoggerFactory.getLogger(xxx.class);

2、指定位置打印参数

logger.info("xxxx");




第二种打印方式

直接抽取为一个公共方法

1、将传入当前类这个步骤放到工具类中

public static void logInfo(Class<?> clazz,Throwable e)
    LoggerFactory.getLogger(clazz).info(e.getMessage(),e);

2、每个方法内直接调用

LogUtils.logInfo(this.getClass(),"xxxx");




第三种打印方式

有感于前面的方式太麻烦,且不好看,我想到的一个解决方法。使用时仅需要在类上添加一个@Log注解即可,但是该注解仅能打印接受和返回的对象,对于业务中的日志打印目前是没有办法完成,解析对象是利用的Gson(我甚至想过使用一个模板方法,对应的解析过程给一个扩展接口,自己想修改就重写对应的方法,但想想算了,后期有需要再说)

目前该注解的功能为:

  1. 打印方法接受到的对象的值,使用时只需要添加对应的前缀字符串即可定位日志记录
  2. 可以选择是否打印返回对象

我使用了一个try catch包裹我的代码,并且catch中并未抛出异常,仅仅是添加了一个error的日志记录。原因是因为我怕接受到的参数解析会出现异常,以至于影响了原本正常的业务流程

1、自定义一个Log注解

@Target(ElementType.PARAMETER, ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log 
	// 日志前的标记
	String[] logPreArr();

	// 是否需要打印返回对象
	boolean isNeedReturn() default false;

2、利用Spring的AOP,编写相关的切入方法

@Aspect
@Component
public class LogAspect 

	// 配置织入点
	@Pointcut("@annotation(pers.mobian.logannotation.annotation.Log)")
	public void logPointCut() 
	

	@Before("logPointCut() ")
	public void beforeLog(JoinPoint point) throws Exception 
		// 1、目标类
		Class<?> aClass = point.getTarget().getClass();
		Logger log = LoggerFactory.getLogger(aClass);
		try 
			// 2、目标方法
			Log logAnno = getAnnotationLogByPoint(point);
			if (logAnno != null) 
				// 3、打印目标参数
				Object[] args = point.getArgs();
				Gson gson = new Gson();
				String[] value = logAnno.logPreArr();
				if (value.length == 1) 
					for (Object arg : args) 
						log.info(point.getSignature().getName() + "方法:" + value[0] + ":" + gson.toJson(arg));
					
				 else if (value.length == args.length) 
					for (int i = 0; i < args.length; i++) 
						log.info(point.getSignature().getName() + "方法:" + value[i] + ":" + gson.toJson(args[i]));
					
				 else 
					log.info(point.getSignature().getName() + "方法:" + "参数类型与Log注解指定个数不匹配" + gson.toJson(args));
				
			
		 catch (Exception e) 
			log.error(point.getSignature().getName() + "方法:Log注解解析出现异常:" + e.getMessage());
		
	

	@AfterReturning(pointcut = "logPointCut() ", returning = "jsonResult")
	public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) throws Exception 
		Class<?> aClass = joinPoint.getTarget().getClass();
		Logger log = LoggerFactory.getLogger(aClass);
		try 
			Log logAnno = getAnnotationLogByPoint(joinPoint);
			if (logAnno != null) 
				boolean needReturn = logAnno.isNeedReturn();
				if (needReturn) 
					log.info(joinPoint.getSignature().getName() + "方法:" + "返回结果:" + jsonResult);
				
			
		 catch (Exception e) 
			log.error(joinPoint.getSignature().getName() + "方法:Log注解解析出现异常:" + e.getMessage());
		
	
    
	private Log getAnnotationLogByPoint(JoinPoint joinPoint) throws Exception 
		Signature signature = joinPoint.getSignature();
		MethodSignature methodSignature = (MethodSignature) signature;
		Method method = methodSignature.getMethod();
		if (method != null) 
			return method.getAnnotation(Log.class);
		
		return null;
	

3、编写的测试方法

@Log(logPreArr = "测试方法4",isNeedReturn = true)
public Map<String, Object> show4(Teacher student) 
    Map<String, Object>  dataMap = new HashMap<>();
    dataMap.put("第一个",1);
    dataMap.put("第二个","2");
    dataMap.put("第三个",true);
    dataMap.put("第四个",student);

    Map<String, Object>  returnMap = new HashMap<>();
    returnMap.put("第一个",1);
    returnMap.put("第二个","2");
    returnMap.put("第三个",true);
    returnMap.put("第四个",student);
    returnMap.put("第五个",dataMap);
    return returnMap;


@Log(logPreArr = "接受到的老师实体类", "接收到的学生实体类")
public String show5(Teacher teacher, Student student) 
    return student.toString();

打印结果:

以上是关于自定义注解+AOP,优雅的打印方法接受和返回的参数内容的主要内容,如果未能解决你的问题,请参考以下文章

自定义注解+AOP记录访问日志

自定义注解结合SpringAop实现权限,参数校验,日志等等功能

AOP中获取自定义注解的参数值

通过AOP实现全局日志打印

AOP + 注解 实现通用的接口参数校验

自定义注解并封装成jar包,实现一般方法和Controller类的入参出参和执行时间的日志打印