簡體   English   中英

如何在Spring中包裝JSON響應

[英]How can I wrap a JSON response in Spring

假設我在Spring中有兩組控制器:

  • /jsonapi1/*
  • /jsonapi2/*

兩者都返回要解釋為JSON文本的對象。

我想要某種過濾器來包裝來自這些控制器的響應,以便:

  1. 原始響應包含在另一個對象中。

    例如,如果/ jsonapi1 / count返回:

     {"num_humans":123, "num_androids":456} 

    那么響應應該被包裝並返回如下:

     { "status":0, "content":{"num_humans":123, "num_androids":456} } 
  2. 如果控制器中發生異常,則過濾器應捕獲異常並按如下方式報告

     { "status":5, "content":"Something terrible happened" } 
  3. 其他控制器的響應將保持不變。

目前,我們正在定制MappingJackson2HttpMessageConverter傳遞給WebMvcConfigurerAdapter.configureMessageConverters為了執行上述任務。 工作得很好,除了這種方法似乎不可能選擇它適用的URL(或控制器類)。

是否可以將這些類型的包裝器應用於單個控制器類或URL?


更新:Servlet過濾器看起來像一個解決方案。 是否可以選擇將哪個過濾器應用於哪些控制器方法或哪些URL?

我理解你的問題的方式,你有三個選擇。

選項1

手動將對象包裝在簡單的SuccessResponseErrorResponseSomethingSortOfWrongResponse等具有所需字段的對象中。 此時,您具有每個請求的靈活性,更改其中一個響應包裝器上的字段是微不足道的,唯一真正的缺點是代碼重復,如果許多控制器的請求方法可以並且應該組合在一起。

選項#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的解決方案對我不起作用。 我終於能夠使用ControllerAdviceResponseBodyAdvice來實現這一點。

ResponseBodyAdvice允許在控制器返回的響應上注入自定義轉換邏輯,但在轉換為HttpResponse並提交之前。

這是我的控制器方法的樣子:

@RequestMapping("/global/hallOfFame")
    public List<HallOfFame> getAllHallOfFame() {
        return hallOfFameService.getAllHallOfFame();
}

現在我想在響應中添加一些標准字段,如devmessageusermessage 該邏輯進入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 使用此鏈接中的示例。

此解決方案的優點:

  1. 它可以保持控制器清潔。 您可以從控制器方法支持任何返回類型。
  2. 您的控制器返回類型類不需要按照AOP方法的要求擴展某些基類。
  3. 您不需要通過使用HttpServletResponseWrappers來破解Spring過濾器。 他們提出了性能損失。

我通過利用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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM