0%

SpringBoot2.x(六)拦截器实战和Servlet3.0自定义Filter、Listener

SpringBoot过滤器

从springboot启动日志中我们可以发现springboot默认加载的过滤器:

1
2
3
4
2018-07-19 18:43:32.748  INFO 9332 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-07-19 18:43:32.748 INFO 9332 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-07-19 18:43:32.748 INFO 9332 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-07-19 18:43:32.748 INFO 9332 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]

这四个过滤器位于 spring-boot.jar中的 org.springframework.boot.web.servlet.filter包下。

Ordered

他们都实现了 org.springframework.core.Ordered接口,通过其中的 int getOrder()对过滤器的优先级进行排序。

值得注意的是:Higher values are interpreted as lower priority(返回的int值越高意味着优先级越低,处理被拦截请求的顺序越靠后)。不要试图将两个过滤器返回相同的值,那样会导致排序失败。


Servlet3.0配置过滤器

  1. 创建自定义过滤器类 MyFilter,实现 Filter接口中的方法
    1. init()给过滤器初始化一些变量
    2. doFilter()编写拦截、放行逻辑
    3. destroy()多用来释放资源
  2. MyFilter加上 @WebFilterserlvet3.0提供)注解以注册到servlet容器中,并通过 urlPatterns属性指定拦截路径
    1. urlPatterns指明拦截的路径
    2. filterName,给过滤器命名,名称随意但最好不要重复
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package top.zhenganwen.springbootweb.filter;

import org.apache.catalina.servlet4preview.http.HttpServletRequest;

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

@WebFilter(urlPatterns = "/hello/*",filterName = "myFilter")
public class MyFilter implements Filter {

/**
* 在servlet容器启动时被调用
* @param filterConfig
* @throws ServletException
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("MyFilter被加载了");
}

/**
* 请求被拦截时被调用
* @param request
* @param response
* @param chain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
System.out.println("请求的接口是:" + req.getRequestURL());
System.out.println("经过了MyFilter................");
//放行必须调用doFilter,拦截可以通过response重定向到提示页面(非前后端分离场景)
chain.doFilter(request, response);
}

@Override
public void destroy() {

}
}
  1. 给启动类加上 @ServletComponentScan注解以扫描 servlet组件
1
2
3
4
5
6
7
@SpringBootApplication
@ServletComponentScan
public class Application{
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
}
  1. 访问 localhost:8080/hello/hello
1
2
3
4
5
6
7
8
@RestController
@RequestMapping("/hello")
public class HelloWorldController {
@RequestMapping("/hello")
public String hello() {
return "Hello World";
}
}
  1. 控制台输出
1
2
请求的接口是:http://localhost:8080/hello/hello
经过了MyFilter................

Servlet3.0的注解自定义原生Servlet实战

  1. 创建自定义Servlet类 如UserServlet,继承 HttpServlet并重写 doGetdoPost方法
  2. UserServlet上添加 @WebServlet注解并通过 urlPatterns属性配置请求路径,该注解在 Servlet3.0之后提供
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package top.zhenganwen.springbootweb.servlet;

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 = "userServlet",urlPatterns = "/user/hello")
public class UserServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

resp.getWriter().write("hello");
resp.getWriter().flush();
resp.getWriter().close();
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
}
  1. 在启动类上添加 @ServletComponentScan注解
  2. 访问 localhost:8080/user/hello,页面响应 hello

Servlet3.0的注解自定义原生Listener实战

常用的监听器接口有 ServletContextListenerServletRequestListenerServletSessionListener,分别监听容器、请求、会话的创建和销毁。还有 ServletContextAttributeListenerServletRequestAttributeListenerServletSessionAttributeListener对应监听三者的 addAttributeremoveAttribute方法。

  1. 创建自定义监听器类如 MyListener,按需实现上述6个接口中的一个
  2. MyListener上添加 @WebListener
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package top.zhenganwen.springbootweb.servlet;

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 = "userServlet",urlPatterns = "/user/hello")
public class UserServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

resp.getWriter().write("hello");
resp.getWriter().flush();
resp.getWriter().close();
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
}
  1. 在启动类上添加 @ServletComponentScan注解
  2. 访问 localhost:8080/hello/hello
1
2
3
4
5
@RequestMapping("/hello")
public String hello() {
System.out.println("经过HelloWorldController");
return "Hello World";
}
1
2
3
4
5
request initialized============
请求的接口是:http://localhost:8080/hello/hello
经过了MyFilter................
经过HelloWorldController
request destroyed===============

ServletContextListener主要用来做资源加载如加载缓存,ServletRequestListener则主要用来对请求进行统计


自定义拦截器及新旧配置对比

自定义拦截器

preHandlepostHandleafterCompletion的调用时机已在相应的方法体中输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package top.zhenganwen.springbootweb.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("在调用controller之前============");
//return true表示放行,将走到下一个拦截器或controller中
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("在调用controller之后、返回逻辑视图之前=======");
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("在返回逻辑试图之后,通常用来释放资源=========");
}
}

SpringBoot1.x配置拦截器

SpringBoot2.x与SpringBoot1.x配置拦截器的方式略有不同

  1. 创建一个配置类继承 WebMvcConfigurerAdapter并实现其中的 addInterceptors,将自定义的拦截器加入到拦截器列表中
  2. 在配置类上添加 @Configuration注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package top.zhenganwen.springbootweb.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import top.zhenganwen.springbootweb.interceptor.LoginInterceptor;

@Configuration
public class CustomOldWebMvcInterceptor extends WebMvcConfigurerAdapter {

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/cart/*/**");
super.addInterceptors(registry);
}
}

