简体   繁体   English

如何在Spring中包装JSON响应

[英]How can I wrap a JSON response in Spring

Suppose I have two sets of controllers in Spring: 假设我在Spring中有两组控制器:

  • /jsonapi1/*
  • /jsonapi2/*

both of which return objects that are to be interpretted as JSON text. 两者都返回要解释为JSON文本的对象。

I'd like some kind of filter to wrap the responses from one set of these controllers so that: 我想要某种过滤器来包装来自这些控制器的响应,以便:

  1. the original response is contained within another object. 原始响应包含在另一个对象中。

    For example, if /jsonapi1/count returns: 例如,如果/ jsonapi1 / count返回:

     {"num_humans":123, "num_androids":456} 

    then the response should be wrapped and returned as follows: 那么响应应该被包装并返回如下:

     { "status":0, "content":{"num_humans":123, "num_androids":456} } 
  2. if an exception happens in the controller, then filter should catch the exception and report it as follows 如果控制器中发生异常,则过滤器应捕获异常并按如下方式报告

     { "status":5, "content":"Something terrible happened" } 
  3. The responses from the other controllers are returned unchanged. 其他控制器的响应将保持不变。

We're currently customizing a MappingJackson2HttpMessageConverter passed to WebMvcConfigurerAdapter.configureMessageConverters in order to perform the above tasks. 目前,我们正在定制MappingJackson2HttpMessageConverter传递给WebMvcConfigurerAdapter.configureMessageConverters为了执行上述任务。 Works great except that it doesn't seem possible for this approach to be selective about the URLs (or controller classes) it applies to. 工作得很好,除了这种方法似乎不可能选择它适用的URL(或控制器类)。

Is it possible to apply these kinds of wrappers to individual controller classes or URLs? 是否可以将这些类型的包装器应用于单个控制器类或URL?


Update: Servlet filters look like a solution. 更新:Servlet过滤器看起来像一个解决方案。 Is it possible chose which filter gets applied to which controller methods, or which URLs? 是否可以选择将哪个过滤器应用于哪些控制器方法或哪些URL?

The way I understand your question, you have exactly three choices. 我理解你的问题的方式,你有三个选择。

Option #1 选项1

Manually wrap your objects in simple SuccessResponse , ErrorResponse , SomethingSortOfWrongResponse , etc. objects that have the fields you require. 手动将对象包装在简单的SuccessResponseErrorResponseSomethingSortOfWrongResponse等具有所需字段的对象中。 At this point, you have per-request flexibility, changing the fields on one of the response wrappers is trivial, and the only true drawback is code repetition if many of the controller's request methods can and should be grouped together. 此时,您具有每个请求的灵活性,更改其中一个响应包装器上的字段是微不足道的,唯一真正的缺点是代码重复,如果许多控制器的请求方法可以并且应该组合在一起。

Option #2 选项#2

As you mentioned, and filter could be designed to do the dirty work, but be wary that Spring filters will NOT give you access to request or response data. 正如您所提到的,过滤器可以设计用于执行脏工作,但要小心Spring过滤器不会授予您访问请求或响应数据的权限。 Here's an example of what it might look like: 以下是它的外观示例:

@Component
public class ResponseWrappingFilter extends GenericFilterBean {

    @Override
    public void doFilter(
        ServletRequest request,
        ServletResponse response,
        FilterChain chain) {

        // Perform the rest of the chain, populating the response.
        chain.doFilter(request, response);

        // No way to read the body from the response here. getBody() doesn't exist.
        response.setBody(new ResponseWrapper(response.getStatus(), response.getBody());
    }
} 

If you find a way to set the body in that filter, then yes, you could easily wrap it up. 如果你找到一种方法在该过滤器中设置主体,那么是的,你可以很容易地将它包起来。 Otherwise, this option is a dead end. 否则,这个选项是死路一条。

Option #3 选项#3

A-ha. A-HA。 So you got this far. 所以你到目前为止。 Code duplication is not an option, but you insist on wrapping responses from your controller methods. 代码重复不是一个选项,但您坚持要从控制器方法中包装响应。 I'd like to introduce the true solution - aspect-oriented programming (AOP), which Spring supports fondly. 我想介绍真正的解决方案 - 面向方面的编程(AOP),Spring非常支持。

If you're not familiar with AOP, the premise is as follows: you define an expression that matches (like a regular expression matches) points in the code. 如果您不熟悉AOP,则前提如下:您在代码中定义匹配(如正则表达式匹配)点的表达式。 These points are called join points , while the expressions that match them are called pointcuts . 这些点称为连接点 ,而与它们匹配的表达式称为切入点 You can then opt to execute additional, arbitrary code, called advice , when any pointcut or combination of pointcuts are matched. 然后,当切入任何切入点或切入点组合时,您可以选择执行其他任意代码,称为建议 An object that defines pointcuts and advice is called an aspect . 定义切入点和建议的对象称为方面

It's great for expressing yourself more fluently in Java. 它非常适合在Java中表达自己更流利。 The only drawback is weaker static type checking. 唯一的缺点是较弱的静态类型检查。 Without further ado, here's your response-wrapping in aspect-oriented programming: 不用多说,这是你在面向方面的编程中的响应包装:

@Aspect
@Component
public class ResponseWrappingAspect {

    @Pointcut("within(@org.springframework.stereotype.Controller *)")
    public void anyControllerPointcut() {}

    @Pointcut("execution(* *(..))")
    public void anyMethodPointcut() {}

    @AfterReturning(
        value = "anyControllerPointcut() && anyMethodPointcut()",
        returning = "response")
    public Object wrapResponse(Object response) {

        // Do whatever logic needs to be done to wrap it correctly.
        return new ResponseWrapper(response);
    }

    @AfterThrowing(
        value = "anyControllerPointcut() && anyMethodPointcut()",
        throwing = "cause")
    public Object wrapException(Exception cause) {

        // Do whatever logic needs to be done to wrap it correctly.
        return new ErrorResponseWrapper(cause);
    }
}

The final result will be the non-repeating response wrapping that you seek. 最终结果将是您寻求的非重复响应包装。 If you only want some or one controller receive this effect, then update the pointcut to match methods only within instances of that controller (rather than any class holding the @Controller annotation). 如果您只希望某个或一个控制器收到此效果,则更新切入点以仅匹配该控制器实例内的方法(而不是任何持有@Controller注释的类)。

You'll need to include some AOP dependencies, add the AOP-enabling annotation in a configuration class, and make sure something component-scans the package this class is in. 您需要包含一些AOP依赖项,在配置类中添加启用AOP的注释,并确保组件扫描此类所在的包。

I was struggling on this for multiple days. 我在这方面挣扎了好几天。 The solution by @Misha didn't work for me. @Misha的解决方案对我不起作用。 I was able to finally get this working using ControllerAdvice and ResponseBodyAdvice . 我终于能够使用ControllerAdviceResponseBodyAdvice来实现这一点。

ResponseBodyAdvice allows to inject custom transformation logic on the response returned by a controller but before it is converted to HttpResponse and committed. ResponseBodyAdvice允许在控制器返回的响应上注入自定义转换逻辑,但在转换为HttpResponse并提交之前。

This is how my controller method looks: 这是我的控制器方法的样子:

@RequestMapping("/global/hallOfFame")
    public List<HallOfFame> getAllHallOfFame() {
        return hallOfFameService.getAllHallOfFame();
}

Now i wanted to add some standard fields around the response like devmessage and usermessage . 现在我想在响应中添加一些标准字段,如devmessageusermessage That logic goes into the ResponseAdvice: 该逻辑进入ResponseAdvice:

@ControllerAdvice
public class TLResponseAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
            Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
        ServerHttpResponse response) {
        // TODO Auto-generated method stub
        final RestResponse<Object> output = new RestResponse<>();
        output.setData(body);
        output.setDevMessage("ResponseAdviceDevMessage");
        output.setHttpcode(200);
        output.setStatus("Success");
        output.setUserMessage("ResponseAdviceUserMessage");
        return output;
    }
}

The entity classes look like this: 实体类看起来像这样:

@Setter // All lombok annotations
@Getter
@ToString
public class RestResponse<T> {

    private String status;
    private int httpcode;
    private String devMessage;
    private String userMessage;

    private T data;
}

@Entity
@Data // Lombok
public class HallOfFame {

    @Id
    private String id;
    private String name;
}

To handle exceptions, simply create another ControllerAdvice with ExceptionHandler . 要处理异常,只需使用ExceptionHandler创建另一个ControllerAdvice Use the example in this link . 使用此链接中的示例。

Advantages of this solution: 此解决方案的优点:

  1. It keeps your controllers clean. 它可以保持控制器清洁。 You can support any return type from your controller methods. 您可以从控制器方法支持任何返回类型。
  2. Your controller return type class does not need to extend some base class as required by the AOP approach. 您的控制器返回类型类不需要按照AOP方法的要求扩展某些基类。
  3. You do not need to hack your way through Spring filters by using HttpServletResponseWrappers. 您不需要通过使用HttpServletResponseWrappers来破解Spring过滤器。 They come up with a performance penalty. 他们提出了性能损失。

Simplest way i manage custom responses from controllers is by utilising the Map variable. 我通过利用Map变量管理来自控制器的自定义响应的最简单方法。

so your code ends up looking like: 所以你的代码最终看起来像:

public @ResponseBody Map controllerName(...) {

Map mapA = new HashMap();

mapA.put("status", "5");
mapA.put("content", "something went south");

return mapA;

}

beauty of is is that you can configure it any thousand ways. 美丽的是,你可以配置它任何一千种方式。 Currently i use for object transmition, custom exception handling and data reporting, too easy. 目前我用于对象传输,自定义异常处理和数据报告,太容易了。

Hope this helps 希望这可以帮助

I am also using AOP with @Around. 我也在和@Around一起使用AOP。 Developed a custom annotation and using that for point cut. 开发了自定义注释并将其用于切入点。 I am using a global Response. 我正在使用全球响应。 It has the status, Message and data which is of type List of type 它具有类型为List的类型的状态,消息和数据

List <? extends parent> dataList

( which can solve your class cast exception). (这可以解决你的类强制转换异常)。 All the entities extends this Parent class. 所有实体都扩展了此Parent类。 This way I can set all the data into my List. 这样我就可以将所有数据都设置到我的列表中。 Also I am using the message key as param with the custom annotation and setting it in action. 此外,我使用消息键作为自定义注释的参数并将其设置为运行。 Hope this helps. 希望这可以帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM