Spring

Spring Boot @ConditionalOnThreading 注解

1、简介 本文将带你了解一个相对较新的 Spring Boot 条件注解 @ConditionalOnThreading。 2、条件注解 条件注解提供了一种仅在满足各种特定条件时才在 BeanFactory 中注册 Bean 的方法。开发人员通过使用 Condition 接口为每个注解单独定义这些条件。 Spring Boot 为常见用例提供了大量预定义的条件注解。常见的示例有 @ConditionalOnProperty、@ConditionalOnBean 和 @ConditionalOnClass。 3、@ConditionalOnThreading 原理 @ConditionalOnThreading 只是 Spring Boot 中另一个预定义的条件注解。它是在 3.2 版本中添加的,在本文撰稿时,该版本本身还是候选发布版。也就是说,需要使用专用的 Spring Artifacts 仓库。 <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <releases> <enabled>false</enabled> </releases> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> <pluginRepository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <releases> <enabled>false</enabled> </releases> </pluginRepository> </pluginRepositories> @ConditionalOnThreading 注解仅允许在 Spring 内部配置为使用特定类型的线程时创建 Bean。所谓线程类型,是指平台线程或虚拟线程。回顾一下,从 Java 21 开始,我们就可以 使用虚拟线程来代替平台线程 了。

国际化 Bean Validation 校验失败的错误消息

1、概览 在 Web 应用中,我们通常需要通过 spring-validation 对客户端提交的数据进行校验。如果校验失败则会抛出异常。默认情况下,关于校验失败细节的异常信息是英文。本文将会带你了解如何对校验失败时的异常消息进行国际化(也称为“本地化”)。 2、依赖 首先在 pom.xml 中添加 Spring Boot Starter Web 和 Spring Boot Starter Validation: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> 最新版本可在 Maven Central 上找到。 3、本地化信息存储 在 Java 应用中,通常使用 properties 文件存储本地化信息。一般称为 Resource Bundle。 这些文件是由键值对组成的纯文本文件。key 是信息检索的标识符,而对应的 value 则是相应语言的本地化消息。 接下来,我们创建 2 个 properties 文件。 CustomValidationMessages.properties 是默认的 properties 文件,文件名不包含任何语言名称。只要客户端指定的语言不支持,应用就会使用默认语言: field.personalEmail=Personal Email validation.notEmpty={field} cannot be empty validation.email.notEmpty=Email cannot be empty 再创建一个额外的中文语言的 properties 文件 - CustomValidationMessages_zh.properties。只要客户端指定 zh 或 zh-tw 等变体作为本地语言,应用语言就会切换为中文:

Spring Boot 异常:HttpMessageNotWritableException: No Converter for [class ...] With Preset Content-Type

1、概览 本文将带你了解 “HttpMessageNotWritableException: no converter for [class …] with preset Content-Type” 异常的原因以及解决办法。 2、原因 异常的堆栈信息说明了一切:Spring 找不到合适的 HttpMessageConverter,无法将 Java 对象转换为 HTTP 响应。 Spring 依靠客户端的 Accept Header 来检测它需要响应的媒体类型(Media Type)。 因此,使用未预注册消息转换器(Message Converter)的 Media Type 将导致 Spring 抛出异常。 3、重现异常 通过一个实际例子来重现异常。 创建一个 Handler Method,指定一种媒体 Media Type(用于响应),这种类型没有注册 HttpMessageConverter。 例如,使用 MediaType.APPLICATION_XML_VALUE 或字符串 application/xml: @GetMapping(value = "/student/v3/{id}", produces = MediaType.APPLICATION_XML_VALUE) public ResponseEntity<Student> getV3(@PathVariable("id") int id) { return ResponseEntity.ok(new Student(id, "Robert", "Miller", "BB")); } 接着,使用 cURL 对 http://localhost:8080/api/student/v3/1 发起请求: curl http://localhost:8080/api/student/v3/1 端点响应如下:

OAuth2RestTemplate 简介

