Spring

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.

Spring 的 beanName 设置可能会导致代理失败

一些使用小细节就是在不断的源码探索中逐步发现的,今天就来和大家研究一下通过 beanName 的设置,可以让一个 bean 拒绝被代理的问题! 1. 代码实践 假设我有如下一个切面: @Aspect @EnableAspectJAutoProxy @Component public class LogAspect { @Pointcut("execution(* org.javaboy.demo.service.*.*(..))") public void pc() { } @Before("pc()") public void before(JoinPoint jp) { String name = jp.getSignature().getName(); System.out.println(name + " 方法开始执行了..."); } } 这个切面要拦截的方法是 org.javaboy.demo.service 包下的所有类的所有方法,现在,这个包下有一个 BookService 类,内容如下: @Service("org.javaboy.demo.service.BookService.ORIGINAL") public class BookService { public void hello() { System.out.println("hello bs"); } } 这个 BookService 的 beanName 我没有使用默认的 beanName,而是自己配置了一个 beanName,这个 beanName 的配置方式是 类名的完整路径 + .ORIGINAL。 当我们按照这样的规则给 bean 取名之后,那么即使当前 bean 已经包含在切点所定义的范围内,这个 bean 也不会被代理了。

使用 Spring 注解实例化同一个类的多个 Bean

1、概览 Spring IoC 容器创建和管理 Spring Bean,这些 Bean 是应用的核心。创建一个 Bean 实例与从普通的 Java 类创建对象相同。然而,生成多个相同类的 Bean 可能会比较麻烦一点。 本文将带你了解如何在 Spring 中使用注解创建同一个类的多个 Bean。 2、使用 Java 配置 这是使用注解创建多个同类 Bean 的最简单易行的方法。 举一个简单的例子。我们有一个 Person 类,它有两个字段:firstName 和 lastName: public class Person { private String firstName; private String lastName; public Person(String firstName, String secondName) { super(); this.firstName = firstName; this.lastName = secondName; } @Override public String toString() { return "Person [firstName=" + firstName + ", secondName=" + lastName + "]"; } } 接下来,构建一个名为 PersonConfig 的配置类,并在其中定义 Person 类的多个 Bean: