文档章节

Annotation与SpEL实现系统记录操作日志

清源
 清源
发布于 2014/05/13 20:19
字数 1275
阅读 215
收藏 0

以往都在自己的博客站点上发文章,很少在公共站点上发博文。第一次在OSChina上发博文,真心感觉OSChina确实是国内程序员的风水宝地。这段时间,一心想写点技术文章却迟迟没能提笔。今天终于,打开音乐播放器,戴上耳机。享受着宁静的夜晚与指尖跃起的文字。甚至于还想着,什么时候才能有合适的机会回到山东,守在爸妈身边。好了,废话不多说了,开始记录正文。

需求:

系统中的一个模块属于关键区,它所有的操作主要针对修改与删除是要求记录下日志来的。而这个记录的日志并不是像我们把它们打印在log文件里,而是需要标准的记录到数据库中。以便于后来专门日志操作模块的查询。

思考:

当然针对这个需求并不困难,在Service层的每个方法逻辑里添加一段代码专门用来插入到对应的log表中即可。可是,那太丑陋了!也不宜于维护。要是能用Annotation标注在方法头,然后凡是做标注的方法,当它们执行的时候,会将结果和相关信息插入到DB中就好了。针对这个YY需求,我做了如下的设计:

1.建立一个AnnotationMyLogger),内容用SpringSpEL表达式模版。

2..增加一个AOP方法级的拦截器,切面就在Service层。拦截所标注MyLoggerService方法。

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内容就是SpringSpEL表达式模版语句了,具体语法就不细讲了,我也没有太细的去研究。其中#arg0#arg1代表这方法的第12个参数,当然这个名字是我定义的,方便我调用而已,并不是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

© 著作权归作者所有

清源

清源

粉丝 5
博文 100
码字总数 1275
作品 0
北京
其他
私信 提问
分布式跟踪系统-cicada

https://github.com/Yirendai/cicada/blob/master/cicada-docs/cicadadesign.md 背景与目标 面对日趋复杂的分布式系统,如服务框架、消息中间件、缓存、数据层等,我司在业务性能瓶颈定位、故...

tantexian
2016/11/02
968
1
Spring Cache 介绍

Spring Cache 是实际工作中非常常用的一种提高性能的方法, 我们会在许多场景下来使用缓存。 本文通过一个简单的例子进行展开,通过对比我们原来的自定义缓存和 spring 的基于注释的 cache 配...

嘻哈开发者
2016/09/11
61
0
Spring Bean定义中表达式语言的支持

SpEL(Spring Expression Language,Spring表达式语言)的一个重要的作用就是扩展Spring容器的功能,允许在Bean定义中使用SpEL。XML配置文件和Annotation中都可以使用SpEL。在XML和Annotatio...

摆渡者
2014/03/09
342
0
aop + annotation 实现统一日志记录

aop + annotation 实现统一日志记录 在开发中,我们可能需要记录异常日志。由于异常比较分散,每个 service 方法都可能发生异常,如果我们都去做处理,会出现很多重复编码,也不好维护。这种...

长安一梦
2018/07/22
62
0
Spring Cache的介绍

Spring Cache 缓存是实际工作中非常常用的一种提高性能的方法, 我们会在许多场景下来使用缓存。 本文通过一个简单的例子进行展开,通过对比我们原来的自定义缓存和 spring 的基于注释的 cach...

泳泳啊泳泳
2018/01/07
0
0

没有更多内容

加载失败,请刷新页面

加载更多

java通过ServerSocket与Socket实现通信

首先说一下ServerSocket与Socket. 1.ServerSocket ServerSocket是用来监听客户端Socket连接的类,如果没有连接会一直处于等待状态. ServetSocket有三个构造方法: (1) ServerSocket(int port);...

Blueeeeeee
26分钟前
4
0
用 Sphinx 搭建博客时,如何自定义插件?

之前有不少同学看过我的个人博客(http://python-online.cn),也根据我写的教程完成了自己个人站点的搭建。 点此:使用 Python 30分钟 教你快速搭建一个博客 为防有的同学不清楚 Sphinx ,这...

王炳明
昨天
4
0
黑客之道-40本书籍助你快速入门黑客技术免费下载

场景 黑客是一个中文词语,皆源自英文hacker,随着灰鸽子的出现,灰鸽子成为了很多假借黑客名义控制他人电脑的黑客技术,于是出现了“骇客”与"黑客"分家。2012年电影频道节目中心出品的电影...

badaoliumang
昨天
13
0
很遗憾,没有一篇文章能讲清楚线程的生命周期!

(手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本。 简介 大家都知道线程是有生命周期,但是彤哥可以认真负责地告诉你网上几乎没有一篇文章讲得是完全正确的。 ...

彤哥读源码
昨天
13
0
jquery--DOM操作基础

本文转载于:专业的前端网站➭jquery--DOM操作基础 元素的访问 元素属性操作 获取:attr(name);$("#my").attr("src"); 设置:attr(name,value);$("#myImg").attr("src","images/1.jpg"); ......

前端老手
昨天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部