1、概览 本文将带你了解如何使用 Spring OAuth2RestTemplate 进行 OAuth2 REST 调用。 创建一个 Spring Web 应用,用于列出 GitHub 账户下的所有仓库。 2、Maven 依赖 首先,需要在 pom.xml 中添加 spring-boot-starter-security 和 spring-security-oauth2-autoconfigure 依赖。由于构建的是 Web 应用,因此还需要包含 spring-boot-starter-web 和 spring-boot-starter-thymeleaf Starter。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> <version>2.6.8</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> 3、OAuth2 Properties 接下来,在 application.properties 文件中添加 OAuth 配置,以连接到 GitHub 账户: # 替换为你的 CLIENT_ID github.client.clientId=[CLIENT_ID] # 替换为你的 CLIENT_SECRET github.client.clientSecret=[CLIENT_SECRET] github.client.userAuthorizationUri=https://github.com/login/oauth/authorize github.client.accessTokenUri=https://github.com/login/oauth/access_token github.client.clientAuthenticationScheme=form github.resource.userInfoUri=https://api.github.com/user github.resource.repoUri=https://api.github.com/user/repos 注意,需要用 GitHub OAuth App 中的值替换 [CLIENT_ID] 和 [CLIENT_SECRET] 。你可以按照 创建 OAuth App 指南在 GitHub 上注册一个新的应用:

Spring 6 JdbcClient API 指南

1、概览 本文将带你了解 Spring 6.1 中新添加的 JdbcClient 接口,它提供了 Fluent 风格的 API,统一了 JdbcTemplate 和 NamedParameterJdbcTemplate 的 Facade,支持链式操作。 有了 JdbcClient 后就可以使用 Fluent 风格的 API 定义查询、设置参数以及执行数据库操作了。 该功能简化了 JDBC 操作,使其更易读、更易懂。然而,对于 JDBC 批处理操作和存储过程的调用,仍然需要使用 JdbcTemplate 和 NamedParameterJdbcTemplate 类。 本文使用 H2 数据库来演示 JdbcClient 的功能。 2、数据库设置 创建 student 表: CREATE TABLE student ( student_id INT AUTO_INCREMENT PRIMARY KEY, student_name VARCHAR(255) NOT NULL, age INT, grade INT NOT NULL, gender VARCHAR(10) NOT NULL, state VARCHAR(100) NOT NULL ); -- Student 1 INSERT INTO student (student_name, age, grade, gender, state) VALUES ('John Smith', 18, 3, 'Male', 'California'); -- Student 2 INSERT INTO student (student_name, age, grade, gender, state) VALUES ('Emily Johnson', 17, 2, 'Female', 'New York'); --More insert statements.

Spring 单例 Bean 如何处理并发请求?

