Springboot注解方式实现Filter、Servlet、Listener三大组件

本文讲解的知识点是基于Springboot 2.1.5.RELEASE版本。

一. 关键技术

Filter、Servlet、Listener是Java Web开发的三大利器,本文主要介绍Springboot如何通过注解方式创建它们。
要实现Filter、Servlet、Listener组件的功能需要涉及到以下几个核心注解:

  • @ServletComponentScan
  • @WebFilter
  • @WebServlet
  • WebListener

二. 准备工作

1. 配置POM依赖


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>

    <groupId>com.sdsjgroupId>
    <artifactId>springbootartifactId>
    <version>0.0.1-SNAPSHOTversion>
    <packaging>jarpackaging>

    <name>springbootname>
    <description>Demo project for Spring Bootdescription>

    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.1.5.RELEASEversion>
    parent>

    <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
        <java.version>1.8java.version>
    properties>

    
    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>

    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
        plugins>
    build>
project>

2. 创建启动类和控制器

为了方便,我将启动类和控制器放在一起,如下:

package com.sdsj.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@ServletComponentScan
@RestController
public class SpringbootApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootApplication.class, args);
    }

    @RequestMapping("/")
    public String index() {
        System.out.println("This is index page");
        return "Welcome!";
    }

}

在启动类上添加了三个注解,@SpringBootApplication@RestController应该不需要解释了,我们来看下javadoc中关于@ServletComponentScan的描述:

Enables scanning for Servlet components ({@link WebFilter filters}, {@link WebServlet servlets}, and {@link WebListener listeners}). Scanning is only performed when using an embedded web server.

Typically, one of {@code value}, {@code basePackages}, or {@code basePackageClasses} should be specified to control the packages to be scanned for components. In their absence, scanning will be performed from the package of the class with the annotation.

开启Servlet组件扫描,组件包含WebFilter、WebServlet和WebListener,而且扫描只在嵌入式Web服务器中有效。
应该为value、basePackages或basePackageClasses三个属性中的一个设置值,用于指定组件的扫描包路径。如果都没有配置,应用容器将从带有@ServletComponentScan注解的类所在包路径开始扫描。

意思很清楚了,就是如果我们要使用WebFilter、WebServlet和WebListener这三种servlet组件,可以借助@ServletComponentScan注解使之生效。

三. 实现Filter

  1. 创建子类实现Filter接口,重写接口方法。
  2. 添加@WebFilter注解,必须设置urlPatterns参数的值,这是个字符串数组;其他参数可选择性地设置,不设置也行。
package com.sdsj.springboot.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@WebFilter(filterName = "first_filter", urlPatterns = {"/*"})
public class FirstFilter implements Filter {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("first filter init, filter name: " + filterConfig.getFilterName());
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("first filter uri: " + ((HttpServletRequest) servletRequest).getServletPath());
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        log.info("first filter destroy");
    }
}

  1. 启动应用程序,查看日志有这么一条,这是我们在filter的init方法中打印的信息,说明filter的初始化过程是在程序启动时执行。
2019-06-05 10:56:46.153  INFO 4244 --- [           main] com.sdsj.springboot.config.FirstFilter   : first filter init, filter name: first_filter
  1. 访问http://localhost:8080/,控制台打印日志,可以看到filter的doFilter方法和控制器方法都已经执行,说明我们的filter已经配置成功。
2019-06-05 10:56:51.005  INFO 4244 --- [nio-8080-exec-2] com.sdsj.springboot.config.FirstFilter   : first filter uri: /
2019-06-05 10:56:51.015  INFO 4244 --- [nio-8080-exec-2] ication$$EnhancerBySpringCGLIB$$c2db0e19 : This is index page
  1. 停止应用,控制台打印日志:
2019-06-05 15:10:43.273  INFO 10036 --- [       Thread-8] com.sdsj.springboot.config.FirstFilter   : first filter destroy

四. 实现Servlet

  1. 创建子类继承HttpServlet抽象类,选择性地重写父类方法,比如我只重写了doGet方法用于处理GET请求。
  2. 添加@WebServlet注解,依然是必须设置urlPatterns参数的值,这是个字符串数组;其他参数可选择性地设置,不设置也行。
