在日常开发中经常需要在代码中加入一些记录用户操作日志的log语句,比如谁在什么时间做了什么操作,等等。

把这些对于开发人员开说无关痛痒的代码写死在业务方法中实在不是一件很舒服的事情,于是AOP应运而生。

Spring对AOP的支持有以下4种情况:

1.基于代理的AOP

2.@Aspectj

3.纯POJO

4.注入式Aspectj切面

前三种都是基于方法级的,最后一个可以精确到属性及构造器。

关于Spring对AOP的支持的详细内容,读者可以参考《Spring in Action (第二版)中文版》第四章。

我这里使用的是第三种,纯POJO的方式,这种方式仅能在spring2.0及以后的版本中使用。

ok,言归正传,还是来说一说方法级注解的日志配置方式吧,顾名思义,就是只需要在方法上增加一个注释就可以自动打印日志,所以首先需要创建一个注解,如下:

  1. package com.hqf.common.annotation;
  2. import java.lang.annotation.Documented;
  3. import java.lang.annotation.ElementType;
  4. import java.lang.annotation.Retention;
  5. import java.lang.annotation.RetentionPolicy;
  6. import java.lang.annotation.Target;
  7. @Target({ElementType.METHOD})
  8. @Retention(RetentionPolicy.RUNTIME)
  9. @Documented
  10. public @interface UserOperateLog {
  11. /**
  12. * 用户操作名称
  13. * @return 用户操作名称,默认为空串
  14. */
  15. String value() default "";
  16. /**
  17. * 用户操作类型,默认类型为0<br/>
  18. * 0 - 其他操作 <br/>
  19. * 1 - 查询 <br/>
  20. * 2 - 新增 <br/>
  21. * 3 - 修改 <br/>
  22. * 4 - 删除
  23. * @return 用户操作类型
  24. */
  25. int type() default 0;
  26. /**
  27. * 用户操作名称对应的key,可以通过该key值在属性文件中查找对应的value
  28. * @return key
  29. */
  30. String key() default "";
  31. }

这里只是抛砖引玉,读者可以根据需要建立自己的注解。

有了注解,之后就需要在方法被调用时能解析注解,这就用到了SpringAOP的通知,我这里使用MethodBeforeAdvice,就是在方 法被调用前执行。关于SpringAOP的通知的详细讨论读者可以参考《Spring in Action (第二版)中文版》第四章4.2.1

  1. package com.hqf.common.annotation;
  2. import java.io.FileInputStream;
  3. import java.io.IOException;
  4. import java.lang.reflect.Method;
  5. import java.util.Properties;
  6. import javax.annotation.PostConstruct;
  7. import javax.servlet.http.HttpSession;
  8. import org.apache.log4j.Logger;
  9. import org.springframework.aop.MethodBeforeAdvice;
  10. import org.springframework.core.io.Resource;
  11. import org.springframework.util.Assert;
  12. import org.springframework.util.StringUtils;
  13. public class UserOperateLogAdvisor implements MethodBeforeAdvice {
  14. private Logger logger;//日志句柄
  15. private String loggerName;//日志名称
  16. private Properties properties;//属性文件句柄
  17. /**
  18. * 描述 : <该方法用于初始化属性文件>. <br>
  19. *<p>
  20. 日志内容可以预先配置在配置文件中,在需要打印日志时从配置文件中找到对应的值。
  21. 这 里是做扩展使用,读者可以根据实际情况进行设 计
  22. * @param propertiesFilePath
  23. * @throws IOException
  24. */
  25. public void setPropertiesFilePath(Resource propertiesFilePath)
  26. throws IOException {
  27. if (properties == null)
  28. properties = new Properties();
  29. properties.load(new FileInputStream(propertiesFilePath.getFile()));
  30. }
  31. /*
  32. * (non-Javadoc)
  33. *
  34. * @see
  35. * org.springframework.aop.MethodBeforeAdvice#before(java.lang.reflect.Method
  36. * , java.lang.Object[], java.lang.Object)
  37. */
  38. public void before(Method method, Object[] args, Object target)
  39. throws Throwable {
  40. String username = "未知";
  41. for (Object object : args) {
  42. //这里只提供一种获得操作人的方式,既从HttpSession中获取,但这要求方法参数中包含HttpSession
  43. //这里只是抛砖引玉,读者可以根据实际情况进行设计
  44. if (object instanceof HttpSession) {
  45. username = ((HttpSession) object).getAttribute("username") == null ? "未知"
  46. : (String) ((HttpSession) object)
  47. .getAttribute("username");
  48. }
  49. }
  50. //判断方法是否注解了UserOperateLog
  51. UserOperateLog anno = method.getAnnotation(UserOperateLog.class);
  52. if (anno == null)
  53. return;
  54. String defaultMessage = anno.value();
  55. String methodName = target.getClass().getName() + "."
  56. + method.getName();
  57. String desc = this.handleDescription(anno.key(), StringUtils
  58. .hasText(defaultMessage) ? defaultMessage : methodName);
  59. //装配日志信息
  60. String logline = this.buildLogLine(username, anno.type(), desc);
  61. logger.info(logline);
  62. }
  63. /**
  64. * 构建日志行
  65. *
  66. * @param usrname
  67. *            用户名称
  68. * @param operateType
  69. *            操作类型
  70. * @param description
  71. *            操作描述
  72. * @return 日志行: username - operateType - description
  73. */
  74. protected String buildLogLine(String username, int operateType,
  75. String description) {
  76. StringBuilder sb = new StringBuilder();
  77. sb.append(username).append(" - ").append(operateType).append(" - ")
  78. .append(description);
  79. return sb.toString();
  80. }
  81. /**
  82. * 获取日志内容描述,可以从消息配置文件中找到对应的信息
  83. *
  84. * @param key
  85. *            日志内容key
  86. * @param defaultMessage
  87. *            默认的描述信息
  88. * @return 描述信息
  89. */
  90. protected String handleDescription(String key, String defaultMessage) {
  91. if (properties == null)
  92. return defaultMessage;
  93. if (!StringUtils.hasText(key))
  94. return defaultMessage;
  95. String message = properties.getProperty(key);
  96. if (!StringUtils.hasText(message))
  97. return defaultMessage;
  98. else
  99. return message;
  100. }
  101. @PostConstruct
  102. public void init() {
  103. Assert.notNull(loggerName);
  104. logger = Logger.getLogger(loggerName);
  105. }
  106. /**
  107. * @param loggerName
  108. *            the loggerName to set
  109. */
  110. public void setLoggerName(String loggerName) {
  111. this.loggerName = loggerName;
  112. }
  113. }

