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

0、前言

一、什麼是AOP

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

  使用AOP的橫切,能夠對系統進行無侵入性的日誌監聽、事務、權限管理等;程序員

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

  AOP能夠很好解耦;spring

二、AOP的組成

  Aspect:切面;編程

  Join point:鏈接點;app

  Advice:通知,在切入點上執行的操做;spring-boot

  Poincut:帶有通知的鏈接點;spa

  target:被通知的對象;代理

  AOP proxy;AOP代理;日誌

其中,Advice(通知)分爲如下幾種:

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

 

1、實現示例

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

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

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

  實現:

一、添加依賴

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

二、添加切面類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 //一、添加AOP相關注解
@Component
public class LogAspect
{
    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    //二、定義切入點(能夠匹配、註解的方式,可混用)
//    @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的地方【二、定義切入點(能夠匹配、註解的方式,可混用)】;

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

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

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

 

上面這樣其實就能夠完成一個切面了,

 

好比將切面定義那裏改成:@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)
若是不用註解的話,上面就已經完成一個切面了,若是用註解來定義切面範圍呢,好,也簡單,咱們來定義一個註解

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

--------------增長自定義註解的方式----------------------------

三、添加一個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 
{ }

這樣就能夠了,而後,在須要的地方,加入這個自定義註解:

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

 

同時,修改切面範圍的定義便可:

//二、定義切入點(能夠匹配、註解的方式,可混用)--如下表示範圍爲:controller包下全部包含@LogAnnotation註解的方法
 @Pointcut("execution(public * com.anson.controller.*.*(..)) && @annotation(com.anson.common.annotation.LogAnnotation)") 

public void pointcut(){}

完了,就這麼簡單;

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

相關文章
相關標籤/搜索