之前介紹過一篇攔截器的基本使用姿勢: 【WEB系列】SpringBoot之攔截器Interceptor使用姿勢介紹
在SpringBoot中,通過實現WebMvcConfigurer
的addInterceptors
方法來注冊攔截器,那么當我們的攔截器中希望使用Bean時,可以怎么整?
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