在Spring MVC Controller中选择JsonView

时间:2014-05-14 21:04:51

标签: java spring spring-mvc jackson

我目前正在使用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<Account> getById(@PathVariable(value = "accountId") Long accountId) {
        try {
            return new ResponseEntity<Account>(this.getAccountSrv().getById(accountId), HttpStatus.OK);
        } catch (ServiceException e) {
            return new ResponseEntity<Account>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @Override
    @RequestMapping(value = "/get_by_mail_address/{mail_address}", method = RequestMethod.GET, headers = "Accept=application/json")
    public ResponseEntity<Account> getByMailAddress(@PathVariable(value = "mail_address") String mailAddress) {
        try {
            return new ResponseEntity<Account>(this.getAccountSrv().getByMailAddress(mailAddress), HttpStatus.OK);
        } catch (ServiceException e) {
            return new ResponseEntity<Account>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @Override
    @RequestMapping(value = "/authenticate/{mail_address}/{password}", method = RequestMethod.GET, headers = "Accept=application/json")
    public ResponseEntity<Account> authenticate(@PathVariable(value = "mail_address") String mailAddress, @PathVariable(value = "password") String password) {
        return new ResponseEntity<Account>(HttpStatus.NOT_IMPLEMENTED);
    }
}

4 个答案:

答案 0 :(得分:6)

我已经解决了像这样扩展ResponseEntity的问题:

public class ResponseViewEntity<T> extends ResponseEntity<ContainerViewEntity<T>> {

    private Class<? extends BaseView> view;

    public ResponseViewEntity(HttpStatus statusCode) {
        super(statusCode);
    }

    public ResponseViewEntity(T body, HttpStatus statusCode) {
        super(new ContainerViewEntity<T>(body, BaseView.class), statusCode);
    }

    public ResponseViewEntity(T body, Class<? extends BaseView> view, HttpStatus statusCode) {
        super(new ContainerViewEntity<T>(body, view), statusCode);
    }

}

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

public class ContainerViewEntity<T> {

    private final T object;
    private final Class<? extends BaseView> view;

    public ContainerViewEntity(T object, Class<? extends BaseView> view) {
        this.object = object;
        this.view = view;
    }

    public T getObject() {
        return object;
    }

    public Class<? extends BaseView> 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);
    }

}

要完成,我启用转换器

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="wc.handler.view.JsonViewMessageConverter"/>
    </mvc:message-converters>
</mvc:annotation-driven>

就是这样,我可以选择控制器中的视图

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

答案 1 :(得分:6)

我非常喜欢提供here的解决方案来动态选择控制器方法中的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

答案 2 :(得分:3)

仅供参考,Spring 4.1已经支持在@ResponseBody和ResponseEntity上直接使用@JsonView:

  

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

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;
    }
}

答案 3 :(得分:0)

这很有效:

@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));
    }

}