SpringBoot:注解方式实现 Filter、Servlet、Listener

原文地址blog.csdn.net/u010922732/…

关键技术

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

准备工作

1. 配置 POM 依赖

<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>

  <groupId>com.sdsj</groupId>
  <artifactId>springboot</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>springboot</name>
  <description>Demo project for Spring Boot</description>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.5.RELEASE</version>
  </parent>

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

  <!-- Add typical dependencies for a web application -->
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <!-- Package as an executable jar -->
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </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,页面正常显示了响应信息:

    再查看控制台打印的日志如下,可以发现 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
  1. 添加@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: /
复制代码

猜你喜欢

转载自juejin.im/post/7219688933575376955