本文讲解的知识点是基于Springboot 2.1.5.RELEASE版本。
Filter、Servlet、Listener是Java Web开发的三大利器,本文主要介绍Springboot如何通过注解方式创建它们。
要实现Filter、Servlet、Listener组件的功能需要涉及到以下几个核心注解:
<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>
为了方便,我将启动类和控制器放在一起,如下:
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
接口,重写接口方法。@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");
}
}
2019-06-05 10:56:46.153 INFO 4244 --- [ main] com.sdsj.springboot.config.FirstFilter : first filter init, filter name: first_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
2019-06-05 15:10:43.273 INFO 10036 --- [ Thread-8] com.sdsj.springboot.config.FirstFilter : first filter destroy
HttpServlet
抽象类,选择性地重写父类方法,比如我只重写了doGet方法用于处理GET请求。@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");
}
}
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
2019-06-05 15:10:43.272 INFO 10036 --- [ Thread-8] com.sdsj.springboot.config.FirstServlet : first servlet destroy
我们倒回来看看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)
ServletRequestListener
接口,在Web应用程序接收到请求和做出响应时执行监听器方法。
@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());
}
}
: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的讲解。
load-on-startup
部分有出入,请注意。)