【问题标题】:Select JsonView in the Spring MVC Controller在 Spring MVC Controller 中选择 JsonView
【发布时间】:2014-07-03 03:00:54
【问题描述】:

我目前正在使用 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);
    }
}

【问题讨论】:

标签: java spring spring-mvc jackson


【解决方案1】:

这很好用:

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

}

【讨论】:

    【解决方案2】:

    我真的很喜欢 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
    

    【讨论】:

    • 哇(欧文·威尔逊的耳语),这应该是公认的答案
    【解决方案3】:

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

    Jackson 的 @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;
        }
    }
    

    【讨论】:

      【解决方案4】:

      我已经解决了像这样扩展 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);
      }
      

      【讨论】:

      • 你的 BaseView 是什么?
      • 除了我收到错误“UnsupportedOperationException”,上面写着“忘记注册类型适配器?”。你能帮我解决这些问题吗?
      猜你喜欢
      • 2015-05-06
      • 2011-08-11
      • 1970-01-01
      • 1970-01-01
      • 2015-10-30
      • 2015-07-25
      • 2018-04-18
      • 2020-10-29
      • 2014-06-09
      相关资源
      最近更新 更多