大数跨境
0
0

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

实战技巧!Spring Boot 非常有用的5个开发技巧,请收藏 Spring全家桶实战案例
2025-02-12
1
导读:实战技巧!Spring Boot 非常有用的5个开发技巧,请收藏

Spring Boot 3实战案例合集》现已囊括超过90篇精选实战文章,并且此合集承诺将永久持续更新,为您带来最新的技术与实战案例。订阅用户将特别获赠文末中所有MD文档(详尽学习笔记),以及完整的项目源码,助您在学习道路上畅通无阻。

Spring Boot 3实战案例锦集》PDF电子书现已出炉!

🎉🎉我们精心打造的《Spring Boot 3实战案例锦集》PDF电子书现已正式完成,目前已经有94个案例,后续还将继续更新。文末有电子书目录。

💪💪永久更新承诺

我们郑重承诺,所有订阅合集的粉丝都将享受永久免费的后续更新服务。随着Spring相关技术的更新升级,我们的电子书也将持续更新,确保您始终掌握最前沿、最实用的技术知识。

💌💌如何获取
订阅我们的合集点我订阅,并通过私信联系我们,我们将第一时间将电子书发送给您。

现在就订阅合集




环境:SpringBoot3.2.5



1. 获取请求/响应对象

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

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

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

@GetMappingpublic 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;}

测试接口

@GetMappingpublic 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()) ; }}

注册异步拦截器

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

@Componentpublic class TypeConvertConfig implements WebMvcConfigurer {
@Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new EnumConverter()) ; }}

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

@Componentpublic class EnumConverter implements Converter<Sex, Integer> {
public Integer convert(Sex source) { if (source == null) { return 0 ; } return source.getCode() ; }}

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

关于更多的类型转换器的使用,请查看下面文章:

【必备技能】Spring数据类型转换,让你的代码更优雅!

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

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

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

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

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

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

当发生404后,页面展示:


以上是本篇文章的全部内容,如对你有帮助帮忙点赞+转发+收藏

推荐文章

优雅!Spring 基于 Plugin 插件开发(官方推荐)

优雅!Spring Boot 这样记录操作日志非常灵活强大

弃用HTTP!Spring Boot 集成 GRPC 优化接口调用性能

高级开发!自定义注解100行代码实现 @Resource/@Autowired 注入功能

接口优化!Spring Boot 集成 Protobuf 并使用 RestTemplate 实现微服务间通信

SpringBoot一个注解轻松实现多版本Rest API接口控制

基于注解的Controller接口这些高级功能你都知道吗?

@InitBinder注解会用吗?该如何使用?

快速体验Spring AI 聊天对话&文生图&文生语音

SpringBoot整合RSocket实时数据通信

OpenFeign高级用法:这些强大的功能你用过吗?

SpringBoot3第三方接口调用10种方式,最后一种你肯定没用过

强大!Spring Boot全新模块化管理方式

【声明】内容源于网络
0
0
Spring全家桶实战案例
Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
内容 832
粉丝 0
Spring全家桶实战案例 Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
总阅读38
粉丝0
内容832