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
This commit is contained in:
Sébastien Deleuze
2025-05-13 12:47:20 +02:00
parent 71987a8713
commit d0cd7af7e6
9 changed files with 213 additions and 28 deletions

View File

@@ -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;
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;
* Tests for {@link RequestResponseBodyAdviceChain}.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
* @since 4.2
*/
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 {
}
}
@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