Spring实用系列-深入了解SpringMVC OncePerRequestFilter过滤器原理
导语
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过滤器原理相关推荐
- Spring Boot 系列:过滤器+拦截器+监听器
原 Swagger 文章合并到 Spring Boot 系列:配置 Swagger2 一.过滤器 - Filter 过滤器是处于客户端和服务器资源文件之间的一道过滤网,帮助我们过滤掉一些不符合要求的请 ...
- Spring/Boot/Cloud系列知识:SpringMVC 传参详解(下)
(接上文<Spring/Boot/Cloud系列知识:SpringMVC 传参详解(上)>) 2.3.通过@PathVariable注解基于URL匹配符接收请求传参 为了便于开发人员实现更 ...
- Spring框架系列之AOP思想
微信公众号:compassblog 欢迎关注.转发,互相学习,共同进步! 有任何问题,请后台留言联系! 1.AOP概述 (1).什么是 AOP AOP 为 Aspect Oriented Progra ...
- Spring Security系列之Spring Social实现微信社交登录(九)
社交登录又称作社会化登录(Social Login),是指网站的用户可以使用腾讯QQ.人人网.开心网.新浪微博.搜狐微博.腾讯微博.淘宝.豆瓣.MSN.Google等社会化媒体账号登录该网站. 前言 ...
- spring security系列一:架构概述
一直以来都想好好写一写spring security 系列文章,每每提笔又不知何处下笔,又赖于spring security体系强大又过于繁杂,且spring security 与auth2.0结合的 ...
- shiro学习系列:shiro自定义filter过滤器
shiro学习系列:shiro自定义filter过滤器 自定义JwtFilter的hierarchy(层次体系) 上代码 package com.finn.springboot.common.conf ...
- 国内最全的Spring Boot系列之五
历史文章(累计400多篇文章) <国内最全的Spring Boot系列之一> <国内最全的Spring Boot系列之二> <国内最全的Spring Boot系列之三& ...
- Spring Cloud 系列之OpenFeign:(5)OpenFeign的高级用法
传送门 Spring Cloud Alibaba系列之nacos:(1)安装 Spring Cloud Alibaba系列之nacos:(2)单机模式支持mysql Spring Cloud Alib ...
- Spring Cloud 系列之 Netflix Zuul 服务网关(三)
本篇文章为系列文章,未读前几集的同学请猛戳这里: Spring Cloud 系列之 Netflix Zuul 服务网关(一) Spring Cloud 系列之 Netflix Zuul 服务网关(二) ...
最新文章
- Java项目:仿小米电子产品售卖商城系统(java+SpringBoot+Vue+MySQL+Redis+ElementUI)
- 免费!!3天,吃透JVM!(限时领)
- java定义矩形类rect_Java定义矩形类
- Shell脚本学习-数组
- 大多数项目能不能投资,能不能去创业,取决于自己是站在什么高度看问题
- 矢量图形和位图的不同
- 网站左边栏制作的小技巧
- paip.提升用户体验---提示语
- Android MQTT客户端
- 莫教引动虚阳发,精竭容枯百病 侵
- 如何选择剑桥英语KET,PET课程和老师
- php图书馆注册模板,php微信公众号开发之校园图书馆
- Python模拟post提交表单数据 ——某二手车网站回拨电话的分析与利用
- ensp两个路由的配置(想对全世界说晚安 恰好你就是全世界)
- 微信小程序点击按钮弹出弹窗_微信小程序带图片弹窗简单实现
- 内网建站 NAT穿透 局域网穿透
- 工作流-jbpm入门例子
- 一行代码回显 LayUI表单数据回显 下拉列表select回显
- GNSS-导航卫星受力分析
- 项目管理(PMP)精选题精讲
热门文章
- Yii2语言国际化配置Twig翻译解决方案
- Android屏幕禁止休眠的方法
- Exceeded maximum number of retries. Exceeded max scheduling attempts 3 for instance
- 电梯调度需求调研报告
- Displaying a Refresh Control for Table Views
- 解决Windows 7删除执行过的 EXE、Bat文件有延迟的问题
- GraphQL —— 标量类型
- .net MvcPager+Ajax无刷新分页
- 3分钟快速presentation
- Java 集合系列12之 Hashtable详细介绍(源码解析)和使用示例