SpringAOP+註解實現簡單的日誌管理
今天在再次深入學習SpringAOP之後想著基於註解的AOP實現日誌功能,在面試過程中我們也經常會被問到:假如項目已經上線,如何增加一套日誌功能?我們會說使用AOP,AOP也符合開閉原則:對代碼的修改禁止的,對代碼的擴展是允許的。今天經過自己的實踐簡單的實現了AOP日誌。
在這裏我只是簡單的記錄下當前操作的人、做了什麽操作、操作結果是正常還是失敗、操作時間,實際項目中,如果我們需要記錄的更詳細,可以記錄當前操作人的詳細信息,比如說部門、身份證號等信息,這些信息可以直接從session中獲取,也可以從session中獲取用戶ID之後調用userService從數據庫獲取。我們還可以記錄用戶調用了哪個類的哪個方法,我們可以使用
實現的大致思路是:
1.前期準備,設計日誌表和日誌類,編寫日誌Dao和Service以及實現
2.自定義註解,註解中加入幾個屬性,屬性可以標識操作的類型(方法是做什麽的)
3.編寫切面,切點表達式使用上面的註解直接定位到使用註解的方法,
4.編寫通知,通過定位到方法,獲取上面的註解以及註解的屬性,然後從session中直接獲取或者從數據庫獲取當前登錄用戶的信息,最後根據業務處理一些日誌信息之後調用日誌Service存儲日誌。
其實日誌記錄可以針對Controller層進行切入,也可以選擇Service層進行切入,我選擇的是基於Service層進行日誌記錄。網上的日誌記錄由的用前置通知,有的用環繞通知,我選擇在環繞通知中完成,環繞通知中可以完成前置、後置、最終、異常通知的所有功能,因此我選擇了環繞通知。(關於AOP的通知使用方法以及XML、註解AOP使用方法參考;http://www.cnblogs.com/qlqwjy/p/8729280.html)
下面是具體實現:
1.日誌數據庫:
CREATE TABLE `logtable` ( `id` int(11) NOT NULLAUTO_INCREMENT, `operateor` varchar(5) DEFAULT NULL, `operateType` varchar(20) DEFAULT NULL, `operateDate` datetime DEFAULT NULL, `operateResult` varchar(4) DEFAULT NULL, `remark` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
簡單的記錄操作了操作人,操作的類型,操作的日期,操作的結果。如果想詳細的記錄,可以將操作的類名與操作的方法名以及參數信息也新進日誌,在環繞通知中利用反射原理即可獲取這些參數(參考我的另一篇博客:http://www.cnblogs.com/qlqwjy/p/8729280.html)。
2.日誌實體類:
Logtable.java
package cn.xm.exam.bean.log; import java.util.Date; public class Logtable { private Integer id; private String operateor; private String operatetype; private Date operatedate; private String operateresult; private String remark; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getOperateor() { return operateor; } public void setOperateor(String operateor) { this.operateor = operateor == null ? null : operateor.trim(); } public String getOperatetype() { return operatetype; } public void setOperatetype(String operatetype) { this.operatetype = operatetype == null ? null : operatetype.trim(); } public Date getOperatedate() { return operatedate; } public void setOperatedate(Date operatedate) { this.operatedate = operatedate; } public String getOperateresult() { return operateresult; } public void setOperateresult(String operateresult) { this.operateresult = operateresult == null ? null : operateresult.trim(); } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark == null ? null : remark.trim(); } }
3.日誌的Dao層使用的是Mybatis的逆向工程導出的mapper,在這裏就不貼出來了
4.日誌的Service層和實現類
- LogtableService.java接口
package cn.xm.exam.service.log; import java.sql.SQLException; import cn.xm.exam.bean.log.Logtable; /** * 日誌Service * * @author liqiang * */ public interface LogtableService { /** * 增加日誌 * @param log * @return * @throws SQLException */ public boolean addLog(Logtable log) throws SQLException; }
- LogtableServiceImpl實現類
package cn.xm.exam.service.impl.log; import java.sql.SQLException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import cn.xm.exam.bean.log.Logtable; import cn.xm.exam.mapper.log.LogtableMapper; import cn.xm.exam.service.log.LogtableService; @Service public class LogtableServiceImpl implements LogtableService { @Autowired private LogtableMapper logtableMapper; @Override public boolean addLog(Logtable log) throws SQLException { return logtableMapper.insert(log) > 0 ? true : false; } }
5.自定義註解:
package cn.xm.exam.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 日誌註解 * * @author liqiang * */ @Target(ElementType.METHOD) // 方法註解 @Retention(RetentionPolicy.RUNTIME) // 運行時可見 public @interface LogAnno { String operateType();// 記錄日誌的操作類型 }
6.在需要日誌記錄的方法中使用註解:(此處將註解寫在DictionaryServiceImpl方法上)
package cn.xm.exam.service.impl.common; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.annotation.Resource; import org.springframework.stereotype.Service; import cn.xm.exam.annotation.LogAnno; import cn.xm.exam.bean.common.Dictionary; import cn.xm.exam.bean.common.DictionaryExample; import cn.xm.exam.mapper.common.DictionaryMapper; import cn.xm.exam.mapper.common.custom.DictionaryCustomMapper; import cn.xm.exam.service.common.DictionaryService; /** * 字典表的實現類 * * @author * */ @Service public class DictionaryServiceImpl implements DictionaryService { @Resource private DictionaryMapper dictionaryMapper;/** * 1、添加字典信息 */ @LogAnno(operateType = "添加了一個字典項") @Override public boolean addDictionary(Dictionary dictionary) throws Exception { int result = dictionaryMapper.insert(dictionary); if (result > 0) { return true; } else { return false; } } }
7.編寫通知,切入到切點形成切面(註解AOP實現,環繞通知記錄日誌。)
註意:此處是註解AOP,因此在spring配置文件中開啟註解AOP
<!-- 1.開啟註解AOP --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
LogAopAspect.java
package cn.xm.exam.aop; import java.lang.reflect.Method; import java.sql.SQLException; import java.util.Date; import org.apache.struts2.ServletActionContext; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import cn.xm.exam.annotation.LogAnno; import cn.xm.exam.bean.log.Logtable; import cn.xm.exam.bean.system.User; import cn.xm.exam.service.log.LogtableService; /** * AOP實現日誌 * * @author liqiang * */ @Component @Aspect public class LogAopAspect { @Autowired private LogtableService logtableService;// 日誌Service /** * 環繞通知記錄日誌通過註解匹配到需要增加日誌功能的方法 * * @param pjp * @return * @throws Throwable */ @Around("@annotation(cn.xm.exam.annotation.LogAnno)") public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable { // 1.方法執行前的處理,相當於前置通知 // 獲取方法簽名 MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); // 獲取方法 Method method = methodSignature.getMethod(); // 獲取方法上面的註解 LogAnno logAnno = method.getAnnotation(LogAnno.class); // 獲取操作描述的屬性值 String operateType = logAnno.operateType(); // 創建一個日誌對象(準備記錄日誌) Logtable logtable = new Logtable(); logtable.setOperatetype(operateType);// 操作說明 // 整合了Struts,所有用這種方式獲取session中屬性(親測有效) User user = (User) ServletActionContext.getRequest().getSession().getAttribute("userinfo");//獲取session中的user對象進而獲取操作人名字 logtable.setOperateor(user.getUsername());// 設置操作人 Object result = null; try { //讓代理方法執行 result = pjp.proceed(); // 2.相當於後置通知(方法成功執行之後走這裏) logtable.setOperateresult("正常");// 設置操作結果 } catch (SQLException e) { // 3.相當於異常通知部分 logtable.setOperateresult("失敗");// 設置操作結果 throw e; } finally { // 4.相當於最終通知 logtable.setOperatedate(new Date());// 設置操作日期 logtableService.addLog(logtable);// 添加日誌記錄 } return result; } }
通過攔截帶有 cn.xm.exam.annotation.LogAnno 註解的方法,根據參數獲取到方法,然後獲取方法的LogAnno註解,獲取註解的屬性,在方法執行前後對其進行處理,實現AOP功能。
8.測試:
在頁面上添加一個字典之後打斷點進行查看:
- 會話中當前登錄的用戶信息:
- 當前日誌實體類的信息
- 查看數據庫:
mysql> select * from logtable\G *************************** 1. row *************************** id: 1 operateor: 超級管理員 operateType: 添加了一個字典項 operateDate: 2018-04-08 20:46:19 operateResult: 正常 remark: NULL
到這裏基於註解AOP+註解實現日誌記錄基本實現了。
最後給幾個鏈接,不明白上面的可以參考:
註解的使用:http://www.cnblogs.com/qlqwjy/p/7139068.html
Spring中獲取request和session對象:http://www.cnblogs.com/qlqwjy/p/8747136.html
SpringAOP的使用方法:http://www.cnblogs.com/qlqwjy/p/8729280.html
SpringAOP+註解實現簡單的日誌管理