以往都在自己的博客站点上发文章,很少在公共站点上发博文。第一次在OSChina上发博文,真心感觉OSChina确实是国内程序员的风水宝地。这段时间,一心想写点技术文章却迟迟没能提笔。今天终于,打开音乐播放器,戴上耳机。享受着宁静的夜晚与指尖跃起的文字。甚至于还想着,什么时候才能有合适的机会回到山东,守在爸妈身边。好了,废话不多说了,开始记录正文。
需求:
系统中的一个模块属于关键区,它所有的操作主要针对修改与删除是要求记录下日志来的。而这个记录的日志并不是像我们把它们打印在log文件里,而是需要标准的记录到数据库中。以便于后来专门日志操作模块的查询。
思考:
当然针对这个需求并不困难,在Service层的每个方法逻辑里添加一段代码专门用来插入到对应的log表中即可。可是,那太丑陋了!也不宜于维护。要是能用Annotation标注在方法头,然后凡是做标注的方法,当它们执行的时候,会将结果和相关信息插入到DB中就好了。针对这个YY需求,我做了如下的设计:
1.建立一个Annotation(MyLogger),内容用Spring的SpEL表达式模版。
2..增加一个AOP方法级的拦截器,切面就在Service层。拦截所标注MyLogger的Service方法。
3.拦截到方法时,根据SpEL表达式模版生成记录内容,记录到DB中。
方案:
MyLogger.java
/** * 记录日志标注 * 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 方法拦截器
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 */ 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一下。
看了这些代码,可能你已经懵了,说实话确实有点乱,那我们先看看具体怎么应用呢?
举个简单的例子:
@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就是来解决这个问题的。
@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 ^_^
原文地址:http://www.chenzhiguo.cn/archives/log_with_annotation_and_spel