专业的JAVA编程教程与资源

网站首页 > java教程 正文

SpringBoot返回Json(springboot返回json中文乱码)

temp10 2024-10-15 16:41:12 java教程 8 ℃ 0 评论


在项目开发中,接口与接口之间,前后端之间数据的传输都使用 Json 格式,在 Spring Boot 中,接口返回 Json 格式的数据很简单,在 Controller 中使用@RestController 注解即可。@RestController 也是 Spring Boot 新增的一个注解,看一下该注解都包含什么。

SpringBoot返回Json(springboot返回json中文乱码)

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController { String value() default ""; } 

可以看出, @RestController 注解包含了原来的 @Controller 和 @ResponseBody 注 解,@ResponseBody 注解是将返回的数据结构转换为 Json 格式。所以在默认情况下,使用了 @RestController 注解即可将返回的数据结构转换成 Json 格式,Spring Boot 中默认使用的 Json 解析技术框架是 jackson。pom.xml 中的 spring-boot-starter-web 依赖,可以看到一个 spring-boot-starter-json 依赖:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-json</artifactId>
	<version>2.0.3.RELEASE</version>
	<scope>compile</scope>
</dependency>

Spring Boot 中对依赖都做了很好的封装,可以看到很多 spring-boot-starter-xxx 系列的依赖,这是 Spring Boot 的特点之一,不需要人为去引入很多相关的依赖了,starter-xxx 系列直接都包含了所必要的依赖,所以再点spring-boot-starter-json 依赖,可以看到:

<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-databind</artifactId>
	<version>2.9.6</version>
	<scope>compile</scope>
</dependency>
<dependency>
	<groupId>com.fasterxml.jackson.datatype</groupId>
	<artifactId>jackson-datatype-jdk8</artifactId>
	<version>2.9.6</version>
	<scope>compile</scope>
</dependency>
<dependency>
	<groupId>com.fasterxml.jackson.datatype</groupId>
	<artifactId>jackson-datatype-jsr310</artifactId>
	<version>2.9.6</version>
	<scope>compile</scope></dependency><dependency>
	<groupId>com.fasterxml.jackson.module</groupId>
	<artifactId>jackson-module-parameter-names</artifactId>
	<version>2.9.6</version>
	<scope>compile</scope>
</dependency>

到此为止,我们知道了 Spring Boot 中默认使用的 json 解析框架是 jackson。下面我们 看一下默认的 jackson 框架对常用数据类型的转 Json 处理。

1. Spring Boot 默认对 Json 的处理

在实际项目中,常用的数据结构无非有类对象、List 对象、Map 对象,我们看一下默认 的 jackson 框架对这三个常用的数据结构转成 json 后的格式如何。

1.1 创建 User 实体类

为了测试,我们需要创建一个实体类,这里我们就用 User 来演示。

public class User { 
   private Long id; 
   private String username; 
   private String password; 
   /* 省略 get、set 和带参构造方法 */ 
} 

1.2 创建 Controller 类

然后我们创建一个 Controller,分别返回User对象、List<User>和 Map<String,Oject>。

@RestController 
@RequestMapping("/json") 
public class JsonController { 
  @RequestMapping("/user") 
  public User getUser() { 
    return new User(1, "Java码农", "123456"); 
  } 
  
  @RequestMapping("/list") 
  public List getUserList() { 
    List userList = new ArrayList<>(); 
    User user1 = new User(1, "Java码农1", "123456"); 
    User user2 = new User(2, "Java码农2", "123456"); 
    userList.add(user1); 
    userList.add(user2); 
    return userList; 
  } 
  
  @RequestMapping("/map") 
  public Map getMap() { 
    Map map = new HashMap<>(3); 
    User user = new User(1, "Java码农3", "123456"); 
    map.put("作者信息", user); 
    map.put("博客地址", "http://xxx.xx.com"); 
    map.put("CSDN 地址", "http://xx.xxx.net/1"); 
    map.put("粉丝数量", 4153); 
    return map; 
  } 
} 

1.3 测试不同数据类型返回的 json


在浏览器中输入:localhost:8080/json/user 返回 json 如下:

{"id":1,"username":"Java码农","password":"123456"} 

在浏览器中输入:localhost:8080/json/list 返回 json 如下:

[{"id":1,"username":"Java码农1 ","password":"123456"},
 {"id":2,"username":"Java码农2 ","password":"123456"}] 

在浏览器中输入:localhost:8080/json/map 返回 json 如下:

