主要了解SpringBoot中使用攔截器和過濾器的使用,關於兩者,資料所提及的有:
- 作用域差異:Filter是Servlet規範中規定的,只能用於WEB中,攔截器既可以用於WEB,也可以用於Application、Swing中(即過濾器是依賴於Servlet容器的,和它類似的還有Servlet中的監聽器同樣依賴該容器,而攔截器則不依賴它);
- 規範差異:Filter是Servlet規範中定義的,是Servlet容器支持的,而攔截器是Spring容器內的,是Spring框架支持的;
- 資源差異:攔截器是Spring的一個組件,歸Spring管理配置在Spring的文件中,可以使用Spring內的任何資源、對象(可以粗淺的認爲是IOC容器中的Bean對象),而Filter則不能使用訪問這些資源;
- 深度差異:Filter只在Servlet前後起作用,而攔截器可以深入到方法的前後、異常拋出前後等更深層次的程度作處理(這裏也在一定程度上論證了攔截器是利用java的反射機制實現的),所以在Spring框架中,優先使用攔截器;
1. 關於ApplicationListener
除此之外,還有監聽器,在項目中遇到一個ApplicationListener
,在容器初始化完成後,有一些操作需要處理一下,比如數據的加載、初始化緩存、特定任務的註冊等,此時可以使用這個監聽器,下面是使用方式:
// 1. 定義實現類實現ApplicationListener接口
package com.glodon.tot.listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @author liuwg-a
* @date 2018/11/26 10:48
* @description 容器初始化後要做的數據處理
*/
public class StartupListener implements ApplicationListener<ContextRefreshedEvent> {
private static final Logger logger = LoggerFactory.getLogger(StartupListener.class);
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
try {
logger.info("get local ip is " + InetAddress.getLocalHost().getHostAddress());
} catch (UnknownHostException e) {
e.printStackTrace();
logger.error("occur a exception!");
}
}
}
// 2. 配置上述實現類,返回Bean實例,類似於在xml中配置<bean>標籤
package com.glodon.tot.config;
import com.glodon.tot.listener.StartupListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author liuwg-a
* @date 2018/11/26 11:10
* @description 配置監聽器
*/
@Configuration
public class ListenerConfig {
// 這裏會直接注入
@Bean
public StartupListener startupListener() {
return new StartupListener();
}
}
主要就是上述配置,其他和普通SpringBoot項目一樣,啓動項目即可,最初啓動(即不主動調用接口)的效果如下:
2018-11-26 11:23:18.360 INFO 18792 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-11-26 11:23:18.360 INFO 18792 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-11-26 11:23:18.372 INFO 18792 --- [ main] .m.m.a.ExceptionHandlerExceptionResolver : Detected @ExceptionHandler methods in globalExceptionHandler
2018-11-26 11:23:18.389 INFO 18792 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-11-26 11:23:18.578 INFO 18792 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
welcome to StartupListener...
your ip is 10.4.37.108
2018-11-26 11:23:18.590 INFO 18792 --- [ main] com.glodon.tot.listener.StartupListener : get local ip is 10.4.37.108
2018-11-26 11:23:18.822 INFO 18792 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8081 (http)
2018-11-26 11:23:18.827 INFO 18792 --- [ main] com.glodon.tot.Application : Started Application in 4.616 seconds (JVM running for 11.46)
在調用接口後,添加了如下日誌:
2018-11-26 11:25:46.785 INFO 18792 --- [nio-8081-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-11-26 11:25:46.785 INFO 18792 --- [nio-8081-exec-2] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2018-11-26 11:25:46.803 INFO 18792 --- [nio-8081-exec-2] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 18 ms
2. 關於攔截器
在SpringBoot使用攔截器,流程如下:
// 1. 定義攔截器
package com.glodon.tot.interceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author liuwg-a
* @date 2018/11/26 14:55
* @description 配置攔截器
*/
public class UrlInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(UrlInterceptor.class);
private static final String GET_ALL = "getAll";
private static final String GET_HEADER = "getHeader";
/**
* 進入Controller層之前攔截請求,默認是攔截所有請求
* @param httpServletRequest request
* @param httpServletResponse response
* @param o object
* @return 是否攔截當前請求,true表示攔截當前請求,false表示不攔截當前請求
* @throws Exception 可能出現的異常
*/
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
logger.info("go into preHandle method ... ");
String requestURI = httpServletRequest.getRequestURI();
if (requestURI.contains(GET_ALL)) {
return true;
}
if (requestURI.contains(GET_HEADER)) {
httpServletResponse.sendRedirect("/user/redirect");
}
return true;
}
/**
* 處理完請求後但還未渲染試圖之前進行的操作
* @param httpServletRequest request
* @param httpServletResponse response
* @param o object
* @param modelAndView mv
* @throws Exception E
*/
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
logger.info("go into postHandle ... ");
}
/**
* 視圖渲染後但還未返回到客戶端時的操作
* @param httpServletRequest request
* @param httpServletResponse response
* @param o object
* @param e exception
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
logger.info("go into afterCompletion ... ");
}
}
// 2. 註冊攔截器
package com.glodon.tot.config;
import com.glodon.tot.interceptor.UrlInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author liuwg-a
* @date 2018/11/26 15:30
* @description 配置MVC
*/
@Configuration
public class MvcConfig implements WebMvcConfigurer {
/**
* 註冊配置的攔截器
* @param registry 攔截器註冊器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 這裏的攔截器是new出來的,在Spring框架中可以交給IOC進行依賴注入,直接使用@Autowired注入
registry.addInterceptor(new UrlInterceptor());
}
}
主要就是上述的兩個步驟,需要注意的是preHandle()
方法只有返回true
,Controller中接口方法才能執行,否則不能執行,直接在preHandle()
返回後false
結束流程。上述在配置WebMvcConfigurer
實現類中註冊攔截器時除了使用registry.addInterceptor(new UrlInterceptor())
註冊外,還可以指定哪些URL可以應用這個攔截器,如下:
// 使用自動注入的方式注入攔截器,添加應用、或不應用該攔截器的URI(addPathPatterns/excludePathPatterns)
// addPathPatterns 用於添加攔截的規則,excludePathPatterns 用於排除攔截的規則
registry.addInterceptor(urlInterceptor).addPathPatterns(new UrlInterceptor()).excludePathPatterns("/login");
上述註冊攔截器路徑時(即addPathPatterns
和excludePathPatterns
的參數),是支持通配符的,寫法如下:
通配符 | 說明 |
---|---|
* |
匹配單個字符,如/user/* 匹配到/user/a 等,又如/user/*/ab 匹配到/user/p/ab ; |
** |
匹配任意多字符(包括多級路徑),如/user/** 匹配到user/a 、/user/abs/po 等; |
上述也可以混合使用,如/user/po*/**
、/user/{userId}/*
(pathValue是可以和通配符共存的);
注:
- Spring boot 2.0 後
WebMvcConfigurerAdapter
已經過時,所以這裏並不是繼承它,而是繼承WebMvcConfigurer
; - 這裏在實操時,使用IDEA工具繼承
WebMvcConfigurer
接口時,使用快捷鍵Alt+Enter已經無論如何沒有提示,進入查看發現這個接口中所有的方法變成了default
方法(JDK8新特性,這個修飾符修飾的方法必須要有方法體,此時接口中允許有具體的方法,在實現該接口時,用戶可以選擇是否重寫該方法,而不是必須重寫了),所以沒有提示,可以手動進入接口中複製對應的方法名(不包括default
修飾符)。
對於WebMvcConfigurerAdapter
接口中的常用方法有如下使用示例,可以選擇性重寫:
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Autowired
private HandlerInterceptor urlInterceptor;
private static List<String> myPathPatterns = new ArrayList<>();
/**
* 在初始化Servlet服務時(在Servlet構造函數執行之後、init()之前執行),@PostConstruct註解的方法被調用
*/
@PostConstruct
void init() {
System.out.println("Servlet init ... ");
// 添加匹配的規則, /** 表示匹配所有規則,任意路徑
myPathPatterns.add("/**");
}
/**
* 在卸載Servlet服務時(在Servlet的destroy()方法之前執行),@PreDestroy註解的方法被調用
*/
@PreDestroy
void destroy() {
System.out.println("Servlet destory ... ");
}
/**
* 註冊配置的攔截器
* @param registry 攔截器註冊器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// addPathPatterns 用於添加攔截規則
// excludePathPatterns 用戶排除攔截
registry.addInterceptor(urlInterceptor).addPathPatterns(myPathPatterns).excludePathPatterns("/user/login");
}
// 下面的方法可以選擇性重寫
/**
* 添加類型轉換器和格式化器
* @param registry
*/
@Override
public void addFormatters(FormatterRegistry registry) {
// registry.addFormatterForFieldType(LocalDate.class, new USLocalDateFormatter());
}
/**
* 跨域支持
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "PUT")
.maxAge(3600 * 24);
}
/**
* 添加靜態資源映射--過濾swagger-api
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//過濾swagger
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
registry.addResourceHandler("/swagger-resources/**")
.addResourceLocations("classpath:/META-INF/resources/swagger-resources/");
registry.addResourceHandler("/swagger/**")
.addResourceLocations("classpath:/META-INF/resources/swagger*");
registry.addResourceHandler("/v2/api-docs/**")
.addResourceLocations("classpath:/META-INF/resources/v2/api-docs/");
}
/**
* 配置消息轉換器--這裏用的是ali的FastJson
* @param converters
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//1. 定義一個convert轉換消息的對象;
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
//2. 添加fastJson的配置信息,比如:是否要格式化返回的json數據;
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat,
SerializerFeature.WriteMapNullValue,
SerializerFeature.WriteNullStringAsEmpty,
SerializerFeature.DisableCircularReferenceDetect,
SerializerFeature.WriteNullListAsEmpty,
SerializerFeature.WriteDateUseDateFormat);
//3處理中文亂碼問題
List<MediaType> fastMediaTypes = new ArrayList<>();
fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
//4.在convert中添加配置信息.
fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
//5.將convert添加到converters當中.
converters.add(fastJsonHttpMessageConverter);
}
/**
* 訪問頁面需要先創建個Controller控制類,再寫方法跳轉到頁面
* 這裏的配置可實現直接訪問http://localhost:8080/toLogin就跳轉到login.jsp頁面了
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/toLogin").setViewName("login");
}
/**
* 開啓默認攔截器可用並指定一個默認攔截器DefaultServletHttpRequestHandler,比如在webroot目錄下的圖片:xx.png,
* Servelt規範中web根目錄(webroot)下的文件可以直接訪問的,但DispatcherServlet配置了映射路徑是/ ,
* 幾乎把所有的請求都攔截了,從而導致xx.png訪問不到,這時註冊一個DefaultServletHttpRequestHandler可以解決這個問題。
* 其實可以理解爲DispatcherServlet破壞了Servlet的一個特性(根目錄下的文件可以直接訪問),DefaultServletHttpRequestHandler
* 可以幫助迴歸這個特性的
* @param configurer
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
// 這裏可以自己指定默認的攔截器
configurer.enable("DefaultServletHttpRequestHandler");
}
/**
* 在該方法中可以啓用內容裁決解析器,configureContentNegotiation()方法是專門用來配置內容裁決參數的
* @param configurer
*/
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
// 表示是否通過請求的Url的擴展名來決定media type
configurer.favorPathExtension(true)
// 忽略Accept請求頭
.ignoreAcceptHeader(true)
.parameterName("mediaType")
// 設置默認的mediaType
.defaultContentType(MediaType.TEXT_HTML)
// 以.html結尾的請求會被當成MediaType.TEXT_HTML
.mediaType("html", MediaType.TEXT_HTML)
// 以.json結尾的請求會被當成MediaType.APPLICATION_JSON
.mediaType("json", MediaType.APPLICATION_JSON);
}
}