如何在Spring中包装JSON响应
问题内容:
假设我在Spring中有两组控制器:
/jsonapi1/*
/jsonapi2/*
这两个返回的对象都将被解释为JSON文本。
我想要某种过滤器来包装来自一组这些控制器的响应,以便:
- 原始响应包含在另一个对象中。
例如,如果/ jsonapi1 / count返回:
{"num_humans":123, "num_androids":456}
那么响应应该被包装并返回如下:
{ "status":0,
"content":{"num_humans":123, "num_androids":456}
}
-
如果控制器中发生异常,则过滤器应捕获该异常并报告如下
{ "status":5,
“content”:”Something terrible happened”
} -
来自其他控制器的响应将保持不变。
我们目前正在自定义MappingJackson2HttpMessageConverter
传递给WebMvcConfigurerAdapter.configureMessageConverters
,以执行上述任务。效果很好,除了这种方法似乎不可能对其所应用的URL(或控制器类)具有选择性。
是否可以将此类包装器应用于单个控制器类或URL?
更新:Servlet过滤器看起来像一个解决方案。是否可以选择将哪个过滤器应用于哪个控制器方法或哪个URL?
问题答案:
根据我对您问题的理解,您确实有三种选择。
选项1
手工包装你的对象简单SuccessResponse
,ErrorResponse
,SomethingSortOfWrongResponse
,等有您所需要的字段的对象。在这一点上,您具有每个请求的灵活性,更改一个响应包装器上的字段是微不足道的,并且唯一的缺点是,如果可以并且应该将许多控制器的请求方法组合在一起,则代码重复。
选项#2
正如您提到的,并且filter可以被设计为完成肮脏的工作,但请注意,Spring过滤器不会让您访问请求或响应数据。这是一个可能看起来像的例子:
@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());
}
}
如果找到在该过滤器中设置主体的方法,那么可以,可以轻松地将其包裹起来。否则,该选项将无济于事。
选项#3
阿哈 因此,您已经走了这么远。代码复制 不是 一个选择,但是您 坚持 包装来自控制器方法的响应。我想介绍一个真正的解决方案-
面向方面的编程(AOP),Spring对此很喜欢。
如果您不熟悉AOP,则前提如下:您定义一个与代码中的点匹配(如正则表达式匹配)的表达式。这些点称为 连接点 ,而与它们匹配的表达式称为 切入点
。然后,当匹配任何切入点或切入点组合时,您可以选择执行其他任意代码,称为 advice 。定义切入点和建议的对象称为 方面 。
用Java更加流畅地表达自己非常有用。唯一的缺点是较弱的静态类型检查。事不宜迟,这是面向方面编程中的响应包装:
@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);
}
}
最终结果将是您寻求的非重复响应包装。如果只希望某个或一个控制器收到此效果,则更新切入点以仅在该控制器的实例内匹配方法(而不是任何带有@Controller批注的类)。
您需要包括一些AOP依赖项,在配置类中添加启用AOP的批注,并确保有组件扫描该类所在的包。