SpringBoot的Web应用开发——过滤器、监听器和拦截器!

360影视 日韩动漫 2025-06-06 15:36 2

摘要:Servlet规范中有3个非常特殊的Servlet,分别是过滤器、监听器和拦截器,如果使用得当,利用它们可以简单地完成一般Servlet才能实现的烦琐功能。下面分别介绍过滤器、监听器和拦截器。

Servlet规范中有3个非常特殊的Servlet,分别是过滤器、监听器和拦截器,如果使用得当,利用它们可以简单地完成一般Servlet才能实现的烦琐功能。下面分别介绍过滤器、监听器和拦截器。

过滤器(Filter)放在Web资源之前,可以在前端请求抵达Web资源之前被截获,并且还可以在资源返回客户之前截获输出的请求。过滤器是用来拦截请求的,处于客户端与被请求资源之间,目的是重用代码。在一个项目中可以配置多个过滤器,一个请求会依次通过配置的所有过滤器。

Web项目常用的过滤器有以下3种:

用户授权的过滤器:负责检查用户请求,根据请求信息过滤用户的非法请求。

日志过滤器:详细记录某些特殊的用户请求。

负责编码/解码的过滤器:对请求参数的编码和解码。

Java中的过滤器是一种特殊的Servlet,它不能处理用户请求,也不能为客户端生成响应信息,它主要用于对HttpServletRequest进行前处理,也可以对HttpServletResponse进行后处理,是一个典型的处理链程序。

监听器也是一种特殊的servlet,能够监听Web项目中特定的事件。例如,监听ServletContext、HttpSession和ServlerRequest对象的创建和销毁及各种变量的创建、销毁和修改等,还可以在一些请求前后增加监听处理,实现监听。

监听器的父接口为java.util.EventListener,所有监听器都需要实现此接口。常见的监听器有:HttpSessionListener用来监听Session,ContextLoaderListener是在启动Web容器时自动监听装配ApplicationContext的配置信息,RequestContextListener监听请求的处理。

拦截器(Interceptor)有点类似于Servlet中的过滤器,它主要用于拦截用户发送的请求并进行相应的处理。拦截器可以在项目中进行权限验证、记录请求信息的日志、判断用户是否登录等。

通过前面小节的介绍可知,过滤器和拦截器在功能上有部分重叠,定义也很相似,一些功能既可以通过过滤器实现,也可以通过拦截器实现,但是它们还是有区别的:

拦截器是基于Java的反射机制,而过滤器是基于函数回调。

拦截器的使用不依赖于Servlet容器,而过滤器依赖于Servlet容器。

拦截器只能对Controller请求起作用,而过滤器则可以对几乎所有的请求(包括静态资源和文件等)起作用。

拦截器可以访问请求的上下文、值栈里的对象,而过滤器不能访问。

在一个请求的生命周期中,可以设置多个拦截器依次运行,而过滤器只能在容器初始化时被调用一次。

拦截器可以获取Spring IoC容器中的各个Bean,而过滤器却获取不到,在拦截器中可以注入业务service,处理业务逻辑。

过滤器是在请求Servlet之前拦截请求,对请求进行预处理。请求结束返回也是在Servlet处理完后再返回给前端。而拦截器是在请求处理之前进行拦截处理。

Spring Web提供了很多过滤器,这些过滤器都在org.springframework.web.filter包中,全部实现了javax.servlet.Filter接口。如果项目中需要自定义过滤器,有以下4种实现方式:直接实现Filter接口。

继承抽象类GenericFilterBean,此类已经实现了javax.servlet.Filter接口,是普通的Filter实现。

继承抽象类OncePerRequestFilter,此类是GenericFilterBean的直接子类,主要用于对请求参数的处理。

继承抽象类AbstractRequestLoggingFilter,该类为OncePerRequestFilter的直接子类,主要用于处理日志过滤。

下面实现一个自定义的过滤器,过滤请求URL为/test1的请求。如果是这个请求,则打印这个请求的所有参数并直接返回不再继续处理业务,否则直接放行。首先实现一个自定义的Filter,代码如下:

package com.example.thymeleafdemo.filter;

import org.springframework.stereotype.Component;

import javax.servlet.*;

import javax.servlet.http.HttpServletRequest;

import java.io.IOException;

import java.util.Arrays;

import java.util.Map;

/**

* 自定义过滤器的实现

*/

@Component

public class MyHttpFilter implements Filter {

/**

* 过滤方法

*/

@Override

public void doFilter(ServletRequest request, ServletResponse

response,

FilterChain chain) throws IOException, Servlet

Exception {

HttpServletRequest servletRequest = (HttpServletRequest)

request;

String requestURI = servletRequest.getRequestURI;

if ("/test1".equals(requestURI)) { Mapmap =

servletRequest.getParameterMap;

for (Map.Entryentry : map.entrySet) {

System.out.println("请求的参数名字是:" + entry.getKey

+ " ,请求的值是:" +

Arrays.toString(entry.getValue));

return;

}

}

//放行,继续后面的业务处理

chain.doFilter(servletRequest,response);

}

}

