[英]How can I wrap a JSON response in Spring
假設我在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
正如您所提到的,過濾器可以設計用於執行臟工作,但要小心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-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);
}
}
最終結果將是您尋求的非重復響應包裝。 如果您只希望某個或一個控制器收到此效果,則更新切入點以僅匹配該控制器實例內的方法(而不是任何持有@Controller注釋的類)。
您需要包含一些AOP依賴項,在配置類中添加啟用AOP的注釋,並確保組件掃描此類所在的包。
我在這方面掙扎了好幾天。 @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;
}
要處理異常,只需使用ExceptionHandler
創建另一個ControllerAdvice
。 使用此鏈接中的示例。
此解決方案的優點:
我通過利用Map變量管理來自控制器的自定義響應的最簡單方法。
所以你的代碼最終看起來像:
public @ResponseBody Map controllerName(...) {
Map mapA = new HashMap();
mapA.put("status", "5");
mapA.put("content", "something went south");
return mapA;
}
美麗的是,你可以配置它任何一千種方式。 目前我用於對象傳輸,自定義異常處理和數據報告,太容易了。
希望這可以幫助
我也在和@Around一起使用AOP。 開發了自定義注釋並將其用於切入點。 我正在使用全球響應。 它具有類型為List的類型的狀態,消息和數據
List <? extends parent> dataList
(這可以解決你的類強制轉換異常)。 所有實體都擴展了此Parent類。 這樣我就可以將所有數據都設置到我的列表中。 此外,我使用消息鍵作為自定義注釋的參數並將其設置為運行。 希望這可以幫助。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.