Browse Source

Introduce hints support in advices

This commit introduces RequestBodyAdvice#determineReadHints and
ResponseBodyAdvice#determineWriteHints in order to be able to support
SmartHttpMessageConverter hints, as well as related `@JsonView`
support.

See gh-33798
pull/34893/head
Sébastien Deleuze 7 months ago
parent
commit
d0cd7af7e6
  1. 9
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMappingJacksonResponseBodyAdvice.java
  2. 16
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java
  3. 8
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java
  4. 29
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/JsonViewRequestBodyAdvice.java
  5. 19
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/JsonViewResponseBodyAdvice.java
  6. 20
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestBodyAdvice.java
  7. 41
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyAdviceChain.java
  8. 20
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyAdvice.java
  9. 79
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyAdviceChainTests.java

9
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMappingJacksonResponseBodyAdvice.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,6 +20,7 @@ import org.jspecify.annotations.Nullable; @@ -20,6 +20,7 @@ import org.jspecify.annotations.Nullable;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractJacksonHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonValue;
@ -39,7 +40,8 @@ public abstract class AbstractMappingJacksonResponseBodyAdvice implements Respon @@ -39,7 +40,8 @@ public abstract class AbstractMappingJacksonResponseBodyAdvice implements Respon
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
return AbstractJacksonHttpMessageConverter.class.isAssignableFrom(converterType) ||
AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
}
@Override
@ -50,6 +52,9 @@ public abstract class AbstractMappingJacksonResponseBodyAdvice implements Respon @@ -50,6 +52,9 @@ public abstract class AbstractMappingJacksonResponseBodyAdvice implements Respon
if (body == null) {
return null;
}
if (AbstractJacksonHttpMessageConverter.class.isAssignableFrom(converterType)) {
return body;
}
MappingJacksonValue container = getOrCreateContainer(body);
beforeBodyWriteInternal(container, contentType, returnType, request, response);
return container;

16
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -175,7 +175,7 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements @@ -175,7 +175,7 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
ResolvableType targetResolvableType = null;
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterClass = (Class<HttpMessageConverter<?>>) converter.getClass();
Class<? extends HttpMessageConverter<?>> converterClass = (Class<? extends HttpMessageConverter<?>>) converter.getClass();
ConverterType converterTypeToUse = null;
if (converter instanceof GenericHttpMessageConverter<?> genericConverter) {
if (genericConverter.canRead(targetType, contextClass, contentType)) {
@ -195,17 +195,17 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements @@ -195,17 +195,17 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
}
if (converterTypeToUse != null) {
if (message.hasBody()) {
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterClass);
HttpInputMessage msgToUse = this.advice.beforeBodyRead(message, parameter, targetType, converterClass);
body = switch (converterTypeToUse) {
case BASE -> ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse);
case GENERIC -> ((GenericHttpMessageConverter<?>) converter).read(targetType, contextClass, msgToUse);
case SMART -> ((SmartHttpMessageConverter<?>) converter).read(targetResolvableType, msgToUse, null);
case SMART -> ((SmartHttpMessageConverter<?>) converter).read(targetResolvableType, msgToUse,
this.advice.determineReadHints(parameter, targetType, (Class<SmartHttpMessageConverter<?>>) converterClass));
};
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterClass);
body = this.advice.afterBodyRead(body, msgToUse, parameter, targetType, converterClass);
}
else {
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterClass);
body = this.advice.handleEmptyBody(null, message, parameter, targetType, converterClass);
}
break;
}
@ -213,7 +213,7 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements @@ -213,7 +213,7 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
}
if (body == NO_VALUE && noContentType && !message.hasBody()) {
body = getAdvice().handleEmptyBody(
body = this.advice.handleEmptyBody(
null, message, parameter, targetType, NoContentTypeHttpMessageConverter.class);
}
}