在UserController.java文件中添加test1方法的代码如下:

@ResponseBody

@GetMapping("/test1")

public String test1{

return "success";

}

启动Spring Boot项目,打开浏览器访问http://localhost:8080/test1?name=cc,页面返回结果如图4.5所示,没有出现业务代码返回的成功提示,查看控制台发现已经打印出入参的参数,如图4.6所示。至此就完成了自定义过滤器过滤请求信息的操作。

综合以上的自定义过滤器,总结过滤器的使用场景如下:

执行目标资源之前执行预处理,如设置编码;

通过条件判断是否放行,如校验当前用户是否已经登录,或某些用户的IP是否被禁用;

目标资源执行后,后续的特殊处理工作,如处理目标资源输出的数据。

要使用Spring MVC中的拦截器,首先需要对拦截器类进行定义和配置。

通常,拦截器类可以通过两种方式来定义。

通过实现HandlerInterceptor接口或继承HandlerInterceptor接口的实现类(如HandlerInterceptorAdapter)来定义拦截器。

通过实现WebRequestInterceptor接口或继承WebRequestInterceptor接口的实现类来定义拦截器,此接口专门用于处理Web请求。

在项目开发中,一个常见的需求就是打印所有的请求入参,方便以后问题的定位和接口的调试。下面我们自定义一个拦截器来处理所有的请求,并且把所有请求的URL和日志都打印出来,具体代码如下:

package com.example.thymeleafdemo.inter;

import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Component;

import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.util.Arrays;

import java.util.Map;

/**

* 自定义拦截器

*/

@Slf4j

@Component