package com.sdsj.springboot.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "first_servlet", urlPatterns = "/first_servlet")
public class FirstServlet extends HttpServlet {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        log.info("first servlet init, servlet name: " + servletConfig.getServletName());
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        log.info("first servlet uri: " + req.getServletPath());
        PrintWriter out = resp.getWriter();
        out.write("first servlet is running");
        out.flush();
    }

    @Override
    public void destroy() {
        log.info("first servlet destroy");
    }
}
  1. 启动应用程序,查看日志发现servlet的init方法没有执行,先放着,后面会加以说明。
  2. 由于因为我们定义的servlet的urlPatterns = “/first_servlet”,所以访问http://localhost:8080/first_servlet,页面正常显示了响应信息:
    Springboot注解方式实现Filter、Servlet、Listener三大组件_第1张图片
    再查看控制台打印的日志如下,可以发现servlet的init和doGet方法都已经执行,说明servlet已经生效。
2019-06-05 11:26:09.985  INFO 8984 --- [nio-8080-exec-4] com.sdsj.springboot.config.FirstServlet  : first servlet init, servlet name: first_servlet
2019-06-05 11:26:09.988  INFO 8984 --- [nio-8080-exec-4] com.sdsj.springboot.config.FirstServlet  : first servlet uri: /first_servlet
  1. 停止应用,控制台打印日志:
2019-06-05 15:10:43.272  INFO 10036 --- [       Thread-8] com.sdsj.springboot.config.FirstServlet  : first servlet destroy
  1. 我们倒回来看看init的执行时机,为什么servlet的init方法是在请求发起后执行,而不是在程序启动时执行?

    其实这与一个load-on-startup参数有关,init方法的执行时机分两种情况:

    • load-on-startup的值大于等于0,servlet在应用启动时被加载,因此init方法也是在应用启动时执行。
    • load-on-startup的值小于0或者不配置(默认),则init方法会在servlet第一次处理请求前执行,而且只执行一次。

    @WebServlet注解中定义了loadOnStartup属性设置load-on-startup的值,因此如果我们想在servlet实例化时调用init方法,可以改为下面的配置,其中loadOnStartup的值越大,表示init方法越晚执行:

@WebServlet(name = "first_servlet", urlPatterns = "/first_servlet", loadOnStartup = 1)

五. 实现Listener

  1. 创建子类实现以下接口中的一个或多个,并且重写相应的接口方法。
    根据实现的接口不同,监听器作用范围也不相同,本文不做详细介绍,可以查看对应的javadoc描述,案例中我实现了ServletRequestListener接口,在Web应用程序接收到请求和做出响应时执行监听器方法。
    • javax.servlet.http.HttpSessionAttributeListener
    • javax.servlet.http.HttpSessionListener
    • javax.servlet.ServletContextAttributeListener
    • javax.servlet.ServletContextListener
    • javax.servlet.ServletRequestAttributeListener
    • javax.servlet.ServletRequestListener
    • javax.servlet.http.HttpSessionIdListener
  2. 添加@WebListener注解,该注解只有一个value属性用于描述监听器信息,可以不设置。
package com.sdsj.springboot.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpServletRequest;

@WebListener
public class FirstListener implements ServletRequestListener {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
        log.info("first listener has sent response, uri: " + request.getServletPath());
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
        log.info("first listener has received request, uri: " + request.getServletPath());
    }

}
  1. 启动应用程序,访问http://localhost:8080,查看控制台打印的日志,可以发现在执行控制器业务前后各调用了一次自定义监听器的方法,监听器已经生效。
:28:39.031  INFO 2708 --- [nio-8080-exec-8] c.sdsj.springboot.config.FirstListener   : first listener has received request, uri: /
2019-06-05 16:28:39.032  INFO 2708 --- [nio-8080-exec-8] ication$$EnhancerBySpringCGLIB$$20890345 : This is index page
2019-06-05 16:28:39.034  INFO 2708 --- [nio-8080-exec-8] c.sdsj.springboot.config.FirstListener   : first listener has sent response, uri: /

六. 参考

这里列出一些有关Servlet、Filter、Listener的讲解。

  • Java Web(一) Servlet详解
    (这篇文章大部分都写得不错,但是关于load-on-startup部分有出入,请注意。)

你可能感兴趣的:(springboot)