SpringBoot Filter

过滤器Filter

  • Filter是依赖于Servlet容器,属于Servlet规范的一部分,而拦截器则是独立存在的,可以在任何情况下使用。
  • Filter的执行由Servlet容器回调完成,而拦截器通常通过动态代理的方式来执行。
  • Filter的生命周期由Servlet容器管理,而拦截器则可以通过IoC容器来管理,因此可以通过注入等方式来获取其他Bean的实例,因此使用会更方便。

    过滤器的配置

    现在通过过滤器来实现记录请求执行时间的功能,其实现如下:
    1. public class LogCostFilter implements Filter {
    2. @Override
    3. public void init(FilterConfig filterConfig) throws ServletException {
    4. }
    5. @Override
    6. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    7. long start = System.currentTimeMillis();
    8. filterChain.doFilter(servletRequest,servletResponse);
    9. System.out.println("Execute cost="+(System.currentTimeMillis()-start));
    10. }
    11. @Override
    12. public void destroy() {
    13. }
    14. }
    这段代码的逻辑比较简单,就是在方法执行前先记录时间戳,然后通过过滤器链完成请求的执行,在返回结果之间计算执行的时间。这里需要主要,这个类必须继承Filter类,这个是Servlet的规范,这个跟以前的Web项目没区别。
    但是,有了过滤器类以后,以前的web项目可以在web.xml中进行配置,但是spring boot项目并没有web.xml这个文件,那怎么配置?在Spring boot中,需要FilterRegistrationBean来完成配置。其实现过程如下:
    1. @Configuration
    2. public class FilterConfig {
    3. @Bean
    4. public FilterRegistrationBean registFilter() {
    5. FilterRegistrationBean registration = new FilterRegistrationBean();
    6. registration.setFilter(new LogCostFilter());
    7. registration.addUrlPatterns("/*");
    8. registration.setName("LogCostFilter");
    9. registration.setOrder(1);
    10. return registration;
    11. }
    12. }
    这样配置就完成了,需要配置的选项主要包括实例化Filter类,然后指定url的匹配模式,设置过滤器名称和执行顺序,这个过程和在web.xml中配置其实没什么区别,只是形式不同而已。现在可以启动服务器访问任意URL:
    640.webp

    基于注解方式的配置

    直接用@WebFilter就可以进行配置,同样,可以设置url匹配模式,过滤器名称等。这里需要注意一点的是@WebFilter这个注解是Servlet3.0的规范,并不是Spring boot提供的。除了这个注解以外,还需在配置类中加另外一个注解:@ServletComponetScan,指定扫描的包。
    1. @SpringBootApplication
    2. @MapperScan("com.fcant.blog.dao")
    3. @ServletComponentScan("com.fcant.blog.filters")
    4. public class Application {
    5. public static void main(String[] args) throws Exception {
    6. SpringApplication.run(Application.class, args);
    7. }
    8. }
    首先需要写一个方法继承Filter类,写如下两个自己的Filter类,首先是FirstFilter类,其中@Order里边的数字越小代表越先被该Filter过滤,@WebFilter代表这是个Filter类并把这个类注入到容器中: ```java import java.io.IOException;

import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter;

import org.springframework.core.annotation.Order;

@Order(1) @WebFilter(filterName=”firstFilter”, urlPatterns=”/*”) public class FirstFilter implements Filter {

  1. @Override
  2. public void init(FilterConfig filterConfig) throws ServletException {
  3. }
  4. @Override
  5. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  6. throws IOException, ServletException {
  7. System.out.println("first filter 1");
  8. chain.doFilter(request, response);
  9. System.out.println("first filter 2");
  10. }
  11. @Override
  12. public void destroy() {
  13. }

}

  1. 然后是第二个FilterSecondFilter类:
  2. ```java
  3. import java.io.IOException;
  4. import javax.servlet.Filter;
  5. import javax.servlet.FilterChain;
  6. import javax.servlet.FilterConfig;
  7. import javax.servlet.ServletException;
  8. import javax.servlet.ServletRequest;
  9. import javax.servlet.ServletResponse;
  10. import javax.servlet.annotation.WebFilter;
  11. import org.springframework.core.annotation.Order;
  12. @Order(2)
  13. @WebFilter(filterName="secondFilter", urlPatterns="/*")
  14. public class SecondFilter implements Filter {
  15. @Override
  16. public void init(FilterConfig filterConfig) throws ServletException {
  17. }
  18. @Override
  19. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  20. throws IOException, ServletException {
  21. System.out.println("second filter 1");
  22. System.out.println("before:" + response);
  23. chain.doFilter(request, response);
  24. System.out.println("after:" + response);
  25. System.out.println("second filter 2");
  26. }
  27. @Override
  28. public void destroy() {
  29. }
  30. }

然后把Controller类也写出来:

  1. import java.text.DateFormat;
  2. import java.text.SimpleDateFormat;
  3. import java.util.Date;
  4. import javax.annotation.Resource;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.web.bind.annotation.GetMapping;
  7. import org.springframework.web.bind.annotation.RestController;
  8. import com.example.executor_test.task.OldTask;
  9. import com.example.executor_test.task.OldTaskThread;
  10. @RestController
  11. public class TestController {
  12. @GetMapping("/test1")
  13. public String test1() {
  14. System.out.println("method in controller");
  15. return "test1";
  16. }
  17. }