public class MyHandlerInterceptor implements HandlerInterceptor {

/**

* 在业务代码处理之前进行参数记录

*/

@Override

public boolean preHandle(HttpServletRequest request, HttpServlet

Response response, Object handler) throws Exception {

System.out.println("拦截器: preHandle在控制器的处理请求方法调用之后解

析视图之前执行");

String requestURI = request.getRequestURI;

MapparameterMap = request.getParameterMap;

StringBuilder sb = new StringBuilder;

for (Map.Entryentry :

parameterMap.entrySet) {

sb.append(entry.getKey).append("=").append(Arrays.toString

(entry.getValue));

sb.append(",");

}

log.info("拦截器: 请求的url是:{},请求的参数是:

{}",requestURI,sb.toString);

return true;

}

/**

* 在业务代码处理之后

*/

@Override

public void postHandle(HttpServletRequest request,

HttpServletResponse response, Object handler, ModelAndView modelAndView)

throws

Exception {

System.out.println("拦截器: postHandle方法在控制器的处理请求方法调用

之后解析视图之前执行");

}

@Override

public void afterCompletion(HttpServletRequest request,

HttpServlet Response response,

Object handler, Exception ex) throws

Exception {

System.out.println("拦截器: afterCompletion方法在控制器的处理请求方

法执行完成后执行," + "即视图渲染结束之后执行");

}

}

完成拦截的具体方法后,配置拦截器拦截哪些URL,放行静态资源的请求,拦截剩下的URL。

package com.example.thymeleafdemo.inter;

import org.Springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.servlet.config.annotation.Interceptor

Registration;

Registry;

import org.springframework.web.servlet.config.annotation.WebMvc

Configurer;

@Configuration

public class MyHandlerInterceptorConfig implements WebMvcConfigurer {

@Autowired

private MyHandlerInterceptor myHandlerInterceptor;

@Override

public void addInterceptors(InterceptorRegistry registry) { //注册TestInterceptor拦截器

InterceptorRegistration registration = registry.addInterceptor

(myHandlerInterceptor);

//所有路径都被拦截

registration.addPathPatterns("/**");

//添加不拦截路径

registration.excludePathPatterns(

"/**/*.html", //HTML静态资源

"/**/*.js", //JS静态资源

"/**/*.css" //CSS静态资源

);

}

}

启动项目,访问http://localhost:8080/addUser能看到结果页面,如图4.7所示。同时IDEA的控制台中打印出了请求日志,如图4.8所示,至此已经成功完成了拦截器请求参数的拦截打印。

根据以上自定义拦截器的实现代码,总结拦截器的执行步骤如下:

(1)根据请求的URL,找到可以处理请求的处理器和所有拦截器。

(2)按照配置顺序执行所有拦截器的preHandle方法。如果当前拦截器的preHandle方法返回true,则执行下一个拦截器的preHandle方法(执行下一个拦截器)。如果当前拦截器返回false,倒序执行所有已经执行了的拦截器的afterCompletion。

(3)如果任何一个拦截器返回false,则执行返回,不执行目标方法。

(4)所有拦截器都返回true,则执行目标方法。

(5)倒序执行所有拦截器的postHandle方法。

注:前面的步骤有任何异常都会触发倒序执行afterCompletion方法。

(6)页面成功渲染后,再倒序执行afterCompletion方法。

Java中的事件提供监听、订阅的功能,其内部实现原理是观察者设计模式,设计时间的发布和监听的目的也是为了系统业务逻辑之间的解耦,提高系统的可扩展性和可维护性。事件包括3个重要部分:EventObject、EventListener和Source。

EventObject:java.util.EventObject是事件状态对象的基类,它封装了事件源对象以及和事件相关的信息,所有Java的事件类都需要继承该类。

EventListener:java.util.EventListener是一个标记接口,所有事件监听器都需要实现该接口。事件监听器注册在事件源中,如果事件源的属性或状态发生改变,则会调用相应监听器内的回调方法。

Source:事件源不需要实现或继承任何接口或类,它是事件最初发生的源头。

Java的事件机制是一个典型的观察者模式。当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式,其类图如图4.9所示。

观察者模式使用3个类:Subject目标类、Observer观察者类和ConcreteSubject通知观察者类。Spring Boot框架对事件也做了支持,其对Java事件的实现、发布与监听一共分为3步。

(1)事件定义。

自定义的事件类继承自ApplicationEvent类,从而可以方便地重写发送事件的方法。

(2)事件发布。

注入事件发布类ApplicationEventPublisher使用publishEvent方法发布相应的事件。

(3)事件监听。

实现ApplicationListener接口,重写onApplicationEvent方法或者使用注解 @Event- Listener进行事件监听。根据上面的步骤自定义实现一个事件的发布和监听。新建一个事件来监听Spring Boot项目的启动,代码如下:

package com.example.thymeleafdemo.event;

import

org.springframework.boot.context.event.ApplicationStartingEvent;

import org.springframework.context.ApplicationListener;

import java.text.SimpleDateFormat;

import java.util.Date;

public class MyEventListener implements

ApplicationListener{

@Override

public void onApplicationEvent(ApplicationStartingEvent event) {

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd

HH:MM:ss");

Date date = new Date(event.getTimestamp);

System.out.println("ApplicationStartingEvent事件发布,时间是:" +

format.format(date));

}

}

同时修改Spring Boot的启动类:

package com.example.thymeleafdemo;

import com.example.thymeleafdemo.event.MyEventListener;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication

public class ThymeleafDemoApplication {

public static void main(String args) {

SpringApplication application = new

SpringApplication(Thymeleaf DemoApplication.class); //加入自定义的监听类

application.addListeners(new MyEventListener);

application.run(args);

//SpringApplication.run(ThymeleafDemoApplication.class, args);

}

}

启动项目即可看到监听的Spring Boot启动事件,并在控制台上打印了日志,如图4.10所示。

完成了Spring Boot启动的监听后,下面新建一个Controller,通过程序来发布事件并且进行监听,代码如下:

package com.example.thymeleafdemo.event;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.ApplicationEventPublisher;

import org.springframework.web.bind.annotation.*;

/**

* 模拟触发事件

*/

@RestController

@RequestMapping("/event")

@Slf4jpublic class EventDemoController {

/**

* 注入 事件发布类

*/

@Autowired

ApplicationEventPublisher eventPublisher;

@GetMapping("/pushObject")

public String pushObject(@RequestParam("code") int code,

@RequestParam("message") String message) {

log.info("发布对象事件:{},{}", code, message);

Result result = new Result;

result.setCode(code);

result.setMessage(message);

eventPublisher.publishEvent(result);

return "对象事件发布成功!";

}

}

配置当前Controller的事件监听:

package com.example.thymeleafdemo.event;

import lombok.extern.slf4j.Slf4j;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.event.EventListener;

/**

* 监听配置类

*/

@Configuration

@Slf4j

public class EventListenerConfig {

@EventListener

public void handleEvent(Object event) {

log.info("事件:{}", event); }

/**

* 监听 code为cc的事件

*/

@EventListener(condition = "#myCustomEvent.result.code == 'cc'")

public void handleCustomEventByCondition(MyCustomEvent customEvent)

{

//监听 MyCustomEvent事件

log.info("监听到code为'cc'的MyCustomEvent事件," +

"消息为:{}, 发布时间:{}", customEvent.getResult,

customEvent.getTimestamp);

}

@EventListener

public void handleObjectEvent(Result result) {

log.info("监听到对象事件,消息为:{}", result);

}

}

定义发布的事件和结果代码如下:

package com.example.thymeleafdemo.event;

import org.springframework.context.ApplicationEvent;

public class MyCustomEvent extends ApplicationEvent {

private Result result;

public MyCustomEvent(Object source, Result result) {

super(source);

this.result = result;

}

public Result getResult {

return this.result;

}

}

package com.example.thymeleafdemo.event;import lombok.Data;

import lombok.ToString;

@ToString

public class Result

private String message;

private int code;

private T data;

}

启动当前项目,访问http://localhost:8080/event/pushObject?code=1&message=cc,查看IDEA控制台可以看到打印的Controller被访问的事件监听信息,如图4.11所示。

至此就完成了Spring Boot的事件发布和监听。事件的发布和监听通常用在要接收消息进行业务处理,但消息的来源不固定,触发的事件也不固定的场景。最简单的方式是通过写接口然后二次调用接口的方式来实现,但二次调用接口多了一次操作,会降低性能,因此通过事件监听的方式来实现。

来源:程序员高级码农II一点号

相关推荐