基于AOP操作日志

来源:互联网 发布:微信收款盒子对接端口 编辑:程序博客网 时间:2024/05/29 18:38

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

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

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

1.基于代理的AOP

2.@Aspectj

3.纯POJO

4.注入式Aspectj切面

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

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

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

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

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

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

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

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

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

view plaincopy to clipboardprint?
  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.     
  9. <!-- 定义日志文件写入位置,需要在log4j.properties中加入名称为 useroperatorlog的日志配置-->    
  10. <bean id="userOperateLogAdvisor" class="com.hqf.common.annotation.UserOperateLogAdvisor"    
  11.     p:loggerName="useroperatorlog" p:propertiesFilePath="classpath:messages/messages.properties"/>   
[java] view plaincopyprint?
  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.     
  9. <!-- 定义日志文件写入位置,需要在log4j.properties中加入名称为 useroperatorlog的日志配置-->    
  10. <bean id="userOperateLogAdvisor" class="com.hqf.common.annotation.UserOperateLogAdvisor"    
  11.     p:loggerName="useroperatorlog" p:propertiesFilePath="classpath:messages/messages.properties"/>   

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

例如:

view plaincopy to clipboardprint?
  1. @RequestMapping(value = "/demo/index2.do")    
  2.     @UserOperateLog(value="注解日志",type=1,key="annotation.log")    
  3.     public String handleIndex2(Model model,HttpSession session){    
  4.                 
  5.         return "demo/list";    
  6.     
  7.     }    
[java] view plaincopyprint?
  1. @RequestMapping(value = "/demo/index2.do")    
  2.     @UserOperateLog(value="注解日志",type=1,key="annotation.log")    
  3.     public String handleIndex2(Model model,HttpSession session){    
  4.                 
  5.         return "demo/list";    
  6.     
  7.     }    

日志输出结果如下:

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

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

详细的代码请参考附件。

原帖在:点击查看原帖

另一种形式的AOP操作Log

0 0