Spring MVC 控制器中 JsonView 的动态选择

Dynamic Selection Of JsonView in Spring MVC Controller

我知道可以使用 @JsonView(...) 注释控制器方法以在 Spring MVC 中静态定义单个视图 class。不幸的是,这意味着对于我可能拥有的每种类型的视图,我都需要一个不同的端点。

我看到其他人也问过这个 before。虽然这种方法可能有效,但 Spring 通常有很多方法可以做同样的事情。有时,如果您对某些内部结构有一些了解,解决方案可能比乍看起来简单得多。

我想要一个控制器端点,它可以动态 select 基于当前主体的适当视图。我是否可以 return 具有包含适当视图 class 或 MappingJacksonValue 实例的属性的 Model 直接?

我在 org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#writeInternal 中看到有一段代码确定要使用的视图:

if (value instanceof MappingJacksonValue) {
            MappingJacksonValue container = (MappingJacksonValue) object;
            value = container.getValue();
            serializationView = container.getSerializationView();
        }

这似乎来自 org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyAdvice#beforeBodyWriteInternal,但我无法解决是否有办法通过 return 包含必要信息的特定值来绕过它Jackson2HttpMessageConverter 选择合适的视图。

非常感谢任何帮助。

万一别人想实现同样的事情,其实很简单。

您可以直接从您的控制器中 return 一个 org.springframework.http.converter.json.MappingJacksonValue 实例,其中包含您要序列化的对象和视图 class。

这将由 org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#writeInternal 方法选取并使用适当的视图。

它的工作原理是这样的:

@RequestMapping(value = "/accounts/{id}", method = GET, produces = APPLICATION_JSON_VALUE)
public MappingJacksonValue getAccount(@PathVariable("id") String accountId, @AuthenticationPrincipal User user) {
    final Account account = accountService.get(accountId);
    final MappingJacksonValue result = new MappingJacksonValue(account);
    final Class<? extends View> view = accountPermissionsService.getViewForUser(user);
    result.setSerializationView(view);
    return result;
}

这是对我有帮助的上述答案的变体。我在使用 Spring HATEOAS 负载时直接 returning MappingJacksonValue 发现了问题。如果我 return 它直接来自控制器的处理程序,由于某种原因 ResourcesResourceSupport mixins 没有得到正确应用并且 JSON HAL _links 被呈现为链接。 Spring ResponseEntity 也没有呈现,因为它应该在有效负载中显示 bodystatus 对象。

使用 ControllerAdvice 实现同样的效果,现在我的有效负载已正确呈现,视图已根据需要应用

@ControllerAdvice(assignableTypes = MyController.class)
public class MyControllerAdvice extends AbstractMappingJacksonResponseBodyAdvice {

  @Override
  protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType, MethodParameter returnType,
                                         ServerHttpRequest req, ServerHttpResponse res) {
    ServletServerHttpRequest request = (ServletServerHttpRequest)req;
    String view = request.getServletRequest().getParameter("view");
    if ("hello".equals(view)) {
      bodyContainer.setSerializationView(HelloView.class);
    }
  }
}