在Spring MVC Controller中选择JsonView

我目前正在使用Jackson(2.4.0-rc3)和spring mvc(4.0.3)编写REST api,我正在努力使其安全。

通过这种方式,我尝试使用JsonView来选择可以序列化的对象部分。

我找到了解决方案(不适合我)用我想要的视图注释我的Controller方法。 但我想动态选择控制器内部的视图。

是否可以扩展ResponseEntity类以指定我想要的JsonView?

一小段代码:

这是帐户类

public class Account { @JsonProperty(value = "account_id") private Long accountId; @JsonProperty(value = "mail_address") private String mailAddress; @JsonProperty(value = "password") private String password; @JsonProperty(value = "insert_event") private Date insertEvent; @JsonProperty(value = "update_event") private Date updateEvent; @JsonProperty(value = "delete_event") private Date deleteEvent; @JsonView(value = PublicView.class) public Long getAccountId() { return accountId; } @JsonView(value = PublicView.class) public void setAccountId(Long accountId) { this.accountId = accountId; } @JsonView(value = OwnerView.class) public String getMailAddress() { return mailAddress; } @JsonView(value = OwnerView.class) public void setMailAddress(String mailAddress) { this.mailAddress = mailAddress; } @JsonIgnore public String getPassword() { return password; } @JsonView(value = OwnerView.class) public void setPassword(String password) { this.password = password; } @JsonView(value = AdminView.class) public Date getInsertEvent() { return insertEvent; } @JsonView(value = AdminView.class) public void setInsertEvent(Date insertEvent) { this.insertEvent = insertEvent; } @JsonView(value = AdminView.class) public Date getUpdateEvent() { return updateEvent; } @JsonView(value = AdminView.class) public void setUpdateEvent(Date updateEvent) { this.updateEvent = updateEvent; } @JsonView(value = AdminView.class) public Date getDeleteEvent() { return deleteEvent; } @JsonView(value = OwnerView.class) public void setDeleteEvent(Date deleteEvent) { this.deleteEvent = deleteEvent; } @JsonProperty(value = "name") public abstract String getName(); } 

这是帐户控制器

 @RestController @RequestMapping("/account") public class AccountCtrlImpl implements AccountCtrl { @Autowired private AccountSrv accountSrv; public AccountSrv getAccountSrv() { return accountSrv; } public void setAccountSrv(AccountSrv accountSrv) { this.accountSrv = accountSrv; } @Override @RequestMapping(value = "/get_by_id/{accountId}", method = RequestMethod.GET, headers = "Accept=application/json") public ResponseEntity getById(@PathVariable(value = "accountId") Long accountId) { try { return new ResponseEntity(this.getAccountSrv().getById(accountId), HttpStatus.OK); } catch (ServiceException e) { return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); } } @Override @RequestMapping(value = "/get_by_mail_address/{mail_address}", method = RequestMethod.GET, headers = "Accept=application/json") public ResponseEntity getByMailAddress(@PathVariable(value = "mail_address") String mailAddress) { try { return new ResponseEntity(this.getAccountSrv().getByMailAddress(mailAddress), HttpStatus.OK); } catch (ServiceException e) { return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); } } @Override @RequestMapping(value = "/authenticate/{mail_address}/{password}", method = RequestMethod.GET, headers = "Accept=application/json") public ResponseEntity authenticate(@PathVariable(value = "mail_address") String mailAddress, @PathVariable(value = "password") String password) { return new ResponseEntity(HttpStatus.NOT_IMPLEMENTED); } } 

我已经解决了扩展ResponseEntity的问题,如下所示:

 public class ResponseViewEntity extends ResponseEntity> { private Class view; public ResponseViewEntity(HttpStatus statusCode) { super(statusCode); } public ResponseViewEntity(T body, HttpStatus statusCode) { super(new ContainerViewEntity(body, BaseView.class), statusCode); } public ResponseViewEntity(T body, Class view, HttpStatus statusCode) { super(new ContainerViewEntity(body, view), statusCode); } } 

