在日常开发中经常需要在代码中加入一些记录用户操作日志的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. BZOJ2090: [Poi2010]Monotonicity 2【线段树优化DP】

    BZOJ2090: [Poi2010]Monotonicity 2[线段树优化DP] Description 给出N个正整数a[1..N],再给出K个关系符号(>.<或=)s[1..k]. ...

  2. Java Garbage Collection

    在C/C++中,需要自己负责object的creation 和 destruction. 如果忘记了destruction, 就容易出现OutOfMemoryErrors. Java中会有GC直接处理 ...

  3. mock的使名用一(生成随机数据)

    Mock.Random 是一个工具类,用于生成各种随机数据. Mock.Random 的方法在数据模板中称为『占位符』,书写格式为 @占位符(参数 [, 参数]) . var Random = Moc ...

  4. 构建docker私有库

    前提: ip:     172.16.0.9 docker:   Version:  18.05.0-ce   1下载registry  docker pull registry   2 建库 将库像 ...

  5. 服务器用 git 进行部署出现代码冲突的处理

    服务器用 git 进行部署出现代码冲突的处理 起因: 由于项目是之前很久之前上传的,且并没上线.使用 git pull 进行代码更新时出现很多冲突. 因为服务器上的代码有移动过位置,不知道为什么就冲突 ...

  6. 笔记:Node.js 的 Buffer 缓冲区

    笔记:Node.js 的 Buffer 缓冲区 node.js 6.0 之前创建的 Buffer 对象使用 new Buffer() 构造函数来创建对象实例,但权限很大,可以获得敏感信息,所以建议使用 ...

  7. Spark的CombineByKey

    combineBykey关键是要明白里面的三个函数: 1. 当某个key第一次出现的时候,走的是第一个函数(createCombin):A function that creates a combin ...

  8. MySQL 性能优化技巧

    原文地址:MySQL 性能优化技巧 博客地址:http://www.extlight.com 一.背景 最近公司项目添加新功能,上线后发现有些功能的列表查询时间很久.原因是新功能用到旧功能的接口,而这 ...

  9. UCML 2.0 For ASP.NET开发平台简介

    互联网时代,我们能跟上网络变革的步伐吗?我们的产品领先于竞争对手吗?我们能够满足日益个性化的客户需求吗? 采用新的软件开发方法是我们的首要选择. 第一个全面支持ASP.NET的应用框架开发平台诞生了— ...

  10. (转)Inno Setup入门(四)——为程序创建桌面快捷方式

    本文转载自:http://blog.csdn.net/augusdi/article/details/8564810 Icons这一可选段定义所有创建在开始菜单和\或其它位置 (比如桌面) 的快捷方式 ...