再談Spring(二):AOP面向切面編程 - Aspect & 攔截器

(尊重勞動成果,轉載請註明出處:https://yangwenqiang.blog.csdn.net/article/details/103327498冷血之心的博客)

本小節對Spring中的AOP技術進行相應的總結與介紹。

Spring是一個輕量級的IOC和AOP容器框架。是爲Java應用程序提供基礎性服務的一套框架,目的是用於簡化企業應用程序的開發,它使得開發者只需要關心業務需求。

AOP:

面向切面編程,指擴展功能不修改源代碼,將功能代碼從業務邏輯代碼中分離出來。

主要功能:

日誌記錄,性能統計,安全控制,事務處理,異常處理等等。

主要意圖:

將日誌記錄,性能統計,安全控制,事務處理,異常處理等代碼從業務邏輯代碼中劃分出來,通過對這些行爲的分離,我們希望可以將它們獨立到非指導業務邏輯的方法中,進而改變這些行爲的時候不影響業務邏輯的代碼。

AOP的特點:

採用橫向抽取機制,取代了傳統縱向繼承體系重複性代碼。

AOP操作術語:

  • Joinpoint(連接點): 類裏面可以被增強的方法,這些方法稱爲連接點
  • Pointcut(切入點):所謂切入點是指我們要對哪些Joinpoint進行攔截的定義
  • Advice(通知/增強):所謂通知是指攔截到Joinpoint之後所要做的事情就是通知.通知分爲前置通知,後置通知,異常通知,最終通知,環繞通知(切面要完成的功能)
  • Aspect(切面):是切入點和通知(引介)的結合
  • Introduction(引介):引介是一種特殊的通知在不修改類代碼的前提下, Introduction可以在運行期爲類動態地添加一些方法或Field.
  • Target(目標對象):代理的目標對象(要增強的類)
  • Weaving(織入):是把增強應用到目標的過程,把advice 應用到 target的過程
  • Proxy(代理):一個類被AOP織入增強後,就產生一個結果代理類

其實我們只需要這麼簡單記憶即可:

切入點 就是在類裏邊可以有很多方法被增強,比如實際操作中,只是增強了個別方法,則定義實際被增強的某個方法爲切入點。

通知/增強 就是指增強的邏輯,比如擴展日誌功能,這個日誌功能稱爲增強。

切面 就是把增強應用到具體方法上面的過程稱爲切面。

AOP實現方式:

Spring的AOP技術底層使用了動態代理來實現,在SpringBoot中,針對AOP技術的實現有Aspect,攔截器和過濾器。本篇博文,我們主要介紹常用的Aspect和攔截器實現方式。

再談Spring(一):Bean的作用域 文章中我們搭建了一個簡單的SpringBoot項目,這裏我們繼續複用該項目來進行AOP技術的演示。

Aspect

在上一小節的SpringBoot項目中,我們首先是添加aop所需的依賴:

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
 </dependency>

然後創建一個切面類,如下所示:

WebAcpect:

package com.ywq.interceptor;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
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;

/**
 * created by yanwenqiang on 2019-11-30
 * 定義一個切面類
 */
@Aspect
@Component
public class WebAcpect {

    /**
     * 定義切入點,切入點爲com.ywq.controller.TestController下的所有函數
     */
    @Pointcut("execution(* com.ywq.controller.TestController.*(..))")
    public void acpectMethod(){}

    /**
     * 前置通知:在連接點之前執行的通知
     * @param joinPoint
     * @throws Throwable
     */
    @Before("acpectMethod()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // 接收到請求,記錄請求內容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // 記錄下請求內容
        System.out.println("URL:" + request.getRequestURL().toString());
        System.out.println("方法:" + request.getMethod());
        System.out.println("類方法 : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        System.out.println("參數 : " + Arrays.toString(joinPoint.getArgs()));
    }

}

在切面類中,我們需要通過註解@Aspect來標註這是一個切面類,然後通過註解@Component來表明這是一個Bean,然後我們需要配置切入點。配置方式如下:

使用表達式配置切入點,常用的表達式:
execution(<訪問修飾符>?<返回類型><方法名>(<參數>)<異常>)

  • execution(* com.ywq.controller.TestController.add(…)) 表示當前路徑下的add方法
  • execution(* com.ywq.controller.TestController.*(…)) :表示當前路徑下的所有方法
  • execution(* .(…)) :表示所有方法

比如說,匹配所有save開頭的方法可以這麼配置 execution(* save*(…))

本例中,我們僅僅展示前置通知,其餘通知也是一樣的。

然後來看下TestController:

package com.ywq.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

/**
 * created by yangwenqiang on 2019-11-30
 */
@RequestMapping("/test")
@RestController
public class TestController {

    @RequestMapping(value = "/scopeTest", method = RequestMethod.GET)
    public String testScope(HttpServletRequest request,
                            @RequestParam(value = "name") String name) {

        return "Hello, " + name;
    }

}

接下來,我們啓動SpringBoot服務,如下所示:
在這裏插入圖片描述
在瀏覽器中,輸入:http://localhost:8632/test/scopeTest?name=ywq 結果如下:
在這裏插入圖片描述
這樣,我們就使用Aspect來完成了面向切面編程,在業務邏輯之外增加了一些功能模塊邏輯。

但是,使用Aspect來實現AOP,尤其是對Controller層的接口進行攔截,看起來有點怪怪的。其實,在SpringBoot中,我們一般使用攔截器Interceptor來實現面向接口編程,實現攔截請求,增加相應的功能模塊。

攔截器

SpringBoot中的攔截器可以通過繼承抽象類HandlerInterceptorAdapter 或者實現接口HandlerInterceptor 來實現。

在抽象類和接口中,有如下的三個方法:

  • preHandle:攔截於請求剛進入時,進行判斷,需要boolean返回值,如果返回true將繼續執行,如果返回false,將不進行執行。一般用於登錄校驗。
  • postHandle:攔截於方法成功返回後,視圖渲染前,可以對modelAndView進行操作。
  • afterCompletion:攔截於方法成功返回後,視圖渲染後,可以進行成功返回的日誌記錄。

接下來,我們看下JDK中HandlerInterceptor 的介紹:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

接下里,我們首先看使用抽象類HandlerInterceptorAdapter來實現攔截器:

AuthInterceptor:

package com.ywq.interceptor;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * created by yangwenqiang on 2019-11-30
 * 繼承抽象類 HandlerInterceptorAdapter 實現攔截器
 */
@Component
public class AuthInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 接口執行前先執行該方法,false表示攔截,true表示放行
        String name = request.getParameter("name");
        System.out.println("參數中的名字:" + name);

        return true;
    }

}

