我正在尝试使用 Converter 将来自 POST 请求的 String 输入转换为 CountryDTO 对象。我已经创建了 CountryConverter 类并实现了来自 Spring 的 Converter 接口,我还将转换器添加到了 WebConfig 类中的 FormatterRegistry 中。
它适用于普通的 @Controller 类方法,但是,如果我使用一些 HTML 模板和表单,它在执行任何 @RestController 类方法之前不起作用。这是我在控制台中收到的消息,我了解此消息的原因。由于我的转换器不起作用,应用程序尝试将 String 反序列化为 CountryDTO 对象,但它失败了,因为它无法以 String“1”为例,并将其反序列化为整个 CountryDTO 对象。
控制台消息:
已解决[org.springframework.http.converter.HttpMessageNotReadableException:JSON解析错误:无法构造com.cydeo.dto.CountryDTO
的实例(尽管至少存在一个创建者):没有字符串参数构造函数/工厂方法可以从字符串值(“1”)反序列化);嵌套异常是 com.fasterxml.jackson.databind.exc.MismatchedInputException:无法构造
com.cydeo.dto.CountryDTO
的实例(尽管至少存在一个 Creator):没有字符串参数构造函数/工厂方法可以从字符串值('1')反序列化[来源:(org.springframework.util.StreamUtils$NonClosingInputStream);行:5,列:16](通过参考链:com.cydeo.dto.UserDTO[“country”])]我正在使用
Spring Boot版本2.7.7
这是我的JSON 请求正文:
{
"username": "MikeS",
"password": "Abc1",
"country": "1"
}
这是我的代码:
我的国家转换器课程:
import com.cydeo.dto.CountryDTO;
import com.cydeo.service.CountryService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
@Component
public class CountryConverter implements Converter<String, CountryDTO> {
private final CountryService countryService;
public CountryConverter(CountryService countryService) {
this.countryService = countryService;
}
@Override
public CountryDTO convert(String source) {
if (source.isEmpty()) {
return null;
}
try {
return countryService.findById(Long.parseLong(source));
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
我的WebConfig类:
import com.cydeo.converter.CountryConverter;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
private final CountryConverter countryConverter;
public WebConfig(CountryConverter countryConverter) {
this.countryConverter = countryConverter;
}
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(countryConverter);
}
}
我的 UserDTO 课程:
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.validation.constraints.*;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {
@JsonIgnore
private Long id;
@NotBlank(message = "Username is required")
@Size(min = 3, max = 16, message = "Username length should be min 2, max 16")
private String username;
@NotBlank(message = "Password is required")
@Pattern(regexp = "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{4,}", message = "The password should be at least 4 characters long and include at least 1 capital letter, 1 small letter and 1 digit")
private String password;
@NotNull(message = "Country is required")
private CountryDTO country;
}
我的国家DTO课程:
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class CountryDTO {
@JsonIgnore
private Long id;
private String countryName;
}
我的 UserController 类:
import com.cydeo.dto.ResponseWrapper;
import com.cydeo.dto.UserDTO;
import com.cydeo.service.UserService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.*;
@RestController
@RequestMapping("/api/user")
public class UserRestController {
private final UserService userService;
public UserRestController(UserService userService) {
this.userService = userService;
}
@PostMapping("/create")
public ResponseEntity<ResponseWrapper> createUser(@Valid @RequestBody UserDTO userDTO, BindingResult bindingResult) throws Exception {
if (bindingResult.hasErrors()) {
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
Map<String, Map<String, String>> response = new HashMap<>();
for (FieldError fieldError : fieldErrors) {
if (response.containsKey(fieldError.getField())) {
response.get(fieldError.getField()).put(fieldError.getCode(), fieldError.getDefaultMessage());
} else {
response.put(fieldError.getField(), new HashMap<>());
response.get(fieldError.getField()).put(fieldError.getCode(), fieldError.getDefaultMessage());
}
}
return ResponseEntity.badRequest().body(new ResponseWrapper(false, "Please check the information.", HttpStatus.BAD_REQUEST, response));
}
return ResponseEntity.status(HttpStatus.CREATED).body(new ResponseWrapper("User is created.", userService.create(userDTO), HttpStatus.CREATED));
}
}
org.springframework.core.convert.converter.Converter主要用于应用层的类型转换,例如服务或控制器层内不同对象类型之间的转换。不过,它并不直接参与HTTP消息转换。
在处理JSON数据时,Spring Boot默认使用Jackson来序列化和反序列化请求和响应主体。在这种情况下,Jackson 不会自动利用 Spring Converter 接口进行 JSON 反序列化。
您有几个选择:
示例:
public class CountryDTODeserializer extends JsonDeserializer<CountryDTO> {
private final CountryService countryService;
public CountryDTODeserializer(CountryService countryService) {
this.countryService = countryService;
}
@Override
public CountryDTO deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
Long id = node.asLong(); // Something like this
try {
return countryService.findById(id);
} catch (Exception e) {
throw new RuntimeException("Error during deserialization", e);
}
}
}
另外不要忘记添加以下注释:
@JsonDeserialize(using = CountryDTODeserializer.class)
@NotNull(message = "Country is required")
private CountryDTO country;