8
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -324,7 +324,7 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe @@ -324,7 +324,7 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
}
}
else if (converter instanceof SmartHttpMessageConverter smartConverter) {
targetResolvableType = getNestedTypeIfNeeded(ResolvableType.forMethodParameter(returnType));
targetResolvableType = getNestedTypeIfNeeded(ResolvableType.forType(targetType));
if (smartConverter.canWrite(targetResolvableType, valueType, selectedMediaType)) {
converterTypeToUse = ConverterType.SMART;
}
@ -343,7 +343,9 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe @@ -343,7 +343,9 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
switch (converterTypeToUse) {
case BASE -> converter.write(body, selectedMediaType, outputMessage);
case GENERIC -> ((GenericHttpMessageConverter) converter).write(body, targetType, selectedMediaType, outputMessage);
case SMART -> ((SmartHttpMessageConverter) converter).write(body, targetResolvableType, selectedMediaType, outputMessage, null);
case SMART -> ((SmartHttpMessageConverter) converter).write(body, targetResolvableType,
selectedMediaType, outputMessage, getAdvice().determineWriteHints(body, returnType,
selectedMediaType, (Class<? extends HttpMessageConverter<?>>) converter.getClass()));
}
}
else {

29
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/JsonViewRequestBodyAdvice.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,12 +18,17 @@ package org.springframework.web.servlet.mvc.method.annotation; @@ -18,12 +18,17 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonView;
import org.jspecify.annotations.Nullable;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.AbstractJacksonHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.SmartHttpMessageConverter;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonInputMessage;
import org.springframework.util.Assert;
@ -52,14 +57,28 @@ public class JsonViewRequestBodyAdvice extends RequestBodyAdviceAdapter { @@ -52,14 +57,28 @@ public class JsonViewRequestBodyAdvice extends RequestBodyAdviceAdapter {
public boolean supports(MethodParameter methodParameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return (AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType) &&
methodParameter.getParameterAnnotation(JsonView.class) != null);
return methodParameter.getParameterAnnotation(JsonView.class) != null &&
(AbstractJacksonHttpMessageConverter.class.isAssignableFrom(converterType) ||
AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType));
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter,
Type targetType, Class<? extends HttpMessageConverter<?>> selectedConverterType) throws IOException {
if (AbstractJacksonHttpMessageConverter.class.isAssignableFrom(selectedConverterType)) {
return inputMessage;
}
return new MappingJacksonInputMessage(inputMessage.getBody(), inputMessage.getHeaders(), getJsonView(methodParameter));
}
@Override
public @Nullable Map<String, Object> determineReadHints(MethodParameter parameter, Type targetType, Class<? extends SmartHttpMessageConverter<?>> converterType) {
return Collections.singletonMap(JsonView.class.getName(), getJsonView(parameter));
}
private static Class<?> getJsonView(MethodParameter methodParameter) {
JsonView ann = methodParameter.getParameterAnnotation(JsonView.class);
Assert.state(ann != null, "No JsonView annotation");
@ -68,8 +87,6 @@ public class JsonViewRequestBodyAdvice extends RequestBodyAdviceAdapter { @@ -68,8 +87,6 @@ public class JsonViewRequestBodyAdvice extends RequestBodyAdviceAdapter {
throw new IllegalArgumentException(
"@JsonView only supported for request body advice with exactly 1 class argument: " + methodParameter);
}
return new MappingJacksonInputMessage(inputMessage.getBody(), inputMessage.getHeaders(), classes[0]);
return classes[0];
}
}

19
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/JsonViewResponseBodyAdvice.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,7 +16,11 @@ @@ -16,7 +16,11 @@
package org.springframework.web.servlet.mvc.method.annotation;
import java.util.Collections;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonView;
import org.jspecify.annotations.Nullable;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
@ -55,6 +59,15 @@ public class JsonViewResponseBodyAdvice extends AbstractMappingJacksonResponseBo @@ -55,6 +59,15 @@ public class JsonViewResponseBodyAdvice extends AbstractMappingJacksonResponseBo
protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType,
MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
bodyContainer.setSerializationView(getJsonView(returnType));
}
@Override
public @Nullable Map<String, Object> determineWriteHints(@Nullable Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType) {
return Collections.singletonMap(JsonView.class.getName(), getJsonView(returnType));
}
private static Class<?> getJsonView(MethodParameter returnType) {
JsonView ann = returnType.getMethodAnnotation(JsonView.class);
Assert.state(ann != null, "No JsonView annotation");
@ -63,8 +76,6 @@ public class JsonViewResponseBodyAdvice extends AbstractMappingJacksonResponseBo @@ -63,8 +76,6 @@ public class JsonViewResponseBodyAdvice extends AbstractMappingJacksonResponseBo
throw new IllegalArgumentException(
"@JsonView only supported for response body advice with exactly 1 class argument: " + returnType);
}
bodyContainer.setSerializationView(classes[0]);
return classes[0];
}
}

