[SPRING] Jackson의 @JsonView, @JsonFilter 및 Spring
SPRINGJackson의 @JsonView, @JsonFilter 및 Spring
Spring의 @ResponseBody와 @RequestBody annotation을 사용하면서 Spring MVC 컨트롤러에 의해 반환 된 JSON을 수정하기 위해 Jackson @JsonView와 @JsonFilter 주석을 사용할 수 있습니까?
public class Product
{
private Integer id;
private Set<ProductDescription> descriptions;
private BigDecimal price;
...
}
public class ProductDescription
{
private Integer id;
private Language language;
private String name;
private String summary;
private String lifeStory;
...
}
클라이언트가 제품 모음을 요청하면 각 ProductDescription의 최소 버전 (아마도 ID 만)을 반환하고 싶습니다. 이후에 클라이언트는이 ID를 사용하여 모든 속성이있는 ProductDescription의 전체 인스턴스를 요청할 수 있습니다.
호출 된 메소드가 클라이언트가 데이터를 요청한 컨텍스트를 정의하기 때문에 Spring MVC 컨트롤러 메소드에서 이것을 지정할 수있는 것이 이상적이다.
해결법
-
==============================
1.이 문제는 해결되었습니다! 팔로우
이 문제는 해결되었습니다! 팔로우
이것은 SPR-7156입니다.
Spring 버전 4.1 이상에서 사용 가능
이 링크를 따라 가십시오. @JsonView 주석의 예를 설명합니다.
-
==============================
2.궁극적으로 우리는 StaxMan이 JAX-RS에서 보여준 것과 유사한 표기법을 사용하고자합니다. 불행히도, Spring은 이것을 기본적으로 지원하지 않으므로 직접 처리해야합니다.
궁극적으로 우리는 StaxMan이 JAX-RS에서 보여준 것과 유사한 표기법을 사용하고자합니다. 불행히도, Spring은 이것을 기본적으로 지원하지 않으므로 직접 처리해야합니다.
이것은 내 해결책이며,별로 예쁘지 않지만 일을합니다.
@JsonView(ViewId.class) @RequestMapping(value="get", method=RequestMethod.GET) // Spring controller annotation public Pojo getPojo(@RequestValue Long id) { return new Pojo(id); }
public class JsonViewAwareJsonView extends MappingJacksonJsonView { private ObjectMapper objectMapper = new ObjectMapper(); private boolean prefixJson = false; private JsonEncoding encoding = JsonEncoding.UTF8; @Override public void setPrefixJson(boolean prefixJson) { super.setPrefixJson(prefixJson); this.prefixJson = prefixJson; } @Override public void setEncoding(JsonEncoding encoding) { super.setEncoding(encoding); this.encoding = encoding; } @Override public void setObjectMapper(ObjectMapper objectMapper) { super.setObjectMapper(objectMapper); this.objectMapper = objectMapper; } @Override protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { Class<?> jsonView = null; if(model.containsKey("json.JsonView")){ Class<?>[] allJsonViews = (Class<?>[]) model.remove("json.JsonView"); if(allJsonViews.length == 1) jsonView = allJsonViews[0]; } Object value = filterModel(model); JsonGenerator generator = this.objectMapper.getJsonFactory().createJsonGenerator(response.getOutputStream(), this.encoding); if (this.prefixJson) { generator.writeRaw("{} && "); } if(jsonView != null){ SerializationConfig config = this.objectMapper.getSerializationConfig(); config = config.withView(jsonView); this.objectMapper.writeValue(generator, value, config); } else this.objectMapper.writeValue(generator, value); } }
public class JsonViewInterceptor extends HandlerInterceptorAdapter { @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { HandlerMethod handlerMethod = (HandlerMethod) handler; JsonView jsonViewAnnotation = handlerMethod.getMethodAnnotation(JsonView.class); if(jsonViewAnnotation != null) modelAndView.addObject("json.JsonView", jsonViewAnnotation.value()); } }
spring-servlet.xml에서
<bean name="ViewResolver" class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> <property name="mediaTypes"> <map> <entry key="json" value="application/json" /> </map> </property> <property name="defaultContentType" value="application/json" /> <property name="defaultViews"> <list> <bean class="com.mycompany.myproject.JsonViewAwareJsonView"> </bean> </list> </property> </bean>
과
<mvc:interceptors> <bean class="com.mycompany.myproject.JsonViewInterceptor" /> </mvc:interceptors>
-
==============================
3.JAX-RS 메소드의 @JsonView 주석을 사용할 수있는 Jackson 1.9는 Spring (일종의)이 어떻게 작동하는지 알지 못합니다.
JAX-RS 메소드의 @JsonView 주석을 사용할 수있는 Jackson 1.9는 Spring (일종의)이 어떻게 작동하는지 알지 못합니다.
@JsonView(ViewId.class) @GET // and other JAX-RS annotations public Pojo resourceMethod() { return new Pojo(); }
Jackson은 ViewId.class로 식별 된 View를 활성보기로 사용합니다. 아마도 스프링은 비슷한 기능을 가지고있을 것인가? JAX-RS에서는 표준 JacksonJaxrsProvider가이를 처리합니다.
-
==============================
4.같은 대답을 찾고 나는 ResponseBody 객체를보기로 감싸는 아이디어를 생각해 냈습니다.
같은 대답을 찾고 나는 ResponseBody 객체를보기로 감싸는 아이디어를 생각해 냈습니다.
컨트롤러 클래스 조각 :
@RequestMapping(value="/{id}", headers="Accept=application/json", method= RequestMethod.GET) public @ResponseBody ResponseBodyWrapper getCompany(HttpServletResponse response, @PathVariable Long id){ ResponseBodyWrapper responseBody = new ResponseBodyWrapper(companyService.get(id),Views.Owner.class); return responseBody; }
public class ResponseBodyWrapper { private Object object; private Class<?> view; public ResponseBodyWrapper(Object object, Class<?> view) { this.object = object; this.view = view; } public Object getObject() { return object; } public void setObject(Object object) { this.object = object; } @JsonIgnore public Class<?> getView() { return view; } @JsonIgnore public void setView(Class<?> view) { this.view = view; }
}
그런 다음 writeInternal 메소드 form MappingJackson2HttpMessageConverter를 재정 의하여 직렬화 할 객체가 instanceof wrapper인지 확인합니다. 필요한 경우 객체를 직렬화합니다.
public class CustomMappingJackson2 extends MappingJackson2HttpMessageConverter { private ObjectMapper objectMapper = new ObjectMapper(); private boolean prefixJson; @Override protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType()); JsonGenerator jsonGenerator = this.objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding); try { if (this.prefixJson) { jsonGenerator.writeRaw("{} && "); } if(object instanceof ResponseBodyWrapper){ ResponseBodyWrapper responseBody = (ResponseBodyWrapper) object; this.objectMapper.writerWithView(responseBody.getView()).writeValue(jsonGenerator, responseBody.getObject()); }else{ this.objectMapper.writeValue(jsonGenerator, object); } } catch (IOException ex) { throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex); } } public void setObjectMapper(ObjectMapper objectMapper) { Assert.notNull(objectMapper, "ObjectMapper must not be null"); this.objectMapper = objectMapper; super.setObjectMapper(objectMapper); } public ObjectMapper getObjectMapper() { return this.objectMapper; } public void setPrefixJson(boolean prefixJson) { this.prefixJson = prefixJson; super.setPrefixJson(prefixJson); }
}
-
==============================
5.많은 사람들이 죽은 목숨을 위협하고 괴상한 분노를 겪은 후 이에 대한 답은 .... 너무 간단합니다. 이 유즈 케이스에서는 복잡한 객체 주소가 포함 된 Customer Bean이 포함되어 있으며 json 직렬화가 발생할 때 주소의 주소 surburb 및 street의 직렬화를 방지하려고합니다.
많은 사람들이 죽은 목숨을 위협하고 괴상한 분노를 겪은 후 이에 대한 답은 .... 너무 간단합니다. 이 유즈 케이스에서는 복잡한 객체 주소가 포함 된 Customer Bean이 포함되어 있으며 json 직렬화가 발생할 때 주소의 주소 surburb 및 street의 직렬화를 방지하려고합니다.
우리는 고객 클래스의 필드 주소에 @JsonIgnoreProperties ({ "suburb"}) 주석을 적용하여이 작업을 수행합니다. 무시할 필드의 수는 무한합니다. 예를 들어 나는 교외와 거리 모두를 잉태하고 싶습니다. @JsonIgnoreProperties ({ "suburb", "street"})로 주소 필드에 주석을 달았습니다.
이 모든 것을함으로써 우리는 HATEOAS 타입 아키텍처를 만들 수 있습니다.
전체 코드는 다음과 같습니다.
Customer.java
public class Customer { private int id; private String email; private String name; @JsonIgnoreProperties({"suburb", "street"}) private Address address; public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getName() { return name; } public void setName(String name) { this.name = name; }
}
Address.java public class Address {
private String street; private String suburb; private String Link link; public Link getLink() { return link; } public void setLink(Link link) { this.link = link; } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public String getSuburb() { return suburb; } public void setSuburb(String suburb) { this.suburb = suburb; }
}
-
==============================
6.@ user356083 외에도 @ResponseBody가 반환 될 때이 예제가 작동하도록 일부 수정했습니다. ThreadLocal을 사용하는 약간의 해킹이지만 Spring은 좋은 방법을 수행하는 데 필요한 컨텍스트를 제공하지 않는 것 같습니다.
@ user356083 외에도 @ResponseBody가 반환 될 때이 예제가 작동하도록 일부 수정했습니다. ThreadLocal을 사용하는 약간의 해킹이지만 Spring은 좋은 방법을 수행하는 데 필요한 컨텍스트를 제공하지 않는 것 같습니다.
public class ViewThread { private static final ThreadLocal<Class<?>[]> viewThread = new ThreadLocal<Class<?>[]>(); private static final Log log = LogFactory.getLog(SocialRequestUtils.class); public static void setKey(Class<?>[] key){ viewThread.set(key); } public static Class<?>[] getKey(){ if(viewThread.get() == null) log.error("Missing threadLocale variable"); return viewThread.get(); } } public class JsonViewInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle( HttpServletRequest request, HttpServletResponse response, Object handler) { HandlerMethod handlerMethod = (HandlerMethod) handler; JsonView jsonViewAnnotation = handlerMethod .getMethodAnnotation(JsonView.class); if (jsonViewAnnotation != null) ViewThread.setKey(jsonViewAnnotation.value()); return true; } } public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConverter<Object> { @Override protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType()); JsonGenerator jsonGenerator = this.objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding); // This is a workaround for the fact JsonGenerators created by ObjectMapper#getJsonFactory // do not have ObjectMapper serialization features applied. // See https://github.com/FasterXML/jackson-databind/issues/12 if (objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)) { jsonGenerator.useDefaultPrettyPrinter(); } //A bit of a hack. Class<?>[] jsonViews = ViewThread.getKey(); ObjectWriter writer = null; if(jsonViews != null){ writer = this.objectMapper.writerWithView(jsonViews[0]); }else{ writer = this.objectMapper.writer(); } try { if (this.prefixJson) { jsonGenerator.writeRaw("{} && "); } writer.writeValue(jsonGenerator, object); } catch (JsonProcessingException ex) { throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex); } }
-
==============================
7.4.2.0.Release 이후부터는 다음과 같이 간단하게 할 수 있습니다.
4.2.0.Release 이후부터는 다음과 같이 간단하게 할 수 있습니다.
@RequestMapping(method = RequestMethod.POST, value = "/springjsonfilter") public @ResponseBody MappingJacksonValue byJsonFilter(...) { MappingJacksonValue jacksonValue = new MappingJacksonValue(responseObj); jacksonValue.setFilters(customFilterObj); return jacksonValue; }
참고 문헌 : 1. https://jira.spring.io/browse/SPR-12586 2. http://wiki.fasterxml.com/JacksonFeatureJsonFilter
from https://stackoverflow.com/questions/7637467/jacksons-jsonview-jsonfilter-and-spring by cc-by-sa and MIT license
'SPRING' 카테고리의 다른 글
[SPRING] 지역 및 제품 환경 (Spring)에 대한 다른 속성 변수 (0) | 2019.01.28 |
---|---|
[SPRING] 주석을 사용하여 Spring Lookup Method Injection하는 방법? (0) | 2019.01.28 |
[SPRING] Spring AOP - 왜 aspectjweaver가 필요한가요? (0) | 2019.01.28 |
[SPRING] 봄 스케줄러가 예기치 않게 멈 춥니 다. (0) | 2019.01.28 |
[SPRING] 기존 스프링 애플리케이션을 스프링 부트로 마이그레이션하고, 스프링 부트를 수동으로 구성 하시겠습니까? (0) | 2019.01.28 |