1、引言
我们在开发过程中都会遇到需要统一处理接口或者参数的场景,这个时候我们就会用到过滤器(Filter)或者拦截器(Intercepter)。
测试代码参考 chapter-2-springmvc-quickstart:
https://gitee.com/leo825/spring-framework-learning-example.git
2、共同点
1、都可以拦截请求和过滤请求
2、都用了责任链设计模式,并且都可以对请求进行预处理和后处理
3、区别
3.1、拦截器
- 依赖于web框架实现,在我们使用的SpringMVC这种就是依赖于SpringMVC框架
- 在实现上基于Java的反射机制,属于面向切面(AOP)的一种应用
- 可以在一个Controller生命周期内进行多次调用,但是只能对Controller进行拦截
- 主要作用:由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,并且一个拦截器可以在Controller生命周期内进行多次调用。
3.2、过滤器
- 过滤器依赖于Servlet容器
- 过滤器实现上基于函数回调,可以几乎对所有请求进行过滤(包括静态资源过滤)
- 过滤器实例只能在容器初始化的时候调用一次
- 主要作用:执行过滤操作,比如敏感信息、特殊请求、xss方漏洞、统一加解密参数等
4、具体实现
4.1、拦截器
4.1.1、拦截实现方式
SpringMVC拦截器(Interceptor)实现对每一个请求处理前后进行相关业务是通过HandlerInterceptor来实现的。定义一个拦截器,可以通过以下3种方式:
- 实现Spring提供的的
HandlerInterceptor
接口; - 继承Spring中的抽象类
HandlerInterceptorAdapter
,此抽象类实现了HandlerInterceptor
接口; - 实现Spring提供的
WebRequestInterceptor
接口;
4.1.2、拦截实现
具体实现举一个例子,测试代码如下:
package com.leo.interceptor;
import com.leo.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.List;
/**
* @ClassName: HandlerInterceptor2
* @Description: 测试拦截器2
* 简单说明一下:本工程是采用xml方式注册的拦截器,因此可以直接依赖注入,
* 如果是SpringBoot方式注册拦截器,不要使用new的方式创建拦截器对象,
* 要把拦截器通过@Autowired注入进来,然后注册到Spring容器中,
* 不然这个拦截器中通过依赖注入到的userInfoService永远是null
* @Author: leo825
* @Date: 2020-02-03 16:05
* @Version: 1.0
*/
public class HandlerInterceptor2 extends HandlerInterceptorAdapter {
@Autowired
UserInfoService userInfoService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("HandlerInterceptor2 preHandle");
HttpSession session = request.getSession();
session.setAttribute("startTime",System.currentTimeMillis());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("HandlerInterceptor2 postHandle");
//访问数据库
List userInfoList = userInfoService.getUserInfoList();
System.out.print("HandlerInterceptor2 信息: ");
System.out.println(userInfoList);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("HandlerInterceptor2 afterCompletion");
HttpSession session = request.getSession();
long startTime = (long)session.getAttribute("startTime");
System.out.println("HandlerInterceptor2 过滤的接口耗时:" + (System.currentTimeMillis() - startTime) + "ms");
}
}
spring配置文件要添加相应的配置,如下所示:
<mvc:interceptor>
<!-- /* 是一级目录下的路径; /** 不分目录等级, 即所有请求 -->
<mvc:mapping path="/test2/**"/>
<bean class="com.leo.interceptor.HandlerInterceptor2"></bean>
</mvc:interceptor>
访问地址:http://localhost:8080/springmvc/test2/hello,运行结果如下所示:
HandlerInterceptor1 preHandle
HandlerInterceptor2 preHandle
HandlerInterceptor3 preHandle
HandlerInterceptor4 preHandle
HandlerInterceptor5 preHandle
使用配置实现 hello controller 跳转到 success
HandlerInterceptor5 postHandle
HandlerInterceptor4 postHandle
HandlerInterceptor3 postHandle
HandlerInterceptor2 postHandle
HandlerInterceptor2 信息: [UserInfo{id=3, name='晓玲', gender='女', age='22', remarks='工程师'}, UserInfo{id=4, name='晓玲', gender='女', age='24', remarks='工程师'}]
HandlerInterceptor1 postHandle
HandlerInterceptor5 afterCompletion
HandlerInterceptor5 过滤的接口耗时:330ms
HandlerInterceptor4 afterCompletion
HandlerInterceptor4 过滤的接口耗时:330ms
HandlerInterceptor3 afterCompletion
HandlerInterceptor3 过滤的接口耗时:330ms
HandlerInterceptor2 afterCompletion
HandlerInterceptor2 过滤的接口耗时:330ms
HandlerInterceptor1 afterCompletion
HandlerInterceptor1 过滤的接口耗时:330ms
具体代码可以自行下载阅览。
4.2、过滤器
4.2.1、过滤器实现方式
- 直接实现
Filter
接口,这一类过滤器只有CompositeFilter
; - 继承抽象类
GenericFilterBean
,该类实现了Filter
,这一类的过滤器只有一个,即DelegatingFilterProx; - 继承抽象类
OncePerRequestFilter
,该类为GenericFilterBean
的直接子类,这一类过滤器包括CharacterEncodingFilter、HiddenHttpMethodFilter、HttpPutFormContentFilter、RequestContextFilter和ShallowEtagHeaderFilter; - 继承抽象类
AbstractRequestLoggingFilter
,该类为OncePerRequestFilter
的直接子类,这一类过滤器包括CommonsRequestLoggingFilter、Log4jNestedDiagnosticContextFilter和ServletContextRequestLoggingFilter。
这些类之间的关系就略了,可以参考这篇文章Spring MVC过滤器-超类
4.2.2、过滤器实现
过滤器的实现方法就举一个例子了,其他的可自行参考测试
public class MyFilter3 extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
System.out.println("MyFilter3 执行过滤");
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
还有莫忘了在web.xml中增加过滤器的拦截配置
<filter>
<filter-name>MyFilter3</filter-name>
<filter-class>com.leo.filter.MyFilter3</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter3</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
还可以使用注解方式去实现例如在类上添加如下注解也可以实现
@WebFilter(urlPatterns = "/*", filterName = "myFilter3")
5、过滤器中依赖注入(深坑)
5.1、问题描述
在项目中需要在filter中注入@Service注解的Service服务,但是尝试了很多方法都无法实现(笔者也是),使用@Autowired注解注入的Service对象一致都是null,请问是这怎么回事?
分析:既然是注入的Service对象一直是null,那就是考虑原因是否filter的创建要早于@Service注解的对象。
尝试:如果是Filter比Service的Bean实例更早创建,那就改变创建的顺序,将@Service早一步创建不就行了。尝试之后仍然报null,那就不是这方面的问题了。
再次从网上查阅资料,这块涉及web启动的原理,web应用启动的顺序是:Listener->Filter->Servlet,因为我们在web项目中一般都会用到两个配置文件applicationContext.xml和springmvc-servlet.xml,配置spring的时候会添加一个Listener,它会读取applicationContext.xm配置信息对Spring Context进行配置。因此在applicationContext.xml中的bean首先被初始化和注入,然后在对Filter进行初始化,在接着对DispatcherServlet进行初始化。因此我们在Filter中注入的Bean会失败。这里提供两种方式获取Bean
5.2、Filter依赖注入实现
5.2.1、方法一:web.xml配置实现
通过配置代理,将自定义的Filter注入,配置代码如下:
<filter>
<filter-name>DelegatingFilterProxy</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>myFilter</param-value>
</init-param>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value><!-- 此参数必需设置-->
</init-param>
</filter>
<filter-mapping>
<filter-name>DelegatingFilterProxy</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
过滤器的代码如下:
@Component("myFilter")
public class MyFilter implements Filter {
@Autowired
UserInfoService userInfoService;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("MyFilter过滤器初始化");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("MyFilter过滤器执行过滤");
//访问数据库
List userInfoList = userInfoService.getUserInfoList();
System.out.println(userInfoList);
chain.doFilter(request, response);
}
@Override
public void destroy() {
System.out.println("MyFilter过滤器销毁了");
}
}
以上代码都是经过测试,真实可用的。
5.2.2、方法二:继承WebApplicationInitializer,并注册Filter
实现代码示例:
public class MyFilterConfig implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
DelegatingFilterProxy delegatingFilterProxy = new DelegatingFilterProxy();
//Bean实例名称
String BeanId = "myFilter5";
//代理的过滤器的Bean
delegatingFilterProxy.setTargetBeanName(BeanId);
//设置"targetFilterLifecycle"为True,则spring来管理Filter.init()和Filter.destroy();若为false,则这两个方法失效!
delegatingFilterProxy.setTargetFilterLifecycle(true);
//注册过滤器
FilterRegistration filterRegistration = servletContext.addFilter(BeanId, delegatingFilterProxy);
filterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
System.out.println("代理拦截器,将Bean注入到Web容器中");
}
}
其中的myFilter5就是过滤器的Bean实例,如下:
@Component
public class MyFilter5 implements Filter {
@Autowired
UserInfoService userInfoService;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("MyFilter5 执行过滤");
//访问数据库
List userInfoList = userInfoService.getUserInfoList();
System.out.print("MyFilter5 信息: ");
System.out.println(userInfoList);
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
5.2.3、方法三:实现ApplicationContextAware接口的工具
首先编写ApplicationContextUtil
工具类实现ApplicationContextAware
接口
@Component
public class ApplicationContextUtil implements ApplicationContextAware{
private static ApplicationContext applicationContext;
/**
* 通过bean的id获取bean对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
return applicationContext.getBean(beanName);
}
/**
* 根据bean的id和类型获取bean对象
* @param beanName
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(String beanName,Class<T> clazz){
return clazz.cast(getBean(beanName));
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
其次在拦截器中通过这个工具获取Bean对象
@Component
@WebFilter(urlPatterns = "/*", filterName = "myFilter6")
public class MyFilter6 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("MyFilter6 执行过滤");
//访问数据库,这个地方要注意使用的是“userInfoServiceImpl”,因为默认是按照类名首字符小写注入Spring中的
UserInfoService userInfoService = (UserInfoService) ApplicationContextUtil.getBean("userInfoServiceImpl");
List userInfoList = userInfoService.getUserInfoList();
System.out.print("MyFilter6 信息: ");
System.out.println(userInfoList);
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
5.2.4、方法四:获取WebApplicationContext对象
获取WebApplicationContext
对象从而获取相应的Bean对象
@Component
@WebFilter(urlPatterns = "/*", filterName = "myFilter7")
public class MyFilter7 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("MyFilter7 执行过滤");
//访问数据库,这个地方要注意使用的是“userInfoServiceImpl”,因为默认是按照类名首字符小写注入Spring中的
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
UserInfoService userInfoService = (UserInfoService) webApplicationContext.getBean("userInfoServiceImpl");
List userInfoList = userInfoService.getUserInfoList();
System.out.print("MyFilter7 信息: ");
System.out.println(userInfoList);
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
通过WebApplicationContext获取Bean对象的方式有很多,上面只是举了一个普通的常见方式。
例如:WebApplicationContextUtils 和 ContextLoader方式
可以参考《Spring容器中获取Bean实例的七种方式(附实战源码》这篇文章里面的示例。
注意:平时都是使用 @Autowired按照类型注入UserInfoService,但是实际上Spring中注入的id=userInfoServiceImpl的Bean
5、拦截器执行顺序
拦截器和过滤器执行顺序可以根据下图来说明
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/72744.html