主要了解SpringBoot中使用拦截器和过滤器的使用,关于两者,资料所提及的有:

  • 作用域差异:Filter是Servlet规范中规定的,只能用于WEB中,拦截器既可以用于WEB,也可以用于Application、Swing中(即过滤器是依赖于Servlet容器的,和它类似的还有Servlet中的监听器同样依赖该容器,而拦截器则不依赖它);
  • 规范差异:Filter是Servlet规范中定义的,是Servlet容器支持的,而拦截器是Spring容器内的,是Spring框架支持的;
  • 资源差异:拦截器是Spring的一个组件,归Spring管理配置在Spring的文件中,可以使用Spring内的任何资源、对象(可以粗浅的认为是IOC容器中的Bean对象),而Filter则不能使用访问这些资源;
  • 深度差异:Filter只在Servlet前后起作用,而拦截器可以深入到方法的前后、异常抛出前后等更深层次的程度作处理(这里也在一定程度上论证了拦截器是利用java的反射机制实现的),所以在Spring框架中,优先使用拦截器;

1. 关于ApplicationListener

 除此之外,还有监听器,在项目中遇到一个ApplicationListener,在容器初始化完成后,有一些操作需要处理一下,比如数据的加载、初始化缓存、特定任务的注册等,此时可以使用这个监听器,下面是使用方式:

// 1. 定义实现类实现ApplicationListener接口
package com.glodon.tot.listener;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;import java.net.InetAddress;
import java.net.UnknownHostException;/*** @author liuwg-a* @date 2018/11/26 10:48* @description 容器初始化后要做的数据处理*/
public class StartupListener implements ApplicationListener<ContextRefreshedEvent> {private static final Logger logger = LoggerFactory.getLogger(StartupListener.class);@Overridepublic void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {try {logger.info("get local ip is " + InetAddress.getLocalHost().getHostAddress());} catch (UnknownHostException e) {e.printStackTrace();logger.error("occur a exception!");}}
}// 2. 配置上述实现类,返回Bean实例,类似于在xml中配置<bean>标签
package com.glodon.tot.config;import com.glodon.tot.listener.StartupListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @author liuwg-a* @date 2018/11/26 11:10* @description 配置监听器*/
@Configuration
public class ListenerConfig {// 这里会直接注入@Beanpublic StartupListener startupListener() {return new StartupListener();}
}

主要就是上述配置,其他和普通SpringBoot项目一样,启动项目即可,最初启动(即不主动调用接口)的效果如下:

2018-11-26 11:23:18.360  INFO 18792 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-11-26 11:23:18.360  INFO 18792 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-11-26 11:23:18.372  INFO 18792 --- [           main] .m.m.a.ExceptionHandlerExceptionResolver : Detected @ExceptionHandler methods in globalExceptionHandler
2018-11-26 11:23:18.389  INFO 18792 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-11-26 11:23:18.578  INFO 18792 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
welcome to StartupListener...
your ip is 10.4.37.108
2018-11-26 11:23:18.590  INFO 18792 --- [           main] com.glodon.tot.listener.StartupListener  : get local ip is 10.4.37.108
2018-11-26 11:23:18.822  INFO 18792 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8081 (http)
2018-11-26 11:23:18.827  INFO 18792 --- [           main] com.glodon.tot.Application               : Started Application in 4.616 seconds (JVM running for 11.46)

在调用接口后,添加了如下日志:

