1. 程式人生 > >SpringAOP+註解實現簡單的日誌管理

SpringAOP+註解實現簡單的日誌管理

vax 技術 eight 如果 日誌類 lex 時間 str component

  

  今天在再次深入學習SpringAOP之後想著基於註解的AOP實現日誌功能,在面試過程中我們也經常會被問到:假如項目已經上線,如何增加一套日誌功能?我們會說使用AOP,AOP也符合開閉原則:對代碼的修改禁止的,對代碼的擴展是允許的。今天經過自己的實踐簡單的實現了AOP日誌。

  在這裏我只是簡單的記錄下當前操作的人、做了什麽操作、操作結果是正常還是失敗、操作時間,實際項目中,如果我們需要記錄的更詳細,可以記錄當前操作人的詳細信息,比如說部門、身份證號等信息,這些信息可以直接從session中獲取,也可以從session中獲取用戶ID之後調用userService從數據庫獲取。我們還可以記錄用戶調用了哪個類的哪個方法,我們可以使用

JoinPoint參數獲取或者利用環繞通知ProceedingJoinPoint去獲取。可以精確的定位到類、方法、參數,如果有必要我們就可以記錄在日誌中,看業務需求和我們的日誌表的設計。

  實現的大致思路是:

    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 NULL
AUTO_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+註解實現簡單的日誌管理