Web開發(fā)使用 Controller 基本上可以完成大部分需求,但是我們還可能會(huì)用到 Servlet、Filter、Listener、Interceptor 等等。
當(dāng)使用Spring-Boot時(shí),嵌入式Servlet容器通過(guò)掃描注解的方式注冊(cè)Servlet、Filter和Servlet規(guī)范的所有監(jiān)聽器(如HttpSessionListener監(jiān)聽器)。
Spring boot 的主 Servlet 為 DispatcherServlet,其默認(rèn)的url-pattern為“/”。也許我們?cè)趹?yīng)用中還需要定義更多的Servlet,該如何使用SpringBoot來(lái)完成呢?
在spring boot中添加自己的Servlet有兩種方法,代碼注冊(cè)Servlet和注解自動(dòng)注冊(cè)(Filter和Listener也是如此)。
一、代碼注冊(cè)通過(guò)ServletRegistrationBean、 FilterRegistrationBean 和ServletListenerRegistrationBean 獲得控制。也可以通過(guò)實(shí)現(xiàn) ServletContextInitializer 接口直接注冊(cè)。
二、在 SpringBootApplication 上使用@ServletComponentScan 注解后,Servlet、Filter、Listener 可以直接通過(guò) @WebServlet、@WebFilter、@WebListener 注解自動(dòng)注冊(cè),無(wú)需其他代碼。
通過(guò)代碼注冊(cè)Servlet示例代碼:
SpringBootSampleApplication.java
package org.springboot.sample;
import org.springboot.sample.servlet.MyServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.DispatcherServlet;
@SpringBootApplication
public class SpringBootSampleApplication {
/**
* 使用代碼注冊(cè)Servlet(不需要@ServletComponentScan注解)
*
* @return
* @author SHANHY
* @create 2016年1月6日
*/
@Bean
public ServletRegistrationBean servletRegistrationBean() {
return new ServletRegistrationBean(new MyServlet(), "/xs/*");// ServletName默認(rèn)值為首字母小寫,即myServlet
}
public static void main(String[] args) {
SpringApplication.run(SpringBootSampleApplication.class, args);
}
}
MyServlet.java
package org.springboot.sample.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet
*
* @author 單紅宇(365384722)
* @myblog http://blog.csdn.net/catoop/
* @create 2016年1月6日
*/
//@WebServlet(urlPatterns="/xs/*", description="Servlet的說(shuō)明")
public class MyServlet extends HttpServlet{
private static final long serialVersionUID = -8685285401859800066L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(">>>>>>>>>>doGet()<<<<<<<<<<<");
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(">>>>>>>>>>doPost()<<<<<<<<<<<");
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>Hello World</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>大家好,我的名字叫Servlet</h1>");
out.println("</body>");
out.println("</html>");
}
}
使用注解注冊(cè)Servlet示例代碼
SpringBootSampleApplication.java
package org.springboot.sample;
import org.springboot.sample.servlet.MyServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.DispatcherServlet;
@SpringBootApplication
@ServletComponentScan
public class SpringBootSampleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootSampleApplication.class, args);
}
}
MyServlet2.java
package org.springboot.sample.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet
*
* @author 單紅宇(365384722)
* @myblog http://blog.csdn.net/catoop/
* @create 2016年1月6日
*/
@WebServlet(urlPatterns="/xs/myservlet", description="Servlet的說(shuō)明") // 不指定name的情況下,name默認(rèn)值為類全路徑,即org.springboot.sample.servlet.MyServlet2
public class MyServlet2 extends HttpServlet{
private static final long serialVersionUID = -8685285401859800066L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(">>>>>>>>>>doGet2()<<<<<<<<<<<");
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(">>>>>>>>>>doPost2()<<<<<<<<<<<");
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>Hello World</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>大家好,我的名字叫Servlet2</h1>");
out.println("</body>");
out.println("</html>");
}
}
使用 @WebServlet 注解,其中可以設(shè)置一些屬性。
有個(gè)問題:DispatcherServlet 默認(rèn)攔截“/”,MyServlet 攔截“/xs/*”,MyServlet2 攔截“/xs/myservlet”,那么在我們?cè)L問http://localhost:8080/xs/myservlet 的時(shí)候系統(tǒng)會(huì)怎么處理呢?
如果訪問 http://localhost:8080/xs/abc 的時(shí)候又是什么結(jié)果呢?這里就不給大家賣關(guān)子了,其結(jié)果是匹配的優(yōu)先級(jí)是從精確到模糊,符合條件的Servlet并不會(huì)都執(zhí)行
既然系統(tǒng)DispatcherServlet 默認(rèn)攔截“/”,那么我們是否能做修改呢,答案是肯定的,我們?cè)赟pringBootSampleApplication中添加代碼:
/**
* 修改DispatcherServlet默認(rèn)配置
*
* @param dispatcherServlet
* @return
* @author SHANHY
* @create 2016年1月6日
*/
@Bean
public ServletRegistrationBean dispatcherRegistration(DispatcherServlet dispatcherServlet) {
ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet);
registration.getUrlMappings().clear();
registration.addUrlMappings("*.do");
registration.addUrlMappings("*.json");
return registration;
}
當(dāng)然,這里可以對(duì)DispatcherServlet做很多修改,并非只是UrlMappings。
直接使用@WebFilter和@WebListener的方式,完成一個(gè)Filter 和一個(gè) Listener。
過(guò)濾器(Filter)文件
MyFilter.java
package org.springboot.sample.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
/**
* 使用注解標(biāo)注過(guò)濾器
* @WebFilter將一個(gè)實(shí)現(xiàn)了javax.servlet.Filter接口的類定義為過(guò)濾器
* 屬性filterName聲明過(guò)濾器的名稱,可選
* 屬性u(píng)rlPatterns指定要過(guò)濾 的URL模式,也可使用屬性value來(lái)聲明.(指定要過(guò)濾的URL模式是必選屬性)
*
* @author 單紅宇(365384722)
* @myblog http://blog.csdn.net/catoop/
* @create 2016年1月6日
*/
@WebFilter(filterName="myFilter",urlPatterns="/*")
public class MyFilter implements Filter {
@Override
public void destroy() {
System.out.println("過(guò)濾器銷毀");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("執(zhí)行過(guò)濾操作");
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig config) throws ServletException {
System.out.println("過(guò)濾器初始化");
}
}
ServletContext監(jiān)聽器(Listener)文件
MyServletContextListener.java
package org.springboot.sample.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
/**
* 使用@WebListener注解,實(shí)現(xiàn)ServletContextListener接口
*
* @author 單紅宇(365384722)
* @myblog http://blog.csdn.net/catoop/
* @create 2016年1月6日
*/
@WebListener
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContex初始化");
System.out.println(sce.getServletContext().getServerInfo());
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("ServletContex銷毀");
}
}
ServletContext監(jiān)聽器(Listener)文件
MyHttpSessionListener.java
package org.springboot.sample.listener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* 監(jiān)聽Session的創(chuàng)建與銷毀
*
* @author 單紅宇(365384722)
* @myblog http://blog.csdn.net/catoop/
* @create 2016年1月6日
*/
@WebListener
public class MyHttpSessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("Session 被創(chuàng)建");
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("ServletContex初始化");
}
}
注意不要忘記在 SpringBootSampleApplication.java 上添加 @ServletComponentScan 注解。
在啟動(dòng)的過(guò)程中我們會(huì)看到輸出:
ServletContex初始化
Apache Tomcat/8.0.30
過(guò)濾器初始化
服務(wù)啟動(dòng)后,隨便訪問一個(gè)頁(yè)面,會(huì)看到輸出:
執(zhí)行過(guò)濾操作
Session 被創(chuàng)建
至于如何使用代碼的方式注冊(cè)Filter和Listener,請(qǐng)參考Servlet的介紹。不同的是需要使用 FilterRegistrationBean 和 ServletListenerRegistrationBean 這兩個(gè)類。
最后上一張工程結(jié)構(gòu)圖:

