实战技巧!Spring Boot 非常有用的五个开发技巧,请收藏

开发 开发工具
Spring提供一个专门处理异步请求的拦截器AsyncWebRequestInterceptor,该接口包含一个在处理异步请求期间被调用的回调方法。

环境:SpringBoot3.2.5

1. 获取请求/响应对象

在编写Controller中,我们通常可以通过如下的2种方式直接获取Request和Response对象,如下示例:

@RestController
public class UserController {
  private final HttpServletRequest request ;
  private final HttpServletResponse response ;
  public ContextFilterController(HttpServletRequest request, HttpServletResponse response) {
    this.request = request;
    this.response = response;
  }
}

直接在Controller中注入对象,你也可以在方法参数中获取:

@GetMapping
public ResponseEntity<Object> query(HttpServletRequest request, 
  HttpServletResponse response)
  // ...
}

如果你需要在其它组件中获取该对象,如果通过方法参数传递那就太麻烦了,这时候我们就可以使用如下方式:

ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes() ;
HttpServletRequest request = attributes.getRequest() ;
HttpServletResponse response = attributes.getResponse() ;

直接通过ThreadLocal获取,而这个数据的存入则是由DispatcherServlet完成,不过默认还有一个RequestContextFilter也会做这个事,但是会被该Servlet覆盖。

ThreadLocal绑定当前线程,如果遇到子线程怎么办呢?

如果希望在子线程中也能获取当前上下文,那么你需要进行如下的配置才可:

@Bean(name = "dispatcherServletRegistration")
DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
    WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
  DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
      webMvcProperties.getServlet().getPath());
  // 设置从父线程中继承
  dispatcherServlet.setThreadContextInheritable(true) ;
  // ...
  return registration;
}

测试接口

@GetMapping
public ResponseEntity<Object> context() throws Exception {
  ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes() ;
  HttpServletRequest request = attributes.getRequest() ;
  HttpServletResponse response = attributes.getResponse() ;
  System.err.printf("%s - %s, %s%n", Thread.currentThread().getName(), request, response) ;
  Thread t = new Thread(() -> {
    ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes() ;
    HttpServletRequest req = attr.getRequest() ;
    HttpServletResponse resp = attr.getResponse() ;
    System.err.printf("%s - %s, %s%n", Thread.currentThread().getName(), req, resp) ;
  }, "T1") ;
  t.start() ;
  return ResponseEntity.ok("success") ;
}

控制台输出如下

图片图片

成功获取。

警告!如下方式操作,你将收获一个错误

Thread t = new Thread(() -> {
  try {
    TimeUnit.SECONDS.sleep(1) ;
  } catch (Exception e) {
    e.printStackTrace();
  }
  ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes() ;
  HttpServletRequest req = attr.getRequest() ;
  System.err.println(req.getParameter("token")) ;
}, "T1") ;

如上代码,休眠1s后,在从request中获取数据,将抛出如下错误

图片图片

这是因为主线程已经将Request,Response对象回收了。

总结:不建议开启子线程共享。

2. 异步拦截器

Spring提供一个专门处理异步请求的拦截器AsyncWebRequestInterceptor,该接口包含一个在处理异步请求期间被调用的回调方法。

当处理器开始处理异步请求时,DispatcherServlet会像平常一样退出,而不调用postHandle和afterCompletion方法,因为请求处理的结果(例如ModelAndView)在当前线程中不可用,且处理尚未完成。在这种情况下,会调用afterConcurrentHandlingStarted(WebRequest)方法,允许实现执行诸如清理线程绑定属性之类的任务。

当异步处理完成时,请求会被分发到容器中进行进一步处理。在这个阶段,DispatcherServlet会像平常一样调用preHandle、postHandle和afterCompletion方法。

public class LogInterceptor implements AsyncWebRequestInterceptor {


  // 请求一开始会执行一次
  @Override
  public void preHandle(WebRequest request) throws Exception {
    System.err.printf("AsyncWebRequestInterceptor >>> %s, %s, 开始处理%n", System.currentTimeMillis(), Thread.currentThread().getName()) ;
  }
  // 当异步请求结束时执行
  @Override
  public void postHandle(WebRequest request, ModelMap model) throws Exception {
    System.err.printf("AsyncWebRequestInterceptor >>> %s, postHandle%n", Thread.currentThread().getName()) ;
  }
  // 当异步请求结束时执行
  @Override
  public void afterCompletion(WebRequest request, Exception ex) throws Exception {
    System.err.printf("AsyncWebRequestInterceptor >>> %s afterCompletion%n", Thread.currentThread().getName()) ;
  }
  // 异步请求开始时执行
  @Override
  public void afterConcurrentHandlingStarted(WebRequest request) {
    System.err.printf("AsyncWebRequestInterceptor >>> %s, %s, 异步处理%n", System.currentTimeMillis(), Thread.currentThread().getName()) ;
  }
}