{"作者信息":{"id":1,"username":"Java码农3","password":"123456"},
  "CSDN 地址 ":"http://xx.xxx.net/1","粉丝数量":4153,"博客地址 ":"http://xxx.xx.com"} 

可以看出,map 中不管是什么数据类型,都可以转成相应的 json 格式,这样就非常方 便。

1.4 jackson 中对 null 的处理

在实际项目中,我们难免会遇到一些 null 值出现,我们转 json 时,是不希望有这些 null 出现的,比如我们期望所有的 null 在转 json 时都变成 "" 这种空字符串,那怎么做呢?在 Spring Boot 中,我们做一下配置即可,新建一个 jackson 的配置类:

@Configuration
public class JacksonConfig {
	@Bean
	@Primary
	@ConditionalOnMissingBean(ObjectMapper.class)
	public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
		ObjectMapper objectMapper = builder.createXmlMapper(false).build();
		objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
			@Override
			public void serialize(Object o, JsonGenerator jsonGenerator, 
                  SerializerProvider serializerProvider) throws IOException {
				jsonGenerator.writeString("");
			}
		});
		return objectMapper;
	}
}

然后我们修改一下上面返回 map 的接口,将几个值改成 null 测试一下:

@RequestMapping("/map") 
public Map getMap() { 
  Map map = new HashMap<>(3); 
  User user = new User(1, "Java码农3", null); 
  map.put("作者信息", user); 
  map.put("博客地址", "http://xxx.xx.com"); 
  map.put("CSDN 地址", null); 
  map.put("粉丝数量", 4153); 
  return map; 
} 

重启项目,再次输入:localhost:8080/json/map,可以看到 jackson 已经将所有 null 字段转成了空字符串了。

{"作者信息":{"id":1,"username":"Java码农3","password":""},
  "CSDN 地址":"","粉 丝数量":4153,"博客地址":"http://xxx.xx.com"} 

2. 使用阿里巴巴 FastJson 的设置

2.1 fastJson 依赖导入

<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>fastjson</artifactId>
	<version>1.2.35</version>
</dependency> 

2.2 使用 fastJson 处理 null

使用 fastJson 时,对 null 的处理和 jackson 有些不同,需要继承 WebMvcConfigurationSupport 类,然后覆盖 configureMessageConverters 方 法,在方法中,我们可以选择对要实现 null 转换的场景,配置好即可。如下:

@Configuration 
public class fastJsonConfig extends WebMvcConfigurationSupport {
  /** 
  * 使用阿里 FastJson 作为 JSON MessageConverter 
  * @param converters 
  */ 
  @Override 
  public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { 
    FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); 
    FastJsonConfig config = new FastJsonConfig(); 
    config.setSerializerFeatures( 
      // 保留 map 空的字段 
      SerializerFeature.WriteMapNullValue, 
      // 将 String 类型的 null 转成"" 
      SerializerFeature.WriteNullStringAsEmpty, 
      // 将 Number 类型的 null 转成 0 
      SerializerFeature.WriteNullNumberAsZero, 
      // 将 List 类型的 null 转成[] 
      SerializerFeature.WriteNullListAsEmpty, 
      // 将 Boolean 类型的 null 转成 false 
      SerializerFeature.WriteNullBooleanAsFalse, 
      // 避免循环引用 
      SerializerFeature.DisableCircularReferenceDetect); 
    converter.setFastJsonConfig(config); 
    converter.setDefaultCharset(Charset.forName("UTF-8")); 
    List mediaTypeList = new ArrayList<>(); 
    // 解决中文乱码问题,相当于在 Controller 上的@RequestMapping 中加 了个属性 produces = "application/json" mediaTypeList.add(MediaType.APPLICATION_JSON); 
    converter.setSupportedMediaTypes(mediaTypeList); 
    converters.add(converter); 
  } 
}

3. 封装统一返回的数据结构

以上是 Spring Boot 返回 json 的几个代表的例子,但是在实际项目中,除了要封装数据之外,我们往往需要在返回的 json 中添加一些其他信息,比如返回一些状态码 code ,返回一些 msg 给调用者,这样调用者可以根据 code 或者 msg 做一些逻辑判断。所以在实际项目中,我们需要封装一个统一的 json 返回结构存储返回信息。

3.1 定义统一的 json 结构

由于封装的 json 数据的类型不确定,所以在定义统一的 json 结构时,我们需要用到泛 型。统一的 json 结构中属性包括数据、状态码、提示信息即可,构造方法可以根据实 际业务需求做相应的添加即可,一般来说,应该有默认的返回结构,也应该有用户指定 的返回结构。如下:

