Spring Boot 监听器、拦截器以及过滤器的使用
监听器
在一些业务场景中,当容器初始化完成之后,需要处理一些操作,比如一些数据的加载、初始化缓存、特定任务的注册等等。这个时候我们就可以使用Spring提供的ApplicationListener来进行操作。
拦截器
Interceptor 在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。比如日志,安全等。一般拦截器方法都是通过动态代理的方式实现。可以通过它来进行权限验证,或者判断用户是否登陆,或者是像12306 判断当前时间是否是购票时间。
它依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上,基于Java的反射机制,属于面向切面编程(AOP)的一种运用,就是在service或者一个方法前,调用一个方法,或者在方法后,调用一个方法,比如动态代理就是拦截器的简单实现,在调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在调用方法后打印出字符串,甚至在抛出异常的时候做业务逻辑的操作。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理。
过滤器
Filter是Servlet技术中最实用的技术,它依赖于servlet容器。在实现上,基于函数回调,Web开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。它主要用于对用户请求进行预处理,也可以对HttpServletResponse进行后处理。使用Filter的完整流程:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。
使用过滤器的目的,是用来做一些过滤操作,获取我们想要获取的数据,比如:在Javaweb中,对传入的request、response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者Controller进行业务逻辑操作。通常用的场景是:在过滤器中修改字符编码(CharacterEncodingFilter)、在过滤器中修改HttpServletRequest的一些参数(XSSFilter(自定义过滤器)),如:过滤低俗文字、危险字符等。
1. 监听器的使用
创建一个Spring Boot就不用说了,现在假设有这么一个场景:我需要再Spring启动的时候在用户表里insert条数据,便于启动完成后去读取,那就可以使用监听器,看如下代码:
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.ContextStartedEvent;
import org.springframework.context.event.ContextStoppedEvent;
@Component
public class AppListener implements ApplicationListener {
private static final Logger LOGGER = LoggerFactory.getLogger(AppListener.class);
@Autowired
private UserService userService;
@Override
public void onApplicationEvent(ApplicationEvent event) {
// 在这里可以监听到Spring Boot的生命周期
if (event instanceof ApplicationEnvironmentPreparedEvent) {
// 初始化环境变量
LOGGER.info("**************onApplicationEvent 初始化环境变量 **************");
} else if (event instanceof ApplicationPreparedEvent) {
// 初始化完成
LOGGER.info("**************onApplicationEvent 初始化完成 **************");
} else if (event instanceof ContextRefreshedEvent) {
// 应用刷新
init((ContextRefreshedEvent) event);
LOGGER.info("**************onApplicationEvent 应用刷新完成 **************");
} else if (event instanceof ApplicationReadyEvent) {
//初始化操作
User user= new User ();
user.setName("超级管理员");
user.setPassWord("123");
userService.insert(user);
}
// 应用已启动完成
LOGGER.info("**************onApplicationEvent 应用启动完成 **************");
} else if (event instanceof ContextStartedEvent) {
// 应用启动,需要在代码动态添加监听器才可捕获
LOGGER.info("**************onApplicationEvent ContextStartedEvent 完成 **************");
} else if (event instanceof ContextStoppedEvent) {
LOGGER.info("**************onApplicationEvent 应用停止完成 **************");
} else if (event instanceof ContextClosedEvent) {
// 应用关闭
LOGGER.info("**************onApplicationEvent 应用关闭完成 **************");
} else {
//LOGGER.info("**************onApplicationEvent 其他事件未处理 **************");
}
}
}
从以上代码中我们可以看到,ApplicationEvent
是有多个类型的事件,在不同类型的事件中做不同类型的事情,实际根据业务来做,利用好Spring Boot的生命周期,另外,添加监听器的方法有多种,普遍的是在启动类里添加,但我这个人比较懒,直接在监听器这个类上加上@Component
也是可以的,但要注意的是在启动类上要加上@ComponentScan(basePackages = "com.xx")
注解开启扫描
2. 拦截器的使用
需要实现HandlerInterceptor
接口,实现它的三个方法
public class ApiInterceptor implements HandlerInterceptor {
//Controller方法调用之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//可以在这里实现鉴权等
String token = request.getHeader("token");
if("".equals(token) || token == null){
Result result = new Result();
result.setResMsg("token is null");
result.setCode(500);
response.getWriter().write(JSON.toJSONString(result));
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("控制器被执行");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("控制器被执行结束");
}
}
添加注册拦截器
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//这里可以添加多个拦截器
registry.addInterceptor(new ApiInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/login");
}
}
这个拦截器做了一个简单的例子,在添加拦截器的时候,有个addPathPatterns
方法,里面输入的是要按照的请求,我这里拦截所有请求,但是有个excludePathPatterns
方法,意思是登录接口请求不拦截,在拦截器中,获取了请求头中的token,要是没有的话,返回false,会结束所有请求,不放行,DispatcherServlet处理器认为拦截器已经处理完了请求,而不继续执行执行链中的其它拦截器和处理器。要是有token,通过认证,返回true,会走postHandle
方法,请求结束,周期完成,会走afterCompletion
方法,也是在为true的情况下。
3. 过滤器的使用
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ServiceFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceFilter.class);
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
HttpServletResponse resp = (HttpServletResponse) response;
HttpServletRequest req = (HttpServletRequest) request;
// 也可以用过滤器来鉴权,除去登录接口
if ("/login".equals(req.getRequestURI())){
filterChain.doFilter(req, resp);
return;
}
String token = resp.getHeader("token");
if("".equals(token) || token == null){
Result result = new Result();
result.setResMsg("token is null");
result.setCode(500);
response.getWriter().write(JSON.toJSONString(result));
return;
}
filterChain.doFilter(req, resp);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
添加过滤器配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Bean
public FilterRegistrationBean omsFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new ServiceFilter());
registration.addUrlPatterns("/*");
registration.setName("MainFilter");
registration.setAsyncSupported(true);
return registration;
}
}
添加配置里的addUrlPatterns
指定所有请求都将会经过滤器处理,在过滤器中做了判断,如果是登录请求,直接放行,不是登录请求的其他接口请求都需要鉴权。
总结
拦截器(Interceptor)是基于Java的反射机制,而过滤器(Filter)是基于函数回调。从灵活性上说拦截器功能更强大些,Filter能做的事情,都能做,而且可以在请求前,请求后执行,比较灵活。Filter主要是针对URL地址做一个编码的事情、过滤掉没用的参数、安全校验(比较泛的,比如登录不登录之类),太细的话,还是建议用interceptor。不过还是根据不同情况选择合适的。
执行顺序:
Request请求——监听器——过滤器——拦截器——AOP——controller