注册异步拦截器:

@Component
public class WebInterceptorConfig implements WebMvcConfigurer{
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addWebRequestInterceptor(new LogInterceptor()).addPathPatterns("/admin/**") ;
  }
}

下面通过如下异步接口进行测试

@GetMapping("/async")
public Callable<String> async() {
  System.err.println("async interface...") ;
  return new Callable<String>() {
    public String call() throws Exception {
      System.err.printf("%s, %s - 执行任务%n", System.currentTimeMillis(), Thread.currentThread().getName()) ;
      TimeUnit.SECONDS.sleep(3) ;
      return "异步数据" ;
    }
  };
}

输出结果

图片图片

等待异步处理完成以后再执行preHandle、postHandle和afterCompletion方法。

实际整个异步请求从开始到结束,preHandle是执行了两回。

3. 获取当前请求相关信息

Spring MVC在处理一个请求时,会为我们做很多的事,其中会往Request对象设置一些非常有用的数据,如下所示:

获取当前的请求路径

String key = ServletRequestPathUtils.PATH ;
String requestPath = request.getAttribute(key) ;

获取当前请求最佳匹配的路径(Controller中定义的路径)

String key = HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE;
String pathPattern = request.getAttribute(key) ;

返回:/params/{type}/{id}

获取当前请求中的路径参数值

String key = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE ;
String pathPattern = request.getAttribute(key) ;

返回:{id=666, type=s0}

4. 类型转换器注册方式

Spring 本身提供了非常多的类型转换器,绝大多数情况下我们是不需要再自定义类型转换器的。如果确实需要自定义类型转换器,那么我们通常会通过如下的方法进行注册自定义的转换器:

@Component
public class TypeConvertConfig implements WebMvcConfigurer {


  @Override
  public void addFormatters(FormatterRegistry registry) {
    registry.addConverter(new EnumConverter()) ;
  }
}

感觉是不是有点麻烦。其实我们可以直接定义类型转换器为Bean对象即可,并且支持:GenericConverterConverterPrinterParser类型的转换器。

@Component
public class EnumConverter implements Converter<Sex, Integer> {


  public Integer convert(Sex source) {
    if (source == null) {
      return 0 ;
    }
    return source.getCode() ;
  }
}

注意:这里的自定义转换器并不支持有关属性配置的类型转换。

5. 接口不存在时特殊处理

当我们访问的接口不存在时,默认输出如下:

图片图片

或者我们也可以在如下位置提供对应的404.html或4xx.html页面

图片图片

如上位置添加页面后,当出现404错误,将会调转到该页面。

其实,我们还可以通过如下全局异常的方式处理404错误,默认如果出现404错误会抛出NoHandlerFoundException异常。

@RestControllerAdvice
public class GlobalExceptionAdvice {


  @ExceptionHandler(NoHandlerFoundException.class)
  public ResponseEntity<Object> noHandlerFount(NoHandlerFoundException e) {
    return ResponseEntity.ok(Map.of("code", -1, "message", "接口不存在")) ;
  }
}

当发生404后,页面展示:

图片图片


责任编辑:武晓燕 来源: Spring全家桶实战案例源码
相关推荐

2025-02-12 09:04:20

2024-06-13 08:01:19

2025-02-06 09:28:00

2023-11-27 07:03:58

2025-02-07 15:03:08

2024-09-30 13:11:09

2021-08-04 10:34:00

MySQL主键int32

2020-10-10 14:54:11

HPAPaaS社区

2025-02-11 00:00:25

2025-02-08 08:00:00

JavaDeepSeekIDEA

2025-02-12 08:21:55

OllamaChatboxDeepSeek

2025-02-13 07:36:41

DeepSeekAI应用

2021-07-14 10:14:25

Docker IDEA开发

2025-02-11 08:06:43

DeepSeekAI工具

2024-12-10 09:56:00

2014-10-13 12:54:15

微软Windows 10

2025-02-11 09:17:57

2024-12-30 20:32:36

2025-02-12 07:08:33

点赞
收藏

51CTO技术栈公众号