use*_*956 5 spring json spring-mvc
假设我在Spring中有两组控制器:
/jsonapi1/*/jsonapi2/*两者都返回要解释为JSON文本的对象.
我想要某种过滤器来包装来自这些控制器的响应,以便:
原始响应包含在另一个对象中.
例如,如果/ jsonapi1/count返回:
{"num_humans":123, "num_androids":456}
Run Code Online (Sandbox Code Playgroud)
那么响应应该被包装并返回如下:
{ "status":0,
"content":{"num_humans":123, "num_androids":456}
}
Run Code Online (Sandbox Code Playgroud)如果控制器中发生异常,则过滤器应捕获异常并按如下方式报告
{ "status":5,
"content":"Something terrible happened"
}
Run Code Online (Sandbox Code Playgroud)其他控制器的响应将保持不变.
我们目前正在自定义MappingJackson2HttpMessageConverter传递给以WebMvcConfigurerAdapter.configureMessageConverters执行上述任务.工作得很好,除了这种方法似乎不可能选择它适用的URL(或控制器类).
是否可以将这些类型的包装器应用于单个控制器类或URL?
更新:Servlet过滤器看起来像一个解决方案.是否可以选择将哪个过滤器应用于哪些控制器方法或哪些URL?
我理解你的问题的方式,你有三个选择.
选项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());
}
}
Run Code Online (Sandbox Code Playgroud)
如果你找到一种方法在该过滤器中设置主体,那么是的,你可以很容易地将它包起来.否则,这个选项是死路一条.
选项#3
A-HA.所以你到目前为止.代码重复不是一个选项,但您坚持要从控制器方法中包装响应.我想介绍真正的解决方案 - 面向方面的编程(AOP),Spring非常支持.
如果您不熟悉AOP,则前提如下:您在代码中定义匹配(如正则表达式匹配)点的表达式.这些点称为连接点,而与它们匹配的表达式称为切入点.然后,当切入任何切入点或切入点组合时,您可以选择执行其他任意代码,称为建议.定义切入点和建议的对象称为方面.
它非常适合在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);
}
}
Run Code Online (Sandbox Code Playgroud)
最终结果将是您寻求的非重复响应包装.如果您只希望某个或一个控制器收到此效果,则更新切入点以仅匹配该控制器实例内的方法(而不是任何持有@Controller注释的类).
您需要包含一些AOP依赖项,在配置类中添加启用AOP的注释,并确保组件扫描此类所在的包.
我为此苦苦挣扎了好几天。@Misha的解决方案对我不起作用。我终于能够使用ControllerAdvice和ResponseBodyAdvice使其工作。
ResponseBodyAdvice允许在控制器返回的响应上(但将其转换为HttpResponse并提交之前)注入自定义转换逻辑。
这是我的控制器方法的外观:
@RequestMapping("/global/hallOfFame")
public List<HallOfFame> getAllHallOfFame() {
return hallOfFameService.getAllHallOfFame();
}
Run Code Online (Sandbox Code Playgroud)
现在,我想在响应周围添加一些标准字段,例如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;
}
}
Run Code Online (Sandbox Code Playgroud)
实体类如下所示:
@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;
}
Run Code Online (Sandbox Code Playgroud)
处理异常,只需创建另一个ControllerAdvice用ExceptionHandler。在此链接中使用示例。
该解决方案的优点:
编辑-2019年9月17日
要处理异常,请使用@ExceptionHandler。请参考下面的代码。
@ExceptionHandler(Exception.class)
@ResponseBody
public MyResponseEntity<Object> handleControllerException(HttpServletRequest request, Throwable ex) {
// default value
int httpCode = HttpStatus.INTERNAL_SERVER_ERROR.value();
if(ex instanceof ResourceNotFoundException) {
httpCode = HttpStatus.NOT_FOUND.value();
}
...
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
9789 次 |
| 最近记录: |