- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
我正在看一段代码,其中我假设spring决定在幕后使用Jackson为@RestController自动将对象转换为json
@RestController
@RequestMapping("/api")
public class ApiController {
private RoomServices roomServices;
@Autowired
public ApiController(RoomServices roomServices) {
this.roomServices = roomServices;
}
@GetMapping("/rooms")
public List<Room> getAllRooms() {
return this.roomServices.getAllRooms();
}
}
Room类只是一个普通的java类,包含一些字段getter/setter。代码中没有Jackson或任何其他显式序列化。尽管这在检查url时会返回json。我试图浏览spring文档,但我不太确定我要找的是什么。在spring/这个过程是如何工作的?我试着用@Controller,结果它坏了。此功能是否来自@RestController?
如果您使用的是Spring Boot Starter Web,那么可以通过编译依赖项看到它使用的是Spring Boot StarterJSON,Jackson是Start JSON库的依赖项。所以,您的假设是对的(默认情况下,(Spring使用Jackson进行Json转换)
Spring使用它的AOP机制截获@Controller中的映射方法(您可以看到@RestController实际上是带有@ResponseBody的@Controller),spring为带有@Controller注释的类创建一个代理对象(使用JDK proxy或通过cglib)。
在处理请求流时,真正调用映射方法的程序将首先被引导到代理,代理将调用真正的@Controller对象的方法,并使用Jackson库将其返回值转换为Json字符串(如果该方法用@ResponseBody注释),然后将Json字符串返回给调用程序。
在Springboot中根本不需要做什么,主要返回值是一个对象,传递给浏览器后,都会将这个对象转换成json字符串,key对应对象字段名称,value对应字段值
注解 @ResponseBody,使用在控制层(controller)的方法上。
作用:将方法的返回值,以特定的格式写入到response的body区域,进而将数据返回给客户端。
当方法上面没有写ResponseBody,底层会将方法的返回值封装为ModelAndView对象。
如果返回值是字符串,那么直接将字符串写到客户端;如果是一个对象,会将对象转化为json串,然后写到客户端。
注解中我们可以手动修改编码格式,例如@RequestMapping(value=“/cat/query”,produces=“text/html;charset=utf-8”),前面是请求的路径,后面是编码格式。
控制层方法的返回值是如何转化为json格式的字符串的?其实是通过HttpMessageConverter中的方法实现的,它本是一个接口,在其实现类完成转换。如果是bean对象,会调用对象的getXXX()方法获取属性值并且以键值对的形式进行封装,进而转化为json串。如果是map集合,采用get(key)方式获取value值,然后进行封装。
注:SpringMVC版本 5.2.15
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()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
// 处理返回值
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
该方法中调用了 handleReturnValue() 方法去处理返回值。SpringMVC 中使用 RequestResponseBodyMethodProcessor 类来处理 @ResponseBody 标记的方法
RequestResponseBodyMethodProcessor # handleReturnValue
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
// 设置请求已经被完全处理了,则后面不再做视图解析
// 后面我还会提到的
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
该方法中通过 mavContainer.setRequestHandled(true); 设置请求已经被完全处理了,则后面不再做视图解析。然后调用了 writeWithMessageConverters() 方法。
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
// 存储响应体的信息
Object body;
// 返回值类型
Class<?> valueType;
// 目标类型
Type targetType;
// 返回值类型是否是 CharSequence
// 是则将 返回值类型和目标类型设置为 String.class
if (value instanceof CharSequence) {
body = value.toString();
valueType = String.class;
targetType = String.class;
}
// 不是 CharSequence 类型,一般是我们的自定义类
else {
body = value;
valueType = getReturnValueType(body, returnType);
targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
}
// 返回值类型是否是实现了 Resource 接口的资源
// 这里我就不分析了
if (isResourceType(value, returnType)) {
outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
outputMessage.getServletResponse().getStatus() == 200) {
Resource resource = (Resource) value;
try {
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
body = HttpRange.toResourceRegions(httpRanges, resource);
valueType = body.getClass();
targetType = RESOURCE_REGION_LIST_TYPE;
}
catch (IllegalArgumentException ex) {
outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
}
}
}
// 选中的媒体类型
MediaType selectedMediaType = null;
MediaType contentType = outputMessage.getHeaders().getContentType();
boolean isContentTypePreset = contentType != null && contentType.isConcrete();
if (isContentTypePreset) {
if (logger.isDebugEnabled()) {
logger.debug("Found 'Content-Type:" + contentType + "' in response");
}
selectedMediaType = contentType;
}
else {
HttpServletRequest request = inputMessage.getServletRequest();
// 可接受的媒体类型
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
// 可产生的媒体类型
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
if (body != null && producibleTypes.isEmpty()) {
throw new HttpMessageNotWritableException(
"No converter found for return value of type: " + valueType);
}
// 将要被使用的媒体类型
List<MediaType> mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (mediaTypesToUse.isEmpty()) {
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(producibleTypes);
}
if (logger.isDebugEnabled()) {
logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
}
return;
}
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
for (MediaType mediaType : mediaTypesToUse) {
// 该媒体类型是否是具体的
// 也就是不包含类似于 * 这样的通配符
if (mediaType.isConcrete()) {
// 选中要使用的媒体类型
selectedMediaType = mediaType;
break;
}
else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (logger.isDebugEnabled()) {
logger.debug("Using '" + selectedMediaType + "', given " +
acceptableTypes + " and supported " + producibleTypes);
}
}
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
// 使用类型转换器将请求写入到 response body 中
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
return;
}
}
}
if (body != null) {
Set<MediaType> producibleMediaTypes =
(Set<MediaType>) inputMessage.getServletRequest()
.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
throw new HttpMessageNotWritableException(
"No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
}
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
}
该方法中通过调用 getAcceptableMediaTypes() 方法获取到 acceptableTypes,getProducibleMediaTypes() 方法获取到 producibleTypes,然后调用 isCompatibleWith() 方法比较 acceptableTypes 和 producibleTypes,获取到两者都兼容的类型。最后通过调用 isConcrete() 获取到一个具体使用的媒体类型。
AbstractMessageConverterMethodProcessor # getProducibleMediaTypes
protected List<MediaType> getProducibleMediaTypes(
HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {
Set<MediaType> mediaTypes =
(Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (!CollectionUtils.isEmpty(mediaTypes)) {
return new ArrayList<>(mediaTypes);
}
else if (!this.allSupportedMediaTypes.isEmpty()) {
List<MediaType> result = new ArrayList<>();
// 遍历类型转化器,获取支持的媒体类型
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter instanceof GenericHttpMessageConverter && targetType != null) {
if (((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
else if (converter.canWrite(valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
return result;
}
else {
return Collections.singletonList(MediaType.ALL);
}
}
该方法中通过遍历类型转换器,根据类型转换器获取到支持的媒体类型。常见的类型转化器有 StringHttpMessageConverter 支持转换为 String 类型,MappingJackson2HttpMessageConverter 支持转换为 json 类型,MappingJackson2XmlHttpMessageConverter 支持转换为 XML 类型。
以转换为 JSON 数据为例。我们最终选择的媒体类型就是 “application/json” ,然后调用 AbstractGenericHttpMessageConverter # write 方法将数据写入到 response body 中。
AbstractGenericHttpMessageConverter # write
public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType,
HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
final HttpHeaders headers = outputMessage.getHeaders();
// 添加响应头
// 设置 Content-Type 为 application/json
addDefaultHeaders(headers, t, contentType);
if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(outputStream -> writeInternal(t, type, new HttpOutputMessage() {
@Override
public OutputStream getBody() {
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}));
}
else {
// 写入数据到 response body 中
writeInternal(t, type, outputMessage);
outputMessage.getBody().flush();
}
}
该方法中设置了响应头的 Content-Type 为 application/json,然后调用 writeInternal() 方法写数据
AbstractJackson2HttpMessageConverter # writeInternal
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
// 获取媒体类型为 application/json
MediaType contentType = outputMessage.getHeaders().getContentType();
// 获取 JSON 数据的编码为 UTF-8
JsonEncoding encoding = getJsonEncoding(contentType);
// 获取到 HttpServletResponse 的输出流对象
// 用于将数据写入到 response body 中
OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody());
// 生成 JSON 数据的类
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputStream, encoding);
try {
writePrefix(generator, object);
Object value = object;
Class<?> serializationView = null;
FilterProvider filters = null;
JavaType javaType = null;
if (object instanceof MappingJacksonValue) {
MappingJacksonValue container = (MappingJacksonValue) object;
value = container.getValue();
serializationView = container.getSerializationView();
filters = container.getFilters();
}
if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
// 获取 java 类型,一般是我们自定义的类
javaType = getJavaType(type, null);
}
// 用于操作可序列化对象的类
// 我们自定义的类一定要实现 Serializable 接口,并设置 get/set 方法
ObjectWriter objectWriter = (serializationView != null ?
this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer());
if (filters != null) {
objectWriter = objectWriter.with(filters);
}
if (javaType != null && javaType.isContainerType()) {
objectWriter = objectWriter.forType(javaType);
}
SerializationConfig config = objectWriter.getConfig();
if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) &&
config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
objectWriter = objectWriter.with(this.ssePrettyPrinter);
}
// 写入数据
objectWriter.writeValue(generator, value);
writeSuffix(generator, object);
generator.flush();
generator.close();
}
catch (InvalidDefinitionException ex) {
throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
}
catch (JsonProcessingException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex);
}
}
该方法中通过调用 outputMessage.getBody() 方法获取到了 HttpServletResponse 的输出流对象,用于将数据输出到 response body 中。并设置了 JSON 数据的编码格式为 UTF-8,然后通过 ObjectWriter 对象操作我们自定义的可序列化的对象,将该对象转换为 JSON 格式输出到 response body 中。
AbstractJackson2HttpMessageConverter # getJsonEncoding
protected JsonEncoding getJsonEncoding(@Nullable MediaType contentType) {
if (contentType != null && contentType.getCharset() != null) {
Charset charset = contentType.getCharset();
JsonEncoding encoding = ENCODINGS.get(charset.name());
if (encoding != null) {
return encoding;
}
}
return JsonEncoding.UTF8;
}
设置 JSON 数据的编码格式为 UTF-8
ServletServerHttpResponse # getBody
public OutputStream getBody() throws IOException {
this.bodyUsed = true;
writeHeaders();
return this.servletResponse.getOutputStream();
}
获取 HttpServletResponse 的输出流
到这里我们已经实现了将数据转化为 JSON 格式输出到 response body 中了。
还记得前面提到的 mavContainer.setRequestHandled(true) 这个方法吗,前面我说了调用了这个方法后,就不再做视图解析了,我们这里再具体分析一下。
RequestMappingHandlerAdapter # invokeHandlerMethod
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
...
invocableMethod.invokeAndHandle(webRequest, mavContainer);
...
return getModelAndView(mavContainer, modelFactory, webRequest);
...
}
可以看到 getModelAndView() 方法是在 invokeAndHandle() 方法之后调用了,也就是在调用 getModelAndView() 方法前,我们已经调用了 mavContainer.setRequestHandled(true) 方法了。getModelAndView() 方法就是做视图解析的,我们来看一下该方法。
RequestMappingHandlerAdapter # getModelAndView
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
modelFactory.updateModel(webRequest, mavContainer);
// 是否已经完全处理了,若为 true,则直接返回 null
// mavContainer.setRequestHandled(true) 已设置为 true 了
if (mavContainer.isRequestHandled()) {
return null;
}
// 下面的代码是做视图解析
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
}
return mav;
}
可以看到,该方法一开始就调用了 mavContainer.isRequestHandled() 方法,如果为 true,则返回 null,并进行下面的视图解析。而 mavContainer.setRequestHandled(true) 方法已经将其设置为 true 了。这就是为什么加了 @ResponseBody 注解的方法不做视图解析的原因。
我必须使用文件将一个大数组从一台服务器传输到另一台服务器。这是一个多维但非常简单的数组。现在我正在寻找最有效的方法,将这个文件放入我在第二台服务器上的应用程序中。所以这个问题是关于文件->数组部分,而
我是一名优秀的程序员,十分优秀!