環境:Springboot2.5.12
このようなメッセージ形式を今実装したいとします。
入参:
名前:張三、年齢:20歳
インターフェイスはオブジェクト Users を受け取ります
-
カスタムメッセージコンバーター
public class CustomHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
private static Logger logger = LoggerFactory.getLogger(CustomHttpMessageConverter.class) ;
// 这里指明了只要接收参数是Users类型的都能进行转换
@Override
protected boolean supports(Class<?> clazz) {
return Users.class == clazz ;
}
// 读取内容进行内容的转换
@Override
protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
String content = inToString(inputMessage.getBody()) ;
String[] keys = content.split(",") ;
Users instance = null ;
try {
instance = (Users) clazz.newInstance();
} catch (Exception e1) {
e1.printStackTrace() ;
}
for (String key : keys) {
String[] vk = key.split(":") ;
try {
Field[] fields = clazz.getDeclaredFields() ;
for (Field f:fields) {
if (f.getName().equals(vk[0])) {
f.setAccessible(true) ;
Class<?> type = f.getType() ;
if (String.class == type) {
f.set(instance, vk[1]) ;
} else if (Integer.class == type) {
f.set(instance, Integer.parseInt(vk[1])) ;
}
break ;
}
}
} catch (Exception e) {
logger.error("错误:{}", e) ;
}
}
return instance ;
}
// 如果将返回值以什么形式输出,这里就是调用了对象的toString方法。
@Override
protected void writeInternal(Object t, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
outputMessage.getBody().write(t.toString().getBytes()) ;
}
@Override
protected boolean canWrite(MediaType mediaType) {
if (mediaType == null || MediaType.ALL.equalsTypeAndSubtype(mediaType)) {
return true;
}
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
if (supportedMediaType.isCompatibleWith(mediaType)) {
return true;
}
}
return false;
}
private String inToString(InputStream is) {
byte[] buf = new byte[10 * 1024] ;
int leng = -1 ;
StringBuilder sb = new StringBuilder() ;
try {
while ((leng = is.read(buf)) != -1) {
sb.append(new String(buf, 0, leng)) ;
}
return sb.toString() ;
} catch (IOException e) {
throw new RuntimeException(e) ;
}
}
}
-
メッセージコンバーターを構成する
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
CustomHttpMessageConverter messageConvert = new CustomHttpMessageConverter() ;
List<MediaType> supportedMediaTypes = new ArrayList<>() ;
supportedMediaTypes.add(new MediaType("application", "fm")) ;
messageConvert.setSupportedMediaTypes(supportedMediaTypes) ;
converters.add(messageConvert) ;
WebMvcConfigurer.super.configureMessageConverters(converters);
}
}
メッセージ コンバーターを構成する場合、現在のメッセージ コンバーターが受信できるコンテンツ タイプが指定されます。つまり、クライアントはリクエスト時に Content-Type を application/fm に設定する必要があります。
-
パラメータオブジェクト
public class Users {
private String name ;
private Integer age ;
@Override
public String toString() {
return "【name = " + this.name + ", age = " + this.age + "】" ;
}
}
-
コントローラインターフェース
@RestController
@RequestMapping("/message")
public class MessageController {
@PostMapping("/save")
public Users save(@RequestBody Users user) {
System.out.println("接受到内容:" + user) ;
return user ;
}
}
-
テスト
聞く:
応答
ソース コード分析: メッセージ コンバーターをカスタマイズするときにこれらのメソッドを書き直す必要があるのはなぜですか:
インターフェイスパラメーターには@RequestBodyの注釈が付けられているため 、システムは次を使用します。
パラメーター パーサーRequestResponseBodyMethodProcessorはパラメーターを処理します。
処理フロー全体への入り口は、DispatcherServletの次のコード行です。
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
次に、このコード行を
RequestMappingHandlerAdapter #handleInternalメソッドに入力します。
mav = invokeHandlerMethod(request, response, handlerMethod);
次に、次のコード行を
RequestMappingHandlerAdapter#invokeHandlerMethodメソッドに入力します。
invocableMethod.invokeAndHandle(webRequest, mavContainer);
次に、次のコード行を
ServletInvocableHandlerMethod#invokeAndHandleメソッドに入力します。
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
次に、invokeForRequest メソッドを入力します。
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return doInvoke(args);
}
次に、getMethodArgumentValues メソッドを入力します
1. ここで、それを処理できるパラメータ パーサーがあるかどうかの判断を開始します。そうでない場合は、例外がスローされます。
ここでは、処理されたパラメータ パーサーも見つけてキャッシュします。
this.argumentResolverCache.put(パラメータ, 結果);
このコード行は、現在利用可能なパーサーをキャッシュします。
2. パラメータの解析を開始し、キャッシュから直接パラメータを取得します。パーサーは前のステップで取得されているためです。
パーサーを取得した後:
選択したメソッドは、最終的に、
親クラス AbstractMessageConverterMethodArgumentResolver の次のメソッドに入ります。
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
MediaType contentType;
boolean noContentType = false;
try {
contentType = inputMessage.getHeaders().getContentType();
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
if (contentType == null) {
noContentType = true;
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
Class<?> contextClass = parameter.getContainingClass();
Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
if (targetClass == null) {
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
targetClass = (Class<T>) resolvableType.resolve();
}
HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
Object body = NO_VALUE;
EmptyBodyCheckingHttpInputMessage message;
try {
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
else {
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}
catch (IOException ex) {
throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
}
if (body == NO_VALUE) {
if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
(noContentType && !message.hasBody())) {
return null;
}
throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
}
MediaType selectedContentType = contentType;
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
});
return body;
}
このメソッドの this.messageConverters データは次のとおりです。
ここでカスタム
CustomHttpMessageConverter を確認できます。
カスタマイズされたコンバーターへのデバッグを続行します
ここから、else (:) 内のコードが実行されることがわかります。
targetClass != null && Converter.canRead(targetClass, contentType)
この canRead は親クラスのメソッドです。
サポートはここでカスタムコンバーターを入力します。
引き続き read メソッドに入り、実際にメッセージの内容を処理するコードを読み取ります。
ここの readInternal はカスタム メソッドです
関連する write メソッドは read メソッドと同様で、書き込み可能かどうかを判断し、対応する writeInternal メソッドを呼び出します。