2018-11-26 11:25:46.785  INFO 18792 --- [nio-8081-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-11-26 11:25:46.785  INFO 18792 --- [nio-8081-exec-2] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-11-26 11:25:46.803  INFO 18792 --- [nio-8081-exec-2] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 18 ms

2. 关于拦截器

 在SpringBoot使用拦截器,流程如下:

// 1. 定义拦截器
package com.glodon.tot.interceptor;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** @author liuwg-a* @date 2018/11/26 14:55* @description 配置拦截器*/
public class UrlInterceptor implements HandlerInterceptor {private static final Logger logger = LoggerFactory.getLogger(UrlInterceptor.class);private static final String GET_ALL = "getAll";private static final String GET_HEADER = "getHeader";/*** 进入Controller层之前拦截请求,默认是拦截所有请求* @param httpServletRequest request* @param httpServletResponse response* @param o object* @return 是否拦截当前请求,true表示拦截当前请求,false表示不拦截当前请求* @throws Exception 可能出现的异常*/@Overridepublic boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {logger.info("go into preHandle method ... ");String requestURI = httpServletRequest.getRequestURI();if (requestURI.contains(GET_ALL)) {return true;}if (requestURI.contains(GET_HEADER)) {httpServletResponse.sendRedirect("/user/redirect");}return true;}/*** 处理完请求后但还未渲染试图之前进行的操作* @param httpServletRequest request* @param httpServletResponse response* @param o object* @param modelAndView mv* @throws Exception E*/@Overridepublic void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {logger.info("go into postHandle ... ");}/*** 视图渲染后但还未返回到客户端时的操作* @param httpServletRequest request* @param httpServletResponse response* @param o object* @param e exception* @throws Exception*/@Overridepublic void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {logger.info("go into afterCompletion ... ");}
}// 2. 注册拦截器
package com.glodon.tot.config;import com.glodon.tot.interceptor.UrlInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** @author liuwg-a* @date 2018/11/26 15:30* @description 配置MVC*/
@Configuration
public class MvcConfig implements WebMvcConfigurer {/*** 注册配置的拦截器* @param registry 拦截器注册器*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 这里的拦截器是new出来的,在Spring框架中可以交给IOC进行依赖注入,直接使用@Autowired注入registry.addInterceptor(new UrlInterceptor());}}

主要就是上述的两个步骤,需要注意的是preHandle()方法只有返回true,Controller中接口方法才能执行,否则不能执行,直接在preHandle()返回后false结束流程。上述在配置WebMvcConfigurer实现类中注册拦截器时除了使用registry.addInterceptor(new UrlInterceptor())注册外,还可以指定哪些URL可以应用这个拦截器,如下:

// 使用自动注入的方式注入拦截器,添加应用、或不应用该拦截器的URI(addPathPatterns/excludePathPatterns)
// addPathPatterns 用于添加拦截的规则,excludePathPatterns 用于排除拦截的规则
registry.addInterceptor(urlInterceptor).addPathPatterns(new UrlInterceptor()).excludePathPatterns("/login");

上述注册拦截器路径时(即addPathPatternsexcludePathPatterns的参数),是支持通配符的,写法如下:

通配符 说明
* 匹配单个字符,如/user/*匹配到/user/a等,又如/user/*/ab匹配到/user/p/ab
** 匹配任意多字符(包括多级路径),如/user/**匹配到user/a/user/abs/po等;

上述也可以混合使用,如/user/po*/**/user/{userId}/*(pathValue是可以和通配符共存的);

注:

  1. Spring boot 2.0 后WebMvcConfigurerAdapter已经过时,所以这里并不是继承它,而是继承WebMvcConfigurer
  2. 这里在实操时,使用IDEA工具继承WebMvcConfigurer接口时,使用快捷键Alt+Enter已经无论如何没有提示,进入查看发现这个接口中所有的方法变成了default方法(JDK8新特性,这个修饰符修饰的方法必须要有方法体,此时接口中允许有具体的方法,在实现该接口时,用户可以选择是否重写该方法,而不是必须重写了),所以没有提示,可以手动进入接口中复制对应的方法名(不包括default修饰符)。

对于WebMvcConfigurer接口中的常用方法有如下使用示例,可以选择性重写:

@Configuration
public class MvcConfig implements WebMvcConfigurer {@Autowiredprivate HandlerInterceptor urlInterceptor;private static List<String> myPathPatterns = new ArrayList<>();/*** 在初始化Servlet服务时(在Servlet构造函数执行之后、init()之前执行),@PostConstruct注解的方法被调用*/@PostConstructvoid init() {System.out.println("Servlet init ... ");// 添加匹配的规则, /** 表示匹配所有规则,任意路径myPathPatterns.add("/**");}/*** 在卸载Servlet服务时(在Servlet的destroy()方法之前执行),@PreDestroy注解的方法被调用*/@PreDestroyvoid destroy() {System.out.println("Servlet destory ... ");}/*** 注册配置的拦截器* @param registry 拦截器注册器*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {// addPathPatterns 用于添加拦截规则// excludePathPatterns 用户排除拦截registry.addInterceptor(urlInterceptor).addPathPatterns(myPathPatterns).excludePathPatterns("/user/login");}// 下面的方法可以选择性重写/*** 添加类型转换器和格式化器* @param registry*/@Overridepublic void addFormatters(FormatterRegistry registry) {
//        registry.addFormatterForFieldType(LocalDate.class, new USLocalDateFormatter());}/*** 跨域支持* @param registry*/@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOrigins("*").allowCredentials(true).allowedMethods("GET", "POST", "DELETE", "PUT").maxAge(3600 * 24);}/*** 添加静态资源映射--过滤swagger-api* @param registry*/@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {//过滤swaggerregistry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");registry.addResourceHandler("/swagger-resources/**").addResourceLocations("classpath:/META-INF/resources/swagger-resources/");registry.addResourceHandler("/swagger/**").addResourceLocations("classpath:/META-INF/resources/swagger*");registry.addResourceHandler("/v2/api-docs/**").addResourceLocations("classpath:/META-INF/resources/v2/api-docs/");}/*** 配置消息转换器--这里用的是ali的FastJson* @param converters*/@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {//1. 定义一个convert转换消息的对象;FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();//2. 添加fastJson的配置信息,比如:是否要格式化返回的json数据;FastJsonConfig fastJsonConfig = new FastJsonConfig();fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat,SerializerFeature.WriteMapNullValue,SerializerFeature.WriteNullStringAsEmpty,SerializerFeature.DisableCircularReferenceDetect,SerializerFeature.WriteNullListAsEmpty,SerializerFeature.WriteDateUseDateFormat);//3处理中文乱码问题List<MediaType> fastMediaTypes = new ArrayList<>();fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);//4.在convert中添加配置信息.fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);//5.将convert添加到converters当中.converters.add(fastJsonHttpMessageConverter);}/*** 访问页面需要先创建个Controller控制类,再写方法跳转到页面* 这里的配置可实现直接访问http://localhost:8080/toLogin就跳转到login.jsp页面了* @param registry*/@Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/toLogin").setViewName("login");}/*** 开启默认拦截器可用并指定一个默认拦截器DefaultServletHttpRequestHandler,比如在webroot目录下的图片:xx.png,* Servelt规范中web根目录(webroot)下的文件可以直接访问的,但DispatcherServlet配置了映射路径是/ ,* 几乎把所有的请求都拦截了,从而导致xx.png访问不到,这时注册一个DefaultServletHttpRequestHandler可以解决这个问题。* 其实可以理解为DispatcherServlet破坏了Servlet的一个特性(根目录下的文件可以直接访问),DefaultServletHttpRequestHandler* 可以帮助回归这个特性的* @param configurer*/@Overridepublic void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {configurer.enable();// 这里可以自己指定默认的拦截器configurer.enable("DefaultServletHttpRequestHandler");}/*** 在该方法中可以启用内容裁决解析器,configureContentNegotiation()方法是专门用来配置内容裁决参数的* @param configurer*/@Overridepublic void configureContentNegotiation(ContentNegotiationConfigurer configurer) {// 表示是否通过请求的Url的扩展名来决定media typeconfigurer.favorPathExtension(true)// 忽略Accept请求头.ignoreAcceptHeader(true).parameterName("mediaType")// 设置默认的mediaType.defaultContentType(MediaType.TEXT_HTML)// 以.html结尾的请求会被当成MediaType.TEXT_HTML.mediaType("html", MediaType.TEXT_HTML)// 以.json结尾的请求会被当成MediaType.APPLICATION_JSON.mediaType("json", MediaType.APPLICATION_JSON);}}

3. 关于过滤器

 过滤器是依赖于Servlet的,不依赖于Spring,下面使在SpringBoot中使用过滤器的基本流程:

package com.glodon.tot.filter;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;/*** @author liuwg-a* @date 2018/11/28 9:13* @description 检验缓存中是否有用户信息*/
@Order(2)
@WebFilter(urlPatterns = {"/user/*"}, filterName = "loginFilter")
public class SessionFilter implements Filter {private static final Logger logger = LoggerFactory.getLogger(SessionFilter.class);@Overridepublic void init(FilterConfig filterConfig) throws ServletException {logger.info("come into SessionFilter init...");}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {logger.info("come into SessionFilter and do processes...");// 实际业务处理,这里就是下面图中的before doFilter逻辑HttpServletRequest HRrequest = (HttpServletRequest) request;Cookie[] cookies = HRrequest.getCookies();if (cookies != null) {for (Cookie cookie : cookies) {if (cookie.getName().equals("loginUser")) {logger.info("find loginUser: " + cookie.getValue());break;}}}// 当前过滤器处理完了交给下一个过滤器处理chain.doFilter(request, response);logger.info("SessionFilter's process has completed!");}@Overridepublic void destroy() {logger.info("come into SessionFilter destroy...");}
}

上述的配置中,头部的有两个注解:

  • @Order注解: 用于标注优先级,数值越小优先级越大;
  • @WebFilter注解: 用于标注过滤器,urlPatterns指定过滤的URI(多个URI之间用逗号分隔),filterName指定名字;

注: 使用@WebFilter注解,必须在Springboot启动类上加@ServletComponentScan注解,否则该注解不生效,过滤器无效!

【问题】

实操时,发现@Order(2)注解未生效,即过滤器的执行顺序没有被指定,而是按照默认过滤器类名的排列的顺序执行(即TestFilter.javaAbcFilter.java之后执行),然后发现,如果使用Spring组件注解标注过滤器,比如@Component@Service等注解也是一样的,此时Springboot启动类上无须加@ServletComponentScan注解过滤器即可被扫描),@Order(2)注解生效,多个过滤器按指定顺序执行,但此时又出现一个问题,过滤器上@WebFilter注解设置的过滤URI和名字无效(即urlPatternsfilterName无效,其实此时整个@WebFilter注解都是无效的),在随后排查时,发现控制台有如下日志:

发现先初始化了sessionFilter,然后又初始化了一个loginFilter,但上述两个Filter实际就是上面定义的同一个Filter,即同一个Filter初始化了2次,按结果来看使用@Component注解初始化(过滤URI为/*)的内容覆盖了@WebFilter注解初始化(过滤URI为/school/*)的内容,所以导致@WebFilter注解不生效。

【解决方案】

 最后发现是对@Order注解认识存在误区,这个注解是用于控制Spring组件被加载的顺序,但并不能决定过滤器的执行顺序,应该使用一个配置类来管理各个过滤器的执行顺序和过滤URI、名字等属性(此时,过滤器上不需要加任何注解,springboot启动类上也无需加@ServletComponentScan注解):

package com.glodon.tot.config;import com.glodon.tot.filter.SessionFilter;
import com.glodon.tot.filter.TestFilter2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;import javax.servlet.Filter;
import java.util.Arrays;
import java.util.List;/*** @author liuwg-a* @date 2018/11/28 17:59* @description 配置过滤器*/
@Configuration
public class FilterConfig {// 这种方式过滤器上不需要加任何注解@Beanpublic Filter sessionFilter() {System.out.println("create sessionFilter...");return new SessionFilter();}@Beanpublic Filter testFilter() {System.out.println("create testFilter2...");return new TestFilter2();}// 下面这种方式需要在过滤器上加@Component这类注解,然后完成自动注入// @Autowired// private Filter sessionFilter;// @Autowired// private Filter testFilter;// 有多少个过滤器要配置就写多少,没特殊要求也可以不写@Beanpublic FilterRegistrationBean loginFilterRegistration() {String[] arr = {"/user/go", "/user/login"};List patternList = Arrays.asList(arr);FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();filterRegistrationBean.setFilter(sessionFilter());// filterRegistrationBean.setFilter(sessionFilter);filterRegistrationBean.setUrlPatterns(patternList);filterRegistrationBean.setOrder(2);return filterRegistrationBean;}@Beanpublic FilterRegistrationBean testFilterRegistration() {
//        String[] arr = {"/school/all"};String[] arr = {"/user/go", "/user/login"};List patternList = Arrays.asList(arr);FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();filterRegistrationBean.setFilter(testFilter());// filterRegistrationBean.setFilter(testFilter);filterRegistrationBean.setUrlPatterns(patternList);filterRegistrationBean.setOrder(1);return filterRegistrationBean;}
}

 综合来看拦截器和过滤器,如果过滤器和拦截器有且仅各一个的情况下,运行的流程如下:

多个拦截器和过滤器的运行流程如下(以两个为例):


登录流程可以使用过滤器过滤器所有的URI,在里面检测当前用户是否已经登录,从而判定有无权限访问。

3. 1 关于过滤器链

 这一块主要是将上述定义的过滤器封装成一个自定义链,暴露的问题还比较多,下面是自定义链的过程:

// 过滤器1
@Component("sessionFilter")
public class SessionFilter implements Filter {private static final Logger logger = LoggerFactory.getLogger(SessionFilter.class);@Overridepublic void init(FilterConfig filterConfig) throws ServletException {logger.info("come into SessionFilter init...");}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {logger.info("come into SessionFilter and do processes...");// 实际业务处理...chain.doFilter(request, response);logger.info("SessionFilter's process has completed!");}@Overridepublic void destroy() {logger.info("come into SessionFilter destroy...");}
}// 过滤器2
@Component("testFilter")
public class TestFilter2 implements Filter {private static final Logger logger = LoggerFactory.getLogger(TestFilter2.class);@Overridepublic void init(FilterConfig filterConfig) throws ServletException {logger.info("come into TestFilter2's init...");}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {logger.info("come into TestFilter2 and do processes...");// 实际业务处理...chain.doFilter(request, response);logger.info("TestFilter2's process has completed!");}@Overridepublic void destroy() {logger.info("come into TestFilter2's destroy...");}
}// 封装过滤器List
@Configuration
public class FilterChainBean {@Autowiredprivate Filter sessionFilter;@Autowiredprivate Filter testFilter;@Bean(name = "allMyFilter")public List<Filter> registerFilter() {List<Filter> allMyFilter = new ArrayList<>();allMyFilter.add(sessionFilter);allMyFilter.add(testFilter);return allMyFilter;}
}// 装链
@Service("myFilterChain")
@Order(1)
public class MyChain implements Filter {@Autowiredprivate List<Filter> allMyFilter;@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {if (allMyFilter == null || allMyFilter.size() == 0) {chain.doFilter(request, response);return;}VirtualFilterChain virtualFilterChain = new VirtualFilterChain(chain, allMyFilter);virtualFilterChain.doFilter(request, response);}// 封装过滤器链,参照CompositeFilter中的VirtualFilterChain类代码编写private class VirtualFilterChain implements FilterChain {private final FilterChain originalChain;private final List<Filter> additionalFilters;private final int n;private int pos = 0;private VirtualFilterChain(FilterChain chain, List<Filter> additionalFilters) {this.originalChain = chain;this.additionalFilters = additionalFilters;this.n = additionalFilters.size();}@Overridepublic void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {if (pos >= n) {originalChain.doFilter(request, response);} else {Filter nextFilter = additionalFilters.get(pos++);nextFilter.doFilter(request, response, this);}}}}

链上没有指定过滤器的URI,默认是拦截所有URI,测试上述链的工作流程,发现结果如下:

可以发现两个过滤器执行了2次,重复执行并不是想要的,开始着手追踪原因,在追踪到MyChain中的doFilter()方法时,发现上述自定义链中的内容如下:

猜测问题就是出现在这里,结合debug查看,过滤器链是沿着ApplicationFilterChain不断调用的,内部涉及到链操作的执行函数doFilter()的源码如下:

    @Overridepublic void doFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException {if( Globals.IS_SECURITY_ENABLED ) {final ServletRequest req = request;final ServletResponse res = response;try {java.security.AccessController.doPrivileged(new java.security.PrivilegedExceptionAction<Void>() {@Overridepublic Void run()throws ServletException, IOException {internalDoFilter(req,res);return null;}});} catch( PrivilegedActionException pe) {Exception e = pe.getException();if (e instanceof ServletException)throw (ServletException) e;else if (e instanceof IOException)throw (IOException) e;else if (e instanceof RuntimeException)throw (RuntimeException) e;elsethrow new ServletException(e.getMessage(), e);}} else {internalDoFilter(request,response);}}private void internalDoFilter(ServletRequest request,ServletResponse response)throws IOException, ServletException {// 调用链中下一个过滤器(如果存在的话),可以在这里打断点查看if (pos < n) {ApplicationFilterConfig filterConfig = filters[pos++];try {Filter filter = filterConfig.getFilter();if (request.isAsyncSupported() && "false".equalsIgnoreCase(filterConfig.getFilterDef().getAsyncSupported())) {request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);}if( Globals.IS_SECURITY_ENABLED ) {final ServletRequest req = request;final ServletResponse res = response;Principal principal =((HttpServletRequest) req).getUserPrincipal();Object[] args = new Object[]{req, res, this};SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);} else {filter.doFilter(request, response, this);}} catch (IOException | ServletException | RuntimeException e) {throw e;} catch (Throwable e) {e = ExceptionUtils.unwrapInvocationTargetException(e);ExceptionUtils.handleThrowable(e);throw new ServletException(sm.getString("filterChain.filter"), e);}return;}// We fell off the end of the chain -- call the servlet instancetry {if (ApplicationDispatcher.WRAP_SAME_OBJECT) {lastServicedRequest.set(request);lastServicedResponse.set(response);}if (request.isAsyncSupported() && !servletSupportsAsync) {request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,Boolean.FALSE);}// Use potentially wrapped request from this pointif ((request instanceof HttpServletRequest) &&(response instanceof HttpServletResponse) &&Globals.IS_SECURITY_ENABLED ) {final ServletRequest req = request;final ServletResponse res = response;Principal principal =((HttpServletRequest) req).getUserPrincipal();Object[] args = new Object[]{req, res};SecurityUtil.doAsPrivilege("service",servlet,classTypeUsedInService,args,principal);} else {servlet.service(request, response);}} catch (IOException | ServletException | RuntimeException e) {throw e;} catch (Throwable e) {e = ExceptionUtils.unwrapInvocationTargetException(e);ExceptionUtils.handleThrowable(e);throw new ServletException(sm.getString("filterChain.servlet"), e);} finally {if (ApplicationDispatcher.WRAP_SAME_OBJECT) {lastServicedRequest.set(null);lastServicedResponse.set(null);}}}

上述Demo中没有使用Spring security内容,所以关注点主要集中在internalDoFilter()函数上,说的简单点就是不断调用数组中的过滤器,通过debug查看链的执行流程如下:

其中右侧的过滤器是MyChain自定义链,其他的characterEncodingFilterhiddenHttpMethodFilterformContentFilterrequestContextFilter过滤器是Spring框架内部帮我们自动实现的几个过滤器,不管Spring帮我们定义多少个过滤器,因为过滤器是Servlet规范中的,所以这些过滤器最终还有要汇总到Servelet容器中,对于上述情况具体来说,就是要汇总到ApplicationFilterChain(包位置:org.apache.catalina.core,嗯,更明白了),而Servlet把外部定义的过滤器(包括Spring框架定义的一些必要的过滤器)全部放到我们手动定义的过滤器链中,所以执行了2次,而且不仅仅是我们上面手写的SessionFilterTestFilter2,还有Spring提供的过滤器实际都执行了2次。

解决方案:原因找了,理论上可以想到两种方案:一种是不要让Spring帮我们自定过滤器了,所有的过滤器都由自己实现管理,最后交给Servlet的过滤器链;第二种就是我们所有的过滤器都交给Spring管理,不直接和Filter发生联系,而是通过Spring间接和Filter联系,包括最后链的交接也由Spring和Filter去搞。

方案1

过滤器由自己管理,不通过IOC自动注入,手动new:

// 过滤器1,不加Spring注解
public class SessionFilter implements Filter {// 内容不变,省去...
}// 过滤器2
public class TestFilter2 implements Filter {// 内容不变,省去...
}// 封装过滤器List
@Configuration
public class FilterChainBean {@Bean(name = "allMyFilter")public List<Filter> registerFilter() {List<Filter> allMyFilter = new ArrayList<>();// 通过 new 的方式加入allMyFilter.add(new SessionFilter());allMyFilter.add(new TestFilter2());return allMyFilter;}
}// 装链
@Service("myFilterChain")
@Order(1)
public class MyChain implements Filter {// 内容不变...
}

捉摸着这样不就把ApplicationFilterChain主链中的重复的sessionFiltertestFilter2去除了吗,只有自定义的链MyChain中有这两个过滤器,实际发现,并没有那么简单,debug和结果如下:

我去,发现我自己写的2个过滤器没有起作用,然后找了一下,最后的发现:

主链中确实没有sessionFiltertestFilter2了,但期望发现List中的additionalFilters也没有这两个过滤器,搞得有点懵,为什么通过new的方式没有将上述两个过滤器放进List中,答案未知。。。

方案1:设置标志位

结合account的代码和网络资料以及OncePerRequestFilter源码发现,可以通过在过滤器中设置标志位来解决问题(GenericFilterBean是Spring框架对Filter的实现),代码如下:

// 过滤器1
@Component("sessionFilter")
public class SessionFilter extends GenericFilterBean {private static final Logger logger = LoggerFactory.getLogger(SessionFilter.class);/*** 标识位,存入request中*/private static final String FILTER_APPLIED = SessionFilter.class.getName() + ".FILTERED";@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {if (request.getAttribute(FILTER_APPLIED) != null) {// 如果已经执行过了就啥也不干,直接执行下一个过滤器,执行完直接返回System.out.println("SessionFilter:非第一次执行,啥也不干。。。");chain.doFilter(request, response);System.out.println("SessionFilter: 非第一次执行,下一个Filter执行完毕,即将结束扫尾工作。。。");return;} else {// 设置已执行的标识位,放入request中request.setAttribute(FILTER_APPLIED, true);// 实际业务处理...System.out.println("SessionFilter:第一次开始执行,可以在这里进行业务处理");Cookie[] cookies = ((HttpServletRequest) request).getCookies();if (cookies != null) {for (Cookie cookie : cookies) {if (cookie.getName().equals("loginUser")) {logger.info("find loginUser: " + cookie.getValue());break;}}}chain.doFilter(request, response);System.out.println("SessionFilter: 第一次执行成功");}}@Overridepublic void destroy() {logger.info("come into SessionFilter destroy...");}
}// 过滤器2
@Component("testFilter")
public class TestFilter2 extends GenericFilterBean {private static final Logger logger = LoggerFactory.getLogger(TestFilter2.class);/*** 标识符*/private static final String FILTER_APPLIED = TestFilter2.class.getName() + ".FILTERED";@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {if (request.getAttribute(FILTER_APPLIED) != null) {// 如果已经执行过了就啥也不干,直接执行下一个过滤器,执行完直接返回System.out.println("TestFilter2:非第一次执行,我啥也不干。。。");chain.doFilter(request, response);System.out.println("TestFilter2:非第一次执行,下一个Filter执行完毕,即将结束扫尾工作。。。");return;} else {// 设置已执行的标识位,放入request中request.setAttribute(FILTER_APPLIED, true);// 实际业务处理...System.out.println("TestFilter2:第一次开始执行,可以在这里进行逻辑业务处理");chain.doFilter(request, response);System.out.println("TestFilter2:初次执行成功");}}@Overridepublic void destroy() {logger.info("come into TestFilter2's destroy...");}
}

方案2:交给CompositeFilter管理

 在方案1中,无意间发现CompositeFilter这个类,源码如下:

public class CompositeFilter implements Filter {private List<? extends Filter> filters = new ArrayList<>();public void setFilters(List<? extends Filter> filters) {this.filters = new ArrayList<>(filters);}@Overridepublic void init(FilterConfig config) throws ServletException {for (Filter filter : this.filters) {filter.init(config);}}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {new VirtualFilterChain(chain, this.filters).doFilter(request, response);}@Overridepublic void destroy() {for (int i = this.filters.size(); i-- > 0;) {Filter filter = this.filters.get(i);filter.destroy();}}private static class VirtualFilterChain implements FilterChain {private final FilterChain originalChain;private final List<? extends Filter> additionalFilters;private int currentPosition = 0;public VirtualFilterChain(FilterChain chain, List<? extends Filter> additionalFilters) {this.originalChain = chain;this.additionalFilters = additionalFilters;}@Overridepublic void doFilter(final ServletRequest request, final ServletResponse response)throws IOException, ServletException {if (this.currentPosition == this.additionalFilters.size()) {this.originalChain.doFilter(request, response);}else {this.currentPosition++;Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);nextFilter.doFilter(request, response, this);}}}}

发现猜测它可以帮我们管理手写的过滤器,唯一要做的就是将过滤器通过setFilters()方法塞进去就好了,会自动封装一个虚拟链(之前自定义的封装连代码可以直接丢弃),详细代码如下:

// 过滤器1
public class SessionFilter extends GenericFilterBean {private static final Logger logger = LoggerFactory.getLogger(SessionFilter.class);@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {// 实际业务处理...System.out.println("SessionFilter:开始执行,可以在这里进行业务处理");Cookie[] cookies = ((HttpServletRequest) request).getCookies();if (cookies != null) {for (Cookie cookie : cookies) {if (cookie.getName().equals("loginUser")) {logger.info("find loginUser: " + cookie.getValue());break;}}}chain.doFilter(request, response);System.out.println("SessionFilter: 执行成功");}@Overridepublic void destroy() {logger.info("come into SessionFilter destroy...");}
}// 过滤器2
public class TestFilter2 extends GenericFilterBean {private static final Logger logger = LoggerFactory.getLogger(TestFilter2.class);@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {// 实际业务处理...System.out.println("TestFilter2:开始执行,可以在这里进行逻辑业务处理");chain.doFilter(request, response);System.out.println("TestFilter2:执行成功");}@Overridepublic void destroy() {logger.info("come into TestFilter2's destroy...");}
}// 配置符合过滤器(无需手写虚拟链,全部塞进CompositeFilter即可自动封装,无需再写MyChain)
@Configuration
public class FilterChainBean {@Bean("myChain")public CompositeFilter addFilterInChain() {List<Filter> allMyFilter = new ArrayList<>();allMyFilter.add(new SessionFilter());allMyFilter.add(new TestFilter2());CompositeFilter compositeFilter = new CompositeFilter();compositeFilter.setFilters(allMyFilter);return compositeFilter;}
}

Spring中拦截器和过滤器的使用相关推荐

  1. struts2中拦截器和过滤器的比较

    拦截器和过滤器的区别: 1.拦截器是基于java的反射机制的,而过滤器是基于函数回调 2.过滤器依赖与servlet容器,而拦截器不依赖与servlet容器 3.拦截器只能对action请求起作用,而 ...

  2. Java中拦截器和过滤器的声明用途和区别

    过滤器: 1.基于Servlet框架 2.Oracle公司的任何Java Web框架都可以用 3.可以拦截任何资源,包括 .jsp .java .html等等 拦截器: 1.基于SpringMvc框架 ...

  3. 关于SSM框架设置拦截器和过滤器

    我们知道拦截器和过滤器都是在项目中起到拦截过滤请求的功能,所以可能在设置的时候会傻傻分不清.这里我们先来比较它们的区别. 过滤器Filter是JavaEE标准,在Servlet的规范中定义的,是Ser ...

  4. 详解拦截器和过滤器的区别

    拦截器和过滤器的区别 过滤器和拦截器的区别: ①拦截器是基于java的反射机制的,而过滤器是基于函数回调. ②拦截器不依赖与servlet容器,过滤器依赖与servlet容器. ③拦截器只能对acti ...

  5. 拦截器和过滤器有什么区别?

    首先了解什么是过滤器什么是拦截器 ①过滤器(Filter) 过滤器通过直接实现Filter接口实现,也可以通过@WebFilter注解实现特定的URL拦截 在Filter接口中定义了三个方法: ini ...

  6. 区分Java拦截器和过滤器

    今天带大家分析java拦截器和过滤器的区别,文中有非常详细的解释说明,对正在学习java的小伙伴们有很好的帮助,需要的朋友可以参考下 一.过滤器(filter) 过滤器处于客户端与Web资源(Serv ...

  7. SpringBoot 拦截器和过滤器

    拦截器和过滤器 时光飞逝,最近也是很忙,但是忙到最后发现在自己并没有太多的成长 工作 学习 生活 没想到成长是不经意间的,像是被 推着,让你身不由己 午休时间,写写博客,也是保留一些自己的时间和空间 ...

  8. 【SpringMVC】拦截器和过滤器

    拦截器: 拦截器是springmvc中的一种,需要实现HandlerInterceptor接口. 拦截器和过滤器类似,功能方向侧重点不同. 过滤器是用来过滤器请求参数,设置编码字符集等工作. 拦截器是 ...

  9. java 拦截器和过滤器的区别

    介绍 在 Java Web 应用程序中,拦截器和过滤器是两种不同的机制,用于在请求/响应处理过程中进行拦截和过滤.两者都可以用来在请求到达目标资源之前对其进行预处理.修改或拦截. 但是,拦截器和过滤器 ...

  10. 拦截器和过滤器之间有很多相同之处,但是两者之间存在根本的差别

    转自:https://www.cnblogs.com/shangc/p/5939708.html 拦截器和过滤器之间有很多相同之处,但是两者之间存在根本的差别.其主要区别为以下几点: 1)拦截器是基于 ...

最新文章

  1. Android JNI原理分析
  2. wxWidgets:wxRichTextCtrl概述
  3. Spring Cloud-鸿鹄Cloud分布式微服务云系统—架构图
  4. 顺丰控股:2月速运物流业务营业收入98.49亿元,同比下降3.36%
  5. Anaconda 国内镜像配置
  6. duilib消息类型
  7. 互联网金融一:大额支付系统、小额支付系统介绍
  8. 两款常用的 MQTT 调试工具
  9. 数据采集工具:八爪鱼
  10. 怎样将图片转换成word文字
  11. 推荐一些国外前端的学习网站
  12. 什么是青藤零域·微隔离安全平台?
  13. L1-007 念数字(C语言)
  14. 呸!都TM开始打广告了,垃圾!
  15. 李沐《动手学》-kaggle-房价预测-submission
  16. transformer的简要解读(应该都能看懂)
  17. Word将一个文档模板的样式复制到另外一个文档
  18. 关于公网IP和私有IP的区别
  19. 发布WebGL遇到的问题
  20. DJI大疆创新招聘-自动化测试工程师

热门文章

  1. 电子之220V有效值峰值峰峰值
  2. java实现远程桌面监控
  3. 永中集成Office为何无法随意克隆(Clone)?
  4. 中东国家以色列Avatrade和Rami Kalish的故事
  5. linux 小度 驱动_糯米WiFi安装Linux驱动 (同时支持百度WiFi、小米WiFi、腾讯全民WiFi、360WiFi)...
  6. 中国省份城市数据库表
  7. 如何查看局域网内所有IP
  8. 云服务器Tomcat版本升级(Tomcat6升级至Tomcat7和Tomcat8)问题总结
  9. vue实现滑块拖拽校验
  10. c语言 图形模式 在指定位置显示文本显示文字,C语言图形编程(四、图形文本-01)...