在日常開發中經常需要在代碼中加入一些記錄用戶操作日誌的log語句,比如誰在什麼時間做了什麼操作,等等。
把這些對於開發人員開說無關痛癢的代碼寫死在業務方法中實在不是一件很舒服的事情,於是AOP應運而生。
Spring對AOP的支持有以下4種情況:
1.基於代理的AOP
2.@Aspectj
3.純POJO
4.注入式Aspectj切面
前三種都是基於方法級的,最後一個可以精確到屬性及構造器。
關於Spring對AOP的支持的詳細內容,讀者可以參考《Spring in Action (第二版)中文版》第四章。
我這裏使用的是第三種,純POJO的方式,這種方式僅能在spring2.0及以後的版本中使用。
ok,言歸正傳,還是來說一說方法級註解的日誌配置方式吧,顧名思義,就是只需要在方法上增加一個註釋就可以自動打印日誌,所以首先需要創建一個注 解,如下:
- package com.hqf.common.annotation;
- import java.lang.annotation.Documented;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- @Target ({ElementType.METHOD})
- @Retention (RetentionPolicy.RUNTIME)
- @Documented
- public @interface UserOperateLog {
- /**
- * 用戶操作名稱
- * @return 用戶操作名稱,默認爲空串
- */
- String value() default "" ;
- /**
- * 用戶操作類型,默認類型爲0<br/>
- * 0 - 其他操作 <br/>
- * 1 - 查詢 <br/>
- * 2 - 新增 <br/>
- * 3 - 修改 <br/>
- * 4 - 刪除
- * @return 用戶操作類型
- */
- int type() default 0 ;
- /**
- * 用戶操作名稱對應的key,可以通過該key值在屬性文件中查找對應的value
- * @return key
- */
- String key() default "" ;
- }
這裏只是拋磚引玉,讀者可以根據需要建立自己的註解。
有了註解,之後就需要在方法被調用時能解析註解,這就用到了SpringAOP的通知,我這裏使用MethodBeforeAdvice,就是在方 法被調用前執行。關於SpringAOP的通知的詳細討論讀者可以參考《Spring in Action (第二版)中文版》第四章4.2.1
- package com.hqf.common.annotation;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.lang.reflect.Method;
- import java.util.Properties;
- import javax.annotation.PostConstruct;
- import javax.servlet.http.HttpSession;
- import org.apache.log4j.Logger;
- import org.springframework.aop.MethodBeforeAdvice;
- import org.springframework.core.io.Resource;
- import org.springframework.util.Assert;
- import org.springframework.util.StringUtils;
- public class UserOperateLogAdvisor implements MethodBeforeAdvice {
- private Logger logger; //日誌句柄
- private String loggerName; //日誌名稱
- private Properties properties; //屬性文件句柄
- /**
- * 描述 : <該方法用於初始化屬性文件>. <br>
- *<p>
- 日誌內容可以預先配置在配置文件中,在需要打印日誌時從配置文件中找到對應的值。
- 這 裏是做擴展使用,讀者可以根據實際情況進行設 計
- * @param propertiesFilePath
- * @throws IOException
- */
- public void setPropertiesFilePath(Resource propertiesFilePath)
- throws IOException {
- if (properties == null )
- properties = new Properties();
- properties.load(new FileInputStream(propertiesFilePath.getFile()));
- }
- /*
- * (non-Javadoc)
- *
- * @see
- * org.springframework.aop.MethodBeforeAdvice#before(java.lang.reflect.Method
- * , java.lang.Object[], java.lang.Object)
- */
- public void before(Method method, Object[] args, Object target)
- throws Throwable {
- String username = "未知" ;
- for (Object object : args) {
- //這裏只提供一種獲得操作人的方式,既從HttpSession中獲取,但這要求方法參數中包含 HttpSession
- //這裏只是拋磚引玉,讀者可以根據實際情況進行設計
- if (object instanceof HttpSession) {
- username = ((HttpSession) object).getAttribute("username" ) == null ? "未知"
- : (String) ((HttpSession) object)
- .getAttribute("username" );
- }
- }
- //判斷方法是否註解了UserOperateLog
- UserOperateLog anno = method.getAnnotation(UserOperateLog.class );
- if (anno == null )
- return ;
- String defaultMessage = anno.value();
- String methodName = target.getClass().getName() + "."
- + method.getName();
- String desc = this .handleDescription(anno.key(), StringUtils
- .hasText(defaultMessage) ? defaultMessage : methodName);
- //裝配日誌信息
- String logline = this .buildLogLine(username, anno.type(), desc);
- logger.info(logline);
- }
- /**
- * 構建日誌行
- *
- * @param usrname
- * 用戶名稱
- * @param operateType
- * 操作類型
- * @param description
- * 操作描述
- * @return 日誌行: username - operateType - description
- */
- protected String buildLogLine(String username, int operateType,
- String description) {
- StringBuilder sb = new StringBuilder();
- sb.append(username).append(" - " ).append(operateType).append( " - " )
- .append(description);
- return sb.toString();
- }
- /**
- * 獲取日誌內容描述,可以從消息配置文件中找到對應的信息
- *
- * @param key
- * 日誌內容key
- * @param defaultMessage
- * 默認的描述信息
- * @return 描述信息
- */
- protected String handleDescription(String key, String defaultMessage) {
- if (properties == null )
- return defaultMessage;
- if (!StringUtils.hasText(key))
- return defaultMessage;
- String message = properties.getProperty(key);
- if (!StringUtils.hasText(message))
- return defaultMessage;
- else
- return message;
- }
- @PostConstruct
- public void init() {
- Assert.notNull(loggerName);
- logger = Logger.getLogger(loggerName);
- }
- /**
- * @param loggerName
- * the loggerName to set
- */
- public void setLoggerName(String loggerName) {
- this .loggerName = loggerName;
- }
- }
爲了使通知起作用,需要在spring配置文件加入如下內容:
- <!-- 定義用戶操作日誌切入點和通知器 -->
- <aop:config proxy-target-class = "true" >
- <aop:pointcut id="operatePoint"
- expression="@annotation(com.hqf.common.annotation.UserOperateLog)" />
- <aop:advisor pointcut-ref="operatePoint" id= "logAdvisor"
- advice-ref="userOperateLogAdvisor" />
- </aop:config>
- <!-- 定義日誌文件寫入位置,需要在log4j.properties中加入名稱爲 useroperatorlog的日誌配置-->
- <bean id="userOperateLogAdvisor" class = "com.hqf.common.annotation.UserOperateLogAdvisor"
- p:loggerName="useroperatorlog" p:propertiesFilePath= "classpath:messages/messages.properties" />
ok,配置完成,在使用時只需要在方法上加入@UserOperateLog
例如:
- @RequestMapping (value = "/demo/index2.do" )
- @UserOperateLog (value= "註解日誌" ,type= 1 ,key= "annotation.log" )
- public String handleIndex2(Model model,HttpSession session){
- return "demo/list" ;
- }
日誌輸出結果如下:
2010-03-04 16:01:45 useroperatorlog:68 INFO - hanqunfeng - 1 - 註解日誌
註解裏使用了key,這樣就會從指定的配置文件中查找,如果查找到就替換掉默認的value值。
詳細的代碼請參考附件。
原帖在:點擊查看原帖