SpringBoot之攔截器與過濾器解讀
SpringBoot 攔截器 過濾器
1、過濾器和攔截器觸發(fā)時機不一樣,過濾器是在請求進入容器后,但請求進入servlet之前進行預處理的。請求結(jié)束返回也是,是在servlet處理完后,返回給前端之前。
2、攔截器可以獲取IOC容器中的各個bean,而過濾器就不行,因為攔截器是spring提供并管理的,spring的功能可以被攔截器使用,在攔截器里注入一個service,可以調(diào)用業(yè)務邏輯。而過濾器是JavaEE標準,只需依賴servlet api ,不需要依賴spring。
3、過濾器的實現(xiàn)基于回調(diào)函數(shù)。而攔截器(代理模式)的實現(xiàn)基于反射
4、Filter是依賴于Servlet容器,屬于Servlet規(guī)范的一部分,而攔截器則是獨立存在的,可以在任何情況下使用。
5、Filter的執(zhí)行由Servlet容器回調(diào)完成,而攔截器通常通過動態(tài)代理(反射)的方式來執(zhí)行。
6、Filter的生命周期由Servlet容器管理,而攔截器則可以通過IoC容器來管理,因此可以通過注入等方式來獲取其他Bean的實例,因此使用會更方便。
過濾器和攔截器非常相似,但是它們有很大的區(qū)別最簡單明了的區(qū)別就是**過濾器可以修改request,而攔截器不能過濾器需要在servlet容器中實現(xiàn),攔截器可以適用于javaEE,javaSE等各種環(huán)境攔截器可以調(diào)用IOC容器中的各種依賴,而過濾器不能過濾器只能在請求的前后使用,而攔截器可以詳細到每個方法**區(qū)別很多,大家可以去查下
總的來說過濾器就是篩選出你要的東西,比如requeset中你要的那部分攔截器在做安全方面用的比較多,比如 權(quán)限驗證
下面是攔截器的例子:
攔截器定義:
實現(xiàn)HandleInterceptor接口
自定義攔截器類實現(xiàn)HandleInterceptor接口,并使用@Component注解標注為一個組件。
@Component注解 是為了 注入spring其他組件方便, 如果沒有這個注解,自動注入為空
@Autowired
UserService userService;
public class MySelfInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("在業(yè)務處理器處理請求之前被調(diào)用");
//可以進行權(quán)限校驗,安全控制
MyRequestWrapper requestWrapper = new MyRequestWrapper (request);
// 讀取請求內(nèi)容
BufferedReader br = requestWrapper.getReader();
String line = null;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {
sb.append(line);
}
// 將json字符串轉(zhuǎn)換為json對象
JSONObject body = JSONObject.parseObject(sb.toString());
//業(yè)務處理
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("在業(yè)務處理器處理請求執(zhí)行完成后,生成視圖之前執(zhí)行");
//可以對返回來的ModelAndView進行處理,這個時候還未渲染視圖
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("在DispatcherServlet完全處理完請求后被調(diào)用");
//請求已經(jīng)完成,頁面已經(jīng)渲染,數(shù)據(jù)已經(jīng)返回。這個時候可以做一些資源清理,或者記錄請求調(diào)用時間,做性能監(jiān)控
}
}繼承HandleInterceptorAdapter類
自定義攔截器類繼承HandleInterceptor接口的實現(xiàn)類HandleInterceptorAdapter來定義,并使用@Component注解標注為一個組件。
可以根據(jù)需要覆蓋一些方法
@Component
public class MyInterceptor extends HandlerInterceptorAdapter {
public SingleLoginInterceptor() {
super();
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
super.afterCompletion(request, response, handler, ex);
}
@Override
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
super.afterConcurrentHandlingStarted(request, response, handler);
}
}可以看到HandlerInterceptorAdapter類底層是實現(xiàn)了HandlerInterceptor接口,多了兩個方法,要比實現(xiàn)HandlerInterceptor接口的方式功能強大。
這兩個方法都是HandlerInterceptorAdapter類實現(xiàn)的org.springframework.web.servlet.AsyncHandlerInterceptor接口提供的,而AsyncHandlerInterceptor接口又繼承了HandlerInterceptor接口,所以HandlerInterceptorAdapter底層是實現(xiàn)類HandlerInterceptor接口。
自定義攔截器類實現(xiàn)WebRequestInterceptor接口,并使用@Component注解標注為一個組件。
@Component
public class MyInterceptor implements WebRequestInterceptor {
@Override
public void preHandle(WebRequest webRequest) throws Exception {
}
@Override
public void postHandle(WebRequest webRequest, ModelMap modelMap) throws Exception {
}
@Override
public void afterCompletion(WebRequest webRequest, Exception e) throws Exception {
}
}兩個實現(xiàn)接口方式的異同點 相同點 都可以實現(xiàn)controller層的攔截請求 不同點
- 1.WebRequestInterceptor的入?yún)ebRequest是包裝了HttpServletRequest 和HttpServletResponse的,通過WebRequest獲取Request中的信息更簡便。
- 2.WebRequestInterceptor的preHandle是沒有返回值的,說明該方法中的邏輯并不影響后續(xù)的方法執(zhí)行,所以這個接口實現(xiàn)就是為了獲取Request中的信息,或者預設一些參數(shù)供后續(xù)流程使用。
- 3.HandlerInterceptor的功能更強大也更基礎,可以在preHandle方法中就直接拒絕請求進入controller方法。
實現(xiàn)RequestInterceptor接口
此方式為微服務Feign調(diào)用的自定義攔截器,實現(xiàn)各個微服務之間的參數(shù)傳遞。
@Configuration
public class CenterinsRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
}
}攔截器的注冊
創(chuàng)建一個自定義類,繼承WebMvcConfigurerAdapter類重寫addInterceptors方法。
@Configuration
public class WebCofiguration extends WebMvcConfigurerAdapter {
@Bean
MyInterceptor getMyInterceptor (){
return new MyInterceptor ();
}
public void addInterceptors(InterceptorRegistry registry) {
// 將自己定義的攔截器注入進來進行攔截操作
//registry.addInterceptor(new MySelfInterceptor ()) // 如果是new 出來的對象 會導致 攔截器中自動裝配為空
registry.addInterceptor(getMyInterceptor ())
.addPathPatterns("/**")
.excludePathPatterns("/logout");
//過濾器可以添加多個,這里的addPathPatterns的/**是對所有的請求都做攔截。
//excludePathPatterns代表排除url的攔截路徑,即不攔截
}
}此類在SpringBoot2.0以后已經(jīng)廢除,但仍可使用。
推薦使用以下兩種方式來代替此方式。
1. 創(chuàng)建一個自定義類繼承WebMvcConfigurationSupport類,實現(xiàn)addInterceptors。
@Configuration
public class MyInterceptorConfig extends WebMvcConfigurationSupport {
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
super.addInterceptors(registry);
}
}此方式會導致默認的靜態(tài)資源被攔截,這就需要我們手動將靜態(tài)資源放開。
除了重寫方法外還需要重寫addResourceHandlers方法來釋放靜態(tài)資源
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
super.addResourceHandlers(registry);
}此方式:一個容器內(nèi)只能有一個WebMvcConfigurationSupport的實現(xiàn)類,也就是說不能有多個繼承類,否則只有一個生效,會造成未知的錯誤,如果想在已有實現(xiàn)類的基礎上(基礎jar包中存在webConfig)還想繼續(xù)添加攔截器,可以選擇繼承WebConfig,但是要super.addInterceptors,避免丟失注冊
原因:在WebMvcAutoConfiguration 中 WebMvcConfigurationSupport 是 @ConditionalOnMissingBean 原來SpringBoot做了這個限制,只有當WebMvcConfigurationSupport類不存在的時候才會生效WebMvc自動化配置

