Spring Boot之路[4]--操作Request和Response及源碼分析

專題簡介

SpringBoot之路專題是一個記錄本人在使用Spring和SpringBoot相關技術中所遇到的問題和要解決的問題。每用到一處知識點,就會把這處知識補充到Github一個對應的分支上。會以專題的方式,力爭每一篇博客,由淺入深,把每個知識點講解到實戰(zhàn)級別,并且分析Spring源碼。整個項目會以一個開發(fā)一個博客系統(tǒng)為最終目標,每一個分支都記錄著一步一步搭建的過程。與大家分享,代碼會同步發(fā)布到這里

本節(jié)簡介

本節(jié)介紹如何在SpringBoot的環(huán)境中引入原始的HttpServletRequest和Response。

1. 概念

  1. 什么是Request、Response
    這里指的Request和Response對應的是一次Http請求中的Request,在Java的Servlet協(xié)議中,把其封裝為HttpServletRequest和HttpServletResponse,我們可以方便的從這兩個類中取出Http Request中的內容,包括header、cookie、uri、params等等,也能方便的使用HttpServletResponse處理Http Response。
  2. 應用于什么場景、解決了什么問題
    操作Request和Response是編寫一個web程序的最基本的需求。比如,讀取參數(shù)、讀取Cookie;設置相應內容,設置響應頭等等。

2. 實戰(zhàn)

  1. 可運行的代碼
    本節(jié)代碼在這里
    代碼截圖:
    運行截圖
  2. 代碼串講
  3. 在參數(shù)列表中加入HttpServletRequest
  4. 在參數(shù)列表中加入HttpServletResponse
    上面兩步,Spring會使用反射將這兩個屬性從上下文環(huán)境中注入到對應到本方法中。
  5. 使用reqeust,獲取Header中的參數(shù)。
  6. 使用response,獲取writer。這里的writer可以直接將數(shù)據(jù)寫回到response中。
  7. 使用writer寫入數(shù)據(jù)
  8. 清空write流
    另外,這里沒有調用close()方法關閉writer流,請使用者注意在finally中調用close。
  9. 運行截圖
運行截圖

3. 原理

  1. 原理分析
    大體上,我們知道spring是一個反射框架,使用反射技術對相應的參數(shù)做到了參數(shù)注入。反射,是一個用于操作字節(jié)碼的java類庫,Oracle自身提供了反射的實現(xiàn),也有一些公司根據(jù)java字節(jié)碼規(guī)范實現(xiàn)了基于字節(jié)碼實現(xiàn)的反射類庫,如ASM、Cglib等。
  2. 源碼串講
    在開始前,給大家講講我在讀源碼時的一些技巧。
    代碼不能像散文一樣,從頭到尾的讀,而是要把自己模擬成一個顆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ù)注入。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,973評論 19 139
  • 從三月份找實習到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,372評論 11 349
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,767評論 18 399
  • =========================================================...
    lavor閱讀 3,508評論 0 5
  • 晚上七點半,我在手機上的一個APP上貸款。 我用手機掃描了很長時間的身份證沒能掃描出來。我明明對得很好,它卻老是提...
    期盼明月閱讀 305評論 0 0