Spring--視圖內(nèi)容協(xié)商(四)

本文是學習了小馬哥在慕課網(wǎng)的課程的《Spring Boot 2.0深度實踐之核心技術(shù)篇》的內(nèi)容結(jié)合自己的需要和理解做的筆記。

? 學習了這么久視圖內(nèi)容協(xié)商處理,下面讓我們來自定義 方法參數(shù)解析器和返回值解析器來 處理傳參為 Properties 類型的請求。

大綱

  • 需求說明
  • 定義一個調(diào)試類
  • 自定義方法參數(shù)解析器
  • 自定義返回值解析器
  • 整合以及調(diào)試

需求說明

? 實現(xiàn) Content-Typetext/properties 媒體類型并且傳參是 Properites 的文本(例如 name=neal)的post 請求。

定義一個PropertiesController調(diào)試類

/**
 * 自定義實現(xiàn) Content-Type 為 text/properties
 */
@Controller
public class PropertiesController {

    /**
     * 接受并返回傳入的properties
     * @param properties
     * @return
     */
    @PostMapping(value = "/self/properties",
    consumes = "text/properties;charset=UTF-8")
    public Properties resolveProperties(Properties properties) {
        return properties;
    }
}

這里就是很簡單的一個Controller,在方法中只接受Content-Typetext/properties 的POST請求,同時 入?yún)㈩愋臀覀冊O(shè)置為 Properties。

自定義方法參數(shù)解析器

? 在我們自己實現(xiàn)一個自定義方法參數(shù)解析器時,我們最好先看一下Spring自帶的方法參數(shù)解析器,感興趣的小伙伴可以看一下RequestResponseBodyMethodProcessor 這也是上一篇講解源碼時所用到的解析器,由于它既是參數(shù)解析器又是返回值解析器,所以我們使用它來查看具體實現(xiàn)方法是再好不過了。話不多說,讓我們先看一下實現(xiàn)一個自定義方法參數(shù)解析器的步驟。

具體步驟:

  • 定義一個類并實現(xiàn)接口 HandlerMethodArgumentResolver 。

  • 重寫supportsParameter() 方法,使其支持Properties 類型。

  • 重寫resolveArgument() 方法,實現(xiàn) Properties 格式請求內(nèi)容,解析為 Properties 對象的方法參數(shù)。

代碼:

/**
 * 自定義 Properties 參數(shù)解析器
 */
public class PropertiesHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {


    /**
     * 是否支持參數(shù)解析
     * @param parameter
     * @return
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        //這里判斷方法中的入?yún)㈩愋?是否匹配Properties
        return Properties.class.equals(parameter.getParameterType());
    }

    /**
     * 處理并獲取請求的值
     * @param parameter
     * @param mavContainer
     * @param webRequest
     * @param binderFactory
     * @return
     * @throws Exception
     */
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        //處理請求傳入的值,并返回Properties 類型。
        Properties arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
        return arg;
    }

    /**
     * 參數(shù)讀取
     * @param webRequest
     * @param parameter
     * @param nestedGenericParameterType
     * @return
     */
    private Properties readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter, Type nestedGenericParameterType) throws Exception {
        //獲取原生的HttpServletRequest
        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
        Assert.state(servletRequest != null, "No HttpServletRequest");
        //獲取Spring定義的 ServletServerHttpRequest
        ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
        //獲取請求頭中的 Content-Type
        MediaType contentType = inputMessage.getHeaders().getContentType();
        // 獲取字符編碼
        Charset charset = contentType.getCharset();
        // 當 charset 不存在時,使用 UTF-8
        charset = charset == null ? Charset.forName("UTF-8") : charset;
        // 獲取字節(jié)流
        InputStream inputStream = inputMessage.getBody();
        //Properties的load方法需要一個實現(xiàn)Reader接口的字節(jié)流
        InputStreamReader reader = new InputStreamReader(inputStream, charset);
        //聲明一個properties對象
        Properties properties = new Properties();
        // 加載字符流成為 Properties 對象
        properties.load(reader);
        reader.close();
        return properties;
    }
}

每一步的解釋我都寫在了注釋里,相信大家都可以看懂。

自定義返回值解析器

? 自定義返回解析器也可以參考RequestResponseBodyMethodProcessor 中的實現(xiàn),但是它里面的實現(xiàn)比較復(fù)雜,做了很多校驗以及邏輯判斷,我們只需要簡單實現(xiàn)一個將返回值解析成Properties 類型的解析器就行了。

具體步驟:

  • 定義一個類并實現(xiàn)接口 HandlerMethodReturnValueHandler。
  • 重寫supportsReturnType() 方法,使其支持Properties 類型。
  • 重寫handleReturnValue() 方法,實現(xiàn) Properties 類型方法返回值,轉(zhuǎn)化為 Properties 格式內(nèi)容響應(yīng)內(nèi)容。

代碼:

/**
 * 自定義Properties 返回值解析器
 */
public class PropertiesHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {

    /**
     * 是否支持返回值類型
     * @param returnType
     * @return
     */
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        //這里判斷方法中的返回值類型 是否匹配Properties
        return Properties.class.equals(returnType.getParameterType());
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        //這里這是為true,告訴Spring已經(jīng)完成了所有的處理,不需要往下處理了
        mavContainer.setRequestHandled(true);
        //獲取Spring定義的 ServletServerHttpRequest
        ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(webRequest.getNativeRequest(HttpServletRequest.class));
        //獲取Spring定義的 ServletServerHttpResponse
        ServletServerHttpResponse response = new ServletServerHttpResponse(webRequest.getNativeResponse(HttpServletResponse.class));
        //獲取響應(yīng)頭中的 Content-Type
        MediaType contentType = response.getHeaders().getContentType();
        //如果響應(yīng)頭為空
        if(contentType == null) {
            //將請求頭中的Content-Type 設(shè)置成 響應(yīng)頭的Content-Type
            contentType = inputMessage.getHeaders().getContentType();
        }
        //設(shè)置響應(yīng)頭為請求頭的Content-type
        response.getHeaders().setContentType(contentType);
        // 獲取字符編碼
        Charset charset = contentType.getCharset();
        // 當 charset 不存在時,使用 UTF-8
        charset = charset == null ? Charset.forName("UTF-8") : charset;

        //獲取響應(yīng)流
        OutputStream outputStream = response.getBody();
        // 字符輸出流
        Writer writer = new OutputStreamWriter(outputStream, charset);
        // Properties 寫入到字符輸出流
        Properties properties = (Properties)returnValue;
        //響應(yīng)回客戶端
        properties.store(writer,"From Neal Self");
        writer.close();
    }
}

同樣的每一步的解釋我都寫在了注釋里,相信大家都可以看懂,至于Properties 寫方法,可以參考一下API。

整合以及調(diào)試

整合

在整合前需要說明一下,正常來說整合自定義的參數(shù)解析器和返回值解析器只需要繼承org.springframework.web.servlet.config.annotation.WebMvcConfigurer并 實現(xiàn) addArgumentResolvers()addReturnValueHandlers() 把對應(yīng)的解析器添加到容器中就可以了 PS:都是在末尾添加。

/**
 * Add resolvers to support custom controller method argument types.
 * <p>This does not override the built-in support for resolving handler
 * method arguments. To customize the built-in support for argument
 * resolution, configure {@link RequestMappingHandlerAdapter} directly.
 * @param resolvers initially an empty list
 */
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
}

/**
 * Add handlers to support custom controller method return value types.
 * <p>Using this option does not override the built-in support for handling
 * return values. To customize the built-in support for handling return
 * values, configure RequestMappingHandlerAdapter directly.
 * @param handlers initially an empty list
 */
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
}

但是我們的Properties 類型的參數(shù)是可以被 之前Spring內(nèi)置的解析器解析,所以在解析器順序迭代時,就不會被我們自定義的解析器解析。所以在這里參照小馬哥的方法來實現(xiàn),將我們自定義的參數(shù)解析器都放在第一個。

具體代碼和注釋如下:

/**
 * 視圖協(xié)商相關(guān)配置
 */
@Configuration   //配置
public class RestWebMvcConfig implements WebMvcConfigurer {


    @Autowired
    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;

    //在注入完成后被自動調(diào)用
    @PostConstruct
    public void init() {
        // 獲取當前 RequestMappingHandlerAdapter 所有的 Resolver 對象
        List<HandlerMethodArgumentResolver> resolvers = requestMappingHandlerAdapter.getArgumentResolvers();
        List<HandlerMethodArgumentResolver> newResolvers = new ArrayList<>(resolvers.size() + 1);
        // 添加 PropertiesHandlerMethodArgumentResolver 到集合首位
        newResolvers.add(new PropertiesHandlerMethodArgumentResolver());
        // 添加 已注冊的 Resolver 對象集合
        newResolvers.addAll(resolvers);
        // 重新設(shè)置 Resolver 對象集合
        requestMappingHandlerAdapter.setArgumentResolvers(newResolvers);

        // 獲取當前 HandlerMethodReturnValueHandler 所有的 Handler 對象
        List<HandlerMethodReturnValueHandler> handlers = requestMappingHandlerAdapter.getReturnValueHandlers();
        List<HandlerMethodReturnValueHandler> newHandlers = new ArrayList<>(handlers.size() + 1);
        // 添加 PropertiesHandlerMethodReturnValueHandler 到集合首位
        newHandlers.add(new PropertiesHandlerMethodReturnValueHandler());
        // 添加 已注冊的 Handler 對象集合
        newHandlers.addAll(handlers);
        // 重新設(shè)置 Handler 對象集合
        requestMappingHandlerAdapter.setReturnValueHandlers(newHandlers);

    }

}
調(diào)試

代碼已經(jīng)介紹完了。接下來讓我們啟動Spring-boot,來試試我們剛剛寫的解析器是否起到了作用。

打開Postman,并設(shè)置請求頭和body。

p1.png
p2.png

發(fā)送請求并查看結(jié)果

p3.png

我們可以看到,我們自定義的解析器已經(jīng)成功解析并正確返回了。

總結(jié)

? 通過這么久的學習,對于內(nèi)容協(xié)商有了一定的了解,并且可以自定義一個方法參數(shù)解析器和返回值解析器。Spring博大精深,還需要繼續(xù)努力探索。

DEMO地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容