SpringMVC中controller中方法返回类型为ResponseEntity乱码的问题

本文详细介绍了SpringMVC中消息转换器的工作原理及如何配置以解决不同类型的响应数据转换问题,特别是针对ResponseEntity<T>类型的数据转换。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

返回类型为ResponseEntity<T>

代表我们返回的数据是一个对象,在springMVC中,请求数据到对象和对象到响应数据的转换是通过消息转换器来完成的。

HttpMessageConverter是消息转换器的顶层接口,所有的消息转换器都必须实现这个接口

package org.springframework.http.converter;

import java.io.IOException;
import java.util.List;

import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;

public interface HttpMessageConverter<T> {

	boolean canRead(Class<?> clazz, MediaType mediaType);

	boolean canWrite(Class<?> clazz, MediaType mediaType);

	List<MediaType> getSupportedMediaTypes();

	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;

	void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}



我们可以看到针对不同的类型,实现了具体消息转换器


--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

下面先来说说<mvc:annotation-driven></mvc:annotation-driven>,通过这个线索一直下去,找到问题的根源。

通常我们在springMVC配置文件中会配置<mvc:annotation-driven></mvc:annotation-driven>注解驱动,它对应的类是AnnotationDrivenBeanDefinitionParser


在 spring-webmvc.jar包下我们可以找到AnnotationDrivenBeanDefinitionParser类


分析getMessageConverters()方法后发现执行流程如下:

1.它首先会去配置中查找<mvc:annotation-driven>标签中是否含有<mvc:message-converters>子标签,如果有则把<mvc:message-converters>下的所有自定义消息转化器封装在message-converter对象中

2.然后判断message-converter是否为空。如果不为空,则把message-converter中封装的的所有自定义消息转换器添加到managedList集合中。

3.a)如果<mvc:annotation-driven>的属性register-defaults为真,还会加载默认列出的消息转换器,并加入到managedList集合里。b)如果为假,则只会加载我们自定义的。tips:通常我们都会是它为真,或不配置这个属性,不配置,他默认为真

class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
	…
		private ManagedList<?> getMessageConverters(Element element, Object source, ParserContext parserContext) {
		Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters");
		ManagedList<? super Object> messageConverters = new ManagedList<Object>();
		if (convertersElement != null) {
			messageConverters.setSource(source);
			for (Element beanElement : DomUtils.getChildElementsByTagName(convertersElement, "bean", "ref")) {
				Object object = parserContext.getDelegate().parsePropertySubElement(beanElement, null);
				messageConverters.add(object);
			}
		}

		if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) {
			messageConverters.setSource(source);
			messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.class, source));

			RootBeanDefinition stringConverterDef = createConverterDefinition(StringHttpMessageConverter.class, source);
			stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);
			messageConverters.add(stringConverterDef);

			messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source));
			messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source));
			messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source));

			if (romePresent) {
				messageConverters.add(createConverterDefinition(AtomFeedHttpMessageConverter.class, source));
				messageConverters.add(createConverterDefinition(RssChannelHttpMessageConverter.class, source));
			}

			if (jackson2XmlPresent) {
				RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2XmlHttpMessageConverter.class, source);
				GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);
				jacksonFactoryDef.getPropertyValues().add("createXmlMapper", true);
				jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);
				messageConverters.add(jacksonConverterDef);
			}
			else if (jaxb2Present) {
				messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
			}

			if (jackson2Present) {
				RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2HttpMessageConverter.class, source);
				GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);
				jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);
				messageConverters.add(jacksonConverterDef);
			}
			else if (gsonPresent) {
				messageConverters.add(createConverterDefinition(GsonHttpMessageConverter.class, source));
			}
		}
		return messageConverters;
	}
}

从其中我们可以找到添加StringHttpMessageConverter转换器的代码段


和之前输出JSON用到的MappingJackson2HttpMessageConverter转换器,这也可以看处springMVC对JSON的支持为什么要加入jackson的依赖




我这里为什么要单独挑出这两个转换器,是为了说明他们的区别和调用时机的,和进一步引出问题。

         a)之前如果我们要把对象以JSON格式输出,可以给controller的方法加@ResponseBody注解,到后来我们依照resultful的思想来做项目时,函数的返回值不会再是POJO,而是ResponseEntity<T>,有了它我们向往前台输出对象对应的JSON就不再需要再添加@ResponseBody注解了

         b)在SpringMVC中是怎么把我们的对象转换为JSON输出的呢,这里依旧要提到spring mvc的消息转换器,是它完成了这个转换过程。

         c)但是函数值为ResponseEntity<T>并不是所有的对象都会再后来转换为json,比如String,它会调用对应的StringHttpMessageConverter的消息转换器,而不是MappingJackson2HttpMessageConverter转换器。所以如果返回值类型为ResponseEntity<String> ,要输出json需要我们自己把json数据写到string里,spring是不会帮我们转换的。


接下来我们看StringHttpMessageConverter.class类 org.springframework.http.converter.StringHttpMessageConverter

public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {
	
public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");
	private final Charset defaultCharset;
	…
public StringHttpMessageConverter() {
		this(DEFAULT_CHARSET);
	}

	public StringHttpMessageConverter(Charset defaultCharset) {
		super(new MediaType("text", "plain", defaultCharset), MediaType.ALL);
		this.defaultCharset = defaultCharset;
		this.availableCharsets = new ArrayList<Charset>(Charset.availableCharsets().values());
	}
	…
}
	

发现它的默认转换编码是iso-8859-1,前台解码一般用utf-8,编解码不一致导致乱码,但我们发现这给类的构造方法预留了一个参数让我自己指定编码,这里我们看到了一丝希望哈哈。

