按照我理解你的问题的方式,你有三个选择。
选项#1
手动将您的对象包装在简单的SuccessResponse、ErrorResponse、SomethingSortOfWrongResponse 等具有您需要的字段的对象中。在这一点上,您拥有每个请求的灵活性,更改其中一个响应包装器上的字段是微不足道的,唯一真正的缺点是代码重复,如果控制器的许多请求方法可以而且应该组合在一起。
选项 #2
正如您所提到的,过滤器可能被设计用来完成脏活,但请注意 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。定义切入点和建议的对象称为 aspect。
非常适合用 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 的注解,并确保对此类所在的包进行组件扫描。