1. Filter简介
Filter,过滤器,顾名思义,用于过滤东西的,在JavaWeb中的过滤器位于客户端和后端服务器资源之间,过滤客户端发送过来的请求。
在java中常用于权限拦截,放行符合条件的请求,如,对于某些敏感资源,需要携带token或者需要登陆后才可以访问,如果请求没有携带token或没有登陆,则拦截不允许访问,否则放行请求。
也用于做功能增强,在某些请求中,想要记录请求的ip,请求参数等,可以在过滤器中添加相关逻辑实现,因为操作粒度的问题,目前springmvc的拦截器使用更多一点。
注意,拦截器是一项Servlet技术,它和servlet以及servletListener是servlet的三大最重要组件。它依赖于tomcat等web容器。
2. 过滤器使用
2.1 springboot配置过滤器
2.1.1 创建拦截器类
先创建Filter类Filter1,实现Filter接口,注意这里实现的接口是servlet下的,它是一项servlet技术。
package com.william.filter;
import javax.servlet.*;
import java.io.IOException;
public class Filter1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
2.1.2 重写方法
Filter接口中定义了三个方法,init/doFilter/destory,它们就是过滤器的生命周期方法。
init在filter初始化阶段被调用,destroy在filter摧毁的时候调用,整个生命周期中都只会被调用一次。
doFilter方法是最重要的方法,他在过滤请求的时候调用,默认传入的参数中我们可以拿到请求中携带的所有参数以及请求返回的所有参数。
一般init方法和destroy方法很少使用,doFilter方法使用最多,过滤器的业务基本都是围绕着这个方法展开的。
2.2.3 doFilter方法
doFilter方法的默认参数中,servletRequest和servletResponse不必多说,是当前请求的请求对象和返回对象
filterChain是过滤器链,暂时可以理解为由多个filter组成的一个链子,责任链设计模式的体现。
在当前过滤器执行完之后,如果放行,则调用filterChain的doFilter方法,执行下一个过滤器的逻辑,这里需要传入 servletRequest和servletResponse对象,我们可以将处理后的这两个对象传进去实现业务增强。
if (servletRequest.getLocalAddr().equals("127.1.1.1")){
servletRequest.setAttribute("xxx","xxx");
filterChain.doFilter(servletRequest,servletResponse);
}
如果不放行,则不需要调用,直接返回即可,return null,或者自定义一个返回类型放到servletResponse的输出流中。
if (!servletRequest.getLocalAddr().equals("127.1.1.1")){
PrintWriter writer = servletResponse.getWriter();
writer.write("xxxxxx");
writer.flush();
return;
}
注意,如果没有调用filterChain.doFilter()方法,则不会进入后端的请求处理逻辑。
2.2.4 注册过滤器
注册过滤器有两个途径,一种是通过@WebFilter和@ServletComponentScan注解,另一种是通过FilterRegistrationBean。
第一种方法只需要在创建的Filter实现类上添加@WebFilter注解,然后在项目启动类上添加@ServletComponentScan注解即可。
@WebServlet
public class Filter1 implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
}
}
@ServletComponentScan会扫描所有servlet相关的类,将其注入到spring容器中,可以联想到,添加了@WebListener和@WebServlet的类也会被这种方法扫描注入。
@SpringBootApplication
@ServletComponentScan
public class JavaWebFilterApplication {
public static void main(String[] args) {
SpringApplication.run(JavaWebFilterApplication.class, args);
}
}
第二种方法需要新建一个配置类,然后在配置类中使用@Bean注入FilterRegistrationBean实例对象,在注入的时候将过滤器添加到FilterRegistrationBean实例的依赖中。
再创建一个filter2拦截器并按照第二种方法注入到spring中。
package com.william.config;
import com.william.filter.Filter2;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(filter2());
return registrationBean;
}
@Bean
public Filter2 filter2(){
return new Filter2();
}
}
注意,这里在setFilter的时候,如果使用new Filter2()的话,会导致Filter2中的依赖无法注入,只有被spring管理的bean实例中的依赖才会被自动注入。
此外,一个Filter对应一个FilterRegistrationBean,不能把所有的filter在一个FilterRegistrationBean里面注入。
2.2.5 配置过滤器拦截
以上只是简单的注入了过滤器,还没有添加拦截规则,默认会拦截所有请求,但是有时候我们只需要拦截一些特定的请求,这里就要添加拦截规则了。
这里在项目中添加两个处理器方法,getMessage1和getMessage2
package com.william.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MainController {
@GetMapping("/getMessage1")
public String getMessage1(){
return "hello,world!1";
}
@GetMapping("/getMessage2")
public String getMessage2(){
return "hello,world!2";
}
}
在filter1的@WebFilter注解后面添加拦截规则 ,这样它就只会拦截过滤getMessage1这个请求了。
@WebFilter(value = "/getMessage1")
在注入filter2的配置方法中添加下面代码就可以让filter2只拦截过滤getMessage2的请求了。
registrationBean.addUrlPatterns("/getMessage2");
过滤器主要有四种拦截方式,REQUEST/FORWARD/INCLUDE/ERROR,其中REQUEST和FORWARD使用相对多点,默认拦截的请求方式是REQUEST,实际上基本也只用REQUEST,如果需要其他方式,我们可以使用和上面相似的方式进行设置。
@WebFilter(value = "/getMessage1",dispatcherTypes = DispatcherType.FORWARD)
registrationBean.setDispatcherTypes(DispatcherType.FORWARD);
拦截规则中的通配符使用请自行百度,无非是/*,/xx/*,/*.xxx之类的,没什么好说的。
2.2 过滤器的顺序问题
在@WebFilter注解中并没有找到filter排序相关的参数设置,但是经过简单测试,默认的执行顺序应该是按照类名的首字母从小到大一次排序的。
public @interface WebFilter {
String description() default "";
String displayName() default "";
WebInitParam[] initParams() default {};
String filterName() default "";
String smallIcon() default "";
String largeIcon() default "";
String[] servletNames() default {};
String[] value() default {};
String[] urlPatterns() default {};
DispatcherType[] dispatcherTypes() default {DispatcherType.REQUEST};
boolean asyncSupported() default false;
}
在registrationBean中可以找到setOrder方法,所以如果需要给过滤器排序的话,尽量使用第二种方式注入过滤器,设置的order数值越小,越早执行。
registrationBean.setOrder(1);
经过测试和查找资料,设置了order的filter永远在没有设置order的filter之前执行,如果都没有设置order,则按照类名排序。