解决办法:

我们只要在spring-mvc.xml配置文件中自己为这个消息转换器配置utf-8编码就ok了

<mvc:annotation-driven>
		<!-- 自定义消息转换器 -->
		<mvc:message-converters register-defaults="true">
			<!-- 自定义消息转换器,设置编码为utf-8,防止responseEntity<String>转换成json对象输出乱码 -->
			<bean class="org.springframework.http.converter.StringHttpMessageConverter">
				<constructor-arg index="0" value="utf-8"></constructor-arg>
			</bean> 
		</mvc:message-converters>
</mvc:annotation-driven>





转载于:https://www.cnblogs.com/chenny3/p/10226183.html

<think>嗯,用户想了解Spring MVC的响应数据方式。我得先回忆一下Spring MVC处理响应的各种方法。首先,控制器返回字符串通常用于视图名称,但用户可能更关心数据绑定,比如@ResponseBody和@RestController的区别。还有ModelAndView对象,这个可能比较基础,但需要提到。 然后,返回对象或集合,配合@ResponseBody自动转JSON或XML,这部分很重要,特别是现在RESTful API流行。可能需要解释消息转换器的工作原理,比如HttpMessageConverter接口,以及如何根据Content-Type处理不同格式。 还有直接写回HttpServletResponse的情况,比如用OutputStream手动输出,虽然不常用,但在特定场景下有用。比如文件下载或者需要细粒度控制响应时。 可能用户还会关心视图技术,比如JSP、Thymeleaf,虽然现在前后端分离多了,但传统方法还是有必要提一下。此外,ResponseEntity可以自定义状态码和头信息,适合需要更多控制的场景。 别忘了@RestController是组合了@Controller和@ResponseBody,简化了REST控制器的编写。另外,路径变量@PathVariable和请求参数@RequestParam如何绑定到响应数据里,可能需要举例说明。 还要注意不同的内容类型,比如JSON和XML的配置,可能需要依赖如Jackson或JAXB库。消息转换器的配置方法,比如WebMvcConfigurer里添加转换器。 可能用户在实际开发中会遇到乱码问题,所以需要提到produces属性设置charset=UTF-8,或者在配置中统一设置消息转换器的字符编码。 最后,总结各种方式的适用场景,帮助用户根据需求选择合适的方法。比如前后端分离常用@RestController返回JSON,传统应用可能用ModelAndView结合模板引擎。</think>Spring MVC 提供了多种响应数据的方式,以下是核心方法分步详解: 1. **返回视图名称(传统模式)** ```java @Controller @RequestMapping("/demo") public class DemoController { @GetMapping("/view") public String showView(Model model) { model.addAttribute("message", "Hello Thymeleaf"); return "demoPage"; // 对应src/main/resources/templates/demoPage.html } } ``` - 配合视图解析器(如Thymeleaf、JSP) - 数据通过Model对象传递 2. **返回字符串内容** ```java @Controller public class TextController { @GetMapping(value = "/text", produces = "text/plain;charset=UTF-8") @ResponseBody public String getText() { return "直接文本响应"; } } ``` - 需要@ResponseBody注解 - 设置produces属性定义MIME类型 3. **返回JSON/XML(RESTful常用)** ```java @RestController // 组合了@Controller + @ResponseBody @RequestMapping("/api") public class ApiController { @GetMapping("/user/{id}") public User getUser(@PathVariable int id) { return new User(id, "张三", 25); } @GetMapping("/list") public List<User> getUsers() { return Arrays.asList( new User(1, "李四", 30), new User(2, "王五", 28) ); } } ``` - 自动通过Jackson库转换对象为JSON - 需要配置消息转换器: ```xml <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> ``` 4. **ModelAndView 对象** ```java @Controller public class MavController { @GetMapping("/mav") public ModelAndView getMAV() { ModelAndView mav = new ModelAndView("viewName"); mav.addObject("key", "value"); return mav; } } ``` - 可同时控制视图和数据 - 适合需要灵活设置响应头的场景 5. **ResponseEntity 精控响应** ```java @GetMapping("/custom") public ResponseEntity<Map<String, Object>> customResponse() { Map<String, Object> body = new HashMap<>(); body.put("status", 200); body.put("data", "自定义响应"); return ResponseEntity.status(HttpStatus.ACCEPTED) .header("X-Custom-Header", "value") .body(body); } ``` - 可设置状态码、响应头、内容类型 - 适用于需要精确控制HTTP响应的场景 6. **直接操作HttpServletResponse** ```java @GetMapping("/manual") public void manualResponse(HttpServletResponse response) throws IOException { response.setContentType("text/html"); response.setCharacterEncoding("UTF-8"); PrintWriter writer = response.getWriter(); writer.write("<h1>手动输出</h1>"); writer.flush(); } ``` - 完全控制响应对象 - 适用于文件下载等特殊需求 **配置关键点:** 1. 消息转换器配置(WebMvcConfigurer): ```java @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(new MappingJackson2HttpMessageConverter()); converters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8)); } } ``` 2. 常见注解: - `@ResponseBody`:方法返回值直接作为响应体 - `@RestController`:类级别组合注解 - `@RequestMapping(produces = "application/json")`:指定输出格式 **选择策略:** - 前后端分离:优先使用@RestController + JSON - 传统Web应用:视图名称 + 模板引擎 - 特殊需求:ResponseEntity或直接操作HttpServletResponse 这些方式覆盖了从传统Web应用到现代RESTful服务的各种场景,开发者可根据具体需求灵活选择组合使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值