過(guò)濾器屬于Servlet范疇的API,與Spring 沒什么關(guān)系。
Web開發(fā)中,我們除了使用 Filter 來(lái)過(guò)濾web請(qǐng)求外,還可以使用Spring提供的HandlerInterceptor(攔截器)。
HandlerInterceptor 的功能跟過(guò)濾器類似,但是提供更精細(xì)的的控制能力:在request被響應(yīng)之前、request被響應(yīng)之后、視圖渲染之前以及request全部結(jié)束之后。我們不能通過(guò)攔截器修改request內(nèi)容,但是可以通過(guò)拋出異常(或者返回false)來(lái)暫停request的執(zhí)行。
實(shí)現(xiàn) UserRoleAuthorizationInterceptor 的攔截器有:
- ConversionServiceExposingInterceptor
- CorsInterceptor
- LocaleChangeInterceptor
- PathExposingHandlerInterceptor
- ResourceUrlProviderExposingInterceptor
- ThemeChangeInterceptor
- UriTemplateVariablesHandlerInterceptor
- UserRoleAuthorizationInterceptor
其中 LocaleChangeInterceptor 和 ThemeChangeInterceptor 比較常用。
配置攔截器也很簡(jiǎn)單,Spring提供了基礎(chǔ)類WebMvcConfigurerAdapter ,我們只需要重寫 addInterceptors 方法添加注冊(cè)攔截器。
實(shí)現(xiàn)自定義攔截器只需要3步:
- 創(chuàng)建我們自己的攔截器類并實(shí)現(xiàn) HandlerInterceptor 接口。
- 創(chuàng)建一個(gè)Java類繼承WebMvcConfigurerAdapter,并重寫 addInterceptors 方法。
- 實(shí)例化我們自定義的攔截器,然后將對(duì)象手動(dòng)添加到攔截器鏈中(在addInterceptors方法中添加)。
PS:本文重點(diǎn)在如何在Spring-Boot中使用攔截器,關(guān)于攔截器的原理請(qǐng)大家查閱資料了解。
代碼示例:
MyInterceptor1.java
package org.springboot.sample.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
* 自定義攔截器1
*
* @author 單紅宇(365384722)
* @myblog http://blog.csdn.net/catoop/
* @create 2016年1月7日
*/
public class MyInterceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println(">>>MyInterceptor1>>>>>>>在請(qǐng)求處理之前進(jìn)行調(diào)用(Controller方法調(diào)用之前)");
return true;// 只有返回true才會(huì)繼續(xù)向下執(zhí)行,返回false取消當(dāng)前請(qǐng)求
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println(">>>MyInterceptor1>>>>>>>請(qǐng)求處理之后進(jìn)行調(diào)用,但是在視圖被渲染之前(Controller方法調(diào)用之后)");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println(">>>MyInterceptor1>>>>>>>在整個(gè)請(qǐng)求結(jié)束之后被調(diào)用,也就是在DispatcherServlet 渲染了對(duì)應(yīng)的視圖之后執(zhí)行(主要是用于進(jìn)行資源清理工作)");
}
}
MyInterceptor2.java
package org.springboot.sample.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
* 自定義攔截器2
*
* @author 單紅宇(365384722)
* @myblog http://blog.csdn.net/catoop/
* @create 2016年1月7日
*/
public class MyInterceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println(">>>MyInterceptor2>>>>>>>在請(qǐng)求處理之前進(jìn)行調(diào)用(Controller方法調(diào)用之前)");
return true;// 只有返回true才會(huì)繼續(xù)向下執(zhí)行,返回false取消當(dāng)前請(qǐng)求
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println(">>>MyInterceptor2>>>>>>>請(qǐng)求處理之后進(jìn)行調(diào)用,但是在視圖被渲染之前(Controller方法調(diào)用之后)");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println(">>>MyInterceptor2>>>>>>>在整個(gè)請(qǐng)求結(jié)束之后被調(diào)用,也就是在DispatcherServlet 渲染了對(duì)應(yīng)的視圖之后執(zhí)行(主要是用于進(jìn)行資源清理工作)");
}
}
MyWebAppConfigurer.java
package org.springboot.sample.config;
import org.springboot.sample.interceptor.MyInterceptor1;
import org.springboot.sample.interceptor.MyInterceptor2;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class MyWebAppConfigurer extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 多個(gè)攔截器組成一個(gè)攔截器鏈
// addPathPatterns 用于添加攔截規(guī)則
// excludePathPatterns 用戶排除攔截
registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**");
super.addInterceptors(registry);
}
}
然后在瀏覽器輸入地址: http://localhost:8080/index 后,控制臺(tái)的輸出為:
>>>MyInterceptor1>>>>>>>在請(qǐng)求處理之前進(jìn)行調(diào)用(Controller方法調(diào)用之前)
>>>MyInterceptor2>>>>>>>在請(qǐng)求處理之前進(jìn)行調(diào)用(Controller方法調(diào)用之前)
>>>MyInterceptor2>>>>>>>請(qǐng)求處理之后進(jìn)行調(diào)用,但是在視圖被渲染之前(Controller方法調(diào)用之后)
>>>MyInterceptor1>>>>>>>請(qǐng)求處理之后進(jìn)行調(diào)用,但是在視圖被渲染之前(Controller方法調(diào)用之后)
>>>MyInterceptor2>>>>>>>在整個(gè)請(qǐng)求結(jié)束之后被調(diào)用,也就是在DispatcherServlet 渲染了對(duì)應(yīng)的視圖之后執(zhí)行(主要是用于進(jìn)行資源清理工作)
>>>MyInterceptor1>>>>>>>在整個(gè)請(qǐng)求結(jié)束之后被調(diào)用,也就是在DispatcherServlet 渲染了對(duì)應(yīng)的視圖之后執(zhí)行(主要是用于進(jìn)行資源清理工作)
根據(jù)輸出可以了解攔截器鏈的執(zhí)行順序(具體原理介紹,大家找度娘一問便知)
最后強(qiáng)調(diào)一點(diǎn):只有經(jīng)過(guò)DispatcherServlet 的請(qǐng)求,才會(huì)走攔截器鏈,我們自定義的Servlet 請(qǐng)求是不會(huì)被攔截的,比如我們自定義的Servlet地址 http://localhost:8080/xs/myservlet 是不會(huì)被攔截器攔截的。并且不管是屬于哪個(gè)Servlet 只要符合過(guò)濾器的過(guò)濾規(guī)則,過(guò)濾器都會(huì)攔截。
最后說(shuō)明下,我們上面用到的 WebMvcConfigurerAdapter 并非只是注冊(cè)添加攔截器使用,其顧名思義是做Web配置用的,它還可以有很多其他作用,通過(guò)下面截圖便可以大概了解,具體每個(gè)方法都是干什么用的,留給大家自己研究(其實(shí)都大同小異也很簡(jiǎn)單)。
