目录

  • 背景
  • 自定义注解
  • 动态代理/AOP
  • 使用
  • 总结

本文涉及到的技术有:SLF4J,JDK动态代理,AspectJ,Java自定义注解,Java反射。

背景

最近工作中发现为了方便排查服务器日志,公司的service层都会有方法的访问日志,类似----------getXXX(String name=xujian,User user = {"name":"xujian","age":20})----------START,----------getXXX(String name=xujian,User user = {"name":"xujian","age":20})----------END,其中包括了方法参数的值(也可能有响应的数据)。

但是现实情况让是最终打印出来的东西各有不同,有的短横线的个数不一样,有的参数的值的形式不一样(这大部分存在于一个对象参数,如果对象重写了toString方法打印具体对象的属性值的话还好,如果没有重写那只会打印对象所属的类的全路径+hashcode),甚至有的直接忘了给log的占位符填充值,导致日志里面并没有参数信息。

自定义注解

考虑使用自定义注解,通过给方法增加注解来自动打印收尾日志。

/*** 用于自动织入方法访问日志,加上该注解即可自动添加方法首尾日志* @author xujian*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AccessLog {String[] paramsName() default {};//方法的形参列表
}

动态代理/AOP

有了自定义注解,就该用AspectJ让注解起作用,将日志打印自动织入代码。

/*** 访问日志织入,自动添加方法首尾日志** @author xujian* @create 2019-01-11 10:05**/
@Aspect
@Component
public class AccessLogAop {@Pointcut(value = "@annotation(com.xxx.AccessLog)")public void pointcut() {}@Around(value = "pointcut()")public Object around(ProceedingJoinPoint pjp) throws Throwable {Class targetClass = pjp.getTarget().getClass();//获取被代理的原始类String methodName = pjp.getSignature().getName();//获取当前调用的方法名Class<?>[] parameterTypes = ((MethodSignature) pjp.getSignature()).getMethod().getParameterTypes();//获取参数类型列表Method targetMethod = targetClass.getDeclaredMethod(methodName,parameterTypes);AccessLog accessLog = targetMethod.getAnnotation(AccessLog.class);String[] paramsName = accessLog.paramsName();//获取指定的形参列表Field field = targetClass.getDeclaredField("logger");field.setAccessible(true);FLogger logger = (FLogger) field.get(targetClass);//获取service声明的logger属性String targetMethodName = pjp.getSignature().getName();StringBuilder sb = new StringBuilder();Object[] args = pjp.getArgs();//获取参数值列表/**拼装日志内容*/for (int i=0;i<args.length;i++) {Object o = args[i];if (o == null) {o = "null";}Object paramValue = o;//判断是否是基本类型,非基本类型的话转为json字符串if (!o.getClass().isPrimitive() && !"java.lang.String".equals(o.getClass().getName())) {paramValue = JSON.toJSONString(o);}sb.append(o.getClass().getSimpleName()+" "+paramsName[i]+" = "+paramValue);sb.append(",");}String beginLog = "----------"+targetMethodName+"("+sb.substring(0,sb.length()-1)+")----------START";logger.info(beginLog);Object result = pjp.proceed();String endLog = "----------"+targetMethodName+"("+sb.substring(0,sb.length()-1)+")----------END";logger.info(endLog);return result;}
}

这里需要说明一下,自定义注解里面有一个属性是“形参列表”,本来不想用这个属性,而是通过javassist字节码技术自动获取(代码如下),但是实测发现不是很完美,所以就弃用了。

/*** 字节码技术来获取方法形参列表,结果不太完美(已废弃)* @param cls* @param clazzName* @param methodName* @return* @throws NotFoundException*/@Deprecatedprivate List<String> getParamNames(Class cls, String clazzName, String methodName) throws NotFoundException {List<String> result=new ArrayList<>();ClassPool pool = ClassPool.getDefault();ClassClassPath classPath = new ClassClassPath(cls);pool.insertClassPath(classPath);CtClass cc = pool.get(clazzName);CtMethod cm = cc.getDeclaredMethod(methodName);MethodInfo methodInfo = cm.getMethodInfo();CodeAttribute codeAttribute = methodInfo.getCodeAttribute();LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);if (attr == null) {// exception}int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;CtClass[] paramsType = cm.getParameterTypes();for (int i = 0; i < paramsType.length; i++){result.add(attr.variableName(i + pos));}return result;}

使用

使用很简单,你只需要在你的service类里面声明SLF4J下的Logger logger对象,然后在你的service方法里面加上自定义的注解即可,如下:

@Service
public class TemplateMappingServiceImpl implements TemplateMappingService {private static final Logger logger = LoggerFactory.getLogger(TemplateMappingServiceImpl.class);@Override@AccessLog(paramsName = {"mainContractId","signerId","isAgent","templateVar","calculatedVars"})public Map<String,String> getTemplateMapping(Integer mainContractId, Integer signerId, boolean isAgent, Set<String> templateVar, Map<String,String> calculatedVars)
}

总结

虽然这个规避了日志格式混乱和忘记打印日志的问题,但是还有很多地方可以继续完善和优化。如

  1. 可以增加异常日志的打印
  2. 由于使用了代理,所以在本类里面直接进行方法调用,其实最终调用的是this对象(被代理的原始对象)的方法,并没有增强的相关代码,所以不能打印出日志,针对这种情况可以使用代理类的方法调用。

虽然不是很复杂,但也不失为一个AOP+自定义注解的完整实践。


相关代码github:自定义注解增加service层方法访问日志

转载于:https://www.cnblogs.com/xuxiaojian/p/11453724.html

【自定义注解使用】增加service层方法访问日志相关推荐

  1. SpringBoot在自定义类中调用service层等Spring其他层

    解决方案: 1.上代码@Component public class ServerHandler extends IoHandlerAdapter {@Autowiredprotected Healt ...

  2. 自定义工具类Static方法调用业务中Service层方法

    文章目录 前言 一.如何实现? 二.Util工具类实现 代码相关注解 总结 前言 今天需要验证OpenId,来看是否为系统用户,直接封装一个工具类,遇到了调用业务层service的方法.记一次学习笔记 ...

  3. aop注解配置切点 spring_springboot aop 自定义注解方式实现一套完善的日志记录

    一:功能简介 本文主要记录如何使用aop切面的方式来实现日志记录功能. 主要记录的信息有: 操作人,方法名,参数,运行时间,操作类型(增删改查),详细描述,返回值. 二:项目结构图 如果想学习Java ...

  4. SpringAOP拦截Controller,Service实现日志管理(自定义注解的方式)

    2019独角兽企业重金招聘Python工程师标准>>> 首先我们为什么需要做日志管理,在现实的上线中我们经常会遇到系统出现异常或者问题.这个时候就马上打开CRT或者SSH连上服务器拿 ...

  5. 自定义注解实现拦截sql,并在sql中增加相应的条件

    功能介绍 先说下这期实现的这个功能. 其实看起来很简单的一个功能. 原sql:select * from users WHERE username = ? 增加自定义注解后: 变成这样: SELECT ...

  6. 为什么事务普遍加在service层

    1.对比事务加在service层与dao层 结合事务的四大特性(ACID),即可很好的解释为什么加在service层.首先,如果事务注解@Transactional加在dao层,那么只要与数据库做增删 ...

  7. java自定义注解实现日志功能

    一.spring aop的通知类型 1.前置通知(@Before):在连接点前执行,不会影响连接点的执行,除非抛异常: 2.后置通知(@AfterReturning):在连接点正常执行完成后执行,若连 ...

  8. spring事务到底用于service层还是dao层

    Spring事务为业务逻辑进行事务管理,保证业务逻辑上数据的原子性. 事务得根据项目性质来细分:事务可以设置到三个层面(dao层.service层和web层). 第一:web层事务,这一般是针对那些安 ...

  9. Spring Boot自定义注解+AOP实现日志记录

    访问Controller打印的日志效果如下: *********************************Request请求*********************************** ...

最新文章

  1. Matlab与线性代数--广义逆矩阵
  2. 清华 CVer 对自监督学习的一些思考
  3. Java学习笔记26
  4. 24点——判断4个数能否经过运算使得结果为24
  5. pycharm里怎么关闭一个项目_【周末分享】一个完整的项目复盘到底要怎么做?...
  6. el-calendar 怎么设置上一年和下一年_为什么香港硕士一年的含金量那么高?
  7. Linux pwd命令:显示当前路径
  8. ASP.NET MVC 3和Razor中的@helper 语法
  9. 如何扩展计算机c盘的控件,电脑C盘空间不足,怎么把c盘空间可以扩大
  10. android自动生成cardview,CardView
  11. vxe-table checkbox range 实现复选框鼠标快速选择
  12. 无法执行磁盘检查,因为windows无法访问该磁盘的一种解决思路
  13. mysql查找喜欢的女孩_看着自己喜欢的女生喜欢上别人是什么感觉?
  14. PAT题目详解-----愿天下有情人都是失散多年的兄妹
  15. 程序员专业常用英语词汇
  16. 全球与中国聚 (3,4-亚乙基二氧噻吩) (PEDOT)市场深度研究分析报告
  17. C++中的全局变量声明和定义
  18. 快排为什么一定要从右边开始?
  19. Adobe Premiere Pro快速入门教程
  20. 200行JS代码为你的网页挂上红灯笼

热门文章

  1. JavaScript中的true和false
  2. 结对项目-四则运算 “软件”之升级版
  3. 安卓手机如何解压缩文件
  4. 一个类怎样引用另外一个类的成员变量或方法
  5. C语言(九)C语言概述
  6. 算法学习:AC自动机
  7. 露雨资源库(第一个.net2.0软件)二
  8. 列表表格以及媒体元素
  9. ExecutorService对象的shutdown()和shutdownNow()的区别
  10. 常用SQL语句和HQL语句写法