然後還需要一個配置類AuthConfig 如下:

package com.ywq.config;

import com.ywq.interceptor.AuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class AuthConfig implements WebMvcConfigurer {

    // 注入攔截器
    @Autowired
    private AuthInterceptor authInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 註冊攔截器
        registry.addInterceptor(authInterceptor).addPathPatterns("/test/scopeTest");
    }

}

這樣,我們就使用HandlerInterceptorAdapter實現了一個攔截器,啓動SpringBoot 服務,在瀏覽器中輸入http://localhost:8632/test/scopeTest?name=ywq,就可以看到結果如下:
在這裏插入圖片描述

接下來,我們再來看下使用接口HandlerInterceptor來實現攔截器:

CheckInterceptor

package com.ywq.interceptor;

import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class CheckInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 接口執行前先執行該方法,false表示攔截,true表示放行
        String name = request.getParameter("name");
        System.out.println("[CheckInterceptor] - 參數中的名字:" + name);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

CheckConfig配置類如下:

package com.ywq.config;

import com.ywq.interceptor.CheckInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

/**
 * *
 *  * created by yangwenqiang on 2019-11-30
 *  * 這是一個配置類
 */
 @Configuration
public class CheckConfig extends WebMvcConfigurationSupport {

    // 注入攔截器
    @Autowired
    private CheckInterceptor checkInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 註冊攔截器
        registry.addInterceptor(checkInterceptor);
        super.addInterceptors(registry);
    }

}

啓動SpringBoot 服務,在瀏覽器中輸入http://localhost:8632/test/scopeTest?name=yangwenqiang,就可以看到結果如下:
在這裏插入圖片描述
在這裏插入圖片描述

一般情況下,我們會首選抽象類HandlerInterceptorAdapter來實現攔截器,因爲可以按需進行方法的覆蓋。如上我們就完成了攔截器的創建,當然如果你嫌棄AuthConfig和CheckConfig比較麻煩的話,可以在啓動類上完成攔截器的註冊,如下所示:

package com.ywq;

import com.ywq.interceptor.AuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * created by yangwenqiang on 2019-11-30
 */

@SpringBootApplication
@ServletComponentScan
public class StartApplication implements WebMvcConfigurer {

    @Autowired
    private AuthInterceptor authInterceptor;

    // SpringBoot服務的啓動入口
    public static void main(String[] args) {
        SpringApplication.run(StartApplication.class,args);
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 註冊攔截器
        registry.addInterceptor(authInterceptor).addPathPatterns("/test/scopeTest");
    }
}

總結:

本小節中,我們主要闡述了AOP面向切面編程的思想以及其具體實現方式。總結下來就是 Aspect方式實現的AOP一般用在給Service方法或者業務邏輯方法上進行相應的增加;攔截器主要是對Web層Controller接口進行攔截。

本小節涉及到的完整項目代碼詳見:我的GitHub


在後續的章節中,我會持續更新在使用Spring過程中使用到的一些小知識點,希望大家可以關注~


如果對你有幫助,記得點贊哈,歡迎大家關注我的博客,關注公衆號(文強的技術小屋),學習更多技術知識,一起遨遊知識海洋~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章