之前介紹過一篇攔截器的基本使用姿勢: 【WEB系列】SpringBoot之攔截器Interceptor使用姿勢介紹
在SpringBoot中,通過實現WebMvcConfigurer
的addInterceptors
方法來註冊攔截器,那麼當我們的攔截器中希望使用Bean時,可以怎麼整?
<!-- more -->
I. 專案搭建
本專案藉助SpringBoot 2.2.1.RELEASE
+ maven 3.5.3
+ IDEA
進行開發
開一個web服務用於測試
<dependencies>
<!-- 郵件傳送的核心依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
II.攔截器
實現攔截器比較簡單,實現HandlerInterceptor
介面就可以了,比如我們實現一個基礎的許可權校驗的攔截器,通過從請求頭中獲取引數,當滿足條件時表示通過
0.安全校驗攔截器
@Slf4j
public class SecurityInterceptor implements HandlerInterceptor {
/**
* 在執行具體的Controller方法之前呼叫
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 一個簡單的安全校驗,要求請求頭中必須包含 req-name : yihuihui
String header = request.getHeader("req-name");
if ("yihuihui".equals(header)) {
return true;
}
log.info("請求頭錯誤: {}", header);
return false;
}
/**
* controller執行完畢之後被呼叫,在 DispatcherServlet 進行檢視返回渲染之前被呼叫,
* 所以我們可以在這個方法中對 Controller 處理之後的 ModelAndView 物件進行操作。
* <p>
* preHandler 返回false,這個也不會執行
*
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("執行完畢!");
response.setHeader("res", "postHandler");
}
/**
* 方法需要在當前對應的 Interceptor 類的 preHandle 方法返回值為 true 時才會執行。
* 顧名思義,該方法將在整個請求結束之後,也就是在 DispatcherServlet 渲染了對應的檢視之後執行。此方法主要用來進行資源清理。
*
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("回收");
}
}
接下來是這個攔截器的註冊
@RestController
@SpringBootApplication
public class Application implements WebMvcConfigurer {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/**");
}
@GetMapping(path = "show")
public String show() {
return UUID.randomUUID().toString();
}
}
接下來問題來了,我們希望這個用於校驗的值放在配置檔案中,不是在程式碼中寫死,可以怎麼整?
1. 指定配置
在專案資原始檔中,新增一個配置用於表示校驗的請求頭
application.yml
security:
check: yihuihui
配置的讀取,可以使用 Envrioment.getProperty()
,也可以使用 @Value
註解
但是注意上面的攔截器註冊,直接構造的一個方法,新增到InterceptorRegistry
,在攔截器中,即使新增@Value
, @Autowired
註解也不會生效(歸根結底就是這個攔截器並沒有受Spring上下文管理)
2. 攔截器注入Bean
那麼在攔截器中如果想使用Spring容器中的bean物件,可以怎麼整?
2.1 新增靜態的ApplicationContext容器類
一個可行的方法就是在專案中維護一個工具類,其內部持有ApplicationContext
的引用,通過這個工具類來訪問bean物件
@Component
public class SpringUtil implements ApplicationContextAware, EnvironmentAware {
private static ApplicationContext applicationContext;
private static Environment environment;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringUtil.applicationContext = applicationContext;
}
@Override
public void setEnvironment(Environment environment) {
SpringUtil.environment = environment;
}
public static <T> T getBean(Class<T> clz) {
return applicationContext.getBean(clz);
}
public static String getProperty(String key) {
return environment.getProperty(key);
}
}
基於此,在攔截器中,如果想要獲取配置,直接改成下面這樣既可
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 一個簡單的安全校驗,要求請求頭中必須包含 req-name : yihuihui
String header = request.getHeader("req-name");
if (Objects.equals(SpringUtil.getProperty("security.check"), header)) {
return true;
}
log.info("請求頭錯誤: {}", header);
return false;
}
這種方式來訪問bean,優點就是通用性更強,適用範圍廣
2.2 攔截器註冊為bean
上面的方法雖然可行,但是看起來總歸不那麼優雅,那麼有辦法直接將攔截器宣告為bean物件,然後直接使用@Autowired
註解來注入依賴的bean麼
當然是可行的,注意bean註冊的幾種姿勢,我們這裡採用下面這種方式來註冊攔截器
@Bean
public SecurityInterceptor securityInterceptor() {
return new SecurityInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(securityInterceptor()).addPathPatterns("/**");
}
上面通過配置類的方式來宣告bean,然後在註冊攔截器的地方,不直接使用構造方法來建立例項;上面的用法表示是使用spring的bean容器來註冊,基於這種方式來實現攔截器的bean宣告
因此在攔截器中就可以注入其他依賴了
測試就比較簡單了,如下
yihui@M-162D9NNES031U:SpringBlog git:(master) $ curl 'http://127.0.0.1:8080/show' -H 'req-name:yihuihui' -i
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 36
Date: Mon, 15 Nov 2021 10:56:30 GMT
6610e593-7c60-4dab-97b7-cc671c27762d%
3. 小結
本文雖說介紹的是如何在攔截器中注入bean,實際上的知識點依然是建立bean物件的幾種姿勢;上面提供了兩種常見的方式,一個SpringUtil持有SpringContext,然後藉助這個工具類來訪問bean物件,巧用它可以省很多事;
另外一個就是將攔截器宣告為bean,這種方式主要需要注意的點是攔截器的註冊時,不能直接new
攔截器;當然bean的建立,除了上面這個方式之外,還有其他的case,有興趣的小夥伴可以嘗試一下
III. 不能錯過的原始碼和相關知識點
0. 專案
相關博文:
專案原始碼:
- 工程:https://github.com/liuyueyi/spring-boot-demo
- 原始碼:https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/213-web-interceptor
1. 微信公眾號: 一灰灰Blog
盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛
- 一灰灰Blog個人部落格 https://blog.hhui.top
- 一灰灰Blog-Spring專題部落格 http://spring.hhui.top