Spring Boot Servlet、Filter、Listener、Interceptor

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)單)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容