(尊重勞動成果,轉載請註明出處: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過程中使用到的一些小知識點,希望大家可以關注~
如果對你有幫助,記得點贊哈,歡迎大家關注我的博客,關注公衆號(文強的技術小屋),學習更多技術知識,一起遨遊知識海洋~