想了解接口标记为@ResponseBody却不进入ResponseBodyAdvice的新动态吗?本文将为您提供详细的信息,我们还将为您解答关于接口restful的相关问题,此外,我们还将为您介绍关于
想了解接口标记为@ResponseBody却不进入ResponseBodyAdvice的新动态吗?本文将为您提供详细的信息,我们还将为您解答关于接口restful的相关问题,此外,我们还将为您介绍关于@RequestBody, @ResponseBody 注解理解、@RequestBody, @ResponseBody 注解详解、@ResponseBody & @RequestBody、@ResponseBody as the response has already been committed.的新知识。
本文目录一览:- 接口标记为@ResponseBody却不进入ResponseBodyAdvice(接口restful)
- @RequestBody, @ResponseBody 注解理解
- @RequestBody, @ResponseBody 注解详解
- @ResponseBody & @RequestBody
- @ResponseBody as the response has already been committed.
接口标记为@ResponseBody却不进入ResponseBodyAdvice(接口restful)
一、背景:
我们的接口为了统一,在ResponseBodyAdvice中对返回值做统一处理,默认添加了errorNo和errorInfo字段返回。
最近同事改接口代码的时候,发现接口返回值是空的。乍一看,没什么重大修改。
接口代码大致就是下面这个样子:
@ResponseBody
@RequestMapping("test")
public void test(HttpServletRequest request, HttpServletResponse response){
// 业务逻辑处理
}
二、问题分析
顺着这个接口,单步调试跟到Spring的源码ServletInvocableHandlerMethod#invokeAndHandle方法
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
// 直接进入到这里就return出去了
return;
}
} else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
try {
// 要进到这里才会进入到ResponseBodyAdvice中
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
} catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
}
throw ex;
}
}
再对着源码,看下return出去的4个条件,核对一下我们的接口
-
void类型,返回值确实是null
-
isRequestNotModified(webRequest) ,false
-
getResponseStatus() != null ,false
如果在test方法上标记@ResponseStatus(HttpStatus.OK),这里取到的就不是null
-
mavContainer.isRequestHandled(),true
那么问题来了,mavContainer.isRequestHandled()=true是什么时候设置的呢?
重新debug,发现在ServletResponseMethodArgumentResolver#resolveArgument中设置的,源码截取如下:
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
if (mavContainer != null) {
// 就是这里设置的了
mavContainer.setRequestHandled(true);
}
// 略 ...
}
三、问题小结
通过上面的分析,问题已经明朗了。
- 接口一直都是返回null的。开发此次修改的时候,在接口入参里面加了HttpServletResponse
- 因为有HttpServletResponse入参,就会进入到ServletResponseMethodArgumentResolver
- 因为ModelAndViewContainer不为null,mavContainer.setRequestHandled(true);
- 满足前面ServletInvocableHandlerMethod#invokeAndHandle的条件,直接return
- 返回空结果
@RequestBody, @ResponseBody 注解理解
@RequestBody,@ResponseBody 注解理解
自己以前没怎么留意过,来实习后公司采用前后端分离的开发方式,前后端拿到的注释都是 json 格式的,这时候 @RequestBody,@ResponseBody 这两个注解就非常好用,下面详细介绍用法:
@RequestBody
1. 作用:
- 该注解用于读取 Request 请求的 body 部分数据,使用系统默认配置的 HttpMessageConverter 进行解析,然后把相应的数据绑定到要返回的对象上;
- 再把 HttpMessageConverter 返回的对象数据绑定到 controller 中方法的参数上。
2. 使用时机:
GET、POST方式提交时, 根据 request header Content-Type 的值来判断:
-
application/x-www-form-urlencoded
:可选(即非必须,因为这种情况的数据 @RequestParam,@modelattribute 也可以处理,当然@RequestBody也能处理); -
multipart/form-data
:不能处理(即使用@RequestBody不能处理这种格式的数据); - 其他格式:必须(其他格式包括application/json,application/xml等。这些格式的数据,必须使用@RequestBody来处理);
PUT 方式提交时, 根据request header Content-Type的值来判断:
-
application/x-www-form-urlencoded
:必须; -
multipart/form-data
:不能处理; - 其他格式:必须;
3. 举个例子:
也就是说,如果是 json 格式的数据,我们要传入的参数是一个对象,那就必须使用 @RequestBody
。
@RequestMapping(value = "/test",Method = RequestMethod.POST,produces = "application/json;charset="UTF-8") @ResponseBody public JSONObject class Test(@RequestBody Test test) { String name = test.getName(); // 将 name 回显 Map<String,Object> map = new HashMap<>(); map.put("name",name); JSONObject obj = JSONObject.fromObject(map); return obj; }
@ResponseBody
1. 作用:
该注解用于将 Controller 的方法返回的对象,通过适当的 HttpMessageConverter 转换为指定格式后,写入到 Response 对象的body 数据区。
2. 使用时机:
返回的数据不是 html 标签的页面,而是其他某种格式的数据时(如 json、xml 等)使用。
3. 举个例子:
@RequestMapping(value = "/test",produces = "application/json;charset="UTF-8") @ResponseBody public JSONObject class test() { JSONObject obj = new JSONObject; obj.put("test",1); // 将 obj 回显 return obj; }
参考:
https://blog.csdn.net/walkerJong/article/details/7520896
完。
@RequestBody, @ResponseBody 注解详解
引言: 接上一篇文章讲述处理 @RequestMapping 的方法参数绑定之后,详细介绍下 @RequestBody、@ResponseBody 的具体用法和使用时机;同时对曾经看的一篇文章中讲述的某些部分进行澄清 (文章地址:http://www.byywee.com/page/M0/S702/702424.html)。
简介: @RequestBody 作用:
i) 该注解用于读取Request请求的body部分数据,使用系统默认配置的HttpMessageConverter进行解析,然后把相应的数据绑定到要返回的对象上;
ii) 再把HttpMessageConverter返回的对象数据绑定到 controller中方法的参数上。
使用时机:
A) GET、POST 方式提时, 根据 request header Content-Type 的值来判断:
application/x-www-form-urlencoded, 可选(即非必须,因为这种情况的数据@RequestParam, @ModelAttribute也可以处理,当然@RequestBody也能处理);
multipart/form-data, 不能处理(即使用@RequestBody不能处理这种格式的数据);
其他格式, 必须(其他格式包括application/json, application/xml等。这些格式的数据,必须使用@RequestBody来处理);
B) PUT 方式提交时, 根据 request header Content-Type 的值来判断:
application/x-www-form-urlencoded, 必须;
multipart/form-data, 不能处理;
其他格式, 必须;
说明:request 的 body 部分的数据编码格式由 header 部分的 Content-Type 指定;
@ResponseBody 作用:
该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。
使用时机:
返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用;
HttpMessageConverter
[java] view plain copy
<span style="font-family:Microsoft YaHei;">/**
* Strategy interface that specifies a converter that can convert from and to HTTP requests and responses.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @since 3.0
*/
public interface HttpMessageConverter<T> {
/**
* Indicates whether the given class can be read by this converter.
* @param clazz the class to test for readability
* @param mediaType the media type to read, can be {@code null} if not specified.
* Typically the value of a {@code Content-Type} header.
* @return {@code true} if readable; {@code false} otherwise
*/
boolean canRead(Class<?> clazz, MediaType mediaType);
/**
* Indicates whether the given class can be written by this converter.
* @param clazz the class to test for writability
* @param mediaType the media type to write, can be {@code null} if not specified.
* Typically the value of an {@code Accept} header.
* @return {@code true} if writable; {@code false} otherwise
*/
boolean canWrite(Class<?> clazz, MediaType mediaType);
/**
* Return the list of {@link MediaType} objects supported by this converter.
* @return the list of supported media types
*/
List<MediaType> getSupportedMediaTypes();
/**
* Read an object of the given type form the given input message, and returns it.
* @param clazz the type of object to return. This type must have previously been passed to the
* {@link #canRead canRead} method of this interface, which must have returned {@code true}.
* @param inputMessage the HTTP input message to read from
* @return the converted object
* @throws IOException in case of I/O errors
* @throws HttpMessageNotReadableException in case of conversion errors
*/
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
/**
* Write an given object to the given output message.
* @param t the object to write to the output message. The type of this object must have previously been
* passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}.
* @param contentType the content type to use when writing. May be {@code null} to indicate that the
* default content type of the converter must be used. If not {@code null}, this media type must have
* previously been passed to the {@link #canWrite canWrite} method of this interface, which must have
* returned {@code true}.
* @param outputMessage the message to write to
* @throws IOException in case of I/O errors
* @throws HttpMessageNotWritableException in case of conversion errors
*/
void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
</span>
该接口定义了四个方法,分别是读取数据时的 canRead(), read() 和 写入数据时的canWrite(), write()方法。
在使用 <mvc:annotation-driven />标签配置时,默认配置了RequestMappingHandlerAdapter(注意是RequestMappingHandlerAdapter不是AnnotationMethodHandlerAdapter,详情查看spring 3.1 document “16.14 Configuring Spring MVC”章节),并为他配置了一下默认的HttpMessageConverter:
[java] view plain copy
ByteArrayHttpMessageConverter converts byte arrays.
StringHttpMessageConverter converts strings.
ResourceHttpMessageConverter converts to/from org.springframework.core.io.Resource for all media types.
SourceHttpMessageConverter converts to/from a javax.xml.transform.Source.
FormHttpMessageConverter converts form data to/from a MultiValueMap<String, String>.
Jaxb2RootElementHttpMessageConverter converts Java objects to/from XML — added if JAXB2 is present on the classpath.
MappingJacksonHttpMessageConverter converts to/from JSON — added if Jackson is present on the classpath.
AtomFeedHttpMessageConverter converts Atom feeds — added if Rome is present on the classpath.
RssChannelHttpMessageConverter converts RSS feeds — added if Rome is present on the classpath.
ByteArrayHttpMessageConverter: 负责读取二进制格式的数据和写出二进制格式的数据;
StringHttpMessageConverter: 负责读取字符串格式的数据和写出二进制格式的数据;
ResourceHttpMessageConverter:负责读取资源文件和写出资源文件数据;
FormHttpMessageConverter: 负责读取form提交的数据(能读取的数据格式为 application/x-www-form-urlencoded,不能读取multipart/form-data格式数据);负责写入application/x-www-from-urlencoded和multipart/form-data格式的数据;
MappingJacksonHttpMessageConverter: 负责读取和写入json格式的数据;
SouceHttpMessageConverter: 负责读取和写入 xml 中javax.xml.transform.Source定义的数据;
Jaxb2RootElementHttpMessageConverter: 负责读取和写入xml 标签格式的数据;
AtomFeedHttpMessageConverter: 负责读取和写入Atom格式的数据;
RssChannelHttpMessageConverter: 负责读取和写入RSS格式的数据;
当使用@RequestBody和@ResponseBody注解时,RequestMappingHandlerAdapter就使用它们来进行读取或者写入相应格式的数据。
HttpMessageConverter匹配过程:
@RequestBody注解时: 根据Request对象header部分的Content-Type类型,逐一匹配合适的HttpMessageConverter来读取数据;
spring 3.1源代码如下:
[java] view plain copy
<span style="font-family:Microsoft YaHei;">private Object readWithMessageConverters(MethodParameter methodParam, HttpInputMessage inputMessage, Class paramType)
throws Exception {
MediaType contentType = inputMessage.getHeaders().getContentType();
if (contentType == null) {
StringBuilder builder = new StringBuilder(ClassUtils.getShortName(methodParam.getParameterType()));
String paramName = methodParam.getParameterName();
if (paramName != null) {
builder.append('' '');
builder.append(paramName);
}
throw new HttpMediaTypeNotSupportedException(
"Cannot extract parameter (" + builder.toString() + "): no Content-Type found");
}
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
if (this.messageConverters != null) {
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
if (messageConverter.canRead(paramType, contentType)) {
if (logger.isDebugEnabled()) {
logger.debug("Reading [" + paramType.getName() + "] as \"" + contentType
+"\" using [" + messageConverter + "]");
}
return messageConverter.read(paramType, inputMessage);
}
}
}
throw new HttpMediaTypeNotSupportedException(contentType, allSupportedMediaTypes);
}</span> ```
@ResponseBody注解时: 根据Request对象header部分的Accept属性(逗号分隔),逐一按accept中的类型,去遍历找到能处理的HttpMessageConverter;
源代码如下:
[java] view plain copy
<span style="font-family:Microsoft YaHei;">private void writeWithMessageConverters(Object returnValue,
HttpInputMessage inputMessage, HttpOutputMessage outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException {
List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();
if (acceptedMediaTypes.isEmpty()) {
acceptedMediaTypes = Collections.singletonList(MediaType.ALL);
}
MediaType.sortByQualityValue(acceptedMediaTypes);
Class<?> returnValueType = returnValue.getClass();
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
if (getMessageConverters() != null) {
for (MediaType acceptedMediaType : acceptedMediaTypes) {
for (HttpMessageConverter messageConverter : getMessageConverters()) {
if (messageConverter.canWrite(returnValueType, acceptedMediaType)) {
messageConverter.write(returnValue, acceptedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
MediaType contentType = outputMessage.getHeaders().getContentType();
if (contentType == null) {
contentType = acceptedMediaType;
}
logger.debug("Written [" + returnValue + "] as \"" + contentType +
"\" using [" + messageConverter + "]");
}
this.responseArgumentUsed = true;
return;
}
}
}
for (HttpMessageConverter messageConverter : messageConverters) {
allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
}
}
throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes);
}</span>
补充:
MappingJacksonHttpMessageConverter 调用了 objectMapper.writeValue(OutputStream stream, Object)方法,使用@ResponseBody注解返回的对象就传入Object参数内。若返回的对象为已经格式化好的json串时,不使用@RequestBody注解,而应该这样处理:
1、response.setContentType("application/json; charset=UTF-8");
2、response.getWriter().print(jsonStr);
直接输出到body区,然后的视图为void。
参考资料:
1、 Spring 3.1 Doc:
spring-3.1.0/docs/spring-framework-reference/html/mvc.html
2、Spring 3.x MVC 入门4 -- @ResponseBody & @RequestBody
http://www.byywee.com/page/M0/S702/702424.html```
这里输入代码
@ResponseBody & @RequestBody
@RequestBody 将 HTTP 请求正文插入方法中,使用适合的 HttpMessageConverter 将请求体写入某个对象。
@ResponseBody 将内容或对象作为 HTTP 响应正文返回,使用 @ResponseBody 将会跳过视图处理部分,而是调用适合 HttpMessageConverter,将返回值写入输出流。
HttpMessageConverter 接口
<mvc:annotation-driven /> 开启了之后它给 AnnotationMethodHandlerAdapter 初始化 7 个转换器,可以通过调用 AnnotationMethodHandlerAdapter 类的 getMessageConverts () 方法来获取转换器的一个集合 List<HttpMessageConverter>
默认给 AnnotationMethodHandlerAdapter 初始化的有 (当然我们也可以添加自定义的 converter)
ByteArrayHttpMessageConverter
StringHttpMessageConverter
ResourceHttpMessageConverter
SourceHttpMessageConverter<T>
XmlAwareFormHttpMessageConverter
Jaxb2RootElementHttpMessageConverter
MappingJacksonHttpMessageConverter
Spring 是如何寻找最佳的 HttpMessageConverter
1 首先获取注册的所有 HttpMessageConverter 集合
2 然后客户端的请求 header 中寻找客户端可接收的类型,
比如 Accept application/json,application/xml 等,组成一个集合
3 所有的 HttpMessageConverter 都有 canRead 和 canWrite 方法 返回值都是 boolean,看这个 HttpMessageConverter 是否支持当前请求的读与写,读对应 @RequestBody 注解,写对应 @ResponseBody 注解
4 遍历 HttpMessageConverter 集合与前面获取可接受类型进行匹配,如果匹配直接使用当前第一个匹配的 HttpMessageConverter,然后 return(一般是通过 Accept 和返回值对象的类型进行匹配)
例如
StringHttpMessageConverter
支持 String , Accept 所有类型
MappingJacksonHttpMessageConverter
支持 Map List 实体对象等等 ,Accept:application/json
示例:
目标:
使用ResponseBody根据head的Accept不同对同一地址请求分别来呈现一个实体的json与xml结果
由于<context:annotation-config />
默认会初始化AnnotationMethodHanlderAdapter,但我们返回xml内容需要对这个HandlerAdapter进行一定的修改,所以配置文件如下:
<context:component-scan base-package="com.controls" />
<context:annotation-config />
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="stringHttpMessageConverter" />
<ref bean="jsonHttpMessageConverter" />
<ref bean="marshallingHttpMessageConverter" />
</list>
</property>
</bean>
<bean id="stringHttpMessageConverter" class="org.springframework.http.converter.StringHttpMessageConverter" />
<bean id="jsonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
<bean id="marshallingHttpMessageConverter" class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
<constructor-arg ref="jaxbMarshaller" />
<property name="supportedMediaTypes" value="application/xml"></property>
</bean>
<bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>com.model.User</value>
</list>
</property>
</bean>
注:要使用Jaxb2Marshaller我们在对应的实体,比如User类上需要标明
@XmlRootElement 注解,需要引入
import javax.xml.bind.annotation.XmlRootElement;
这个包。
Controller中应对请求的方法
@RequestMapping(value="/user/{userid}", method=RequestMethod.GET)
public @ResponseBody User queryUser(@PathVariable("userid") long userID) {
Calendar d = Calendar.getInstance();
d.set(1987, 12, 9);
User u = new User();
u.setUserID(userID);
u.setUserName("zhaoyang");
u.setBirth(d.getTime());
return u;
}
接着我们使用curl这个工具进行测试
如下图:
@ResponseBody as the response has already been committed.
这种情况是 页面跳转 增加了 @ResponseBody 2017-02-16 19:08:09.131 DEBUG [catalina-exec-8][FilterChainProxy.java:200] - /WEB-INF/jsp/page/newsList.jsp has no matching filters 2017-02-16 19:08:09.132 DEBUG [catalina-exec-8][RequestContextFilter.java:104] - Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade@64327ce7 2017-02-16 19:08:09.132 ERROR [catalina-exec-8][ErrorPageFilter.java:204] - Cannot forward to error page for request [/newsList] as the response has already been committed. As a result, the response may have the wrong status code. If your application is running on WebSphere Application Server you may be able to resolve this problem by setting com.ibm.ws.webcontainer.invokeFlushAfterService to false
这种情况是页面不存在 2017-02-16 19:14:13.561 ERROR [catalina-exec-2][ErrorPageFilter.java:204] - Cannot forward to error page for request [/newsList] as the response has already been committed. As a result, the response may have the wrong status code. If your application is running on WebSphere Application Server you may be able to resolve this problem by setting com.ibm.ws.webcontainer.invokeFlushAfterService to false
@ResponseBody
@RequestMapping("/newsList")
public ModelAndView newsList()
throws Exception
{
LiveNewsInfo newsInfo = new LiveNewsInfo();
List<LiveNewsInfo> newsList = liveNewsInfoMapper.selectLiveNewsInfoList(newsInfo);
// newsInfo.setPageNum(pageNum);
// newsInfo.setPageCount(10);
// // 页面位置 /WEB-INF/jsp/page/page1.jsp
ModelAndView mav = new ModelAndView("page/newsList");
mav.addObject("newsList", newsList);
return mav;
}
/**
* 响应到JSP页面page1
*
* @return
* @author SHANHY
* @create 2016年1月5日
*/
@RequestMapping("/page1")
public ModelAndView page1()
{
// 页面位置 /WEB-INF/jsp/page/page1.jsp
ModelAndView mav = new ModelAndView("page/page1");
mav.addObject("content", hello);
return mav;
}
今天关于接口标记为@ResponseBody却不进入ResponseBodyAdvice和接口restful的介绍到此结束,谢谢您的阅读,有关@RequestBody, @ResponseBody 注解理解、@RequestBody, @ResponseBody 注解详解、@ResponseBody & @RequestBody、@ResponseBody as the response has already been committed.等更多相关知识的信息可以在本站进行查询。
本文标签: