在Spring MVC Controller中选择JsonView

Jer*_*mie 3 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();

}
Run Code Online (Sandbox Code Playgroud)

这是帐户控制器

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

Wim*_*uwe 7

我非常喜欢这里提供的解决方案,以动态选择控制器方法中的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;
}
Run Code Online (Sandbox Code Playgroud)

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

spring.jackson.mapper.default_view_inclusion=true
Run Code Online (Sandbox Code Playgroud)


Jer*_*mie 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);
    }

}
Run Code Online (Sandbox Code Playgroud)

和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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

}
Run Code Online (Sandbox Code Playgroud)

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

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="wc.handler.view.JsonViewMessageConverter"/>
    </mvc:message-converters>
</mvc:annotation-driven>
Run Code Online (Sandbox Code Playgroud)

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

@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);
}
Run Code Online (Sandbox Code Playgroud)


min*_*dex 5

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

@ResponseBody和ResponseEntity控制器方法直接支持Jackson的@JsonView,用于序列化同一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;
    }
}
Run Code Online (Sandbox Code Playgroud)