mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-05-04 05:17:15 +01:00
Add Jackson @JsonView support
Issue: SPR-14693
This commit is contained in:
+5
-3
@@ -60,6 +60,8 @@ import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuild
|
|||||||
import org.springframework.web.reactive.handler.AbstractHandlerMapping;
|
import org.springframework.web.reactive.handler.AbstractHandlerMapping;
|
||||||
import org.springframework.web.reactive.result.SimpleHandlerAdapter;
|
import org.springframework.web.reactive.result.SimpleHandlerAdapter;
|
||||||
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
|
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
|
||||||
|
import org.springframework.http.codec.Jackson2ServerHttpMessageReader;
|
||||||
|
import org.springframework.http.codec.Jackson2ServerHttpMessageWriter;
|
||||||
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
|
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
|
||||||
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
|
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
|
||||||
import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler;
|
import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler;
|
||||||
@@ -286,7 +288,7 @@ public class WebReactiveConfiguration implements ApplicationContextAware {
|
|||||||
readers.add(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder()));
|
readers.add(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder()));
|
||||||
}
|
}
|
||||||
if (jackson2Present) {
|
if (jackson2Present) {
|
||||||
readers.add(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder()));
|
readers.add(new Jackson2ServerHttpMessageReader(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,10 +406,10 @@ public class WebReactiveConfiguration implements ApplicationContextAware {
|
|||||||
}
|
}
|
||||||
if (jackson2Present) {
|
if (jackson2Present) {
|
||||||
Jackson2JsonEncoder jacksonEncoder = new Jackson2JsonEncoder();
|
Jackson2JsonEncoder jacksonEncoder = new Jackson2JsonEncoder();
|
||||||
writers.add(new EncoderHttpMessageWriter<>(jacksonEncoder));
|
writers.add(new Jackson2ServerHttpMessageWriter(new EncoderHttpMessageWriter<>(jacksonEncoder)));
|
||||||
sseDataEncoders.add(jacksonEncoder);
|
sseDataEncoders.add(jacksonEncoder);
|
||||||
}
|
}
|
||||||
writers.add(new ServerSentEventHttpMessageWriter(sseDataEncoders));
|
writers.add(new Jackson2ServerHttpMessageWriter(new ServerSentEventHttpMessageWriter(sseDataEncoders)));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Override this to modify the list of message writers after it has been
|
* Override this to modify the list of message writers after it has been
|
||||||
|
|||||||
+190
@@ -0,0 +1,190 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2016 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.web.reactive.result.method.annotation;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonView;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import org.junit.Test;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.reactive.config.WebReactiveConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Sebastien Deleuze
|
||||||
|
*/
|
||||||
|
public class JacksonHintsIntegrationTests extends AbstractRequestMappingIntegrationTests {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ApplicationContext initApplicationContext() {
|
||||||
|
AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext();
|
||||||
|
wac.register(WebConfig.class);
|
||||||
|
wac.refresh();
|
||||||
|
return wac;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void jsonViewResponse() throws Exception {
|
||||||
|
String expected = "{\"withView1\":\"with\"}";
|
||||||
|
assertEquals(expected, performGet("/response/raw", MediaType.APPLICATION_JSON_UTF8, String.class).getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void jsonViewWithMonoResponse() throws Exception {
|
||||||
|
String expected = "{\"withView1\":\"with\"}";
|
||||||
|
assertEquals(expected, performGet("/response/mono", MediaType.APPLICATION_JSON_UTF8, String.class).getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void jsonViewWithFluxResponse() throws Exception {
|
||||||
|
String expected = "[{\"withView1\":\"with\"},{\"withView1\":\"with\"}]";
|
||||||
|
assertEquals(expected, performGet("/response/flux", MediaType.APPLICATION_JSON_UTF8, String.class).getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void jsonViewWithRequest() throws Exception {
|
||||||
|
String expected = "{\"withView1\":\"with\",\"withView2\":null,\"withoutView\":null}";
|
||||||
|
assertEquals(expected, performPost("/request/raw", MediaType.APPLICATION_JSON,
|
||||||
|
new JacksonViewBean("with", "with", "without"), MediaType.APPLICATION_JSON_UTF8, String.class).getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void jsonViewWithMonoRequest() throws Exception {
|
||||||
|
String expected = "{\"withView1\":\"with\",\"withView2\":null,\"withoutView\":null}";
|
||||||
|
assertEquals(expected, performPost("/request/mono", MediaType.APPLICATION_JSON,
|
||||||
|
new JacksonViewBean("with", "with", "without"), MediaType.APPLICATION_JSON_UTF8, String.class).getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void jsonViewWithFluxRequest() throws Exception {
|
||||||
|
String expected = "[{\"withView1\":\"with\",\"withView2\":null,\"withoutView\":null}," +
|
||||||
|
"{\"withView1\":\"with\",\"withView2\":null,\"withoutView\":null}]";
|
||||||
|
List<JacksonViewBean> beans = Arrays.asList(new JacksonViewBean("with", "with", "without"), new JacksonViewBean("with", "with", "without"));
|
||||||
|
assertEquals(expected, performPost("/request/flux", MediaType.APPLICATION_JSON, beans,
|
||||||
|
MediaType.APPLICATION_JSON_UTF8, String.class).getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ComponentScan(resourcePattern = "**/JacksonHintsIntegrationTests*.class")
|
||||||
|
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||||
|
static class WebConfig extends WebReactiveConfiguration {
|
||||||
|
}
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static class JsonViewRestController {
|
||||||
|
|
||||||
|
@GetMapping("/response/raw")
|
||||||
|
@JsonView(MyJacksonView1.class)
|
||||||
|
public JacksonViewBean rawResponse() {
|
||||||
|
return new JacksonViewBean("with", "with", "without");
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/response/mono")
|
||||||
|
@JsonView(MyJacksonView1.class)
|
||||||
|
public Mono<JacksonViewBean> monoResponse() {
|
||||||
|
return Mono.just(new JacksonViewBean("with", "with", "without"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/response/flux")
|
||||||
|
@JsonView(MyJacksonView1.class)
|
||||||
|
public Flux<JacksonViewBean> fluxResponse() {
|
||||||
|
return Flux.just(new JacksonViewBean("with", "with", "without"), new JacksonViewBean("with", "with", "without"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/request/raw")
|
||||||
|
public JacksonViewBean rawRequest(@JsonView(MyJacksonView1.class) @RequestBody JacksonViewBean bean) {
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/request/mono")
|
||||||
|
public Mono<JacksonViewBean> monoRequest(@JsonView(MyJacksonView1.class) @RequestBody Mono<JacksonViewBean> mono) {
|
||||||
|
return mono;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/request/flux")
|
||||||
|
public Flux<JacksonViewBean> fluxRequest(@JsonView(MyJacksonView1.class) @RequestBody Flux<JacksonViewBean> flux) {
|
||||||
|
return flux;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface MyJacksonView1 {}
|
||||||
|
|
||||||
|
private interface MyJacksonView2 {}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static class JacksonViewBean {
|
||||||
|
|
||||||
|
@JsonView(MyJacksonView1.class)
|
||||||
|
private String withView1;
|
||||||
|
|
||||||
|
@JsonView(MyJacksonView2.class)
|
||||||
|
private String withView2;
|
||||||
|
|
||||||
|
private String withoutView;
|
||||||
|
|
||||||
|
|
||||||
|
public JacksonViewBean() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public JacksonViewBean(String withView1, String withView2, String withoutView) {
|
||||||
|
this.withView1 = withView1;
|
||||||
|
this.withView2 = withView2;
|
||||||
|
this.withoutView = withoutView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWithView1() {
|
||||||
|
return withView1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWithView1(String withView1) {
|
||||||
|
this.withView1 = withView1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWithView2() {
|
||||||
|
return withView2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWithView2(String withView2) {
|
||||||
|
this.withView2 = withView2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWithoutView() {
|
||||||
|
return withoutView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWithoutView(String withoutView) {
|
||||||
|
this.withoutView = withoutView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2016 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.http.codec;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonView;
|
||||||
|
|
||||||
|
import org.springframework.core.MethodParameter;
|
||||||
|
import org.springframework.core.ResolvableType;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.codec.json.AbstractJackson2Codec;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ServerHttpMessageReader} that resolves those annotation or request based Jackson 2 hints:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code @JsonView} + {@code @RequestBody} annotated handler method parameter</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author Sebastien Deleuze
|
||||||
|
* @since 5.0
|
||||||
|
* @see com.fasterxml.jackson.annotation.JsonView
|
||||||
|
*/
|
||||||
|
public class Jackson2ServerHttpMessageReader extends AbstractServerHttpMessageReader<Object> {
|
||||||
|
|
||||||
|
public Jackson2ServerHttpMessageReader(HttpMessageReader<Object> reader) {
|
||||||
|
super(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Map<String, Object> resolveReadHintsInternal(ResolvableType streamType,
|
||||||
|
ResolvableType elementType, MediaType mediaType, ServerHttpRequest request) {
|
||||||
|
|
||||||
|
Object source = streamType.getSource();
|
||||||
|
MethodParameter parameter = (source instanceof MethodParameter ? (MethodParameter)source : null);
|
||||||
|
if (parameter != null) {
|
||||||
|
JsonView annotation = parameter.getParameterAnnotation(JsonView.class);
|
||||||
|
if (annotation != null) {
|
||||||
|
Class<?>[] classes = annotation.value();
|
||||||
|
if (classes.length != 1) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"@JsonView only supported for read hints with exactly 1 class argument: " + parameter);
|
||||||
|
}
|
||||||
|
return Collections.singletonMap(AbstractJackson2Codec.JSON_VIEW_HINT, classes[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2016 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.http.codec;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonView;
|
||||||
|
|
||||||
|
import org.springframework.core.MethodParameter;
|
||||||
|
import org.springframework.core.ResolvableType;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.codec.json.AbstractJackson2Codec;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ServerHttpMessageWriter} that resolves those annotation or request based Jackson 2 hints:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code @JsonView} annotated handler method</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author Sebastien Deleuze
|
||||||
|
* @since 5.0
|
||||||
|
* @see com.fasterxml.jackson.annotation.JsonView
|
||||||
|
*/
|
||||||
|
public class Jackson2ServerHttpMessageWriter extends AbstractServerHttpMessageWriter<Object> {
|
||||||
|
|
||||||
|
public Jackson2ServerHttpMessageWriter(HttpMessageWriter<Object> writer) {
|
||||||
|
super(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Map<String, Object> resolveWriteHintsInternal(ResolvableType streamType,
|
||||||
|
ResolvableType elementType, MediaType mediaType, ServerHttpRequest request) {
|
||||||
|
|
||||||
|
Object source = streamType.getSource();
|
||||||
|
MethodParameter returnValue = (source instanceof MethodParameter ? (MethodParameter)source : null);
|
||||||
|
if (returnValue != null) {
|
||||||
|
JsonView annotation = returnValue.getMethodAnnotation(JsonView.class);
|
||||||
|
if (annotation != null) {
|
||||||
|
Class<?>[] classes = annotation.value();
|
||||||
|
if (classes.length != 1) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"@JsonView only supported for write hints with exactly 1 class argument: " + returnValue);
|
||||||
|
}
|
||||||
|
return Collections.singletonMap(AbstractJackson2Codec.JSON_VIEW_HINT, classes[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user