小白的springboot之路(十四)、AOP


0、前言

1、什么是AOP

  AOP(面向切面編程),是一種橫切技術,是對OOP的補充和完善;

  使用AOP的橫切,可以對系統進行無侵入性的日志監聽、事務、權限管理等;

  思想上跟攔截器其實類似;攔截器是對action進行攔截處理,AOP是對切面進行攔截處理,其實切面也屬於一種action集合;

  AOP可以很好解耦;

2、AOP的組成

  Aspect:切面;

  Join point:連接點;

  Advice:通知,在切入點上執行的操作;

  Poincut:帶有通知的連接點;

  target:被通知的對象;

  AOP proxy;AOP代理;

其中,Advice(通知)分為以下幾種:

  • before(前置通知): 在方法開始執行前執行
  • after(后置通知): 在方法執行后執行
  • afterReturning(返回后通知): 在方法返回后執行
  • afterThrowing(異常通知): 在拋出異常時執行
  • around(環繞通知): 在方法執行前和執行后都會執行
通知的執行順序:
around > before > around > after > afterReturning

 

一、實現示例

  光看理論和定義,很多人可能都覺得很難理解,其實用法比較簡單,不難的,

  我們先來個簡單的例子,看完例子你可能就豁然開朗了,

  所謂程序員,好看書不如多動手:

  實現:

1、添加依賴

        <!-- 8、集成AOP  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

2、添加切面類LogAspect

package com.anson.common.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.util.Arrays;

/**
 * @description: AOP切面
 * @author: anson
 * @Date: 2019/12/20 10:11
 */

@Aspect //1、添加AOP相關注解
@Component
public class LogAspect
{
    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    //2、定義切入點(可以匹配、注解的方式,可混用)
//    @Pointcut("execution(public * com.anson.controller.*.*(..))")
@Pointcut("execution(public * com.anson.controller.*.*(..)) && @annotation(com.anson.common.annotation.LogAnnotation)")
  // @Pointcut("execution(public * com.anson.controller.TestController.get*()) && @annotation(com.anson.common.annotation.LogAnnotation)")
    public void pointcut(){}

    //===========通知 多中通知可根據需要靈活選用,一般Before 、AfterReturning即可=======================
    /**
     * 前置通知:在連接點之前執行的通知
     * @param joinPoint
     * @throws Throwable
     */
    @Before("pointcut()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // 接收到請求,記錄請求內容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // 記錄下請求內容
        logger.info("URL : " + request.getRequestURL().toString());
        logger.info("HTTP_METHOD : " + request.getMethod());
        logger.info("IP : " + request.getRemoteAddr());
        logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));
    }

    @AfterReturning(returning = "ret",pointcut = "pointcut()")
    public void doAfterReturning(Object ret) throws Throwable {
        // 處理完請求,返回內容
        logger.info("RESPONSE : " + ret);
    }
//==============================================
    @After("pointcut()")
    public void commit() {
        logger.info("after commit");
    }

    @AfterThrowing("pointcut()")
    public void afterThrowing() {
        logger.info("afterThrowing afterThrowing  rollback");
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            System.out.println("around");
            return joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
            throw e;
        } finally {
            logger.info("around");
        }
    }

}

 

需要注意的是:上面代碼注釋2的地方【2、定義切入點(可以匹配、注解的方式,可混用)】;

簡單點說就是通過匹配或者注解(也可兩種同時使用)匹配哪些類的哪些方法;

再直白點說,就是我們要對哪些類的哪些方法執行處理,要處理的范圍是哪些;

類用*作為通配符,方法用(..)表示,括號中的兩點表示匹配任何參數,包括沒有參數;

 

上面這樣其實就可以完成一個切面了,

 

比如將切面定義那里改為:@Pointcut("execution(public * com.anson.controller.*.*(..))")
那么運行程序后,所有com.anson.controller包下的所有類的所有方法,都會執行這個切面定義的方法進行相關寫日志處理了,結果如下:
around
[2019-12-20 11:19:02,205][INFO ][http-nio-8095-exec-1] URL : http://localhost:8095/user/userall (LogAspect.java:45)
[2019-12-20 11:19:02,205][INFO ][http-nio-8095-exec-1] HTTP_METHOD : GET (LogAspect.java:46)
[2019-12-20 11:19:02,206][INFO ][http-nio-8095-exec-1] IP : 0:0:0:0:0:0:0:1 (LogAspect.java:47)
[2019-12-20 11:19:02,207][INFO ][http-nio-8095-exec-1] CLASS_METHOD : com.anson.controller.UserController.getUserAll (LogAspect.java:48)
[2019-12-20 11:19:02,208][INFO ][http-nio-8095-exec-1] ARGS : [] (LogAspect.java:49)
[2019-12-20 11:19:02,510][DEBUG][http-nio-8095-exec-1] ==>  Preparing: select id, userName, passWord, realName from user  (BaseJdbcLogger.java:143)
[2019-12-20 11:19:02,606][DEBUG][http-nio-8095-exec-1] ==> Parameters:  (BaseJdbcLogger.java:143)
[2019-12-20 11:19:02,631][DEBUG][http-nio-8095-exec-1] <==      Total: 4 (BaseJdbcLogger.java:143)
[2019-12-20 11:19:02,634][INFO ][http-nio-8095-exec-1] around (LogAspect.java:77)
[2019-12-20 11:19:02,635][INFO ][http-nio-8095-exec-1] after commit (LogAspect.java:60)
[2019-12-20 11:19:02,635][INFO ][http-nio-8095-exec-1] RESPONSE : com.anson.common.result.ResultBody@6d9947d (LogAspect.java:55)
如果不用注解的話,上面就已經完成一個切面了,如果用注解來定義切面范圍呢,好,也簡單,我們來定義一個注解

--------------華麗麗的分割線-------------------------------

--------------增加自定義注解的方式----------------------------

3、添加一個LogAnnotation注解

 
         
package com.anson.common.annotation;

import java.lang.annotation.*;

/**
 * @description: 自定義注解
 * @author: anson
 * @Date: 2019/12/20 10:32
 */

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogAnnotation 
{ }

這樣就可以了,然后,在需要的地方,加入這個自定義注解:

    //2、獲取所有用戶
    @ApiOperation(value = "獲取所有用戶", notes = "獲取所有用戶")
    @RequestMapping(value="/userall",method= RequestMethod.GET)
    @LogAnnotation //自定義注解
    public ResultBody getUserAll()
    {
        List<User> users = userservice.getAll();
        return  ResultBody.success(users,"獲取所有用戶信息成功");
    }

 

同時,修改切面范圍的定義即可:

//2、定義切入點(可以匹配、注解的方式,可混用)--以下表示范圍為:controller包下所有包含@LogAnnotation注解的方法
 @Pointcut("execution(public * com.anson.controller.*.*(..)) && @annotation(com.anson.common.annotation.LogAnnotation)") 

public void pointcut(){}

完了,就這么簡單;

至於什么地方該使用AOP,以及AOP和攔截器用哪個比較好,這個就要根據業務場景靈活取舍了,掌握了思想,具體使用那就可以靈活發揮了



 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM