导语
  OncePerRequestFilter作为SpringMVC中的一个过滤器,在每次请求的时候都会执行。它是对于Filter的抽象实现。比起特定的过滤器来说,这个过滤器对于每次的请求都进行请求过滤。下面就来分析OncePerRequestFilter

文章目录

  • OncePerRequestFilter介绍
    • org.springframework.web.filter.OncePerRequestFilter
    • Filter接口 介绍
    • OncePerRequestFilter类继承关系
  • 如何实现一个自定义的Filter
    • 第一种方式
      • 第一步 实现Filter接口来实现
      • 使用@Bean注入
    • 第二种方式
      • 第一步 实现Filter接口
      • 第二步 添加@ServletComponentScan注解
  • 内嵌过滤器的使用
    • 第一步 实现WebSecurityConfigurerAdapter的扩展
    • 第二步 向SpringBoot中注入扩展配置
    • 第三步 设置一个登陆后置拦截器 DeptSelectFilter
  • OncePerRequestFilter 类继承关系扩展
  • 总结

OncePerRequestFilter介绍

   Filter 拦截器也叫做过滤器,它的作用就是帮助拦截一些用户请求,并对用户请求做一些初步的处理工作全局被初始化一次,这里介绍的OncePerRequestFilter,网上很多的资料都是说它只会拦截一次请求,这里需要更正一点,就是OncePerRequestFilter表示每次的Request都会进行拦截,不管是资源的请求还是数据的请求。有兴趣的可以了解一下Servlet相关的知识。这里主要是对OncePerRequestFilter的说明。

org.springframework.web.filter.OncePerRequestFilter


  从上图中可以看到OncePerRequestFilter存在于spring-web模块中,也就是它是Spring框架对于Web Servlet的封装。并且可以看到Spring MVC提供很多的Filter过滤器。其实这些Filter的实现都是大同小异的。下面先来看看Filter接口。

Filter接口 介绍

  几乎所有的Filter都是这个接口的实现,对于一个接口来讲就是定义一个规则,接下来它的实现类都是扩展这些规则,完成一些自定义的Filter开发。

public interface Filter {/** * Called by the web container to indicate to a filter that it is* being placed into service.** <p>The servlet container calls the init* method exactly once after instantiating the filter. The init* method must complete successfully before the filter is asked to do any* filtering work.* * <p>The web container cannot place the filter into service if the init* method either* <ol>* <li>Throws a ServletException* <li>Does not return within a time period defined by the web container* </ol>*/public void init(FilterConfig filterConfig) throws ServletException;/*** The <code>doFilter</code> method of the Filter is called by the* container each time a request/response pair is passed through the* chain due to a client request for a resource at the end of the chain.* The FilterChain passed in to this method allows the Filter to pass* on the request and response to the next entity in the chain.** <p>A typical implementation of this method would follow the following* pattern:* <ol>* <li>Examine the request* <li>Optionally wrap the request object with a custom implementation to* filter content or headers for input filtering* <li>Optionally wrap the response object with a custom implementation to* filter content or headers for output filtering* <li>* <ul>* <li><strong>Either</strong> invoke the next entity in the chain* using the FilterChain object* (<code>chain.doFilter()</code>),* <li><strong>or</strong> not pass on the request/response pair to* the next entity in the filter chain to* block the request processing* </ul>* <li>Directly set headers on the response after invocation of the* next entity in the filter chain.* </ol>*/public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain)throws IOException, ServletException;/*** Called by the web container to indicate to a filter that it is being* taken out of service.** <p>This method is only called once all threads within the filter's* doFilter method have exited or after a timeout period has passed.* After the web container calls this method, it will not call the* doFilter method again on this instance of the filter.** <p>This method gives the filter an opportunity to clean up any* resources that are being held (for example, memory, file handles,* threads) and make sure that any persistent state is synchronized* with the filter's current state in memory.*/public void destroy();
}

  从上面的描述中可以看到,这个接口定义了三个方法,也就是说凡是继承这个接口的类都要实现这三个方法,对于这三个方法而言,最重要的就是doFilter 方法,也就是在实现自定义的Filter的时候最为主要的就是如何去实现这个方法。那么既然OncePerRequestFilter作为它的实现类下面就来看看OncePerRequestFilter是如何实现这个方法

  public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain)throws IOException, ServletException;

OncePerRequestFilter类继承关系


  结合上面的内容来看一下OncePerRequestFilter是如何实现doFilter()

@Override
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {//首先判断Request是否是一个HttpServletRequest的请求if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {throw new ServletException("OncePerRequestFilter just supports HTTP requests");}//对于请求类型转换HttpServletRequest httpRequest = (HttpServletRequest) request;HttpServletResponse httpResponse = (HttpServletResponse) response;//获取准备过滤的参数名称String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;//如果过滤的参数为空或者跳过Dispatch或者是不做任何的Filter,那么就从筛选器链中找其他的筛选器if (hasAlreadyFilteredAttribute || skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {// Proceed without invoking this filter...//筛选链filterChain.doFilter(request, response);}//否则执行这个filterelse {// Do invoke this filter...//设置标识request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);//执行内过滤器try {//执行内过滤器doFilterInternal(httpRequest, httpResponse, filterChain);}finally {// Remove the "already filtered" request attribute for this request.//移除标识request.removeAttribute(alreadyFilteredAttributeName);}}
}

  会看到**doFilterInternal()**方法是一个抽象方法,也就是说,继承这个类的子类需要重写这个方法才能完全的实现它的内容。否则功能将不会被实现。

 /*** Same contract as for {@code doFilter}, but guaranteed to be* just invoked once per request within a single request thread.* See {@link #shouldNotFilterAsyncDispatch()} for details.* <p>Provides HttpServletRequest and HttpServletResponse arguments instead of the* default ServletRequest and ServletResponse ones.*/protected abstract void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException;

如何实现一个自定义的Filter

第一种方式

第一步 实现Filter接口来实现

注意 要是它作为Spring的组件被Spring容器接管,要在类上加上@Component注解,或者使用@Bean注解进行注册

@Component
public class MyFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println(" myfilter init");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("myfilter execute");}@Overridepublic void destroy() {System.out.println("myfilter destroy");}
}

使用@Bean注入

@Configuration
public class FilterConfig {@Beanpublic FilterRegistrationBean filterRegistration(){// 新建过滤器注册类FilterRegistrationBean registration = new FilterRegistrationBean();// 添加自定义 过滤器registration.setFilter(globalFilter());// 设置过滤器的URL模式registration.addUrlPatterns("/*");//设置过滤器顺序registration.setOrder(1);return registration;}@Beanpublic GlobalFilter globalFilter(){return new GlobalFilter();}
}

第二种方式

第一步 实现Filter接口

@Order(1)
@WebFilter(filterName = "MSecurity",urlPatterns = {"*.html"})
public class MSecurityFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response= (HttpServletResponse) servletResponse;System.out.println(request.getRequestURI());//检查是否是登录页面if(request.getRequestURI().equals("/web/index.html"))filterChain.doFilter(servletRequest,servletResponse);//检测用户是否登录HttpSession session =request.getSession();String status= (String) session.getAttribute("isLogin");if(status==null || !status.equals("true")){try{  response.sendRedirect("/web/index.html");}catch (Exception e){}}filterChain.doFilter(servletRequest,servletResponse);}@Overridepublic void destroy() {}
}

  上面内容是检查是否登陆如果登陆了就显示index页面如果不是则进入登陆页面

第二步 添加@ServletComponentScan注解

@SpringBootApplication
@ServletComponentScan(basePackages = "com.nihui.security")
public class MsSupplyAndSaleApplication {public static void main(String[] args) {SpringApplication.run(MsSupplyAndSaleApplication.class, args);}}

内嵌过滤器的使用

  这里以一个项目实战的登陆功能最为演示,整合了结合了SpringSecurity的相关知识

第一步 实现WebSecurityConfigurerAdapter的扩展

public class WebSecurityConfigurerAdapterExt extends WebSecurityConfigurerAdapter {@Autowiredprivate AuthenticationProvider authenticationProvider;@Autowiredprivate AuthenticationSuccessHandler authenticationSuccessHandler;@Autowiredprivate AuthenticationFailureHandler authenticationFailureHandler;@Autowiredprivate AccessDeniedHandler accessDeniedHandler;@Autowiredprivate AuthenticationEntryPoint authenticationEntryPoint;@Autowiredprivate LogoutSuccessHandler logoutSuccessHandler;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.authenticationProvider(authenticationProvider);}@Overrideprotected void configure(HttpSecurity http) throws Exception {ValidateCodeFilter validateCodeFilter=new ValidateCodeFilter();http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);//      http.csrf().disable();http.authorizeRequests()//Spring Security 5.0 之后需要过滤静态资源.antMatchers("/mgmt/**").permitAll().antMatchers("/swagger*/**","/webjars/**","/api/swagger.json").permitAll().antMatchers("/login","/css/**","/js/**","/img.*").permitAll().anyRequest().authenticated().and().formLogin().usernameParameter("loginName").passwordParameter("password").successHandler(authenticationSuccessHandler).failureHandler(authenticationFailureHandler).and().logout().logoutSuccessHandler(logoutSuccessHandler).permitAll().and().exceptionHandling().accessDeniedHandler(accessDeniedHandler).authenticationEntryPoint(authenticationEntryPoint);}}

