SpringMVC注解の@ResonseBody注解の原理


紹介します
  • @ResponseBodyコメントの役割は、メソッドの戻り値を適切な変換器で指定されたフォーマットに変換した後、レスポンスオブジェクトのbody領域に書き込み、JSON、XMLデータを返すために使用されることが多い。
  • @ResponseBodyタグを使用した方法は、もはやビュー解析
  • を行わない。
    二、作用範囲
  •  方法に表記する
  • クラスにマークされています。
  • @RestitController注解により実現しました。この時、すべての方法は@ResponseBody注を添付します。
    三、ソース分析
    具体的にはなぜ以下の方法を使って私のもう一つの文章を見ることができますか?SpringMVC実行フロー解析
    Servlet Invocable Handler Method玦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;
    	}
    }
    
    この方法では、handleReturn Value()方法を呼び出して、戻り値を処理します。SpringMVCでは、Request ResonseBodMethodProcessor類を使って@ReponseBodyマークを処理する方法です。
    Request ResonseBody MethodProcessor
    
    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);
    }
    
    この方法は、mavContiner.set Request Handledを介して行われる。設定要求はすでに完全に処理されているので、後でビュー解析を行いません。そして、writeWithMessage Coverters()メソッドを呼び出しました。
    
    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);
    	}
    }
    
    この方法では、getAcceptable MediaTypes()を呼び出してacceptable Types、get Produccible MediaTypes()を取得し、isComptible With()を呼び出してacceptablesとproducible Typesを比較し、両方に対応するタイプを取得します。最後に、isConcrete()を呼び出して、具体的に使用されるメディアタイプを取得します。
    AbstractMessage ConverterMethodProcessor啝get Produccible MediaTypes
    
    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);
    	}
    }
    
    この方法は、タイプ変換器を巡回することにより、タイプ変換器に従ってサポートされたメディアタイプを取得する。一般的なタイプのトランスファーはStringHttp Message CoverterサポートがStringタイプに変換され、MappingJackson 2 HttpMessage Coverterサポートがjsonタイプに変換され、MappingJackson 2 XmlHttp Message ConterサポートがXMLタイプに変換されます。
    JSONデータに変換する例を示します。私たちが最終的に選んだメディアのタイプは「appration/json」です。そしてAbstractGeneranicHttp Message Coverter咻嗳write方法を呼び出して、データをreponse bodyに書き込みます。
    AbstractGeneraicHttp Message Coverter萶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();
    	}
    }
    
    この方法では、レスポンスヘッドを設定したConteet-Typeがappliation/jsonであり、その後、writeInternal()を呼び出してデータを書き込みます。
    Abstract Jackson 2 Http Message Coverter菗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.get Body()を呼び出すことにより、HttpServletResponseの出力ストリームオブジェクトを取得し、データをreponse bodyに出力する。JSONデータの符号化フォーマットをUTF-8に設定し、ObjectWriterオブジェクトを介してカスタマイズされた順序付け可能なオブジェクトを操作して、このオブジェクトをJSON形式に変換して、レスポンスbodyに出力します。
    Abstract Jackson 2 Http Message Coverter菗get Json Enccoding
    
    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に設定します。
    Servlet ServerHttpResonse菗get Body
    
    public OutputStream getBody() throws IOException {
    	this.bodyUsed = true;
    	writeHeaders();
    	return this.servletResponse.getOutputStream();
    }
    HttpServletResonseの出力ストリームを取得します。
    ここに来て、データをJSON形式に変換して、レスリングボーディに出力することができました。
    前に述べたmavContiner.set Request Handledという方法を覚えていますか?前にこの方法を呼び出したと言っていましたが、もうビュー解析をしません。ここで詳しく分析します。
    Request MappingHandler Adapter菗invoke Handler Method
    
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
    			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
    	...
    	invocableMethod.invokeAndHandle(webRequest, mavContainer);
    	...
    	return getModelAndView(mavContainer, modelFactory, webRequest);
    	... 
    }
    
    get ModelAndView()方法はinvoke AndHandle()メソッドの後に呼び出されました。つまりgetModelAndView()メソッドを呼び出す前に、既にmavContiner.set Request Handled(true)メソッドを呼び出しました。get ModelAndView()方法はビュー解析を行う方法です。この方法を見てみます。
    Request MappingHandler Adapter菗get ModelAndView
    
    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;
    }
    
    この方法は、最初からmavContiner.isrequest Handled()方法を呼び出し、trueであればnullに戻り、次のビュー解析を行うことができる。mavContiner.set Request Handled(true)メソッドはすでにtrueに設定されています。これはなぜ@ResponseBodyの注釈を付けたのですか?方法はビュー解析をしないからです。
    四、まとめ
  • @ResponseBodyコメントをメソッドに追加することができます。@RestitController注釈をクラスに加えることもできます。
  • クラスに@RestitController注釈を追加しました。このクラスのすべての方法に@ResonseBody注釈を追加するのと同等です。
  • @ResponseBodyは、データをString、JSON、XMLなどのフォーマットに変換する様々なタイプの変換器でデータの変換を実現する。データをレスポンスbodyに書き込みます。そしてそれらはUTF-8コードを使用しています。
  • は、カスタムJavaクラスについてJSONフォーマットのデータに変換し、このクラスが順序付け可能である場合。
  • @ResponseBody注釈タグを使用した方法は、もはや視認図解
  • を行わない。
    以上、Javaソース解析の@ResonseBodyの注釈原理に関する文章を紹介しました。@ResponseBodyの注釈原理の内容は以前の文章を検索してください。または下記の関連記事を引き続きご覧ください。これからもよろしくお願いします。