最后是springboot的主方法入口,注意,由于使用注解注入的Filter,所以要在下边这个Application类中加入@ServletComponentScan注解:

  1. import org.omg.CORBA.PRIVATE_MEMBER;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. import org.springframework.boot.web.servlet.ServletComponentScan;
  6. import org.springframework.context.ConfigurableApplicationContext;
  7. import com.example.executor_test.task.OldTaskThread;
  8. @SpringBootApplication
  9. @ServletComponentScan
  10. public class ExecutorTestApplication {
  11. public static void main(String[] args) {
  12. ConfigurableApplicationContext applicationContext = SpringApplication.run(ExecutorTestApplication.class, args);
  13. }
  14. }

首先来看一下执行结果,启动后访问127.0.0.1:8080/test1。
可以看出代码执行的流程,首先请求被firstfilter截获,打印出first filter 1,然后去执行 chain.doFilter(request, response) ,这句话代表着请求会转发给过滤器链上下一个对象,也就是secondfilter,所以打印出secondfilter里的second filter 1,接下来再执行secondfilter里的chain.dofilter()方法,请求再转发给下一个对象,由于没有其他的filter了,所以会转发给controller,打印出了controller类中的method in controller,接下来再去内存栈里调用secondfilter的print("second filter 2"),然后再去内存栈里调用firstfilterprint("first filter 1")。所以如果在自己实现的Filter类的doFilter方法里不加chain.doFilter(req, rep)是万万不行的,那样会导致请求到了这个filter里就不再往下走了,永远进不了controller中。
也可以在print("before:" + response)print("after:" + response)这两个地方打上断点,然后调试一下,发现在before那里的response里是什么都么有的,而在after那里的response里则是已经有了test1字符串,也就是说controller类test1方法的返回值已经添加进了response,所以如果想对请求的response做一下过滤处理,那么一定要在chain.doFilter(res, rep)之后业务逻辑。

责任链模式的实现

接下来看一下FilterFilterChain都是怎么用责任链模式实现的,来模拟一下简单的实现SpringBoot中的Filter接口和FilterChain类:
首先是自己写的Filter接口,里边就一个doFilter方法:

  1. package filterchain_pattern;
  2. public interface Filter {
  3. public void doFilter(Request request, Response response, FilterChain chain);
  4. }

接下来是自己写的FilterChain类:

  1. package filterchain_pattern;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. public class FilterChain implements Filter {
  5. private List<Filter> filters = new ArrayList<>();
  6. int index = 0;
  7. public FilterChain addFilter(Filter filter) {
  8. filters.add(filter);
  9. return this;
  10. }
  11. @Override
  12. public void doFilter(Request request, Response response, FilterChain chain) {
  13. if(index == filters.size()) {
  14. return;
  15. }
  16. Filter filter = filters.get(index);
  17. index++;
  18. filter.doFilter(request, response, chain);
  19. }
  20. }

接下来模拟Request类和Response类:

  1. package filterchain_pattern;
  2. public class Request {
  3. public String requestStr;
  4. }
  1. package filterchain_pattern;
  2. public class Response {
  3. public String responseStr;
  4. }

然后下一个Filter接口的实现类HTMLFilter类,该类会将requestStr中的<>替换成[],并给responseStr添加-HTML response filter字符串,并在控制台打印出来:

  1. package filterchain_pattern;
  2. public class HTMLFilter implements Filter {
  3. @Override
  4. public void doFilter(Request request, Response response, FilterChain chain) {
  5. request.requestStr = request.requestStr.replace("<", "[").replace(">", "]") + "--------HTML Request Filter";
  6. System.out.println("HTML Filter request Str:" + request.requestStr);
  7. chain.doFilter(request, response, chain);
  8. response.responseStr = response.responseStr + "-HTML response filter";
  9. System.out.println("HTML Filter response Str:" + response.responseStr);
  10. }
  11. }

然后是另外一个Filter接口的实现类SensitiveFilter类, 该类会给requestStr添加一段字符串,给responseStr添加一段字符串,并在控制台打印出来:

  1. package filterchain_pattern;
  2. public class SensitiveFilter implements Filter {
  3. @Override
  4. public void doFilter(Request request, Response response, FilterChain chain) {
  5. request.requestStr = request.requestStr + "---------------Sensitive request Filter";
  6. System.out.println("sensitiveFilter request str:" + request.requestStr);
  7. chain.doFilter(request, response, chain);
  8. response.responseStr = response.responseStr + "---------------------sensitive response filter";
  9. System.out.println("sensitiveFilter response str:" + response.responseStr);
  10. }
  11. }

最后是Main方法类:

  1. package filterchain_pattern;
  2. public class MainTest {
  3. public static void main(String[] args) {
  4. String msg = "<html>testMsg</html>";
  5. Request request = new Request();
  6. request.requestStr = msg;
  7. Response response = new Response();
  8. response.responseStr = "responseStr";
  9. FilterChain fc = new FilterChain();
  10. fc.addFilter(new HTMLFilter()).addFilter(new SensitiveFilter());
  11. fc.doFilter(request, response, fc);
  12. }
  13. }

这就是责任链模式的实际应用了。