SpringBoot2.x配置拦截器

创建一个配置类实现 WebMvcConfigurer接口并重写 addInterceptors方法,在该配置类上添加 @Configuration注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package top.zhenganwen.springbootweb.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import top.zhenganwen.springbootweb.interceptor.LoginInterceptor;

@Configuration
public class CustomWebMvcConfigure implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/cart/*/**");
WebMvcConfigurer.super.addInterceptors(registry);
}
}

值得注意的是,在 Java8中,接口中的方法是可以有方法体的,并且可以被 defautl修饰作为方法的默认实现。

测试

添加 CartController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package top.zhenganwen.springbootweb.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("cart")
public class CartController {

@RequestMapping("addItem")
public Object addItem() {
System.out.println("添加商品到购物车");
return "success";
}
}

访问 http://localhost:8080/cart/addItem ,控制台输出如下:

1
2
3
4
在调用controller之前============
添加商品到购物车
在调用controller之后、返回逻辑视图之前=======
在返回逻辑试图之后,通常用来释放资源=========

注意:不要试图启用新旧两种配置方式。

拦截器不生效的常见原因

  1. 没有在配置类上添加 @Configuration,这样你的配置类不会生效,配置类中将自定义拦截器添加到拦截器列表的操作自然也不会生效。或者配置类不在启动类扫描范围之内
  2. 请求路径与addPathPattern()的拦截规则不匹配。拦截所有目录应该用 /*/,末尾一定要写成 /***表示匹配当前目录的子目录,**表示匹配当前目录下的所有目录

拦截器和过滤器的区别

  • Filter是基于函数回调doFilter(),而Interceptor则是基于AOP思想
  • Filter在只在Servlet起作用,而Interceptor够深入到方法前后异常抛出前后
  • 依赖于Servlet容器即web应用中,而Interceptor不依赖于Servlet容器所以可以运行在多种环境。在接口调用的生命周期里,Interceptor可以被多次调用,而Filter只能在容器初始化时调用一次。
  • FilterInterceptor的执行顺序:过滤前->拦截前->action执行->拦截后->过滤后
鼓励一下~