2. 實現(xiàn)WebMvcConfigurer接口
@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 實現(xiàn)WebMvcConfigurer不會導致靜態(tài)資源被攔截
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
}
}另外可以寫個配置注解,根據(jù)注解攔截需要的方法
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogData {
}在controller中需要攔截的方法上加上 @LogData
@LogData
public ResponseMessage getUserList(@RequestParam Long id) {
return ResponseMessage.ok();
}可以在攔截器中
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//這個方法將在請求處理之前進行調(diào)用。注意:如果該方法的返回值為false ,將視為當前請求結(jié)束,不僅自身的攔截器會失效,還會導致其他的攔截器也不再執(zhí)行。
log.info("進入到攔截器中:preHandle() 方法");
HandlerMethod handlerMethod = (HandlerMethod) handler;
LogData loginVerify = handlerMethod.getMethodAnnotation(LogData .class);
if (loginVerify == null) {
log.info("不需要對該路徑 進行攔截");
return true;
}else {
log.info("對該路徑 進行攔截");
log.info("業(yè)務操作...");
return true;
}
}攔截器中request請求被讀取一次后,controller獲取為空
繼承HandleInterceptorAdapter類 和 實現(xiàn)HandleInterceptor接口 實現(xiàn)WebRequestInterceptor接口 自定義類實現(xiàn)RequestInterceptor接口
HttpServletRequest的輸入流只能讀取一次的原因當我們調(diào)用getInputStream()方法獲取輸入流時得到的是一個InputStream對象,而實際類型是ServletInputStream,它繼承與InputStream。
InputStream的read()方法內(nèi)部有一個position,標志當前流被讀取到的位置,每讀取一次,該標志就會移動一次,如果讀到最后,read()返回-1,表示已經(jīng)讀取完了,如果想要重新讀取,則需要調(diào)用reset()方法,position就會移動到上次調(diào)用mark的位置,mark默認是0,所有就能重頭再讀了。調(diào)用reset()方法的前提是已經(jīng)重寫了reset()方法,當然能否reset也是有條件的,它取決于markSupported()方法是否返回true。
InputStream默認不實現(xiàn)reset(),并且markSupported()默認也是返回false
我們可以把流讀取出來后用容器存起來,后面就可以多次利用了。JavaEE提供了一個HttpServletRequestWrapper類,它是一個http請求包裝器,基于裝飾者模式實現(xiàn)類HttpServletRequest界面。
繼承HttpServletRequestWrapper,將請求體中的流copy一份,可以重寫getinputStream()和getReader()方法,或自定義方法供外部使用
import dm.jdbc.e.e;
import dm.jdbc.util.StreamUtil;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.StandardCharsets;
/**
* 重寫 HttpServletRequestWrapper
*
*/
@Slf4j
public class MyRequestWrapper extends HttpServletRequestWrapper {
private byte[] body; //用于保存讀取body中數(shù)據(jù)
public MyRequestWrapper (HttpServletRequest request) throws IOException {
super(request);
//讀取請求的數(shù)據(jù)保存到本類當中
//body = StreamUtil.readBytes(request.getReader(), "UTF-8");
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
InputStream inputStream = null;
try{
inputStream = request.getInputStream();
if(inputStream != null){
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead =-1;
while((bytesRead = bufferedReader.read(charBuffer)) >0){
stringBuilder.append(charBuffer,0, bytesRead);
}
}else{
stringBuilder.append("");
}
}catch (Exception e){
}finally {
if(inputStream != null){
inputStream.close();
}
if(bufferedReader != null){
bufferedReader.close();
}
}
body = stringBuilder.toString().getBytes();
}
//覆蓋(重寫)父類的方法
@SuppressFBWarnings("DM_DEFAULT_ENCODING")
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
//覆蓋(重寫)父類的方法
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isReady() {
// TODO Auto-generated method stub
return false;
}
@Override
public void setReadListener(ReadListener arg0) {
// TODO Auto-generated method stub
}
};
}
/**
* 獲取body中的數(shù)據(jù)
* @return
*/
public byte[] getBody() {
return body;
}
/**
* 把處理后的參數(shù)放到body里面
* @param body
*/
public void setBody(byte[] body) {
this.body = body;
}
}定義過濾器
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* 過濾器
*
*/
@Slf4j
@WebFilter(urlPatterns = "/*", filterName = "logSignDataFilter")
public class LogSignDataFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if(request instanceof HttpServletRequest){
requestWrapper = new MyRequestWrapper ((HttpServletRequest) request);
}
if(requestWrapper == null){
filterChain.doFilter(request, response);
}else{
filterChain.doFilter(requestWrapper, response);
}
}
@Override
public void init(FilterConfig config) throws ServletException {
}
}總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
JDK生成WebService客戶端代碼以及調(diào)用方式
WebService 是一種跨編程語言和跨操作系統(tǒng)平臺的遠程調(diào)用技術(shù),下面這篇文章主要給大家介紹了關(guān)于JDK生成WebService客戶端代碼以及調(diào)用方式的相關(guān)資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2024-08-08
Java使用JDBC向MySQL數(shù)據(jù)庫批次插入10W條數(shù)據(jù)(測試效率)
使用JDBC連接MySQL數(shù)據(jù)庫進行數(shù)據(jù)插入的時候,特別是大批量數(shù)據(jù)連續(xù)插入(100000),如何提高效率呢?今天小編通過本教程給大家介紹下2016-12-12
Java中如何使用Redis GEO測算經(jīng)緯度距離
GEO也是Redis中提供的一種數(shù)據(jù)類型,可以將其理解成ZSet有序集合類型,本文給大家介紹Java-使用Redis GEO測算經(jīng)緯度距離,感興趣的朋友一起看看吧2023-11-11
Spring Boot 如何將 Word 轉(zhuǎn)換為 PDF
這篇文章主要介紹了Spring Boot將Word轉(zhuǎn)換為 PDF,本文通過示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-08-08
SpringBoot+MyBatis簡單數(shù)據(jù)訪問應用的實例代碼
這篇文章主要介紹了SpringBoot+MyBatis簡單數(shù)據(jù)訪問應用的實例代碼,需要的朋友可以參考下2017-05-05

