1. 程式人生 > >利用SpringMVC的AOP來實現後臺系統的操作日誌記錄

利用SpringMVC的AOP來實現後臺系統的操作日誌記錄

最近在專案中要求把後臺的一些關鍵操作記錄下來,想了好半天能想到的也就那兩三種方式,要麼就是寫一個攔截器,然後再web.xml裡面進行配置,要麼就是就是在每個需要記錄操作日誌的程式碼裡面進行攔截,最後我選擇了第三種,也就是基於AOP的攔截,用這種方式,只需要在需記錄操作的介面方法上,新增上自定義註解就好了。其實在專案開發裡面真正用到AOP感覺不是很多,我也一樣很少就沒有怎麼用到。剛好這次碰見了,就記錄下來,也算是自己學習一次。
我在做的專案是基於SSH(Spring,Spring MVC,Hibernate)框架。

  1. 首先我們先想想我們做的功能到底是什麼,我們要記錄操作,我們首先就應該有一張記錄操作記錄的表
  `@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@Entity
@Table(name = "表名")
public class LogModel extends IdEntity {

    //使用者
    private User user;
    //IP
    private String remoteAddr;
    //異常
    private String exception;
    //日誌標題
    private String title;
    //請求地址
    private String requestUri;
    //日誌型別
private String type; //日誌記錄描述 private String description;
以上是我所用到的實體類

2. 關於實體類的Service層,Dao層,Impl等等我就不再這裡過多敘述,無非就是一些與資料庫操作掛鉤的一些程式碼。
3. 自定義一個切面註解

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MethodLog {
    /**
     * 該註解作用於方法上時需要備註資訊
     */
String remarks() default ""; }

4. 接下來就是重點了,切面類的具體實現。

@Component
@Aspect
public class SystemLogAspect {
//這段程式碼呼叫了org.slf4j.LoggerFactory line:280
private static final Logger logger = LoggerFactory.getLogger(SystemLogAspect.class);
private static final ThreadLocal<Date> beginTimeThreadLocal =
            new NamedThreadLocal("ThreadLocal beginTime");
    private static final ThreadLocal<LogModel> logThreadLocal =
            new NamedThreadLocal("ThreadLocal log");

    private static final ThreadLocal<User> currentUser = new NamedThreadLocal("ThreadLocal user");
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    /**
     * Controller層切點 註解攔截
     */
    @Pointcut("@annotation(com.shopping.logrecord.MethodLog)")
    public void controllerAspect() {
    }
    /**
     * 用於攔截Controller層記錄使用者的操作的開始時間
     *
     * @param joinPoint 切點
     * @throws InterruptedException
     */
    @Before("controllerAspect()")
    public void doBefore(JoinPoint joinPoint) throws InterruptedException {
        Date beginTime = new Date();
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes()).getRequest();
        beginTimeThreadLocal.set(beginTime);//執行緒繫結變數(該資料只有當前請求的執行緒可見)
        if (logger.isDebugEnabled()) {//這裡日誌級別為debug
            logger.debug("開始計時: {}  URI: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
                    .format(beginTime), request.getRequestURI());
        }
        //讀取session中的使用者
        HttpSession session = request.getSession();
        User user = (User) session.getAttribute("user");
        System.out.println(user);
        currentUser.set(user);
    }
    /**
     * 用於攔截Controller層記錄使用者的操作
     *
     * @param joinPoint 切點
     */
    @SuppressWarnings("unchecked")
    @After("controllerAspect()")
    public void doAfter(JoinPoint joinPoint)throws Exception {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes()).getRequest();
        User user = currentUser.get();
        if (user != null) {
            String title = "";
            String type = "info";                       //日誌型別(info:入庫,error:錯誤)
            String remoteAddr = SystemLogAspect.getIp();//請求的IP
            String requestUri = request.getRequestURI();//請求的Uri
            try {
                title = getControllerMethodDescription2(joinPoint);
            } catch (Exception e) {
                e.printStackTrace();
            }
            LogModel logModel = new LogModel();
            logModel.setTitle(title);
            logModel.setType(type);
            logModel.setRemoteAddr(remoteAddr);
            logModel.setRequestUri(requestUri);
            logModel.setException("無異常");
            logModel.setUserId(user.getId());
            User users = userService.getObjById(user.getId());
            Date date = new Date();
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String format = simpleDateFormat.format(date);
            logModel.setAddTime(date);
            logModel.setUserName(users.getUserName());
            //通過執行緒池來執行日誌儲存
            threadPoolTaskExecutor.execute(new SaveLogThread(logModel, logModelService));
            logThreadLocal.set(logModel);
            }
         }
         /**
     *  異常通知 記錄操作報錯日誌
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut = "controllerAspect()", throwing = "e")
    public  void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        LogModel logModel = logThreadLocal.get();
        logModel.setType("error");
        logModel.setException(e.toString());
        new UpdateLogThread(logModel, logModelService).start();
    }
/**
         * 獲取註解中對方法的描述資訊 用於Controller層註解
         * @param joinPoint 切點
         * @return 方法描述
         */

    public static String getControllerMethodDescription2(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        MethodLog controllerLog = method
                .getAnnotation(MethodLog.class);
        String discription = controllerLog.remarks();
        return discription;
    }


    /**
     * 獲取請求ip
     */
    public static String getIp()throws Exception {
        InetAddress ia=null;
            ia=ia.getLocalHost();
            String localip=ia.getHostAddress();
            return localip;
    }

    /**
     * 儲存日誌執行緒
     *
     * @author lin.r.x
     *
     */
    private static class SaveLogThread implements Runnable {
        private LogModel logModel;
        private LogModelService logModelService;

        public SaveLogThread(LogModel logModel, LogModelService logModelService) {
            this.logModel = logModel;
            this.logModelService = logModelService;
        }

        @Override
        public void run() {
            logModelService.save(logModel);
        }
    }

    /**
     * 日誌更新執行緒
     *
     * @author lin.r.x
     *
     */
    private static class UpdateLogThread extends Thread {
        private LogModel logModel;
        private LogModelService logModelService;
        public UpdateLogThread(LogModel logModel, LogModelService logModelService) {
            super(UpdateLogThread.class.getSimpleName());
            this.logModel = logModel;
            this.logModelService = logModelService;
        }

        @Override
        public void run() {
            this.logModelService.update(logModel);
        }
    }
}

接下來我們需要做一步操作,那就是在XML中開啟對AOP的支援

<!-- 啟動對@AspectJ註解的支援 -->
    <aop:aspectj-autoproxy/>
    <!-- 掃描切點類元件 -->
    //放置切面實現類的包路徑
    <context:component-scan base-package="com.shopping.logrecord.logss"/>
    //日誌表的service的包路徑
    <context:component-scan base-package="com.shopping.logrecord.logservicess"/>
    <bean id="threadPoolTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="corePoolSize" value="5" />
        <property name="maxPoolSize" value="10" />
    </bean>

OKAY,到這我們就基本算是配置完成了。
接下來,加入我們需要執行編輯操作
在編輯介面方法上新增自定義的註解
這裡寫圖片描述
okay,完成,**注意**remarks的值。
看一下表資料
這裡寫圖片描述
大功告成!