1、Spring Bean 和 Java 堆内存 Java 堆是一个全局共享内存,应用中的所有运行线程都可以访问它。当 Spring 容器创建 Singleton Scope Bean 时,该 Bean 将存储在堆中。这样,所有并发线程都能指向同一个 Bean 实例。 2、如何处理并发请求? 举例来说,Spring 应用中有一个名为 ProductService 的单例 Bean: @Service public class ProductService { private final static List<Product> productRepository = asList( new Product(1, "Product 1", new Stock(100)), new Product(2, "Product 2", new Stock(50)) ); public Optional<Product> getProductById(int id) { Optional<Product> product = productRepository.stream() .filter(p -> p.getId() == id) .findFirst(); String productName = product.map(Product::getName) .orElse(null); System.out.printf("Thread: %s; bean instance: %s; product id: %s has the name: %s%n", currentThread().

在 Spring Boot 中处理 GraphQL 异常

1、概览 本文将带你了解如何在 Spring Boot 中处理 GraphQL 异常,以及 GraphQL 规范中关于错误响应的规定。 2、根据 GraphQL 规范进行响应 根据 GraphQL 规范,每个接收到的请求都必须返回一个格式良好的响应。这个格式良好的响应是一个 Map,包含了来自相应成功或失败的请求操作的数据或错误。此外,响应可能包含部分成功的结果数据和字段错误。 响应 Map 的关键 key 是 errors、data 和 extensions。 响应中的 errors 描述了请求操作过程中出现的任何异常、错误。如果没有错误发生,则响应中不得出现 errors 字段。接下来,我们将了解规范中描述的不同类型的错误。 data 字段描述了成功执行所请求操作的结果。如果操作是 Query,该字段就是 Query Root Operation 类型的对象。如果操作是 Mutation,则该字段是 Mutation Root Operation 类型的对象。 如果请求的操作在执行前就因信息缺失、验证错误或语法错误而失败,那么响应中就必须没有 data 字段。如果操作在执行过程中失败,且结果不成功,则 data 必须为 null。 响应 map 可能包含一个名为 extensions 的附加字段,它是一个 map 对象。该字段便于实现者在响应中提供他们认为合适的其他自定义内容。因此,对其内容格式没有额外的限制。 如果响应中没有 data 字段,则必须有 errors 字段,并且必须至少包含一个 error。此外,它还应说明失败的原因。 下面是一个 GraphQL 错误示例: mutation { addVehicle(vin: "NDXT155NDFTV59834", year: 2021, make: "Toyota", model: "Camry", trim: "XLE", location: {zipcode: "75024", city: "Dallas", state: "TX"}) { vin year make model trim } } 当违反唯一性约束时,错误响应如下所示:

Spring 获取所有带自定义注解的 Bean

1、概览 本文将带你了解如何在 Spring 中获取到所有带自定义注解的 Bean。 不同的 Spring 版本,实现方式也不同。 2、使用 Spring Boot 2.2 或更高版本 从 Spring Boot 2.2 起,可以使用 getBeansWithAnnotation 方法。 创建一个示例。首先,定义自定义注解。注意,要使用 @Retention(RetentionPolicy.RUNTIME) 元注解,以确保程序在运行时可以访问注解: @Retention(RetentionPolicy.RUNTIME) public @interface MyCustomAnnotation { } 现在,用注解定义第一个 Bean(还要添加 @Component 注解): @Component @MyCustomAnnotation public class MyComponent { } 然后,定义另一个带有注解的 Bean。不过,这次通过 @Configuration 中的 @Bean 方法来创建: public class MyService { } @Configuration public class MyConfigurationBean { @Bean @MyCustomAnnotation MyService myService() { return new MyService(); } } 现在,写一个测试,测试 getBeansWithAnnotation 方法能否获取到上述两个 Bean:

Spring RestClient 教程

1、简介 RestClient 是 Spring 6.1 M2 中引入的同步 HTTP 客户端,它取代了 RestTemplate。同步 HTTP 客户端以阻塞方式发送和接收 HTTP 请求和响应,这意味着它会等待每个请求完成后才继续下一个请求。 本文将带你了解 RestClient 的功能以及它与 RestTemplate 的比较。 2、RestClient 和 RestTemplate RestTemplate,顾名思义,是基于模板设计模式构建的。它是一种行为设计模式,用于在方法中定义算法的框架,允许子类为某些步骤提供特定的实现。虽然这是一种强大的模式,但它会导致需要进行方法覆写,这可能是不方便的。 为了改进这一点,RestClient 采用了 Fluent API。Fluent API 也是一种设计模式,它允许以一种使代码更易读和表达的方式进行方法链式调用,通常无需使用中间变量。 从创建一个基本的 RestClient 开始: RestClient restClient = RestClient.create(); 3、使用各种 HTTP 方法发起请求 与 RestTemplate 或其他 REST 客户端类似,RestClient 可以使用不同的 HTTP 方法发起请求。 创建一个用于操作的 Article 类: public class Article { Integer id; String title; // 构造函数和 getter/setter } 3.1、使用 GET 请求获取资源 GET 请求用于检索 Web 服务器上指定资源的数据,而不对其进行修改。通常,这是一个只读的操作! 获取服务器响应的字符串(String):

在 Properties / Yaml 中配置 @RequestMapping 的 value 值

在 Spring Boot 中,我们通过 @RequestMapping 注解来定义 API 端点,如下: @RestController @RequestMapping public class DemoController { @GetMapping("/demo") public ResponseEntity<String> demo () { return ResponseEntity.ok("ok"); } } @GetMapping 是 @RequestMapping(method = RequestMethod.POST) 的快捷方式。 通过其 value 属性(path 属性的别名) 设置 API 的映射路径。 在本例中,路径就是:/demo。 启动应用,使用 cURL 进行访问。 $ curl localhost:8080/demo ok 一般来说,API 的路径不会轻易修改。而且路径是硬编码定义在注解中的,如果要修改的话,需要编辑源码重写编译打包。 但是,总有例外。如果我们想要灵活地修改 API 路径,那么可以在 @RequestMapping 中使用表达式从配置文件获取值。 先在配置文件(Properties / Yaml)中定义 API 路径: app: router: demo: "/demo/v1" 然后,在 @RequestMapping 使用 ${app.router.demo} 获取该值,作为 API 的路径: @RestController @RequestMapping public class DemoController { @GetMapping("${app.