第二步 向SpringBoot中注入扩展配置

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapterExt {@Overridepublic void configure(WebSecurity web) throws Exception {// TODO Auto-generated method stubsuper.configure(web);}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable();http.headers().frameOptions().sameOrigin();
//      super.configure(http);//http 请求认证操作http.authorizeRequests()//Spring Security 5.0 之后需要过滤静态资源.antMatchers("/login").permitAll().requestMatchers(CorsUtils::isPreFlightRequest).permitAll().anyRequest().authenticated()
//        .anyRequest().permitAll().and().addFilterAfter(new DeptSelectFilter(), SessionManagementFilter.class).formLogin().usernameParameter("loginName").passwordParameter("password").successHandler(authenticationSuccessHandler).failureHandler(authenticationFailureHandler).authenticationDetailsSource(authenticationDetailsSource).and().logout().logoutSuccessHandler(logoutSuccessHandler).permitAll().and().exceptionHandling().accessDeniedHandler(accessDeniedHandler).authenticationEntryPoint(authenticationEntryPoint);}//用户服务扩展@Beanpublic UserDetailsServiceExt userDetailsServiceExt() {return new UserConsumerAuthServiceImpl();}}

第三步 设置一个登陆后置拦截器 DeptSelectFilter

  这个拦截器被设置到了登陆验证完成之后,用户用来选择对应的部门,如果部门认证通过的话就进入到index页面如果没有经过任何的处理,也就是说第一次登陆就会引导用户选择则对应的部门,并且回传部门信息。

public class DeptSelectFilter extends OncePerRequestFilter {//   @Autowired
//  private UserInfoFeignClient userInfoFeignClient;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {boolean validDept = false;//获取请求的URI字符串String requesturi = request.getRequestURI();//userInfoFeignClient.getUserInfo()//判断是否登录if(SecurityContextHolderExt.isLogin()) {if("/dept".equals(requesturi)) {validDept = false;//匹配字符串内容为     /v[数字]/dept.*}else if(requesturi.matches("/v[0-9]+/dept.*")) {validDept = false;}else {validDept = true;}}if(validDept){List<DeptExt> deptExts = SecurityContextHolderExt.getDepts();if(deptExts==null || deptExts.size()==0) {if(AjaxUtil.isAjaxRequest(request)) {ResultResp<UserResp> resultResp=new ResultResp<>();ExceptionMsg exceptionMsg=new ExceptionMsg();exceptionMsg.setErrorMsg("请先选择部门");resultResp.setExceptionMsg(exceptionMsg);resultResp.setStatus(ResultRespStatus.EXCEPTION);ResponseUtil.doResponse(response, HttpServletResponse.SC_UNAUTHORIZED, MediaType.APPLICATION_JSON_VALUE, resultResp.toString());return;  }else {response.sendRedirect("/dept");return;}}}filterChain.doFilter(request, response);}}

  上面方法就实现了对OncePerRequestFilter拦截器的doFilterInternal()方法的扩展,并且最后结束的时候将请求引入到了Filter链路中。filterChain.doFilter(request, response)。

OncePerRequestFilter 类继承关系扩展


  通过上图的类关系图可以看到,在SpringMVC中对于Filter的扩展都是继承了OncePerRequestFilter。其中都是实现了doFilterInternal()的方法扩展。

总结

  上面内容主要讲述了在实际的开发中如何使用OncePerRequestFilter过滤器。并且结合了一个小例子,描述了在实际开发中如何使用Filter,当然在实际开发中使用到Filter的场景还有其他的使用场景。这里只是这个应用场景的冰山一角。

Spring实用系列-深入了解SpringMVC OncePerRequestFilter过滤器原理相关推荐

  1. Spring Boot 系列:过滤器+拦截器+监听器

    原 Swagger 文章合并到 Spring Boot 系列:配置 Swagger2 一.过滤器 - Filter 过滤器是处于客户端和服务器资源文件之间的一道过滤网,帮助我们过滤掉一些不符合要求的请 ...

  2. Spring/Boot/Cloud系列知识:SpringMVC 传参详解(下)

    (接上文<Spring/Boot/Cloud系列知识:SpringMVC 传参详解(上)>) 2.3.通过@PathVariable注解基于URL匹配符接收请求传参 为了便于开发人员实现更 ...

  3. Spring框架系列之AOP思想

    微信公众号:compassblog 欢迎关注.转发,互相学习,共同进步! 有任何问题,请后台留言联系! 1.AOP概述 (1).什么是 AOP AOP 为 Aspect Oriented Progra ...

  4. Spring Security系列之Spring Social实现微信社交登录(九)

    社交登录又称作社会化登录(Social Login),是指网站的用户可以使用腾讯QQ.人人网.开心网.新浪微博.搜狐微博.腾讯微博.淘宝.豆瓣.MSN.Google等社会化媒体账号登录该网站. 前言 ...

  5. spring security系列一:架构概述

    一直以来都想好好写一写spring security 系列文章,每每提笔又不知何处下笔,又赖于spring security体系强大又过于繁杂,且spring security 与auth2.0结合的 ...

  6. shiro学习系列:shiro自定义filter过滤器

    shiro学习系列:shiro自定义filter过滤器 自定义JwtFilter的hierarchy(层次体系) 上代码 package com.finn.springboot.common.conf ...

  7. 国内最全的Spring Boot系列之五

    ​历史文章(累计400多篇文章) <国内最全的Spring Boot系列之一> <国内最全的Spring Boot系列之二> <国内最全的Spring Boot系列之三& ...

  8. Spring Cloud 系列之OpenFeign:(5)OpenFeign的高级用法

    传送门 Spring Cloud Alibaba系列之nacos:(1)安装 Spring Cloud Alibaba系列之nacos:(2)单机模式支持mysql Spring Cloud Alib ...

  9. Spring Cloud 系列之 Netflix Zuul 服务网关(三)

    本篇文章为系列文章,未读前几集的同学请猛戳这里: Spring Cloud 系列之 Netflix Zuul 服务网关(一) Spring Cloud 系列之 Netflix Zuul 服务网关(二) ...

最新文章

  1. Java项目:仿小米电子产品售卖商城系统(java+SpringBoot+Vue+MySQL+Redis+ElementUI)
  2. 免费!!3天,吃透JVM!(限时领)
  3. java定义矩形类rect_Java定义矩形类
  4. Shell脚本学习-数组
  5. 大多数项目能不能投资,能不能去创业,取决于自己是站在什么高度看问题
  6. 矢量图形和位图的不同
  7. 网站左边栏制作的小技巧
  8. paip.提升用户体验---提示语
  9. Android MQTT客户端
  10. 莫教引动虚阳发,精竭容枯百病 侵
  11. 如何选择剑桥英语KET,PET课程和老师
  12. php图书馆注册模板,php微信公众号开发之校园图书馆
  13. Python模拟post提交表单数据 ——某二手车网站回拨电话的分析与利用
  14. ensp两个路由的配置(想对全世界说晚安 恰好你就是全世界)
  15. 微信小程序点击按钮弹出弹窗_微信小程序带图片弹窗简单实现
  16. 内网建站 NAT穿透 局域网穿透
  17. 工作流-jbpm入门例子
  18. 一行代码回显 LayUI表单数据回显 下拉列表select回显
  19. GNSS-导航卫星受力分析
  20. 项目管理(PMP)精选题精讲

热门文章

  1. Yii2语言国际化配置Twig翻译解决方案
  2. Android屏幕禁止休眠的方法
  3. Exceeded maximum number of retries. Exceeded max scheduling attempts 3 for instance
  4. 电梯调度需求调研报告
  5. Displaying a Refresh Control for Table Views
  6. 解决Windows 7删除执行过的 EXE、Bat文件有延迟的问题
  7. GraphQL —— 标量类型
  8. .net MvcPager+Ajax无刷新分页
  9. 3分钟快速presentation
  10. Java 集合系列12之 Hashtable详细介绍(源码解析)和使用示例