【自定义注解使用】增加service层方法访问日志
目錄
- 背景
- 自定義注解
- 動態(tài)代理/AOP
- 使用
- 總結(jié)
本文涉及到的技術(shù)有:SLF4J,JDK動態(tài)代理,AspectJ,Java自定義注解,Java反射。
背景
最近工作中發(fā)現(xiàn)為了方便排查服務(wù)器日志,公司的service層都會有方法的訪問日志,類似----------getXXX(String name=xujian,User user = {"name":"xujian","age":20})----------START,----------getXXX(String name=xujian,User user = {"name":"xujian","age":20})----------END,其中包括了方法參數(shù)的值(也可能有響應(yīng)的數(shù)據(jù))。
但是現(xiàn)實(shí)情況讓是最終打印出來的東西各有不同,有的短橫線的個(gè)數(shù)不一樣,有的參數(shù)的值的形式不一樣(這大部分存在于一個(gè)對象參數(shù),如果對象重寫了toString方法打印具體對象的屬性值的話還好,如果沒有重寫那只會打印對象所屬的類的全路徑+hashcode),甚至有的直接忘了給log的占位符填充值,導(dǎo)致日志里面并沒有參數(shù)信息。
自定義注解
考慮使用自定義注解,通過給方法增加注解來自動打印收尾日志。
/*** 用于自動織入方法訪問日志,加上該注解即可自動添加方法首尾日志* @author xujian*/ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface AccessLog {String[] paramsName() default {};//方法的形參列表 }動態(tài)代理/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();//獲取當(dāng)前調(diào)用的方法名Class<?>[] parameterTypes = ((MethodSignature) pjp.getSignature()).getMethod().getParameterTypes();//獲取參數(shù)類型列表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();//獲取參數(shù)值列表/**拼裝日志內(nèi)容*/for (int i=0;i<args.length;i++) {Object o = args[i];if (o == null) {o = "null";}Object paramValue = o;//判斷是否是基本類型,非基本類型的話轉(zhuǎn)為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;} }這里需要說明一下,自定義注解里面有一個(gè)屬性是“形參列表”,本來不想用這個(gè)屬性,而是通過javassist字節(jié)碼技術(shù)自動獲取(代碼如下),但是實(shí)測發(fā)現(xiàn)不是很完美,所以就棄用了。
/*** 字節(jié)碼技術(shù)來獲取方法形參列表,結(jié)果不太完美(已廢棄)* @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) }總結(jié)
雖然這個(gè)規(guī)避了日志格式混亂和忘記打印日志的問題,但是還有很多地方可以繼續(xù)完善和優(yōu)化。如
雖然不是很復(fù)雜,但也不失為一個(gè)AOP+自定義注解的完整實(shí)踐。
相關(guān)代碼github:自定義注解增加service層方法訪問日志
轉(zhuǎn)載于:https://www.cnblogs.com/xuxiaojian/p/11453724.html
總結(jié)
以上是生活随笔為你收集整理的【自定义注解使用】增加service层方法访问日志的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: issubclass和isinstanc
- 下一篇: 0x10基本数据结构