• Springboot 过滤器和拦截器


    过滤器和拦截器

    过滤器(Filter)

    过滤器是对数据进行过滤和预处理。开发人员可以对客户端提交的数据进行过滤处理,也可以对服务器返回的数据进行处理。

    比如验证用户的登录情况,权限验证,对静态资源进行访问权限控制等...

    过滤器使用的两种方式

    注解配置

    • 定义一个JwtFilter 实现Filter接口。重写里面的三个方法。(按Ctrl+I 一键重写)

      package com.smile.blog.filter;
      
      import javax.servlet.*;
      import javax.servlet.annotation.WebFilter;
      import java.io.IOException;
      
      @WebFilter(filterName = "jwtFilter",urlPatterns = "/*")
      public class jwtFilter implements Filter {
          @Override
          public void init(FilterConfig filterConfig) throws ServletException {
              //jwt 过滤器开启
              System.out.println("jwt 过滤器开始");
      
              Filter.super.init(filterConfig);
          }
      
          @Override
          public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
              System.out.println("开始验证 ...");
      
              filterChain.doFilter(servletRequest,servletResponse);
      
              System.out.println("验证结束 ....");
          }
      
          @Override
          public void destroy() {
              //jwt过滤器结束
              System.out.println("jwt 过滤器结束");
      
              Filter.super.destroy();
          }
      }
      

      解释说明:

      1. @WebFilter注解,用于将类声明为过滤器,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器。该注解具有下表给出的一些常用属性(以下所有属性均为可选属性,但是 value、urlPatterns、servletNames 三者必需至少包含一个,且 value 和 urlPatterns 不能共存,如果同时指定,通常忽略 value 的取值)

        dnof8a5zf5c00000

      2. @Order注解,用于定义组件的加载顺序,值越小,加载的优先级越高

      3. init()方法只会执行一次,初始化过滤器

      4. destory()只会在项目停止或项目重新部署执行

      5. doFilter()核心方法,配置过滤器的逻辑代码

        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            //预处理
            System.out.println("开始验证 ...");
            
            filterChain.doFilter(servletRequest,servletResponse);
            
            //后处理(处理servletResponse)
            System.out.println("验证结束 ....");
        }
        

        对每个请求及响应增加额外的预处理和后处理逻辑。执行该方法之前,即对用户请求进行预处理,执行该方法后,即对服务器进行后处理。

    • 在Application上添加注解@ServletComponentScan

      解释说明:@ServletComponentScan注解后,Servlet,Filter,Listener可以直接通过@WebServlet,@WebFilter,@WebListener注解自动注册

      package com.smile.blog;
      
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.boot.web.servlet.ServletComponentScan;
      
      @SpringBootApplication
      @ServletComponentScan
      public class Application {
      
          public static void main(String[] args) {
              SpringApplication.run(Application.class, args);
          }
      
      }
      
    • 结果验证

      image-20220310004752730

    java配置

    • 定义java配置文件

      package com.smile.blog.config;
      
      import com.smile.blog.filter.JwtFilter;
      import org.springframework.boot.web.servlet.FilterRegistrationBean;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      @Configuration
      public class FilterConfig {
      
          @Bean
          public FilterRegistrationBean jwtFilterRegistration() {
              FilterRegistrationBean registration = new FilterRegistrationBean();
      
              registration.setFilter(new JwtFilter());
      
              registration.addUrlPatterns("/*");
              registration.setName("JwtFilter");
              registration.setOrder(1);
      
              return registration;
          }
      }
      

      解释说明:@Bean是一个方法级别上的注解,主要用在@Configuration注解的类里,也可以用在@Component注解的类里。添加的bean的id为方法名

    • 定义过滤器

      package com.smile.blog.filter;
      
      import javax.servlet.*;
      import java.io.IOException;
      
      public class JwtFilter implements Filter {
          @Override
          public void init(FilterConfig filterConfig) throws ServletException {
              //jwt 过滤器开启
              System.out.println("jwt 过滤器开始");
      
              Filter.super.init(filterConfig);
          }
      
          @Override
          public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
              System.out.println("开始验证 ...");
      
              filterChain.doFilter(servletRequest,servletResponse);
      
              System.out.println("验证结束 ....");
          }
      
          @Override
          public void destroy() {
              //jwt过滤器结束
              System.out.println("jwt 过滤器结束");
      
              Filter.super.destroy();
          }
      }
      

    拦截器(interceptor)

    拦截器和过滤器一样,他们都是面向切面编程AOP的具体实现。

    拦截器的使用

    拦截器是链式调用,一个应用可以同时存在多个拦截器Interceptor,一个请求也可以触发多个拦截器,而每个拦截器的调用会依据它的声明顺序依次执行

    • 定义一个AuthInterceptors实现HandlerInterceptor接口,重写里面的三个方法(按Ctrl+I一键重写)

      package com.smile.blog.interceptor;
      
      import org.springframework.web.servlet.HandlerInterceptor;
      import org.springframework.web.servlet.ModelAndView;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      
      /**
       * @Notes
       * @Date 2022/3/11
       * @Time 20:06
       * @Author smile
       */
      public class AuthInterceptor implements HandlerInterceptor {
          @Override
          public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
              System.out.println("开始拦截");
              return HandlerInterceptor.super.preHandle(request, response, handler);
          }
      
          @Override
          public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
              System.out.println("正在拦截");
              HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
          }
      
          @Override
          public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
              System.out.println("结束拦截");
              HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
          }
      }
      

      解释说明:

      @Configuration注解:标注在类上,配置Spring容器(应用上下文)。相当于把该类作为spring的xml配置文件中的,@Configuration注解的类中,使用@Bean注解标注的方法,返回类型都会直接注册为bean

      preHandler 预先处理方法,用来进行一些前置初始化操作或对当前请求做预处理,返回boolean类型,true放行请求,会继续调用下一个Interceptor如果已经是最后一个Interceptor会调用当前请求的Controllerfalse终止请求。

      postHandler 过程中处理方法,controller执行完毕后,会返回到postHandler这个方法执行,但是它会在DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller处理后的ModelAndView对象进行操作,执行完成后开始响应浏览器。

      afterCompletion结束后处理方法,当浏览器结束之后,会返回到afterCompletion这个方法,主要用来进行资源清理。

      流程图:

      image-20220312103224985

    • 结果验证

      image-20220312104849886

    过滤器和触发器的区别

    1、实现原理不同

    过滤器和拦截器 底层实现方式大不相同,过滤器 是基于函数回调的,拦截器 则是基于Java的反射机制(动态代理)实现的。

    这里重点说下过滤器!

    在我们自定义的过滤器中都会实现一个 doFilter()方法,这个方法有一个FilterChain 参数,而实际上它是一个回调接口。ApplicationFilterChain是它的实现类, 这个实现类内部也有一个 doFilter() 方法就是回调方法。

    public interface FilterChain {
        void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
    }
    

    在这里插入图片描述 ApplicationFilterChain里面能拿到我们自定义的xxxFilter类,在其内部回调方法doFilter()里调用各个自定义xxxFilter过滤器,并执行 doFilter() 方法。

    public final class ApplicationFilterChain implements FilterChain {
        @Override
        public void doFilter(ServletRequest request, ServletResponse response) {
                ...//省略
                internalDoFilter(request,response);
        }
     
        private void internalDoFilter(ServletRequest request, ServletResponse response){
        if (pos < n) {
                //获取第pos个filter    
                ApplicationFilterConfig filterConfig = filters[pos++];        
                Filter filter = filterConfig.getFilter();
                ...
                filter.doFilter(request, response, this);
            }
        }
     
    }
    

    而每个xxxFilter 会先执行自身的 doFilter() 过滤逻辑,最后在执行结束前会执行filterChain.doFilter(servletRequest, servletResponse),也就是回调ApplicationFilterChaindoFilter() 方法,以此循环执行实现函数回调。

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    
        filterChain.doFilter(servletRequest, servletResponse);
    }
    

    2、使用范围不同

    我们看到过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。 在这里插入图片描述 而拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于ApplicationSwing等程序中。 在这里插入图片描述

    3、触发时机不同

    过滤器拦截器的触发时机也不同,我们看下边这张图。 在这里插入图片描述

    过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。

    拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。

    4、拦截的请求范围不同

    在上边我们已经同时配置了过滤器和拦截器,再建一个Controller接收请求测试一下。

    @Controller
    @RequestMapping()
    public class Test {
    
        @RequestMapping("/test1")
        @ResponseBody
        public String test1(String a) {
            System.out.println("我是controller");
            return null;
        }
    }
    

    项目启动过程中发现,过滤器的init()方法,随着容器的启动进行了初始化。 在这里插入图片描述 此时浏览器发送请求,F12 看到居然有两个请求,一个是我们自定义的 Controller 请求,另一个是访问静态图标资源的请求。 在这里插入图片描述 看到控制台的打印日志如下:

    执行顺序 :Filter 处理中 -> Interceptor 前置 -> 我是controller -> Interceptor 处理中 -> Interceptor 处理后

    Filter 处理中
    Interceptor 前置
    Interceptor 处理中
    Interceptor 后置
    Filter 处理中
    

    过滤器Filter执行了两次,拦截器Interceptor只执行了一次。这是因为过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对Controller中请求或访问static目录下的资源请求起作用。

    5、注入Bean情况不同

    在实际的业务场景中,应用到过滤器或拦截器,为处理业务逻辑难免会引入一些service服务。

    下边我们分别在过滤器和拦截器中都注入service,看看有什么不同?

    @Component
    public class TestServiceImpl implements TestService {
    
        @Override
        public void a() {
            System.out.println("我是方法A");
        }
    }
    

    过滤器中注入service,发起请求测试一下 ,日志正常打印出“我是方法A”

    @Autowired
        private TestService testService;
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    
            System.out.println("Filter 处理中");
            testService.a();
            filterChain.doFilter(servletRequest, servletResponse);
        }
    复制代码
    Filter 处理中
    我是方法A
    Interceptor 前置
    我是controller
    Interceptor 处理中
    Interceptor 后置
    

    在拦截器中注入service,发起请求测试一下 ,竟然TM的报错了,debug跟一下发现注入的service怎么是Null啊? 在这里插入图片描述 这是因为加载顺序导致的问题,拦截器加载的时间点在springcontext之前,而Bean又是由spring进行管理。

    ?

    拦截器:老子今天要进洞房; Spring:兄弟别闹,你媳妇我还没生出来呢!

    ?

    解决方案也很简单,我们在注册拦截器之前,先将Interceptor 手动进行注入。「注意」:在registry.addInterceptor()注册的是getMyInterceptor() 实例。

    @Configuration
    public class MyMvcConfig implements WebMvcConfigurer {
    
        @Bean
        public MyInterceptor getMyInterceptor(){
            System.out.println("注入了MyInterceptor");
            return new MyInterceptor();
        }
        
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
    
            registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
        }
    }
    

    6、控制执行顺序不同

    实际开发过程中,会出现多个过滤器或拦截器同时存在的情况,不过,有时我们希望某个过滤器或拦截器能优先执行,就涉及到它们的执行顺序。

    过滤器用@Order注解控制执行顺序,通过@Order控制过滤器的级别,值越小级别越高越先执行。

    @Order(Ordered.HIGHEST_PRECEDENCE)
    @Component
    public class MyFilter2 implements Filter {
    

    拦截器默认的执行顺序,就是它的注册顺序,也可以通过Order手动设置控制,值越小越先执行。

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**").order(2);
        registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**").order(1);
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").order(3);
    }
    

    看到输出结果发现,先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。

    postHandle() 方法被调用的顺序跟 preHandle() 居然是相反的!如果实际开发中严格要求执行顺序,那就需要特别注意这一点。

    Interceptor1 前置
    Interceptor2 前置
    Interceptor 前置
    我是controller
    Interceptor 处理中
    Interceptor2 处理中
    Interceptor1 处理中
    Interceptor 后置
    Interceptor2 处理后
    Interceptor1 处理后
    

    「那为什么会这样呢?」 得到答案就只能看源码了,我们要知道controller 中所有的请求都要经过核心组件DispatcherServlet路由,都会执行它的 doDispatch() 方法,而拦截器postHandle()preHandle()方法便是在其中调用的。

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
       
            try {
             ...........
                try {
               
                    // 获取可以执行当前Handler的适配器
                    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
                    // Process last-modified header, if supported by the handler.
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if (logger.isDebugEnabled()) {
                            logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                        }
                        if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }
                    // 注意: 执行Interceptor中PreHandle()方法
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
    
                    // 注意:执行Handle【包括我们的业务逻辑,当抛出异常时会被Try、catch到】
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                    applyDefaultViewName(processedRequest, mv);
    
                    // 注意:执行Interceptor中PostHandle 方法【抛出异常时无法执行】
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                }
            }
            ...........
        }
    

    看看两个方法applyPreHandle()applyPostHandle()具体是如何被调用的,就明白为什么postHandle()preHandle() 执行顺序是相反的了。

    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
            HandlerInterceptor[] interceptors = this.getInterceptors();
            if(!ObjectUtils.isEmpty(interceptors)) {
                for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
                    HandlerInterceptor interceptor = interceptors[i];
                    if(!interceptor.preHandle(request, response, this.handler)) {
                        this.triggerAfterCompletion(request, response, (Exception)null);
                        return false;
                    }
                }
            }
    
            return true;
        }
    void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
            HandlerInterceptor[] interceptors = this.getInterceptors();
            if(!ObjectUtils.isEmpty(interceptors)) {
                for(int i = interceptors.length - 1; i >= 0; --i) {
                    HandlerInterceptor interceptor = interceptors[i];
                    interceptor.postHandle(request, response, this.handler, mv);
                }
            }
        }
    

    发现两个方法中在调用拦截器数组 HandlerInterceptor[] 时,循环的顺序竟然是相反的。。。,导致postHandle()preHandle() 方法执行的顺序相反。

  • 相关阅读:
    IOS的系统手机 宽度无法自适应 解决办法
    iframe 设置背景透明
    thinkphp5 常用的2个方法
    thinkphp引入后台模板文件的路径怎么写?
    html的confirm()
    php 如何往数组里添加数据
    thinkphp51 重定向 redirect()
    【Oracle 触发器】(4)触发器应用场景--数据的确认
    【Oracle 触发器】(3)触发器应用场景--复杂的安全性检查
    【Oracle 触发器】(2)触发器的分类(语句级/行级)
  • 原文地址:https://www.cnblogs.com/ywjcqq/p/15987822.html
Copyright © 2020-2023  润新知