<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring Boot如何利用AOP巧妙記錄操作日志?

          共 7507字,需瀏覽 16分鐘

           ·

          2021-01-13 06:08

          走過路過不要錯過

          點擊藍字關(guān)注我們


          本篇要點

          • 簡要回顧SpringAOP的相關(guān)知識點:關(guān)鍵術(shù)語,通知類型,切入點表達式等等。

          • 介紹SpringBoot快速啟動測試AOP,巧妙打印日志信息。

          簡單回顧SpringAOP的相關(guān)知識點

          SpringAOP的相關(guān)的知識點包括源碼解析,我已經(jīng)在之前的文章中詳細說明,如果對AOP的概念還不是特別清晰的話。

          為了加深印象,這邊再做一個簡短的回顧:

          1、AOP關(guān)鍵術(shù)語

          • 切面(Aspect):也就是我們定義的專注于提供輔助功能的模塊,比如安全管理,日志信息等。

          • 連接點(JoinPoint):切面代碼可以通過連接點切入到正常業(yè)務(wù)之中,圖中每個方法的每個點都是連接點。

          • 切入點(PointCut):一個切面不需要通知所有的連接點,而在連接點的基礎(chǔ)之上增加切入的規(guī)則,選擇需要增強的點,最終真正通知的點就是切入點。

          • 通知方法(Advice):就是切面需要執(zhí)行的工作,主要有五種通知:before,after,afterReturning,afterThrowing,around。

          • 織入(Weaving):將切面應(yīng)用到目標對象并創(chuàng)建代理對象的過程,SpringAOP選擇再目標對象的運行期動態(tài)創(chuàng)建代理對

          • 引入(introduction):在不修改代碼的前提下,引入可以在運行期為類動態(tài)地添加方法或字段。

          2、通知的五種類型

          • 前置通知Before:目標方法調(diào)用之前執(zhí)行的通知。

          • 后置通知After:目標方法完成之后,無論如何都會執(zhí)行的通知。

          • 返回通知AfterReturning:目標方法成功之后調(diào)用的通知。

          • 異常通知AfterThrowing:目標方法拋出異常之后調(diào)用的通知。

          • 環(huán)繞通知Around:可以看作前面四種通知的綜合。

          3、切入點表達式

          上面提到:連接點增加切入規(guī)則就相當于定義了切入點,當然切入點表達式分為很多種,這里主要學(xué)習(xí)execution和annotation表達式。

          execution

          • 寫法:execution(訪問修飾符 返回值 包名.包名……類名.方法名(參數(shù)列表))

          • 例:execution(public void com.smday.service.impl.AccountServiceImpl.saveAccount())

          • 訪問修飾符可以省略,返回值可以使用通配符*匹配。

          • 包名也可以使用*匹配,數(shù)量代表包的層級,當前包可以使用..標識,例如* *..AccountServiceImpl.saveAccount()

          • 類名和方法名也都可以使用*匹配:* *..*.*()

          • 參數(shù)列表使用..可以標識有無參數(shù)均可,且參數(shù)可為任意類型。

          全通配寫法:* *…*.*(…)

          通常情況下,切入點應(yīng)當設(shè)置再業(yè)務(wù)層實現(xiàn)類下的所有方法:* com.smday.service.impl.*.*(..)

          @annotation

          匹配連接點被它參數(shù)指定的Annotation注解的方法。也就是說,所有被指定注解標注的方法都將匹配。

          @annotation(com.hyh.annotation.Log):指定Log注解方法的連接點。

          4、AOP應(yīng)用場景

          • 記錄日志

          • 監(jiān)控性能

          • 權(quán)限控制

          • 事務(wù)管理

          快速開始

          引入依賴

          如果你使用的是SpringBoot,那么只需要引入:spring-boot-starter-aop,框架已經(jīng)將spring-aopaspectjweaver整合進去。

                  
          org.springframework.boot
          spring-boot-starter-aop

          定義日志信息封裝

          /**
          * Controller層的日志封裝
          * @author Summerday
          */

          @Data
          @ToString
          public class WebLog implements Serializable {

          private static final long serialVersionUID = 1L;

          // 操作描述
          private String description;

          // 操作時間
          private Long startTime;

          // 消耗時間
          private Integer timeCost;

          // URL
          private String url;

          // URI
          private String uri;

          // 請求類型
          private String httpMethod;

          // IP地址
          private String ipAddress;

          // 請求參數(shù)
          private Object params;

          // 請求返回的結(jié)果
          private Object result;

          // 操作類型
          private String methodType;
          }

          自定義注解@Log

          @Target({ElementType.PARAMETER,ElementType.METHOD})
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          public @interface Log {

          /**
          * 描述
          */

          String description() default "";

          /**
          * 方法類型 INSERT DELETE UPDATE OTHER
          */

          MethodType methodType() default MethodType.OTHER;
          }

          定義測試接口

          @RestController
          public class HelloController {

          @PostMapping("/hello")
          @Log(description = "hello post",methodType = MethodType.INSERT)
          public String hello(@RequestBody User user) {
          return "hello";
          }

          @GetMapping("/hello")
          @Log(description = "hello get")
          public String hello(@RequestParam("name") String username, String hobby) {
          int a = 1 / 0;
          return "hello";
          }
          }

          定義切面Aspect與切點Pointcut

          用@Aspect注解標注標識切面,用@PointCut定義切點。

          /**
          * 定義切面
          * @author Summerday
          */


          @Aspect
          @Component
          public class LogAspect {

          private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

          /**
          * web層切點
          * 1. @Pointcut("execution(public * com.hyh.web.*.*(..))") web層的所有方法
          * 2. @Pointcut("@annotation(com.hyh.annotation.Log)") Log注解標注的方法
          */

          @Pointcut("@annotation(com.hyh.annotation.Log)")
          public void webLog() {
          }
          }

          定義通知方法Advice

          這里使用環(huán)繞通知,

          /**
          * 定義切面
          * @author Summerday
          */


          @Aspect
          @Component
          public class LogAspect {

          private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

          /**
          * web層切點
          * 1. @Pointcut("execution(public * com.hyh.web.*.*(..))") web層的所有方法
          * 2. @Pointcut("@annotation(com.hyh.annotation.Log)") Log注解標注的方法
          */


          @Pointcut("@annotation(com.hyh.annotation.Log)")
          public void webLog() {
          }


          /**
          * 環(huán)繞通知
          */

          @Around("webLog()")
          public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
          //獲取請求對象
          HttpServletRequest request = getRequest();
          WebLog webLog = new WebLog();
          Object result = null;
          try {
          log.info("=================前置通知=====================");
          long start = System.currentTimeMillis();
          result = joinPoint.proceed();
          log.info("=================返回通知=====================");
          long timeCost = System.currentTimeMillis() - start;
          // 獲取Log注解
          Log logAnnotation = getAnnotation(joinPoint);
          // 封裝webLog對象
          webLog.setMethodType(logAnnotation.methodType().name());
          webLog.setDescription(logAnnotation.description());
          webLog.setTimeCost((int) timeCost);
          webLog.setStartTime(start);
          webLog.setIpAddress(request.getRemoteAddr());
          webLog.setHttpMethod(request.getMethod());
          webLog.setParams(getParams(joinPoint));
          webLog.setResult(result);
          webLog.setUri(request.getRequestURI());
          webLog.setUrl(request.getRequestURL().toString());
          log.info("{}", JSONUtil.parse(webLog));
          } catch (Throwable e) {
          log.info("==================異常通知=====================");
          log.error(e.getMessage());
          throw new Throwable(e);
          }finally {
          log.info("=================后置通知=====================");
          }
          return result;
          }

          /**
          * 獲取方法上的注解
          */

          private Log getAnnotation(ProceedingJoinPoint joinPoint) {
          Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
          return method.getAnnotation(Log.class);
          }


          /**
          * 獲取參數(shù) params:{"name":"天喬巴夏"}
          */

          private Object getParams(ProceedingJoinPoint joinPoint) {
          // 參數(shù)名
          String[] paramNames = getMethodSignature(joinPoint).getParameterNames();
          // 參數(shù)值
          Object[] paramValues = joinPoint.getArgs();
          // 存儲參數(shù)
          Map params = new LinkedHashMap<>();
          for (int i = 0; i < paramNames.length; i++) {
          Object value = paramValues[i];
          // MultipartFile對象以文件名作為參數(shù)值
          if (value instanceof MultipartFile) {
          MultipartFile file = (MultipartFile) value;
          value = file.getOriginalFilename();
          }
          params.put(paramNames[i], value);
          }
          return params;
          }

          private MethodSignature getMethodSignature(ProceedingJoinPoint joinPoint) {
          return (MethodSignature) joinPoint.getSignature();
          }


          private HttpServletRequest getRequest() {
          ServletRequestAttributes requestAttributes =
          (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
          return requestAttributes.getRequest();
          }

          }

          這里處理webLog的方式有很多種,考慮性能,可以采用異步方式存入數(shù)據(jù)庫,相應(yīng)代碼已經(jīng)上傳至Gitee。

          測試

          POST http://localhost:8081/hello
          Content-Type: application/json

          { "id" : 1, "username" : "天喬巴夏", "age": 18 }

          結(jié)果如下:

          =================前置通知=====================
          =================返回通知=====================
          {"ipAddress":"127.0.0.1","description":"hello post","httpMethod":"POST","params":{"user":{"id":1,"age":18,"username":"天喬巴夏"}},"uri":"/hello","url":"http://localhost:8081/hello","result":"hello","methodType":"INSERT","startTime":1605596028383,"timeCost":28}
          =================后置通知=====================

          源碼下載

          本文內(nèi)容均為對優(yōu)秀博客及官方文檔總結(jié)而得,原文地址均已在文中參考閱讀處標注。最后,文中的代碼樣例已經(jīng)全部上傳至Gitee:https://gitee.com/tqbx/springboot-samples-learn,另有其他SpringBoot的整合哦。



          往期精彩推薦



          騰訊、阿里、滴滴后臺面試題匯總總結(jié) — (含答案)

          面試:史上最全多線程面試題 !

          最新阿里內(nèi)推Java后端面試題

          JVM難學(xué)?那是因為你沒認真看完這篇文章


          END


          關(guān)注作者微信公眾號 —《JAVA爛豬皮》


          了解更多java后端架構(gòu)知識以及最新面試寶典


          你點的每個好看,我都認真當成了


          看完本文記得給作者點贊+在看哦~~~大家的支持,是作者源源不斷出文的動力

          作者:天喬巴夏

          出處:https://www.cnblogs.com/summerday152/p/13994673.html


          瀏覽 41
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩激情av | 亚洲二区在线观看 | 激情五月丁香色婷婷 | 久久久久久无码日韩欧美电影 | 亚洲性爱一区二区 |