本文是學習了小馬哥在慕課網(wǎng)的課程的《Spring Boot 2.0深度實踐之核心技術(shù)篇》的內(nèi)容結(jié)合自己的需要和理解做的筆記。
? 學習了這么久視圖內(nèi)容協(xié)商處理,下面讓我們來自定義 方法參數(shù)解析器和返回值解析器來 處理傳參為 Properties 類型的請求。
大綱
- 需求說明
- 定義一個調(diào)試類
- 自定義方法參數(shù)解析器
- 自定義返回值解析器
- 整合以及調(diào)試
需求說明
? 實現(xiàn) Content-Type 為 text/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-Type 是 text/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。


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

我們可以看到,我們自定義的解析器已經(jīng)成功解析并正確返回了。
總結(jié)
? 通過這么久的學習,對于內(nèi)容協(xié)商有了一定的了解,并且可以自定義一個方法參數(shù)解析器和返回值解析器。Spring博大精深,還需要繼續(xù)努力探索。