先说几句废话吧。最近工作比较混乱,一遍研究着Hadoop,一遍搞着另外一个系统的开发。这段时间,一心想写点技术文章却迟迟没能提笔。今天终于,打开音乐播放器,戴上耳机。享受着宁静的夜晚与指尖跃起的文字。甚至于还想着,什么时候才能有合适的机会回到山东,守在爸妈身边。好了,废话不多说了,开始记录正文。
需求:
系统中的一个模块属于关键区,它所有的操作主要针对修改与删除是要求记录下日志来的。而这个记录的日志并不是像我们把它们打印在log文件里,而是需要标准的记录到数据库中。以便于后来专门日志操作模块的查询。
思考:
当然针对这个需求并不困难,在Service层的每个方法逻辑里添加一段代码专门用来插入到对应的log表中即可。可是,那太丑陋了!也不宜于维护。要是能用Annotation标注在方法头,然后凡是做标注的方法,当它们执行的时候,会将结果和相关信息插入到DB中就好了。针对这个YY需求,我做了如下的设计:
- 建立一个Annotation(MyLogger),内容用Spring的SpEL表达式模版。
- .增加一个AOP方法级的拦截器,切面就在Service层。拦截所标注MyLogger的Service方法。
- 拦截到方法时,根据SpEL表达式模版生成记录内容,记录到DB中。
方案:
MyLogger.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
/** * 记录日志标注 * Created by chenzhiguo */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyLogger { // 功能分类 public enum serviceType{ /** * 未知类型 */ Unknown, /** * 类型一 */ Type1 } /** * 任务执行类型 * @return */ serviceType type() default serviceType.Unknown; /** * SpEL表达式模板 * @return */ String logModel() default ""; /** * 传入执行类 * @return */ Class[] argPlus() default {}; /** * 是否需要返回某个值到执行方法内 * @return */ boolean needLogId() default false; } |
LoggerInterceptor.java 方法拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.expression.EvaluationContext; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Date; /** * 操作日志记录拦截器 * Created by chenzhiguo on 2014/3/25. */ public class LoggerInterceptor implements MethodInterceptor{ @Autowired private UserService userService; @Autowired private HistoryLogRepository historyLogRepository; //操作日志表 @Autowired private ApplicationContext applicationContext; @Autowired private LoggerService loggerService; //操作日志表的Service private long logId; @Override public synchronized Object invoke(MethodInvocation invocation) { Method method = invocation.getMethod(); Annotation[] annotations = method.getDeclaredAnnotations(); boolean flag = false; int i = -1; Object obj = null; for(Annotation annotation : annotations){ i ++; if(MyLogger.class.isInstance(annotation)){ flag = true; break; } } if(flag) { // 拦截到指定Service try { logId = doLog(method, invocation.getArguments(), (MyLogger) annotations[i], false); obj = invocation.proceed(); } catch (Throwable throwable) { // 记录执行失败日志 logStateUpdate(logId, true); throw new ServiceException(throwable); } return obj; }else{ try { obj = invocation.proceed(); } catch (Throwable throwable) { throw new ServiceException(throwable); } } return obj; } /** * 拦截指定方法 记录工作日志 * @param method * @param annotation */ private Long doLog(Method method, Object[] argObjects, MyLogger annotation, boolean hasException) throws Exception { HistoryLog historyLog = new HistoryLog(); historyLog.setDate(new Date()); historyLog.setActor(userService.getCurrentUser()); historyLog.setType(annotation.type()); // 获取SpEL表达式模板 String content = annotation.logModel(); //method.getParameterTypes() ExpressionParser parser = new SpelExpressionParser(); EvaluationContext ec = new StandardEvaluationContext(); int i = 0; for(Object obj : argObjects){ ec.setVariable("arg"+(i++),obj); } int x = 0; //参数增强 for(Class clazz : annotation.argPlus()){ ec.setVariable("argPlus" + (x++), applicationContext.getBean(clazz)); } //将SpEL模版生成动态内容 content = parser.parseExpression(content).getValue(ec, String.class); historyLog.setContent(content); historyLog.setHasException(hasException); historyLogRepository.save(historyLog); if(annotation.needLogId()) { Class[] clazzs = method.getDeclaringClass().getInterfaces(); Class clazz = clazzs[0]; Object obj = applicationContext.getBean(clazz); // 对Service对象的logId属性进行赋值 clazz.getDeclaredMethod("setLogId",long.class).invoke(obj, historyLog.getId()); } return historyLog.getId(); } /** * 更新日志执行结果状态 * @param logId * @param hasException */ private void logStateUpdate(long logId, boolean hasException){ loggerService.updateState(logId, hasException); } } |
至于AOP的拦截器配置,我就不贴了。这块资料很多,可以随便Google一下。
看了这些代码,可能你已经懵了,说实话确实有点乱,那我们先看看具体怎么应用呢?
举个简单的例子:
1 2 |
@MyLogger(type = MyLogger.serviceType.Type1, logModel = "'XXX:'+#arg0+',XXX:'+#arg1") public void doSomething(String a, String b) {....} |
上面的logModel内容就是Spring的SpEL表达式模版语句了,具体语法就不细讲了,我也没有太细的去研究。其中#arg0,#arg1代表这方法的第1,2个参数,当然这个名字是我定义的,方便我调用而已,并不是SpEL的规范。这样在执行SpEL解析后,logModel的内容就变成了XXX:”变量a的值,XXX:变量b的值”。
你的疑问可能来了,要是我需要的变量不在参数里面怎么办?argPlus就是来解决这个问题的。
1 2 |
@MyLogger(type = MyLogger.serviceType.Type1, logModel = "'XXX:'+#arg0+',XXX:'+#arg1+’, XXX:’+#argPlus0.getSomething()+’", argPlus= ABC.class) public void doSomething(String a, String b) {....} |
其中ABC.class在拦截的时候会被Spring取出它的对象,然后放进SpEL解析器,执行它对应的方法后获取你想要的值。可能还会觉得有点不理解,那只能这样,如果你看到这个方案感觉不错,想在你的项目采用并且还没看明白的话,来信问吧。me[at]chenzhiguo.cn ^_^