为了使通知起作用,需要在spring配置文件加入如下内容:

  1. <!-- 定义用户操作日志切入点和通知器 -->
  2. <aop:config proxy-target-class="true">
  3. <aop:pointcut id="operatePoint"
  4. expression="@annotation(com.hqf.common.annotation.UserOperateLog)" />
  5. <aop:advisor pointcut-ref="operatePoint" id="logAdvisor"
  6. advice-ref="userOperateLogAdvisor" />
  7. </aop:config>
  8. <!-- 定义日志文件写入位置,需要在log4j.properties中加入名称为 useroperatorlog的日志配置-->
  9. <bean id="userOperateLogAdvisor" class="com.hqf.common.annotation.UserOperateLogAdvisor"
  10. p:loggerName="useroperatorlog" p:propertiesFilePath="classpath:messages/messages.properties"/>

ok,配置完成,在使用时只需要在方法上加入@UserOperateLog

例如:

  1. @RequestMapping(value = "/demo/index2.do")
  2. @UserOperateLog(value="注解日志",type=1,key="annotation.log")
  3. public String handleIndex2(Model model,HttpSession session){
  4. return "demo/list";
  5. }

日志输出结果如下:

2010-03-04 16:01:45 useroperatorlog:68  INFO - hanqunfeng - 1 - 注解日志

注解里使用了key,这样就会从指定的配置文件中查找,如果查找到就替换掉默认的value值。

详细的代码请参考附件。

