SpringBoot第二十五篇:SpringBoot與AOP

  • 2019 年 10 月 15 日
  • 筆記

作者:追夢1819
原文:https://www.cnblogs.com/yanfei1819/p/11457867.html
版權聲明:本文為部落客原創文章,轉載請附上博文鏈接!

引言

  作者在實際項目中碰到一個問題,就是需要在系統中加入操作日誌功能。但是目前系統開發已經接近尾聲,功能介面達到一百幾十個。

  如果按照新手的思維(項目組中有人就這樣提建議),但是這樣的話,工作量之大、冗餘程式碼之多,可想而知。對於這種需求,我們應該考慮到 Java 的面向切面編程思想,使用 Spring AOP。下面,闡述在 SpringBoot 中使用 AOP。

在項目中的使用

  為了演示更加完整的功能,此處我將我在實際項目中的需求抽取出來:將整個系統中的操作記錄(比例數據的增、刪、改、登錄、退出等)入庫。

  為了演示實際的應用場景,本處程式碼不做專門的 demo 處理。將本人在真實項目中的實際程式碼展示出來,以便讀者做更好的理解。

以下是核心程式碼:

package com.sunwin.aspect;    import com.sunwin.common.ErrorCode;  import com.sunwin.db.dao.ActionLogDao;  import com.sunwin.db.dto.ActionLogDTO;  import com.sunwin.exception.BusinessException;  import com.sunwin.util.DateUtil;  import com.sunwin.util.IPUtil;  import com.sunwin.util.UserUtil;  import org.aspectj.lang.JoinPoint;  import org.aspectj.lang.annotation.After;  import org.aspectj.lang.annotation.Aspect;  import org.aspectj.lang.annotation.Before;  import org.aspectj.lang.annotation.Pointcut;  import org.slf4j.Logger;  import org.slf4j.LoggerFactory;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.stereotype.Component;  import org.springframework.web.context.request.RequestContextHolder;  import org.springframework.web.context.request.ServletRequestAttributes;    import javax.servlet.http.HttpServletRequest;  import java.text.SimpleDateFormat;  import java.util.Date;    /**   * Created by 追夢1819 on 2019-07-08.   */  @Aspect  @Component  public class LogAspect {        @Autowired      private ActionLogDao actionLogDao;        private final static Logger logger = LoggerFactory.getLogger(LogAspect.class);        // ..表示包及子包 該方法代表controller層的所有方法      @Pointcut(              "execution(public * com.sunwin.web.controller.*.add*(..)) ||  " +              "execution(public * com.sunwin.web.controller.*.insert*(..)) || " +              "execution(public * com.sunwin.web.controller.*.update*(..)) || " +              "execution(public * com.sunwin.web.controller.*.delete*(..))||" +  //            "execution(public * com.sunwin.web.controller.*.login*(..))"+              "execution(public * com.sunwin.web.controller.*.logout*(..))"      )      public void controllerMethod() {      }          @Pointcut("execution(public * com.sunwin.web.controller.*.login*(..))")      public void afterController() {      }        @After("afterController()")      public void afterLogRequestInfo(JoinPoint joinPoint) throws Exception {          common(joinPoint);      }        @Before("controllerMethod()")      public void LogRequestInfo(JoinPoint joinPoint) throws Exception {          common(joinPoint);      }        // aop業務處理邏輯      private void common(JoinPoint joinPoint) throws BusinessException {          ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();          HttpServletRequest request = attributes.getRequest();          StringBuffer requestLog = new StringBuffer();          requestLog.append("請求資訊:")                  .append("URL = {" + request.getRequestURI() + "},t")                  .append("HTTP_METHOD = {" + request.getMethod() + "},t")                  .append("IP = {" + request.getRemoteAddr() + "},t")                  .append("CLASS_METHOD = {" + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName() + "}t");            ActionLogDTO actionLog = new ActionLogDTO();          actionLog.setContent("調用資訊是:" + requestLog);            String ip = IPUtil.getLocalIPAddress();          actionLog.setIp(ip);          String user = UserUtil.getUser() == null ? "" : UserUtil.getUser();          actionLog.setOperator(user);            SimpleDateFormat format = new SimpleDateFormat(DateUtil.DateFormat_yyyyMMddHHmmss);          String now = format.format(new Date());          actionLog.setActionDate(now);            String name = joinPoint.getSignature().getName();          // 類型、操作時間、操作者、內容、ip          // 操作類型:1-登錄;2-退出;3-新增;4-編輯;5-刪除;99-未知          if (name.startsWith("delete")) {  // 刪              actionLog.setActionType(5);          } else if (name.startsWith("update")) { // 改              actionLog.setActionType(4);          } else if (name.startsWith("add") || name.startsWith("insert")) { // 增              actionLog.setActionType(3);          } else if (name.startsWith("login")) {              actionLog.setActionType(1);          } else if (name.startsWith("logout")) {              actionLog.setActionType(2);          } else {              actionLog.setActionType(99);          }            int count = actionLogDao.insert(actionLog);          if (count < 1) {              throw new BusinessException(ErrorCode.INSERT_ERROR);          }      }  }

  以上是項目中的部門真實程式碼。讀者需要關注的有以下幾點:

  1. 註解:@Aspect、@Pointcut、@After、@Before;
  2. 攔截的方法命名規則要統一,比如新增時 addXXX 或者 insertXXX,更新是 updateXXX 或者 editXXX,查詢是 queryXXX 或者 selectXXX ,刪除是 deleteXXX 等;
  3. 處理相關的業務邏輯,比如,數據入庫,導出日誌文件等等;
  4. 需要理解 Spring aop 中的相關概念,比如切點、切面、增強/通知等。SpringBoot Aop 還是對 Spring Aop 的封裝。

下面是本項目中的日誌展示效果:

總結

  本文希望傳到的不僅僅是一個解決方案,更希望傳達一種思想。在需求實現時,多思考,而不是在沒有思考的前提下,就橫衝直撞,胡寫一通。正如方騰飛所說,聰明人的幾個特質:

1、問題是什麼?

2、解決方案有哪些?

3、哪一個方案是所有方案中的捷徑?