20
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestBodyAdvice.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,12 +18,14 @@ package org.springframework.web.servlet.mvc.method.annotation; @@ -18,12 +18,14 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.SmartHttpMessageConverter;
/**
* Allows customizing the request before its body is read and converted into an
@ -36,6 +38,7 @@ import org.springframework.http.converter.HttpMessageConverter; @@ -36,6 +38,7 @@ import org.springframework.http.converter.HttpMessageConverter;
* {@code @ControllerAdvice} in which case they are auto-detected.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
* @since 4.2
*/
public interface RequestBodyAdvice {
@ -63,6 +66,20 @@ public interface RequestBodyAdvice { @@ -63,6 +66,20 @@ public interface RequestBodyAdvice {
HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException;
/**
* Invoked to determine read hints if the converter is a {@link SmartHttpMessageConverter}.
* @param parameter the target method parameter
* @param targetType the target type, not necessarily the same as the method
* parameter type, for example, for {@code HttpEntity<String>}.
* @param converterType the selected converter type
* @return the hints determined otherwise {@code null}
* @since 7.0
*/
default @Nullable Map<String, Object> determineReadHints(MethodParameter parameter,
Type targetType, Class<? extends SmartHttpMessageConverter<?>> converterType) {
return null;
}
/**
* Invoked third (and last) after the request body is converted to an Object.
* @param body set to the converter Object before the first advice is called
@ -90,5 +107,4 @@ public interface RequestBodyAdvice { @@ -90,5 +107,4 @@ public interface RequestBodyAdvice {
@Nullable Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
}

41
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyAdviceChain.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,7 +20,9 @@ import java.io.IOException; @@ -20,7 +20,9 @@ import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jspecify.annotations.Nullable;
@ -28,6 +30,7 @@ import org.springframework.core.MethodParameter; @@ -28,6 +30,7 @@ import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.SmartHttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.CollectionUtils;
@ -36,7 +39,7 @@ import org.springframework.web.method.ControllerAdviceBean; @@ -36,7 +39,7 @@ import org.springframework.web.method.ControllerAdviceBean;
/**
* Invokes {@link RequestBodyAdvice} and {@link ResponseBodyAdvice} where each
* instance may be (and is most likely) wrapped with
* {@link org.springframework.web.method.ControllerAdviceBean ControllerAdviceBean}.
* {@link ControllerAdviceBean ControllerAdviceBean}.
*
* @author Rossen Stoyanchev
* @since 4.2
@ -176,4 +179,38 @@ class RequestResponseBodyAdviceChain implements RequestBodyAdvice, ResponseBodyA @@ -176,4 +179,38 @@ class RequestResponseBodyAdviceChain implements RequestBodyAdvice, ResponseBodyA
}
}
@Override
public @Nullable Map<String, Object> determineReadHints(MethodParameter parameter, Type targetType, Class<? extends SmartHttpMessageConverter<?>> converterType) {
Map<String, Object> hints = null;
for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {
if (advice.supports(parameter, targetType, converterType)) {
Map<String, Object> adviceHints = advice.determineReadHints(parameter, targetType, converterType);
if (adviceHints != null) {
if (hints == null) {
hints = new HashMap<>(adviceHints.size());
}
hints.putAll(adviceHints);
}
}
}
return hints;
}
@Override
@SuppressWarnings("unchecked")
public @Nullable Map<String, Object> determineWriteHints(@Nullable Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType) {
Map<String, Object> hints = null;
for (ResponseBodyAdvice<?> advice : getMatchingAdvice(returnType, ResponseBodyAdvice.class)) {
if (advice.supports(returnType, selectedConverterType)) {
Map<String, Object> adviceHints = ((ResponseBodyAdvice<Object>) advice).determineWriteHints(body, returnType, selectedContentType, selectedConverterType);
if (adviceHints != null) {
if (hints == null) {
hints = new HashMap<>(adviceHints.size());
}
hints.putAll(adviceHints);
}
}
}
return hints;
}
}

20
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyAdvice.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +16,14 @@ @@ -16,11 +16,14 @@
package org.springframework.web.servlet.mvc.method.annotation;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.SmartHttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
@ -35,6 +38,7 @@ import org.springframework.http.server.ServerHttpResponse; @@ -35,6 +38,7 @@ import org.springframework.http.server.ServerHttpResponse;
* will be auto-detected by both.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
* @since 4.1
* @param <T> the body type
*/
@ -65,4 +69,18 @@ public interface ResponseBodyAdvice<T> { @@ -65,4 +69,18 @@ public interface ResponseBodyAdvice<T> {
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response);
/**
* Invoked to determine write hints if the converter is a {@link SmartHttpMessageConverter}.
* @param body the body to be written
* @param returnType the return type of the controller method
* @param selectedContentType the content type selected through content negotiation
* @param selectedConverterType the converter type selected to write to the response
* @return the hints determined otherwise {@code null}
* @since 7.0
*/
default @Nullable Map<String, Object> determineWriteHints(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType) {
return null;
}
}

79
spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyAdviceChainTests.java

@ -17,10 +17,14 @@ @@ -17,10 +17,14 @@
package org.springframework.web.servlet.mvc.method.annotation;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.context.support.StaticApplicationContext;
@ -29,7 +33,9 @@ import org.springframework.core.annotation.AnnotatedElementUtils; @@ -29,7 +33,9 @@ import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.SmartHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
@ -52,6 +58,7 @@ import static org.mockito.Mockito.mock; @@ -52,6 +58,7 @@ import static org.mockito.Mockito.mock;
* Tests for {@link RequestResponseBodyAdviceChain}.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
* @since 4.2
*/
class RequestResponseBodyAdviceChainTests {
@ -131,6 +138,20 @@ class RequestResponseBodyAdviceChainTests { @@ -131,6 +138,20 @@ class RequestResponseBodyAdviceChainTests {
assertThat(actual).isEqualTo(this.body);
}
@Test
void controllerAdviceWithHints() {
Object fooAdviceBean = createControllerAdviceBean(FooHintControllerAdvice.class);
Object barAdviceBean = createControllerAdviceBean(BarHintControllerAdvice.class);
RequestResponseBodyAdviceChain chain = new RequestResponseBodyAdviceChain(List.of(fooAdviceBean, barAdviceBean));
Map<String, Object> readHints = chain.determineReadHints(this.paramType, this.paramType.getGenericParameterType(),
JacksonJsonHttpMessageConverter.class);
assertThat(readHints).containsExactlyInAnyOrderEntriesOf(Map.of("foo", "String", "bar", "String"));
Map<String, Object> writeHints = chain.determineWriteHints(this.body, this.returnType, this.contentType, this.converterType);
assertThat(writeHints).containsExactlyInAnyOrderEntriesOf(Map.of("foo", "body", "bar", "body"));
}
private ControllerAdviceBean createControllerAdviceBean(Class<?> beanType) {
StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton(beanType.getSimpleName(), beanType);
@ -174,6 +195,64 @@ class RequestResponseBodyAdviceChainTests { @@ -174,6 +195,64 @@ class RequestResponseBodyAdviceChainTests {
}
}
@ControllerAdvice
private static class FooHintControllerAdvice extends RequestBodyAdviceAdapter implements ResponseBodyAdvice<String> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public @Nullable String beforeBodyWrite(@Nullable String body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
return body;
}
@Override
public @Nullable Map<String, Object> determineWriteHints(@Nullable String body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType) {
return Collections.singletonMap("foo", Objects.requireNonNull(body));
}
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public @Nullable Map<String, Object> determineReadHints(MethodParameter parameter, Type targetType, Class<? extends SmartHttpMessageConverter<?>> converterType) {
return Collections.singletonMap("foo", parameter.getParameterType().getSimpleName());
}
}
@ControllerAdvice
private static class BarHintControllerAdvice extends RequestBodyAdviceAdapter implements ResponseBodyAdvice<String> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public @Nullable String beforeBodyWrite(@Nullable String body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
return body;
}
@Override
public @Nullable Map<String, Object> determineWriteHints(@Nullable String body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType) {
return Collections.singletonMap("bar", Objects.requireNonNull(body));
}
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public @Nullable Map<String, Object> determineReadHints(MethodParameter parameter, Type targetType, Class<? extends SmartHttpMessageConverter<?>> converterType) {
return Collections.singletonMap("bar", parameter.getParameterType().getSimpleName());
}
}
@SuppressWarnings("unused")
@ResponseBody

Loading…
Cancel
Save