public class JsonResult<T> {
	private T data;
	private String code;
	private String msg;
	/**
	* 若没有数据返回,默认状态码为 0,提示信息为:操作成功!
	*/
	public JsonResult() {
		this.code = "0";
		this.msg = "操作成功!";
	}
	/**
	* 若没有数据返回,可以人为指定状态码和提示信息
	* @param code
	* @param msg
	*/
	public JsonResult(String code, String msg) {
		this.code = code;
		this.msg = msg;
	}
	/**
	* 有数据返回时,状态码为 0,默认提示信息为:操作成功!
	* @param data
	*/
	public JsonResult(T data) {
		this.data = data;
		this.code = "0";
		this.msg = "操作成功!";
	}
	/**
	* 有数据返回,状态码为 0,人为指定提示信息
	* @param data
	* @param msg
	*/
	public JsonResult(T data, String msg) {
		this.data = data;
		this.code = "0";
		this.msg = msg;
	}
	// 省略 get 和 set 方法
}

3.2 修改 Controller 中的返回值类型及测试

由于 JsonResult 使用了泛型,所以所有的返回值类型都可以使用该统一结构,在具体的场景将泛型替换成具体的数据类型即可,非常方便,也便于维护。在实际项目中,还可以继续封装,比如状态码和提示信息可以定义一个枚举类型,以后我们只需要维护这个枚举类型中的数据即可)。根据以上的 JsonResult,我们改写一下 Controller,如下:

@RestController
@RequestMapping("/jsonresult")
public class JsonResultController {
	@RequestMapping("/user")
	public JsonResult<User> getUser() {
		User user = new User(1, "Java码农", "123456");
		return new JsonResult<>(user);
	}
	@RequestMapping("/list")
	public JsonResult<List> getUserList() {
		List<User> userList = new ArrayList<>();
		User user1 = new User(1, "Java码农1", "123456");
		User user2 = new User(2, "Java码农2", "123456");
		userList.add(user1);
		userList.add(user2);
		return new JsonResult<>(userList, "获取用户列表成功");
	}
	@RequestMapping("/map")
	public JsonResult<Map> getMap() {
		Map<String, Object> map = new HashMap<>(3);
		User user = new User(1, "Java码农3", null);
		map.put("作者信息", user);
		map.put("博客地址", "http://xxx.xx.com");
		map.put("CSDN 地址", null);
		map.put("粉丝数量", 4153);
		return new JsonResult<>(map);
	}
}

我们重新在浏览器中输入:localhost:8080/jsonresult/user 返回 json 如下:

 {"code":"0","data":{"id":1,"password":"123456","username":"Java码农 "},"msg":"操作成功!"} 

输入:localhost:8080/jsonresult/list,返回 json 如下:

{"code":"0","data":[{"id":1,"password":"123456","username":"Java码农1 "},
                    {"id":2,"password":"123456","username":"Java码农2"}],"msg":"获取用户列表 成功"} 

输入:localhost:8080/jsonresult/map,返回 json 如下:

 {"code":"0","data":{"作者信息":{"id":1,"password":"","username":"Java码农3 "},
   "CSDN 地址":null,"粉丝数量":4153,"博客地址 ":"http://xxx.xx.com"},"msg":"操作成功!"} 

通过封装,我们不但将数据通过 json 传给前端或者其他接口,还带上了状态码和提示 信息,这在实际项目场景中应用非常广泛。

4、时间格式问题

请求类型为:post,content-type=application/json, 后台用@RequestBody接收,默认接收及返回值格式为:yyyy-MM-dd HH:mm:ss。

方式一:application.propertities文件中配置:

# 格式化全局时间字段
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
# 指定时间区域类型,Greenwich Mean Time (GMT) 格林尼治时间,也叫做世界时间。
spring.jackson.time-zone=GMT+8

个别时间字段格式化可用注解:@JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss", timezone = "GMT+8")

注意:

  • 1、支持(content-type=application/json)请求中格式为yyyy-MM-dd HH:mm:ss的字符串,后台用@RequestBody接收,返回值date转为yyyy-MM-dd HH:mm:ss格式string;
  • 2、不支持(content-type=application/json)请求中yyyy-MM-dd等类型的字符串转为date;
  • 3、不支持java8日期api。

方式二