Spring2.5那些事之基于AOP的方法级注解式日志配置的更多相关文章

  1. 【Spring开发】—— AOP之方法级拦截

    前言: 前面介绍了Spring的核心模块以及相关的依赖注入等概念.这篇讲解一下spring的另一个重点,AOP面向切面编程. 说道AOP不得不提到几个概念: 切面:也就是我们自己的一些业务方法. 通知 ...

  2. net core天马行空系列:原生DI+AOP实现spring boot注解式编程

    写过spring boot之后,那种无处不在的注解让我非常喜欢,比如属性注入@autowire,配置值注入@value,声明式事物@Transactional等,都非常简洁优雅,那么我就在想,这些在n ...

  3. 基于AOP和ThreadLocal实现日志记录

    基于AOP和ThreadLocal实现的一个日志记录的例子 主要功能实现 : 在API每次被请求时,可以在整个方法调用链路中记录一条唯一的API请求日志,可以记录请求中绝大部分关键内容.并且可以自定义 ...

  4. Spring AOP实现注解式的Mybatis多数据源切换

    一.为什么要使用多数据源切换? 多数据源切换是为了满足什么业务场景?正常情况下,一个微服务或者说一个WEB项目,在使用Mybatis作为数据库链接和操作框架的情况下通常只需要构建一个系统库,在该系统库 ...

  5. 6、架构--Nginx虚拟主机(基于多ip、端口、域名方式)、日志配置、Nginx模块(访问控制模块、状态监控模块、访问链接控制模块)

    笔记 1.晨考 2.昨日问题 3.今日内容 1.Nginx虚拟主机 - 基于多IP的方式 - 基于多端口的方式 - 基于多域名的方式 2.日志配置 Nginx有非常灵活的日志记录模式,每个级别的配置可 ...

  6. Spring基于AOP的事务管理

                                  Spring基于AOP的事务管理 事务 事务是一系列动作,这一系列动作综合在一起组成一个完整的工作单元,如果有任何一个动作执行失败,那么事务 ...

  7. 基于AOP的MVC拦截异常让代码更优美

    与asp.net 打交道很多年,如今天微软的优秀框架越来越多,其中微软在基于mvc的思想架构,也推出了自己的一套asp.net mvc 框架,如果你亲身体验过它,会情不自禁的说‘漂亮’.回过头来,‘漂 ...

  8. Java实战之03Spring-05Spring中的事务控制(基于AOP)

    五.Spring中的事务控制(基于AOP) 1.Spring中事务有关的接口 1.1.明确: JavaEE体系进行分层开发,事务处理位于业务层,Spring提供了分层设计业务层的事务处理解决方案 1. ...

  9. 从壹开始前后端分离 40 || 完美基于AOP的接口性能分析

    旁白音:本文是不定时更新的.net core,当前主线任务的Nuxt+VueAdmin教程的 nuxt.js 之 tibug项目已上线,大家可以玩一玩:http://123.206.33.109:70 ...

随机推荐

  1. LOJ2362. 「NOIP2016」蚯蚓【单调队列】

    LINK 思路 良心来说这题还挺思维的 我没看题解也不知道要这样维护 把每次斩断的点分别放进两个队列里面 因为要维护增长,所以可以让新进队的节点来一个负增长? 是不是就好了? 然后很容易发现因为在原始 ...

  2. BZOJ1304 CQOI2009 叶子的染色 【树形DP】

    BZOJ1304 CQOI2009 叶子的染色 Description 给一棵m个结点的无根树,你可以选择一个度数大于1的结点作为根,然后给一些结点(根.内部结点和叶子均可)着以黑色或白色.你的着色方 ...

  3. 解决遇到Linux网络配置,从熟悉网络配置文件入手

    如果接触过Linux,网络配置是一个比较棘手的问题.但是Linux是文件为基础来构建的系统,包括我们windows中设备,Linux也视为文件.所以只要我们明白文件的作用.就能对Linux更加的熟悉, ...

  4. 51nod 1347 旋转字符串

    S[0...n-1]是一个长度为n的字符串,定义旋转函数Left(S)=S[1…n-1]+S[0].比如S=”abcd”,Left(S)=”bcda”.一个串是对串当且仅当这个串长度为偶数,前半段和后 ...

  5. css 2D动画

    2D动画: 通过 CSS3  transform转换,我们能够对元素进行移动.缩放.转动.拉长或拉伸. 2D移动:translate().使用translate()函数,你可以把元素从原来的位置移动. ...

  6. FastAdmin 线上部署流程 (2018-05-03 更新)

    FastAdmin 线上部署流程 首次部署 建立 git 环境. 建立 composer 环境. 建立 bower 环境. 将远程项目代码 git clone 到服务器上. 执行 composer i ...

  7. bzoj1503[NOI2004]郁闷的出纳员——Splay

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1503 好奇怪呀!为什么而TLE? 各种修改终于卡时过了.可是大家比我快多了呀?难道是因为自己 ...

  8. Unix网络编程 3.9 readline函数

    其实看APUE时就想试着写些简单的stdio函数了,但是一直没实践,看到这里时发现书上写得不完整,便敲代码试了下. 第1个readline速度非常慢原因在于每次读取字符都执行了系统调用read(),而 ...

  9. 1095 Cars on Campus

    题意:给出N量车的车牌号,进出的时间,进/出状态.然后给出若干个查询,要求计算在每一查询时刻校园内停着的汽车数量,最后输出这一天中停放时间最长的车辆(若车不止一辆,则按字典序输出)以及停放时间.注:查 ...

  10. ROS的ovpn设置

    转摘至www.80uncle.com 先下载Win下的openvpn客户端http://openvpn.se/download.html 我的证书是用这个客户端做的openvpn-2.0.9-gui- ...