过滤器、拦截器、监听器的区别与使用
一、拦截器与过滤器的区别过滤器 (Filter)过滤器的配置比较简单,直接实现Filter 接口即可,也可以通过@WebFilter注解实现对特定URL拦截,看到Filter 接口中定义了三个方法。init() :该方法在容器启动初始化过滤器时被调用,它在 Filter 的整个生命周期只会被调用一次。注意:这个方法必须执行成功,否则过滤器会不起作用。doFilter() :容器中的每一次请求都会调
一、拦截器与过滤器的区别
- 过滤器 (Filter)
过滤器的配置比较简单,直接实现Filter 接口即可,也可以通过@WebFilter注解实现对特定URL拦截
,看到Filter 接口中定义了三个方法。
- init() :该方法
在容器启动初始化过滤器时被调用,它在 Filter 的整个生命周期只会被调用一次
。注意:这个方法必须执行成功,否则过滤器会不起作用。- doFilter() :容器中的
每一次请求都会调用该方法
,FilterChain 用来调用下一个过滤器 Filter
。- destroy(): 当
容器销毁过滤器实例时调用该方法
,一般在方法中销毁或关闭资源,在过滤器 Filter 的整个生命周期也只会被调用一次
- 拦截器 (Interceptor)
拦截器它是链式调用,一个应用中可以同时存在多个拦截器Interceptor, 一个请求也可以触发多个拦截器 ,而每个拦截器的调用会依据它的声明顺序依次执行。首先编写一个简单的拦截器处理类,请求的拦截是通过HandlerInterceptor 来实现,看到HandlerInterceptor 接口中也定义了三个方法。
- preHandle() :
这个方法将在请求处理之前进行调用。注意:如果该方法的返回值为false,将视为当前请求结束,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行。
- postHandle():只有在
preHandle()方法返回值为true 时才会执行
。会在Controller 中的方法调用之后,DispatcherServlet 返回渲染视图之前被调用。
有意思的是:postHandle() 方法被调用的顺序跟 preHandle() 是相反的,先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。- afterCompletion():
只有在 preHandle()方法返回值为true 时才会执行。在整个请求结束之后, DispatcherServlet 渲染了对应的视图之后执行。
- 过滤器和拦截器触发时机不一样,
过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前。
如下图:
拦截器可以获取IOC容器中的各个bean,而过滤器就不行,因为拦截器是spring提供并管理的,spring的功能可以被拦截器使用,在拦截器里注入一个service,可以调用业务逻辑。
而过滤器是JavaEE标准,只需依赖servlet api ,不需要依赖spring。
过滤器拦截器运行先后步骤:
其中第2步,SpringMVC的机制是由DispaterServlet来分发请求给不同的Controller
,其实这一步是在Servlet的service()方法中执行的
过滤器的实现基于回调函数。而拦截器(代理模式)的实现基于反射,代理分静态代理和动态代理,动态代理是拦截器的简单实现。
- 拦截器加载的时间点在springcontext之前。
- 过滤器
几乎可以对所有进入容器的请求起作用,而拦截器只会对Controller中请求或访问static目录下的资源请求起作用
。 - 过滤器 和 拦截器
均体现了AOP的编程思想,
都可以实现诸如日志记录、登录鉴权等功能
二、何时使用拦截器?何时使用过滤器?何时使用监听器
- 如果是非spring项目,那么拦截器不能用,只能使用过滤器。
- 如果是处理controller前后,既可以使用拦截器也可以使用过滤器。
- 如果是处理dispaterServlet前后,只能使用过滤器。
- 监听 Servlet 上下文用来初始化一些数据、监听 HTTP Session 用来获取当前在线的人数、监听客户端请求的 ServletRequest 对象来获取用户的访问信息等等,使用监听器。
- 具体使用场景
- 过滤器:可以对请求的URL进行过滤, 对敏感词过滤,
- 拦截器:性能分析, 权限检查, 日志记录
灵活性上说拦截器功能更强大些,Filter能做的事情,他都能做,而且可以在请求前,请求后执行,比较灵活
。Filter主要是针对URL地址做一个编码的事情、过滤掉没用的参数、安全校验(比较泛的,比如登录不登录之类),太细的话,还是建议用interceptor。
不过还是根据不同情况选择合适的。
三、spring boot如何使用过滤器、拦截器
- Spring boot过滤器的使用(两种方式)
- 使用spring boot提供的FilterRegistrationBean注册Filter
- 使用原生servlet注解定义Filter
两种方式的本质都是一样的,都是去FilterRegistrationBean注册自定义Filter
- 方式一:
①、先定义Filter:
import javax.servlet.*;
import java.io.IOException;
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// do something 处理request 或response
System.out.println("filter1");
// 调用filter链中的下一个filter
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
②、注册自定义Filter
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean registrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new MyFilter());
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
- 方式二:
// 注入spring容器
@Component
// 定义filterName 和过滤的url
@WebFilter(filterName = "my2Filter" ,urlPatterns = "/*")
public class My2Filter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("filter2");
}
@Override
public void destroy() {
}
}
- Spring boot拦截器的使用:
①、定义拦截器:
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
System.out.println("afterCompletion");
}
}
②、配置拦截器:
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor());
}
}
③Controller演示:
@RestController
public class UController {
@GetMapping("/home")
public String home(){
System.out.println("home");
return "myhome";
}
}
四、过滤器拦截器完整例子
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor());
registry.addInterceptor(new MyInterceptor2());
}
}
@Order(1)
@Component
@WebFilter(filterName = "myFilter",urlPatterns = "/*")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("MyFilter1-----过滤器的init()方法,随着容器的启动进行了初始化");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// do something 处理request 或response
System.out.println("filter1");
// 调用filter链中的下一个filter
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
System.out.println("MyFilter1--------过滤器的destroy()方法,随着容器的关闭而进行");
}
}
@Order(2)
@Component
@WebFilter(filterName = "myFilter2",urlPatterns = "/*")
public class MyFilter2 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("MyFilter2------过滤器的init()方法,随着容器的启动进行了初始化");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// do something 处理request 或response
System.out.println("filter2");
// 调用filter链中的下一个filter
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
System.out.println("MyFilter2-----过滤器的destroy()方法,随着容器的关闭而进行");
}
}
@Order(3)
@Component
@WebFilter(filterName = "myFilter3",urlPatterns = "/*")
public class MyFilter3 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("MyFilter3------过滤器的init()方法,随着容器的启动进行了初始化");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// do something 处理request 或response
System.out.println("filter3");
// 调用filter链中的下一个filter
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
System.out.println("MyFilter3-------过滤器的destroy()方法,随着容器的关闭而进行");
}
}
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
System.out.println("afterCompletion");
}
}
@Controller
public class MyController {
@ResponseBody
@RequestMapping("myTest")
public String test(){
System.out.println("controller.method invoked");
return "ok";
}
}
order定义filter的优先级,值越小优先级越高,因此运行顺序为
filter3—doFilter—>filter2—doFilter—>filter1—doFilter—>控制器方法—>返回到filter1—>返回到filter2—>返回到filter3
注意:@Order或者接口Ordered的作用是定义Spring IOC容器中Bean的执行顺序的优先级,而不是定义Bean的加载顺序,Bean的加载顺序不受@Order或Ordered接口的影响
。
可以看到只有执行的时候,才会按filter1,filter2,filter3的顺序执行。同时我们注意到:先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。也即是:postHandle() 方法被调用的顺序跟 preHandle() 居然是相反的。如果实际开发中严格要求执行顺序,那就需要特别注意这一点。
那为什么会这样呢? 得到答案就只能看源码了
,我们要知道controller 中所有的请求都要经过核心组件DispatcherServlet路由,都会执行它的 doDispatch() 方法,而拦截器postHandle()、preHandle()方法便是在其中调用的。
- doDispatch方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
...........
try {
// 获取可以执行当前Handler的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 注意: 执行Interceptor中PreHandle()方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 注意:执行Handle【包括我们的业务逻辑,当抛出异常时会被Try、catch到】
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 注意:执行Interceptor中PostHandle 方法【抛出异常时无法执行】
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
}
...........
}
看看两个方法applyPreHandle()、applyPostHandle()具体是如何被调用的,就明白为什么postHandle()、preHandle() 执行顺序是相反的了。
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = this.getInterceptors();
if(!ObjectUtils.isEmpty(interceptors)) {
for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
HandlerInterceptor interceptor = interceptors[i];
if(!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
}
return true;
}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = this.getInterceptors();
if(!ObjectUtils.isEmpty(interceptors)) {
for(int i = interceptors.length - 1; i >= 0; --i) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
发现两个方法中在调用拦截器数组 HandlerInterceptor[] 时,循环的顺序竟然是相反的。。。,导致postHandle()、preHandle() 方法执行的顺序相反。
五、监听器
Java Web开发中的监听器(listener)就是由application、session、request三个对象创建、销毁或者往其中添加、修改、删除属性时自动执行代码的功能组件
,如下所示:
- ServletContextListener:
对Servlet上下文的创建和销毁进行监听。
- ServletContextAttributeListener:
监听Servlet上下文属性的添加、删除和修改。
- HttpSessionListener:对Session的创建和销毁进行监听。
- HttpSessionAttributeListener:对Session对象中属性的添加、删除和修改进行监听。
- HttpSessionBindingListener:监听Http会话中对象的绑定信息。
- HttpSessionActivationListener:监听器监听Http会话的情况。
- ServletRequestListener:对请求对象的初始化和销毁进行监听。
- ServletRequestAttributeListener:对请求对象属性的添加、删除和修改进行监听。
- 统计网站最多在线人数监听器的例子
/**
* 上下文监听器,在服务器启动时初始化onLineCount和maxOnLineCount两个变量,
* 并将其置于服务器上下文(ServletContext)中,其初始值都是0。
*/
@WebListener
public class InitListener implements ServletContextListener {
public void contextDestroyed(ServletContextEvent evt) {
}
public void contextInitialized(ServletContextEvent evt) {
evt.getServletContext().setAttribute("onLineCount", 0);
evt.getServletContext().setAttribute("maxOnLineCount", 0);
}
}
/**
* 会话监听器,在用户会话创建和销毁的时候根据情况修改onLineCount和maxOnLineCount的值。
*/
@WebListener
public class MaxCountListener implements HttpSessionListener {
public void sessionCreated(HttpSessionEvent event) {
ServletContext ctx = event.getSession().getServletContext();
int count = Integer.parseInt(ctx.getAttribute("onLineCount").toString());
count++;
ctx.setAttribute("onLineCount", count);
int maxOnLineCount = Integer.parseInt(ctx.getAttribute("maxOnLineCount").toString());
if (count > maxOnLineCount) {
ctx.setAttribute("maxOnLineCount", count);
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
ctx.setAttribute("date", df.format(new Date()));
}
}
public void sessionDestroyed(HttpSessionEvent event) {
ServletContext app = event.getSession().getServletContext();
int count = Integer.parseInt(app.getAttribute("onLineCount").toString());
count--;
app.setAttribute("onLineCount", count);
}
}
- 新建一个servlet处理
@WebServlet(name = "SessionServlet",value = "/sessionCount")
public class SessionServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
//获取上下文对象
ServletContext servletContext = this.getServletContext();
Integer onLineCount = (Integer) servletContext.getAttribute("onLineCount");
System.out.println("invoke doGet");
PrintWriter out = resp.getWriter();
out.println("<html><body>");
out.println("<h1>" + onLineCount + "</h1>");
out.println("</body></html>");
}
}
- springboot监听器的使用(以实现异步Event监听为例子)
- 场景
很多时候当我们完成某些业务后需要给用户推送相关消息提醒。对于这种非核心业务功能我们可以拿出来,创建一个事件去异步执行,从而实现核心业务和子业务的解耦。
- 实现
定义事件类 Event
创建一个类,继承ApplicationEvent,并重写构造函数。ApplicationEvent是Spring提供的所有应用程序事件扩展类。
public class Event extends ApplicationEvent {
private static final long serialVersionUID = 1L;
private String msg ;
private static final Logger logger=LoggerFactory.getLogger(Event.class);
public Event(String msg) {
super(msg);
this.msg = msg;
logger.info("add event success! message: {}", msg);
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
创建一个用于监听指定事件的类,需要实现ApplicationListener接口,说明它是一个应用程序事件的监听类。注意这里需要加上@Component注解,将其注入Spring容器中。
@Component
public class MyListener implements ApplicationListener<Event>{
private static final Logger logger= LoggerFactory.getLogger(MyListener.class);
@Override
public void onApplicationEvent(Event event) {
logger.info("listener get event,sleep 2 second...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("event msg is:{}",event.getMsg());
}
}
- 事件发布
事件发布很简单,只需要使用Spring 提供的ApplicationEventPublisher来发布自定义事件
@RequestMapping("/notice/{msg}")
@ResponseBody
public void notice(@PathVariable String msg){
logger.info("begin>>>>>");
applicationEventPublisher.publishEvent(new Event(msg));
logger.info("end<<<<<<<");
}
}
- 测试
- 异步执行
默认是没有开启异步的,我们需要手动配置开启异步功能,很简单,只需要在配置类上加上@EnableAsync注解就行了,该注解用于声明启用Spring的异步方法执行功能,需要和@Configuration注解一起使用,我们可以直接加在启动类上。然后在监听方法上加上@Async注解,说明当前方法使用异步去执行。
@Component
public class MyListener implements ApplicationListener<Event>{
private static final Logger logger= LoggerFactory.getLogger(MyListener.class);
@Override
@Async
public void onApplicationEvent(Event event) {
logger.info("listener get event,sleep 2 second...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("event msg is:{}",event.getMsg());
}
}
可以发现已经实现了异步功能,主线程为nio-8080-exec-1,监听线程为 task-1。从浏览器反应可以看出,接口直接返回了,并没有等监听线程执行完后才返回。
- 自定义异步线程池
用默认的Spring线程池会有啥问题呢?SimpleAsyncTaskExecutor不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。很有可能导致OOM。
创建配置类(加上@Configuration),实现AsyncConfigurer接口,重写Executor方法。这里我们可以将原先配置在启动类上的@EnableAsync注解放到这个类上而不再直接加在主启动类上。
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
private static final Logger logger = LoggerFactory.getLogger(AsyncConfig.class);
/**
* 自定义异步线程池
*/
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//核心线程数
taskExecutor.setCorePoolSize(2);
//最大线程数
taskExecutor.setMaxPoolSize(10);
//队列大小
taskExecutor.setQueueCapacity(15);
//线程名的前缀
taskExecutor.setThreadNamePrefix("async-thread-");
taskExecutor.initialize();
return taskExecutor;
}
/**
* 捕捉IllegalArgumentException异常
* @return
*/
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new MyAsyncExceptionHandler();
}
static class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler{
@Override
public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
logger.info("TASK Exception message - " + throwable.getMessage());
logger.info("Method name - " + method.getName());
for (Object param : objects) {
logger.info("Parameter value - " + param);
}
}
}
}
六、 过滤器、拦截器、监听器对比
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)