1. 程式人生 > >小白的springboot之路(十四)、AOP

小白的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和攔截器用哪個比較好,這個就要根據業務場景靈活取捨了,掌握了思想,具體使用那就可以靈活發揮了