文章目录
前言
在Spring框架中,Filter(过滤器)和Interceptor(拦截器)都扮演着拦截和处理HTTP请求和响应的重要角色,但它们工作的层次和触发链路有所不同。了解它们的区别和触发链路对于设计和优化Web应用非常重要
链路图:
一、Filter(过滤器)
1、 触发层次
Filter是Servlet规范的一部分,它工作在Servlet容器级别,对所有请求和响应进行拦截。这意味着Filter的触发是早于Spring MVC的DispatcherServlet的。Filter能够修改请求和响应头、记录日志、处理跨域问题、安全认证等。
2、 配置方式
Filter通常在web.xml中配置,或者在Servlet 3.0及以上版本中,可以使用**@WebFilter**注解并注册为Servlet容器的监听器或启动类中的ServletContextInitializer。
3、 触发链路
当Web应用接收到一个HTTP请求时,首先会通过配置的Filter链。如果Filter链中没有任何异常,请求最终会被发送到Servlet(如Spring MVC的DispatcherServlet)。Servlet处理完请求后,响应也会经过Filter链返回给客户端。
4、具体应用
4.1、Filter
校验token信息
@Slf4j
public class AuthFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String tokenKey = "x-token";
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
String token = req.getParameter(tokenKey);
if (StringUtils.isEmpty(token)) {
Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(tokenKey)) {
token = cookie.getValue();
}
}
}
}
if (StringUtils.isEmpty(token)) {
res.setContentType("application/json;charset=utf-8");
res.getWriter().println("不存在相关token");
return;
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
注册到拦截器链
@Configuration
public class ExternalWebConfig {
private static final String custSysUrl = "/sys/*,/user/*";
@Bean
public FilterRegistrationBean<CustSysAuthFilter> sysAuthFilterRegistration() {
FilterRegistrationBean<CustSysAuthFilter> registration = new FilterRegistrationBean<>();
registration.setName("authFilter");
registration.setOrder(1);
String[] urlPatterns = custSysUrl.split(",");
registration.addUrlPatterns(urlPatterns);
registration.setFilter(new AuthFilter());
return registration;
}
}
4.2、OncePerRequestFilter
处理跨域问题
@Component
@WebFilter(urlPatterns = "/*", filterName = "crossDomainFilter")
public class CrossDomainFilter extends OncePerRequestFilter implements Ordered {
@Override
protected void doFilterInternal(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain filterChain) throws ServletException, IOException {
String originHeads = httpRequest.getHeader("Origin");
httpResponse.setHeader("Access-Control-Allow-Origin", originHeads);
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PATCH");
httpResponse.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Token, Accept , Connection, User-Agent, Cookie");
// 在使用CORS方式跨域时,浏览器只会返回 默认的头部 Header,认情况下可用的响应头包括:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma
// name:Access-Control-Expose-Headers 响应报头 value:可以暴露出去的响应头,多个响应头以","隔开
httpResponse.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
httpResponse.setHeader("Access-Control-Max-Age", "3628800");
httpResponse.setHeader("P3P", "CP=\"CAO PSA OUR\"");
filterChain.doFilter(httpRequest, httpResponse);
}
@Override
public int getOrder() {
return -1;
}
}
OncePerRequestFilter
Spring Framework 中的一个抽象类,它扩展自 javax.servlet.Filter 接口,用于在 Spring 的 Web 应用中提供请求过滤的功能。这个类的主要目的是确保在单个请求的处理过程中,过滤器链中的每一个过滤器只被调用一次,即使该过滤器被映射到多个 URL 模式上。
二、Interceptor(拦截器)
1、触发层次
Interceptor是Spring MVC框架提供的一个功能,它工作在Spring MVC的DispatcherServlet级别,主要对Controller的请求和响应进行拦截。它用于实现一些通用的功能,如日志记录、权限检查、数据预处理和转换等。
2、配置方式
Interceptor需要在Spring MVC的配置中注册,这可以通过实现HandlerInterceptor接口并在配置类中通过addInterceptors方法添加到拦截器链中来实现。
3、触发链路
当一个HTTP请求被DispatcherServlet捕获后,它会根据请求的信息(如URL)找到对应的Controller处理器。在处理Controller之前、之后或渲染视图之后,DispatcherServlet会触发配置的Interceptor链。这意味着Interceptor是在Spring MVC处理请求/响应流程的特定阶段被调用的。
4、具体应用
4.1、HandlerInterceptor
@Component
public class ThreadLocalInterceptor implements HandlerInterceptor {
private ThreadLocal<String> threadLocal = new ThreadLocal<>();
// 此方法的作用是在请求进入到Controller进行拦截,有返回值。(返回true则将请求放行进入Controller控制层,false则请求结束返回错误信息)
// 前置处理,类似于切面的before
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
threadLocal.set("进入了preHandle,可以处理相应业务");
return true;
}
// 该方法是在Controller控制器执行完成但是还没有返回模板进行渲染拦截。没有返回值。就是Controller----->拦截------>ModelAndView。
// 可以进一步包装 ModelAndView
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
// 该方法是在ModelAndView返回给前端渲染后执行
// 后置处理,类似于切面的after
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
threadLocal.remove();
}
}
4.2、WebMvcConfigurer
添加拦截规则
@Configuration
public class WebConfigurer implements WebMvcConfigurer {
public WebConfigurer() {
}
// 添加到拦截器链
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ThreadLocalInterceptor ()).addPathPatterns("/**").excludePathPatterns("/**/th","/**/ch");
}
@Bean(name = "multipartResolver")
public MultipartResolver multipartResolver(Environment environment) {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setDefaultEncoding("UTF-8");
// resolveLazily属性启用是为了推迟文件解析,以在在UploadAction中捕获文件大小异常
resolver.setResolveLazily(true);
resolver.setMaxInMemorySize(40960);
// 上传文件大小50M 50*1024*1024
resolver.setMaxUploadSize(5 * 1024 * 1024 * 1024L);
return resolver;
}
}
三、ControllerAdvice、RestControllerAdvice
@ControllerAdvice和@RestControllerAdvice 是Spring MVC框架中的一个注解,它提供了一种全局的控制器增强机制。通过该注解,开发者可以声明一些全局的处理器、数据绑定器和模型处理器,这些处理器可以在Controller的方法执行前后或者异常发生时进行干预,从而实现一些通用的逻辑处理。
1、全局异常处理
结合@ExceptionHandler注解使用,可以捕获Controller中抛出的异常,并进行统一的异常处理。这有助于减少模板代码,提升代码的可读性和可维护性。
示例:定义一个全局异常处理器类,在该类上使用@ControllerAdvice注解,并定义多个带有@ExceptionHandler注解的方法,每个方法处理不同类型的异常。
2、全局数据绑定
结合@ModelAttribute注解使用,可以在Controller的方法执行之前,将一些公共的数据添加到模型中,以便在视图层中使用。
示例:定义一个带有@ModelAttribute注解的方法,该方法返回一个Map或自定义对象,Spring MVC会将该方法的返回值添加到模型中。
3、全局数据预处理
结合@InitBinder注解使用,可以对请求中的参数进行预处理,如自定义参数的解析方式、注册自定义的编辑器等。
示例:定义一个带有@InitBinder注解的方法,该方法接收一个WebDataBinder参数,可以在该方法中对WebDataBinder进行配置,以实现自定义的参数解析逻辑。
4、具体应用
@RestControllerAdvice
public class RestControllerAdviceHandler {
// 处理所有运行时异常
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<Object> handleRuntimeException(RuntimeException ex) {
// 这里可以记录日志、返回自定义的错误信息等
Map<String, Object> body = new HashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
body.put("error", "Internal Server Error");
body.put("message", ex.getMessage());
return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR);
}
// 处理特定类型的异常,例如自定义的 BusinessException
@ExceptionHandler(BusinessException.class)
public ResponseEntity<Object> handleBusinessException(BusinessException ex) {
Map<String, Object> body = new HashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("status", HttpStatus.BAD_REQUEST.value());
body.put("error", "Bad Request");
body.put("message", ex.getMessage());
return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
}
// @ModelAttribute 没有指定值,且被修饰的方法返回值为void,
// 则需要借助map或者model将数据模型放入request域
@ModelAttribute
public void addCommonAttributes(HttpServletRequest request) {
request.setAttribute("appName", "My Application");
}
// 在数据绑定到模型之前对请求参数进行预处理,提高了数据的准确性和应用程序的健壮性
@InitBinder
public void initBinder(WebDataBinder binder) {
// 注册自定义的日期格式化器
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
}
}
@RestController
@RequestMapping("/test")
public class TestController {
// @ExceptionHandler(RuntimeException.class)
@GetMapping("/get1")
public String event1() {
int i = 1/0;
return "success";
}
// @ModelAttribute
@GetMapping("/get2")
public String event2(HttpServletRequest request) {
System.out.println(request.getAttribute("appName"));
// 成功转换
return "success";
}
// @InitBinder
@GetMapping("/get3")
public String event3(Date date) {
// 成功转换
System.out.println(date);
return "success";
}
}
总结
Spring AOP(面向切面编程)是一种编程思想,可以通过很多种方式实现,找到符合业务的需求,去针对性实现,实现解耦,更好维护