和ContainerViewEntity封装对象和选定的视图

 public class ContainerViewEntity { private final T object; private final Class view; public ContainerViewEntity(T object, Class view) { this.object = object; this.view = view; } public T getObject() { return object; } public Class getView() { return view; } public boolean hasView() { return this.getView() != null; } } 

之后,我们只转换了具有良好视图的对象。

 public class JsonViewMessageConverter extends MappingJackson2HttpMessageConverter { @Override protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { if (object instanceof ContainerViewEntity && ((ContainerViewEntity) object).hasView()) { writeView((ContainerViewEntity) object, outputMessage); } else { super.writeInternal(object, outputMessage); } } protected void writeView(ContainerViewEntity view, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { JsonEncoding encoding = this.getJsonEncoding(outputMessage.getHeaders().getContentType()); ObjectWriter writer = this.getWriterForView(view.getView()); JsonGenerator jsonGenerator = writer.getFactory().createGenerator(outputMessage.getBody(), encoding); try { writer.writeValue(jsonGenerator, view.getObject()); } catch (IOException ex) { throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex); } } private ObjectWriter getWriterForView(Class view) { ObjectMapper mapper = new ObjectMapper(); mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false); return mapper.writer().withView(view); } } 

为了完成,我启用了转换器

      

就是这样,我可以在控制器中选择View

 @Override @RequestMapping(value = "/get_by_id/{accountId}", method = RequestMethod.GET, headers = "Accept=application/json") public ResponseViewEntity getById(@PathVariable(value = "accountId") Long accountId) throws ServiceException { return new ResponseViewEntity(this.getAccountSrv().getById(accountId), PublicView.class, HttpStatus.OK); } 

我非常喜欢这里提供的解决方案,以动态选择控制器方法中的json视图。

基本上,您返回一个MappingJacksonValue ,它使用您想要返回的值构造。 之后,使用正确的视图类调用setSerializationView(viewClass) 。 在我的用例中,我根据当前用户返回了不同的视图,如下所示:

 @RequestMapping("/foos") public MappingJacksonValue getFoo(@AuthenticationPrincipal UserDetails userDetails ) { MappingJacksonValue value = new MappingJacksonValue( fooService.getAll() ); if( userDetails.isAdminUser() ) { value.setSerializationView( Views.AdminView.class ); } else { value.setSerializationView( Views.UserView.class ); } return value; } 

顺便说一句:如果您使用的是Spring Boot,则可以通过在application.properties设置它来控制是否序列化了没有关联视图的属性:

 spring.jackson.mapper.default_view_inclusion=true 

仅使用@JsonView直接在@ResponseBody和ResponseEntity上支持FYI,Spring 4.1:

Jackson的@JsonView直接支持@ResponseBody和ResponseEntity控制器方法,用于序列化相同POJO的不同数量的详细信息(例如摘要与详细信息页面)。 通过在特殊键下添加序列化视图类型作为模型属性,基于视图的渲染也支持此function。

在http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-jsonview中,您可以找到更简单的解决方案:

 @RestController public class UserController { @RequestMapping(value = "/user", method = RequestMethod.GET) @JsonView(User.WithoutPasswordView.class) public User getUser() { return new User("eric", "7!jd#h23"); } } public class User { public interface WithoutPasswordView {}; public interface WithPasswordView extends WithoutPasswordView {}; private String username; private String password; public User() { } public User(String username, String password) { this.username = username; this.password = password; } @JsonView(WithoutPasswordView.class) public String getUsername() { return this.username; } @JsonView(WithPasswordView.class) public String getPassword() { return this.password; } } 

这非常有效:

 @RequestMapping(value = "/{id}", method = RequestMethod.GET) public void getZone(@PathVariable long id, @RequestParam(name = "tree", required = false) boolean withChildren, HttpServletResponse response) throws IOException { LOGGER.debug("Get a specific zone with id {}", id); Zone zone = zoneService.findById(id); response.setContentType(MediaType.APPLICATION_JSON_VALUE); if (withChildren) { response.getWriter().append(mapper.writeValueAsString(zone)); } else { response.getWriter().append(mapper.writerWithView(View.ZoneWithoutChildren.class).writeValueAsString(zone)); } }