// 自定义MappingJackson2HttpMessageConverter中objectMapper指定日期类型序列化即反序列化:
@Bean 
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() { 
  MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); 
  ObjectMapper objectMapper = new ObjectMapper(); 
  // 忽略json字符串中不识别的属性 
  objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 
  // 忽略无法转换的对象 
  objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); 
  // PrettyPrinter 格式化输出 
  objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true); 
  // NULL不参与序列化 
  objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 
  // 指定时区 
  objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8:00")); 
  // 日期类型字符串处理 
  objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); 
  // java8日期日期处理 
  JavaTimeModule javaTimeModule = new JavaTimeModule(); 
  javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); 
  javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); 
  javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss"))); 
  javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); 
  javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); 
  javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss"))); 
  objectMapper.registerModule(javaTimeModule); 
  converter.setObjectMapper(objectMapper); 
  return converter; 
}

注意:

  • 1、支持(content-type=application/json)请求中格式为yyyy-MM-dd HH:mm:ss的字符串,后台用@RequestBody接收,返回值date转为yyyy-MM-dd HH:mm:ss格式string;
  • 2、支持java8日期api。
  • 3、不支持(content-type=application/json)请求中yyyy-MM-dd等类型的字符串转为date;

以上两种方式为json入参的全局化处理,推荐使用方式二,尤其适合大型项目在基础包中全局设置。

get请求及post表单日期时间字符串格式转换

方式一:实现org.springframework.core.convert.converter.Converter,自定义参数转换器,如下:

@Component
public class DateConverter implements Converter<String, Date> {
  private static final String dateFormat = "yyyy-MM-dd HH:mm:ss";
  @Override
  public Date convert(String value) {
    /**
     * 可对value进行正则匹配,支持日期、时间等多种类型转换
     * @param value
     * @return
     */
    SimpleDateFormat formatter = new SimpleDateFormat(dateFormat);
    try {
      return formatter.parse(value);
    } catch (Exception e) {
      throw new RuntimeException(String.format("parser %s to Date fail", value));
    }
  }
}

点评: 建议使用方式一时,对前端传递的string进行正则匹配,如yyyy-MM-dd HH:mm:ss、yyyy-MM-dd、 HH:mm:ss等,进行匹配。以适应多种场景。

方式二:使用spring自带注解@DateTimeFormat(pattern = "yyyy-MM-dd"),如下:

@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date startDate;

点评: 如果使用了方式一,spring会优先使用方式一进行处理,即方式二不生效。

@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") // 入参:将前端传入字符串转为指定格式

@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") // 出参:将返回值转为指定格式

总结:

@JsonFormat:是一个时间格式化注解,主要用于前端与后端直接时间格式的转换。单独使用@JsonFormat注解时需要先通过@RequestBody将入参参数映射到实体后,@JsonFormat注解才能去对时间格式进行约束;

1、可以约束时间的接收格式和响应格式 (接收和响应的都是JSON字符串),将日期类型数据在JSON格式和java.util.Date对象之间转换。与传输方向没有关系(前端到后端or后端到前端都可以使用);

2、它只会在类似@ResponseBody返回json数据的时候,才会返回格式化的yyyy-MM-dd HH:mm:ss时间,直接使用System.out.println()输出的话,仍然是类似“Fri Dec 01 21:05:20 CST 2017”这样的时间样式。

3、若存储在mysql中的数据是date类型的,当我们读取出来封装在实体类中的时候,就会变成英文时间格式,而不是yyyy-MM-dd HH:mm:ss这样的中文时间,因此我们需要用到JsonFormat注解来格式化我们的时间。

@DateTimeFormat:用于格式化前端传参日期后,传递给后端(要求前端传参的格式);只能约束前台的时间格式,必须与注解中的格式一样,否则报错。

1、主要是前后端到后台的时间格式的转换;可以接收解析前端传入字符时间数据;

2、只能约束前端入参时间类型的格式,并不会修改原有的日期对象的格式,如果想要获得期望的日期格式,是需要自己手动转换的;

3、单独使用@DateTimeFormat 时,响应给前端的时间会比实际时间晚8个小时(时区原因)

4、SpringBoot项目中单独使用@DateTimeFormat报错;原因是springboot默认采用jackson,而jackson只能识别以下几种日期格式:"yyyy-MM-dd'T'HH:mm:ss.SSSZ"、"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"、"yyyy-MM-dd"、"EEE, dd MMM yyyy HH:mm:ss zzz"、long类型的时间戳(毫秒时间戳)

解决办法有以下几种:1.、采用long时间戳,如:1537191968000;2、在传参的对象上加上@JsonFormat注解并且指定时区;3、使用指定格式 ;4、去掉@RequestBody注解。 5、采用全局处理方式统一处理,推荐这个做法,重写springboot默认转换。


欢迎各位大佬指点。喜欢的朋友来个点赞+转发,谢谢~~~~

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表