一、过滤器
Filter 是对客户端访问资源的过滤,符合条件放行,不符合条件不放行,并且可以对目标资源访问前后进行逻辑处理。
1、过滤器生命周期
Filter对象的生命周期:
- 实例化:服务器启动时,实例化;
- 初始化:调用 init() 方法(只会被初始化一次);
- 过滤:当用户请求与过滤器关联的 url 匹配时,调用 doFilter() 方法。FilterChain 参数可以调用chain.doFilter 方法,将请求传给下一个过滤器(或目标资源),或利用转发、重定向将请求转发到其他资源;
- 销毁:服务器关闭时销毁 Filter 对象,可以通过 destroy() 方法在 Filter 对象销毁前进行处理。
过滤器(Filter)属于 Servlet 的范畴,通过实现 javax.servlet.Filter 接口来实现功能,该接口定义了3个方法:
- void init(FilterConfig filterConfig):容器启动,初始化 Filter 时会被调用,整个生命周期只会被调用一次,可用于完成 Filter 的初始化。其中参数 filterConfig (每次创建 Filter 的时候,也会同时创建一个 FilterConfig)代表该 Filter 对象配置信息的对象;
- void doFilter(ServletRequest request, ServletResponse response, FilterChain chain): 实现过滤功能,就是通过该方法对每个请求增加额外的处理。其参数 FilterChain(过滤器链对象)的 doFilter() 方法可以放行请求;
- void destroy():用于 Filter 销毁前,完成某些资源的回收。
2、过滤器执行顺序
如果配置了多个相同路径的过滤器,服务器会按照过滤器定义的先后顺序组装成一条链。 过滤器链的执行顺序:依次执行每个过滤器放行方法前的代码,最后交给 Servlet 的 Service() 方法,处理后倒序依次执行每个过滤器放行方法后的代码,然后返回给用户请求。
3、过滤器的配置使用
过滤器的配置使用有两种方式:
- 通过 @ServletComponentScan 和 @WebFilter 注解配置;
- 自定义注解,通过配置类进行配置。
3.1、通过注解配置过滤器
使用注解配置过滤器步骤如下:
- 启动类上添加 @ServletComponentScan;
- 自定义过滤器,实现 javax.servlet.Filter 接口,在自定义过滤器上添加注解 @WebFilter。
@Slf4j
@WebFilter(filterName = "loginFilter", urlPatterns = "/*")
public class LoginFilter implements Filter {
public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
private static final String[] URLS = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**",
"/common/**"
};
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("LoginFilter 初始化.......");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String requestURI = request.getRequestURI();
log.info("拦截到请求:{}", requestURI);
boolean check = checkURI(URLS, requestURI);
if (check){
log.info("本次{}请求不需要处理!", requestURI);
filterChain.doFilter(request, response);
return;
}
Long userId = (Long) request.getSession().getAttribute("user");
if (userId != null){
log.info("用户已登录,用户ID为:{}", userId);
filterChain.doFilter(request, response);
return;
}
log.info("用户未登录!");
response.setContentType("application/json; charset=utf-8");
response.getWriter().write(JSON.toJSONString(Result.noPermission()));
}
@Override
public void destroy() {
log.info("LoginFilter 销毁.......");
}
public boolean checkURI(String[] urls, String requestURI){
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURI);
if (match){
return true;
}
}
return false;
}
}
注意:如果有多个通过注解方式配置的过滤器,是无法指定过滤器执行顺序的(默认的执行顺序由 @WebFilter 注解的 filterName 属性根据过滤器名称排序决定)。
3.2、通过配置类配置过滤器
使用配置类配置过滤器步骤如下:
- 自定义过滤器,实现 javax.servlet.Filter 接口;
- 自定义过滤器配置类,指定过滤器拦截配置。
@Configuration
public class FilterConfig {
@Autowired
private LoginFilter loginFilter;
@Autowired
private DepartmentFilter departmentFilter;
@Bean
public FilterRegistrationBean loginFilterRegistration() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(loginFilter);
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setOrder(10);
filterRegistrationBean.setName("loginFilter");
return filterRegistrationBean;
}
@Bean
public FilterRegistrationBean departmentFilterRegistration() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(departmentFilter);
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setOrder(1);
filterRegistrationBean.setName("departmentFilter");
return filterRegistrationBean;
}
}
通过配置类定制过滤器配置信息,可以指定过滤器的执行顺序。
二、拦截器
SpringMVC拦截器(Interceptor)实现对每一个请求处理前后进行相关的业务处理。在SpringMVC中定义一个拦截器非常简单,主要有4种方式:
- 实现 HandlerInterceptor 接口;
- 继承实现了 HandlerInterceptor 接口的类,比如 Spring 已经提供的实现了 HandlerInterceptor 接口的抽象类 WebRequestHandlerInterceptorAdapter;
- 实现 WebRequestInterceptor 接口;
- 继承实现了 WebRequestInterceptor 接口的类。
实现了拦截器之后,可以通过重写 WebMvcConfigurer 接口的 addInterceptors 方法注册拦截器。
1、HandlerInterceptor 接口
HandlerInterceptor 接口定义了如下三个方法:
- preHandle (HttpServletRequest request, HttpServletResponse response, Object handle) 方法:在请求处理之前被调用。Interceptor 是链式调用的,可以存在多个 Interceptor。Interceptor 的调用会依据声明顺序依次执行,最先执行的都是 preHandle 方法,可在该方法中进行一些前置(预)处理,也可进行判断来决定是否要继续执行。当返回为 false 时,表示请求结束,后续的 Interceptor 和 Controller 都不会再执行;当返回值为 true 时,会继续调用下一个 Interceptor 的 preHandle 方法,执行完最后一个 Interceptor 后会调用当前请求的 Controller 方法;
- postHandle (HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView) 方法:会在 Controller 方法调用之后,DispatcherServlet 进行渲染视图之前被调用,所以可以对 Controller 处理之后的 ModelAndView 对象进行操作。postHandle 方法被调用的方向跟 preHandle 是相反的,先声明的 Interceptor 的 postHandle 方法反而会后执行;
- afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex) 方法:该方法也是需要当前对应的 Interceptor 的 preHandle 方法的返回值为true时才会执行,会在整个请求结束之后被调用,也就是在 DispatcherServlet 渲染了对应的视图之后执行。这个方法的主要是用于进行资源清理。
2、WebRequestInterceptor 接口
WebRequestInterceptor 中也定义了三个方法,这三个方法都传递了同一个参数 WebRequest ,WebRequest 是Spring 定义的一个接口,它里面的方法定义都基本跟 HttpServletRequest 一样,在WebRequestInterceptor 中对 WebRequest 进行的所有操作都将同步到 HttpServletRequest 中,然后在当前请求中一直传递:
- preHandle(WebRequest request) 方法:该方法将在请求处理之前进行调用,也就是说会在Controller 方法调用之前被调用。这个方法跟 HandlerInterceptor 中的 preHandle 是不同的,主要区别在于该方法的返回值是 void ,所以一般用它来进行资源的准备工作;
- postHandle(WebRequest request, ModelMap model) 方法:该方法将在请求处理之后,也就是在 Controller 方法调用之后被调用,但是会在视图返回被渲染之前被调用,所以可以在这个方法里面通过改变数据模型 ModelMap 来改变数据的展示。该方法有两个参数:
- WebRequest 对象是用于传递整个请求数据的,比如在 preHandle 中准备的数据都可以通过WebRequest 来传递和访问;
- ModelMap 就是Controller 处理之后返回的 Model 对象,可以通过改变它的属性来改变返回的Model 模型;
- afterCompletion(WebRequest request, Exception ex) 方法:该方法会在整个请求处理完成,也就是在视图返回并被渲染之后执行。所以在该方法中可以进行资源的释放操作:
- Exception 参数表示的是当前请求的异常对象,如果在Controller 中抛出的异常已经被Spring 的异常处理器给处理了的话,那么这个异常对象就是是null 。
3、拦截器执行顺序
如果配置了拦截器,首先会执行 preHandle() 方法,如果方法返回值为true,则会继续向下执行处理器中的方法,否则不再向下执行;在业务控制器 Controller 处理完请求后,会执行 postHandle() 方法,然后会通过 DispatcherServlet 向客户端返回响应;在 DispatcherServlet 处理完请求后,才会执行 afterCompletion() 方法:
如果配置了多个拦截器,每个拦截器都会按照配置的顺序执行,但是 preHadle() 方法会按照配置文件拦截器的配置顺序执行,postHandle() 方法和 afterCompletion() 方法则会按照配置顺序的相反顺序执行:
4、自定义拦截器
自定义拦截器步骤如下:
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
log.info("LoginInterceptor 拦截到请求:{}", requestURI);
HttpSession session = request.getSession();
Object username = session.getAttribute("username");
if(null == username){
log.info("用户未登录!");
response.setContentType("application/json; charset=utf-8");
response.getWriter().write(JSON.toJSONString(Result.noPermission()));
return false;
}
log.info("用户已登录!");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
log.info("postHandle 方法被执行....");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
log.info("afterCompletion 方法被执行....");
}
}
@Configuration
public class WebappAdapter implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
List<String> patterns = new ArrayList<>();
patterns.add("/bootstrap3/**");
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(patterns)
.order(1);
WebMvcConfigurer.super.addInterceptors(registry);
}
}
三、拦截器和过滤器的区别
- 使用范围与规范不同:Filter 是 Servlet 规范中定义的,只能用于 Web 程序中,依赖于 Servlet 容器;拦截器是 Spring 的组件,不依赖 Servlet 容器;
- 使用资源不同:拦截器可以使用Spring里的任何资源、对象,例如 Service 对象、数据源、事务管理等,通过IOC注入到拦截器即可;而 Filter 则不能;
- 作用范围不同:Filter 对几乎所有的请求起作用;而 Interceptor 只能对 action 请求起作用。
|