@Response注解用于表明控制器返回的是json而非jsp页面。
例子:
@RequestMapping("/testBody")
@ResponseBody
public Object testBody(){
Person person = new Person();
person.setAge(10);
person.setName("ly");
return person;
}
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
返回值:
{
"name": "ly",
"age": 10
}
需要注意的是,如果要返回json,需要在classpath下引入json的类库,这里使用的是jackson:
com.fasterxml.jackson.core
jackson-core
2.8.1
com.fasterxml.jackson.core
jackson-databind
2.8.1
com.fasterxml.jackson.core
jackson-annotations
2.8.1
原理部分:
springmvc中对于json返回值是通过一个HttpMessageConverter接口实现的。
public interface HttpMessageConverter {
/**
* 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 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 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;
}
对于上边的例子,使用的jackson,那么就会有一个对应的实现类:MappingJackson2HttpMessageConverter。
从Adapter类开始看调用链。
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
initControllerAdviceCache();
if (this.argumentResolvers == null) {
List resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
这是初始化的部分代码。还记得Adpater中关键的两个点吗?argumentResolver和returnValueHandler。怎么没有messageConverter呢?
别急,看下getDefualReturnValueHandlers方法,其中有一段代码是这样的:
handlers.add(new RequestResponseBodyMethodProcessor(
getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
这里调用了getMessageConverter,把converter放入了一个RequestResponseBodyMethodProcessor类中,这是个什么类?
/**
* Resolves method arguments annotated with {@code @RequestBody} and handles
* return values from methods annotated with {@code @ResponseBody} by reading
* and writing to the body of the request or response with an
* {@link HttpMessageConverter}.
*
* An {@code @RequestBody} method argument is also validated if it is
* annotated with {@code @javax.validation.Valid}. In case of validation
* failure, {@link MethodArgumentNotValidException} is raised and results
* in a 400 response status code if {@link DefaultHandlerExceptionResolver}
* is configured.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.1
*/
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
个人感觉,可以把这个类理解为一个从messageConverter适配到argumentResolver和returnValueHandler的适配器。在Adapter中只有argumentResolver和returnValueHandler。
那么getMessageConverters会返回什么?
/**
* Return the configured message body converters.
*/
public List> getMessageConverters() {
return messageConverters;
}
那么应该是在Adapter被构建时注入的。在AnnotationDrivenBeanDefinitionParser中的parse方法中有这么一段:
ManagedList messageConverters = getMessageConverters(element, source, parserContext);
在getMessageConverter方法中有这么一段:
if (jackson2Present) {
messageConverters.add(createConverterDefinition(MappingJackson2HttpMessageConverter.class, source));
}
这个jackson2Present变量就是检测classpath下是否有jackson的实现类的:
private static final boolean jackson2Present =
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
所以,我们不需要特意注入MappingJackson2HttpMessageConverter,只需要在classpath中加入jackson的实现类即可。
好了,那么,MappingJackson2HttpMessageConverter这个converter就会被注入到Adapter中,具体是注入到RequestResponseBodyMethodProcessor的一个适配器中。
看下RequestResponseBodyMethodProcessor中关于处理返回值的两个重要实现方法:
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null ||
returnType.getMethodAnnotation(ResponseBody.class) != null);
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException {
mavContainer.setRequestHandled(true);
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, webRequest);
}
可以看到,该类专门处理被@ResponseBody注解标注的方法。
所以其实可以这么理解,真正处理@ResponseBody的正是RequestResponseBodyMethodProcessor这个类,其本身又是一个适配器。它与MappingJackson2HttpMessageConverter的关系是组合,即RequestResponseBodyMethodProcessor可以组合多种具体的converter来处理返回值,而MappingJackson2HttpMessageConverter只是其中的一种。看下代码,RequestResponseBodyMethodProcessor的一个父类如下:
public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {
protected final Log logger = LogFactory.getLog(getClass());
protected final List> messageConverters;
protected final List allSupportedMediaTypes;
里面聚合了多种converter。
其他的converter还有比如StringHttpMessageConverter和ByteArrayHttpMessageConverter等等好多种。
再看下RequestResponseBodyMethodProcessor的writeWithMessageConverters方法具体处理流程,具体是在父类中的:
protected void writeWithMessageConverters(T returnValue, MethodParameter returnType, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException {
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
这里的writeWithMessageConverters方法中有一段:
for (HttpMessageConverter messageConverter : this.messageConverters) {
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
returnValue = this.adviceChain.invoke(returnValue, returnType, selectedMediaType,
(Class>) messageConverter.getClass(), inputMessage, outputMessage);
if (returnValue != null) {
((HttpMessageConverter) messageConverter).write(returnValue, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +
messageConverter + "]");
}
}
return;
}
}
如果converter的canWrite为true,就调用write方法,看一下MappingJackson2HttpMessageConverter中的方法:
@Override
public boolean canWrite(Class clazz, MediaType mediaType) {
if (!jackson23Available || !logger.isWarnEnabled()) {
return (this.objectMapper.canSerialize(clazz) && canWrite(mediaType));
}
AtomicReference causeRef = new AtomicReference();
if (this.objectMapper.canSerialize(clazz, causeRef) && canWrite(mediaType)) {
return true;
}
Throwable cause = causeRef.get();
if (cause != null) {
String msg = "Failed to evaluate serialization for type [" + clazz + "]";
if (logger.isDebugEnabled()) {
logger.warn(msg, cause);
}
else {
logger.warn(msg + ": " + cause);
}
}
return false;
}
基本看jackson是否可以序列化返回的类。
write方法的核心实现是writeInternal:
@Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);
try {
writePrefix(generator, object);
Class serializationView = null;
Object value = object;
if (value instanceof MappingJacksonValue) {
MappingJacksonValue container = (MappingJacksonValue) object;
value = container.getValue();
serializationView = container.getSerializationView();
}
if (serializationView != null) {
this.objectMapper.writerWithView(serializationView).writeValue(generator, value);
}
else {
this.objectMapper.writeValue(generator, value);
}
writeSuffix(generator, object);
generator.flush();
}
catch (JsonProcessingException ex) {
throw new HttpMessageNotWritableException("Could not write content: " + ex.getMessage(), ex);
}
}
主要是调用objectMapper写入序列化内容。