專題簡介
SpringBoot之路專題是一個記錄本人在使用Spring和SpringBoot相關技術中所遇到的問題和要解決的問題。每用到一處知識點,就會把這處知識補充到Github一個對應的分支上。會以專題的方式,力爭每一篇博客,由淺入深,把每個知識點講解到實戰(zhàn)級別,并且分析Spring源碼。整個項目會以一個開發(fā)一個博客系統(tǒng)為最終目標,每一個分支都記錄著一步一步搭建的過程。與大家分享,代碼會同步發(fā)布到這里。
本節(jié)簡介
本節(jié)介紹如何在SpringBoot的環(huán)境中引入原始的HttpServletRequest和Response。
1. 概念
- 什么是Request、Response
這里指的Request和Response對應的是一次Http請求中的Request,在Java的Servlet協(xié)議中,把其封裝為HttpServletRequest和HttpServletResponse,我們可以方便的從這兩個類中取出Http Request中的內容,包括header、cookie、uri、params等等,也能方便的使用HttpServletResponse處理Http Response。 - 應用于什么場景、解決了什么問題
操作Request和Response是編寫一個web程序的最基本的需求。比如,讀取參數(shù)、讀取Cookie;設置相應內容,設置響應頭等等。
2. 實戰(zhàn)
- 可運行的代碼
本節(jié)代碼在這里。
代碼截圖:
運行截圖 - 代碼串講
- 在參數(shù)列表中加入HttpServletRequest
- 在參數(shù)列表中加入HttpServletResponse
上面兩步,Spring會使用反射將這兩個屬性從上下文環(huán)境中注入到對應到本方法中。 - 使用reqeust,獲取Header中的參數(shù)。
- 使用response,獲取writer。這里的writer可以直接將數(shù)據(jù)寫回到response中。
- 使用writer寫入數(shù)據(jù)
- 清空write流
另外,這里沒有調用close()方法關閉writer流,請使用者注意在finally中調用close。 - 運行截圖
運行截圖
3. 原理
- 原理分析
大體上,我們知道spring是一個反射框架,使用反射技術對相應的參數(shù)做到了參數(shù)注入。反射,是一個用于操作字節(jié)碼的java類庫,Oracle自身提供了反射的實現(xiàn),也有一些公司根據(jù)java字節(jié)碼規(guī)范實現(xiàn)了基于字節(jié)碼實現(xiàn)的反射類庫,如ASM、Cglib等。 - 源碼串講
在開始前,給大家講講我在讀源碼時的一些技巧。
代碼不能像散文一樣,從頭到尾的讀,而是要把自己模擬成一個顆CPU,更確切一點,把自己模擬成JVM。模擬整個方法的調用,梳理清楚各個方法的調用關系。這樣讀起源碼事半功倍,能梳理清楚主干流程上的關鍵源碼,除掉和關鍵方法調用關系不大的代碼。
而善于使用eclipse和idea的調試工具,直接查看整個調用棧也是很關鍵的一個技巧。
我們來看整個方法的調用棧:
在idea中,index方法的調用棧截部分圖
紅色部分為:java反射包提供的類
黃色部分為:spring-web提供的類
綠色部分為:spring-webmvc提供的類
藍色部分為:servlet接口提供的類,由tomcat-embed-core做的實現(xiàn)(中間一行的由spring-webmvc提供。)
灰色部分為及下面更多的內容:tomcat提供的servlet實現(xiàn)。
這里,我們重點關注
invokeForRequest(), InvocableHandlerMethod (org.springframework.web.method.support)
代碼如下:
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
if(this.logger.isTraceEnabled()) {
StringBuilder returnValue = new StringBuilder("Invoking [");
returnValue.append(this.getBeanType().getSimpleName()).append(".");
returnValue.append(this.getMethod().getName()).append("] method with arguments ");
returnValue.append(Arrays.asList(args));
this.logger.trace(returnValue.toString());
}
Object returnValue1 = this.doInvoke(args);
if(this.logger.isTraceEnabled()) {
this.logger.trace("Method [" + this.getMethod().getName() + "] returned [" + returnValue1 + "]");
}
return returnValue1;
}
其中,調用this.doInvoke(args)
的地方會調用反射相關api直接調用對應的index方法。那么,我們需要關注的,就是這個args是如何生成的。
可以看到在第二行:Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
方法中,對args進行了賦值。那么我們來具體查看這個方法的實現(xiàn):
/**
* Get the method argument values for the current request.
*/
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
args[i] = resolveProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (this.argumentResolvers.supportsParameter(parameter)) {
try {
args[i] = this.argumentResolvers.resolveArgument(
parameter, mavContainer, request, this.dataBinderFactory);
continue;
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug(getArgumentResolutionErrorMessage("Error resolving argument", i), ex);
}
throw ex;
}
}
if (args[i] == null) {
String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
throw new IllegalStateException(msg);
}
}
return args;
}
在上面代碼中:
this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory)
對args數(shù)組的每一個參數(shù)進行了賦值,繼續(xù)深入源碼。
/**
* Attempt to resolve a method parameter from the list of provided argument values.
*/
private Object resolveProvidedArgument(MethodParameter parameter, Object... providedArgs) {
if (providedArgs == null) {
return null;
}
for (Object providedArg : providedArgs) {
if (parameter.getParameterType().isInstance(providedArg)) {
return providedArg;
}
}
return null;
}
如果參數(shù)類型和提供的參數(shù)類型可以匹配,則直接返回。至此,我們分析完了從源碼級別分析了,如何做的參數(shù)注入。