假设我在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
的{{1}}以执行上述任务。效果很好,但这种方法似乎不可能选择适用于它的URL(或控制器类)。
是否可以将这些类型的包装器应用于单个控制器类或URL?
更新:Servlet过滤器看起来像一个解决方案。是否可以选择将哪个过滤器应用于哪些控制器方法或哪些URL?
答案 0 :(得分:7)
我理解你的问题的方式,你有三个选择。
选项#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
A-公顷。所以你到目前为止。代码重复不是选项,但坚持包装来自控制器方法的响应。我想介绍真正的解决方案 - 面向方面的编程(AOP),Spring非常支持。
如果您不熟悉AOP,前提如下:您在代码中定义匹配(如正则表达式匹配)点的表达式。这些点称为连接点,而与它们匹配的表达式称为 pointcuts 。然后,当匹配任何切入点或切入点组合时,您可以选择执行名为 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的注释,并确保组件扫描此类所在的包。
答案 1 :(得分:3)
我为此苦苦挣扎了好几天。 @Misha的解决方案对我不起作用。我终于能够使用ControllerAdvice和ResponseBodyAdvice来完成这项工作。
ResponseBodyAdvice允许对控制器返回的响应注入自定义转换逻辑,但该响应必须转换为HttpResponse并提交。
这是我的控制器方法的外观:
@RequestMapping("/global/hallOfFame")
public List<HallOfFame> getAllHallOfFame() {
return hallOfFameService.getAllHallOfFame();
}
现在,我想在响应周围添加一些标准字段,例如devmessage
和usermessage
。该逻辑进入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;
}
}
实体类如下:
@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;
}
要处理异常,只需使用ControllerAdvice
创建另一个ExceptionHandler
。使用示例in this link。
此解决方案的优点:
答案 2 :(得分:1)
我从控制器管理自定义响应的最简单方法是使用Map变量。
所以你的代码最终看起来像:
public @ResponseBody Map controllerName(...) {
Map mapA = new HashMap();
mapA.put("status", "5");
mapA.put("content", "something went south");
return mapA;
}
美丽的是,你可以配置它任何一种方式。 目前我用于对象传输,自定义异常处理和数据报告,太容易了。
希望这有帮助
答案 3 :(得分:0)
我也在使用AOP和@Around。开发了自定义注释并将其用于切入点。我正在使用全球响应。它具有状态,消息和数据类型为List的类型
List <? extends parent> dataList
(可以解决你的类强制转换异常)。所有实体都扩展了此Parent类。这样我就可以将所有数据都设置到我的列表中。 此外,我使用消息键作为自定义注释的参数并将其设置为运行。 希望这会有所帮助。