Spring是怎样处理form-data和x-www-form-urlencoded请求的参数的

目录

前言

Spring对请求参数的处理方法

对multipart/form-data请求参数的处理

对application/x-www-form-urlencoded请求参数的处理


前言

我们用@RequestMapping标识一个Web请求的映射,可以标识在方法上,当我们向服务器发送一个请求时,由Spring解析请求来的参数,并赋值给方法的参数,比如这样

@RequestMapping(value = "/testRequestMapping", method = RequestMethod.POST)
public void testRequestMapping (String para) {
    //
}

本文关注的是Spring对请求的参数进行封装,并最终转换成java方法的参数的过程。(也就是把请求中的para参数转换成testRequestMapping方法中的para参数)

当请求的ContentType是form-data和x-www-form-urlencoded时,Spring对参数的接收和转换方式不同。

 

Spring对请求参数的处理方法

Spring把请求的参数名和参数值最终保存在了一个LinkedHashMap中,封装关系如下:

1,Spring对请求参数的处理方法来自org.apache.catalina.connector.Request类的parseParameters()方法,这个Request类在tomcat-embed-core-8.5.29.jar下。

2,org.apache.catalina.connector.Request类里有一个org.apache.coyote.Request对象。coyote是草原狼,看来他们真的很喜欢动物。

3,org.apache.coyote.Request类里有一个org.apache.tomcat.util.http.Parameters对象。

4,org.apache.tomcat.util.http.Parameters类里维护了一个属性:paramHashValues,类型是LinkedHashMap,请求的参数名会被映射成Map的key,参数值被映射成Map的value,然后保存在这个Map中。

5,当调用request. getParameterValues()方法时,就是从这个Map中获取参数的。

6,被@RequestMapping标注的方法,是由Spring生成代理并执行的,此时方法中的参数值也是从Map中获取的。


调用到parseParameters()方法时的调用栈信息是这样的:

parseParameters:3216, Request (org.apache.catalina.connector)

getParameter:1137, Request (org.apache.catalina.connector)

getParameter:381, RequestFacade (org.apache.catalina.connector)

doFilterInternal:75, HiddenHttpMethodFilter (org.springframework.web.filter)

doFilter:107, OncePerRequestFilter (org.springframework.web.filter)

internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)

doFilter:166, ApplicationFilterChain (org.apache.catalina.core)

doFilterInternal:200, CharacterEncodingFilter (org.springframework.web.filter)

doFilter:107, OncePerRequestFilter (org.springframework.web.filter)

internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)

doFilter:166, ApplicationFilterChain (org.apache.catalina.core)

invoke:198, StandardWrapperValve (org.apache.catalina.core)

invoke:96, StandardContextValve (org.apache.catalina.core)

invoke:496, AuthenticatorBase (org.apache.catalina.authenticator)

invoke:140, StandardHostValve (org.apache.catalina.core)

invoke:81, ErrorReportValve (org.apache.catalina.valves)

invoke:87, StandardEngineValve (org.apache.catalina.core)

service:342, CoyoteAdapter (org.apache.catalina.connector)

service:803, Http11Processor (org.apache.coyote.http11)

process:66, AbstractProcessorLight (org.apache.coyote)

process:790, AbstractProtocol$ConnectionHandler (org.apache.coyote)

doRun:1459, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)

run:49, SocketProcessorBase (org.apache.tomcat.util.net)

runWorker:1149, ThreadPoolExecutor (java.util.concurrent)

run:624, ThreadPoolExecutor$Worker (java.util.concurrent)

run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)

run:748, Thread (java.lang)

parseParameters()方法代码如下:

protected void parseParameters() {
    this.parametersParsed = true;
    Parameters parameters = this.coyoteRequest.getParameters();
    boolean success = false;

    try {
        parameters.setLimit(this.getConnector().getMaxParameterCount());
        Charset charset = this.getCharset();
        boolean useBodyEncodingForURI = this.connector.getUseBodyEncodingForURI();
        parameters.setCharset(charset);
        if (useBodyEncodingForURI) {
            parameters.setQueryStringCharset(charset);
        }

        parameters.handleQueryParameters();
        if (this.usingInputStream || this.usingReader) {
            success = true;
            return;
        }

        if (this.getConnector().isParseBodyMethod(this.getMethod())) {
            String contentType = this.getContentType();
            if (contentType == null) {
                contentType = "";
            }

            int semicolon = contentType.indexOf(59);
            if (semicolon >= 0) {
                contentType = contentType.substring(0, semicolon).trim();
            } else {
                contentType = contentType.trim();
            }

            if ("multipart/form-data".equals(contentType)) {
                this.parseParts(false);
                success = true;
                return;
            }

            if (!"application/x-www-form-urlencoded".equals(contentType)) {
                success = true;
                return;
            }

            int len = this.getContentLength();
            if (len <= 0) {
                if ("chunked".equalsIgnoreCase(this.coyoteRequest.getHeader("transfer-encoding"))) {
                    Object var21 = null;

                    Context context;
                    byte[] formData;
                    try {
                        formData = this.readChunkedPostBody();
                    } catch (IllegalStateException var17) {
                        parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
                        context = this.getContext();
                        if (context != null && context.getLogger().isDebugEnabled()) {
                            context.getLogger().debug(sm.getString("coyoteRequest.parseParameters"), var17);
                        }

                        return;
                    } catch (IOException var18) {
                        parameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);
                        context = this.getContext();
                        if (context != null && context.getLogger().isDebugEnabled()) {
                            context.getLogger().debug(sm.getString("coyoteRequest.parseParameters"), var18);
                        }

                        return;
                    }

                    if (formData != null) {
                        parameters.processParameters(formData, 0, formData.length);
                    }
                }
            } else {
                int maxPostSize = this.connector.getMaxPostSize();
                Context context;
                if (maxPostSize >= 0 && len > maxPostSize) {
                    context = this.getContext();
                    if (context != null && context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(sm.getString("coyoteRequest.postTooLarge"));
                    }

                    this.checkSwallowInput();
                    parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
                    return;
                }

                context = null;
                byte[] formData;
                if (len < 8192) {
                    if (this.postData == null) {
                        this.postData = new byte[8192];
                    }

                    formData = this.postData;
                } else {
                    formData = new byte[len];
                }

                try {
                    if (this.readPostBody(formData, len) != len) {
                        parameters.setParseFailedReason(FailReason.REQUEST_BODY_INCOMPLETE);
                        return;
                    }
                } catch (IOException var19) {
                    Context context = this.getContext();
                    if (context != null && context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(sm.getString("coyoteRequest.parseParameters"), var19);
                    }

                    parameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);
                    return;
                }

                parameters.processParameters(formData, 0, len);
            }

            success = true;
            return;
        }

        success = true;
    } finally {
        if (!success) {
            parameters.setParseFailedReason(FailReason.UNKNOWN);
        }

    }

}

重点关注一下这一部分:

if ("multipart/form-data".equals(contentType)) {
    this.parseParts(false);
    success = true;
    return;
}

if (!"application/x-www-form-urlencoded".equals(contentType)) {
    success = true;
    return;
}

从这一部分可以看到,Spring对multipart/form-data和application/x-www-form-urlencoded两种ContentType的请求,采用了不同的参数处理方式。

对multipart/form-data请求,调用了

this.parseParts(false);

然后如果ContentType也不是application/x-www-form-urlencoded就直接退出了,注意第二个if前面有个叹号。

所以这段代码之后的部分都是对application/x-www-form-urlencoded类型的处理了。

下面分别看一下Spring是怎么处理两种不同请求的参数的。

 

对multipart/form-data请求参数的处理

如前面所说,当ContentType是multipart/form-data时,调用的是this.parseParts(false);方法,这个方法的代码如下:

private void parseParts(boolean explicit) {
    if (this.parts == null && this.partsParseException == null) {
        Context context = this.getContext();
        MultipartConfigElement mce = this.getWrapper().getMultipartConfigElement();
        if (mce == null) {
            if (!context.getAllowCasualMultipartParsing()) {
                if (explicit) {
                    this.partsParseException = new IllegalStateException(sm.getString("coyoteRequest.noMultipartConfig"));
                    return;
                }

                this.parts = Collections.emptyList();
                return;
            }

            mce = new MultipartConfigElement((String)null, (long)this.connector.getMaxPostSize(), (long)this.connector.getMaxPostSize(), this.connector.getMaxPostSize());
        }

        Parameters parameters = this.coyoteRequest.getParameters();
        parameters.setLimit(this.getConnector().getMaxParameterCount());
        boolean success = false;

        try {
            String locationStr = mce.getLocation();
            File location;
            if (locationStr != null && locationStr.length() != 0) {
                location = new File(locationStr);
                if (!location.isAbsolute()) {
                    location = (new File((File)context.getServletContext().getAttribute("javax.servlet.context.tempdir"), locationStr)).getAbsoluteFile();
                }
            } else {
                location = (File)context.getServletContext().getAttribute("javax.servlet.context.tempdir");
            }

            if (!location.isDirectory()) {
                parameters.setParseFailedReason(FailReason.MULTIPART_CONFIG_INVALID);
                this.partsParseException = new IOException(sm.getString("coyoteRequest.uploadLocationInvalid", new Object[]{location}));
                return;
            }

            DiskFileItemFactory factory = new DiskFileItemFactory();

            try {
                factory.setRepository(location.getCanonicalFile());
            } catch (IOException var29) {
                parameters.setParseFailedReason(FailReason.IO_ERROR);
                this.partsParseException = var29;
                return;
            }

            factory.setSizeThreshold(mce.getFileSizeThreshold());
            ServletFileUpload upload = new ServletFileUpload();
            upload.setFileItemFactory(factory);
            upload.setFileSizeMax(mce.getMaxFileSize());
            upload.setSizeMax(mce.getMaxRequestSize());
            this.parts = new ArrayList();

            try {
                List<FileItem> items = upload.parseRequest(new ServletRequestContext(this));
                int maxPostSize = this.getConnector().getMaxPostSize();
                int postSize = 0;
                Charset charset = this.getCharset();
                Iterator i$ = items.iterator();

                while(true) {
                    if (!i$.hasNext()) {
                        success = true;
                        break;
                    }

                    FileItem item = (FileItem)i$.next();
                    ApplicationPart part = new ApplicationPart(item, location);
                    this.parts.add(part);
                    if (part.getSubmittedFileName() == null) {
                        String name = part.getName();
                        String value = null;

                        try {
                            value = part.getString(charset.name());
                        } catch (UnsupportedEncodingException var28) {
                            ;
                        }

                        if (maxPostSize >= 0) {
                            postSize += name.getBytes(charset).length;
                            if (value != null) {
                                ++postSize;
                                postSize = (int)((long)postSize + part.getSize());
                            }

                            ++postSize;
                            if (postSize > maxPostSize) {
                                parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
                                throw new IllegalStateException(sm.getString("coyoteRequest.maxPostSizeExceeded"));
                            }
                        }

                        parameters.addParameter(name, value);
                    }
                }
            } catch (InvalidContentTypeException var30) {
                parameters.setParseFailedReason(FailReason.INVALID_CONTENT_TYPE);
                this.partsParseException = new ServletException(var30);
            } catch (SizeException var31) {
                parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
                this.checkSwallowInput();
                this.partsParseException = new IllegalStateException(var31);
            } catch (FileUploadException var32) {
                parameters.setParseFailedReason(FailReason.IO_ERROR);
                this.partsParseException = new IOException(var32);
            } catch (IllegalStateException var33) {
                this.checkSwallowInput();
                this.partsParseException = var33;
            }
        } finally {
            if (this.partsParseException != null || !success) {
                parameters.setParseFailedReason(FailReason.UNKNOWN);
            }

        }

    }
}

方法很长,主要流程是这样的:

1,创建一个参数处理器MultipartConfigElement。

2,确定文件上传地址。

3,创建文件上传用的Factory。

4,把请求中的参数按name-value作为一个临时文件的方式依次上传到服务器,每组name-value形成一个临时文件,文件的内容就是value本身,文件名则和name有关。

同时形成FileItem的列表,每个FileItem代表一个文件,也就是代表一组name-value。

也就是这行代码:

List<FileItem> items = upload.parseRequest(new ServletRequestContext(this));

文件的路径由创建参数处理器时的TomcatEmbeddedContext决定,我本地的临时文件是这样的:

C:\Users\pine0\AppData\Local\Temp\tomcat.7096701330036005397.8002\work\Tomcat\localhost\ROOT\upload_7cc5e1e1_48ad_4631_ab1d_4dbcf9df275f_00000000.tmp。

5,循环FileItem列表,从FileItem对象中获得name和value,也就是请求参数名和参数值,并调用

parameters.addParameter(name, value);

方法,把name和value放入Map中。

另外此方法中还有一些比如参数数量的判断,POST参数上限2097152个。

至此,对请求参数的处理结束。

 

可见,对multipart/form-data请求的参数处理,是先上传文件,再获取参数的。

因为multipart/form-data本身就是可以把文件当参数上传的,可能是考虑到缓存或者方便处理文件类型的参数,所以采用了临时文件的处理方式。

 

对application/x-www-form-urlencoded请求参数的处理

在parseParameters()方法中两个if之后的部分,就是对application/x-www-form-urlencoded类型参数的处理,处理的大概流程是这样的:

1,获得参数长度。

就是这行代码:

int len = this.getContentLength();

因为ContentType是application/x-www-form-urlencoded时,请求的参数会被组成

pageSize=10&code=&pro=123

这种形式,跟get方式的参数挺像,只不过get方式的参数写在地址里,Post方式的这些参数写在body里。

在这里请求的参数是以byte数组的形式存在的,不是字符串。

2,根据byte数组和len获取请求参数的name-value,然后保存在Map中,也就是这一行代码:

parameters.processParameters(formData, 0, len);

这个方法执行完成后,对application/x-www-form-urlencoded请求参数的处理就结束了。

 

下面详细介绍一下Spring是如何从byte数组中获得name-value的,processParameters(formData, 0, len)方法的代码如下:

private void processParameters(byte[] bytes, int start, int len, Charset charset) {
    if (log.isDebugEnabled()) {
        log.debug(sm.getString("parameters.bytes", new Object[]{new String(bytes, start, len, DEFAULT_BODY_CHARSET)}));
    }

    int decodeFailCount = 0;
    int pos = start;
    int end = start + len;

    label172:
    while(pos < end) {
        int nameStart = pos;
        int nameEnd = -1;
        int valueStart = -1;
        int valueEnd = -1;
        boolean parsingName = true;
        boolean decodeName = false;
        boolean decodeValue = false;
        boolean parameterComplete = false;

        do {
            switch(bytes[pos]) {
            case 37:
            case 43:
                if (parsingName) {
                    decodeName = true;
                } else {
                    decodeValue = true;
                }

                ++pos;
                break;
            case 38:
                if (parsingName) {
                    nameEnd = pos;
                } else {
                    valueEnd = pos;
                }

                parameterComplete = true;
                ++pos;
                break;
            case 61:
                if (parsingName) {
                    nameEnd = pos;
                    parsingName = false;
                    ++pos;
                    valueStart = pos;
                } else {
                    ++pos;
                }
                break;
            default:
                ++pos;
            }
        } while(!parameterComplete && pos < end);

        if (pos == end) {
            if (nameEnd == -1) {
                nameEnd = pos;
            } else if (valueStart > -1 && valueEnd == -1) {
                valueEnd = pos;
            }
        }

        if (log.isDebugEnabled() && valueStart == -1) {
            log.debug(sm.getString("parameters.noequal", new Object[]{nameStart, nameEnd, new String(bytes, nameStart, nameEnd - nameStart, DEFAULT_BODY_CHARSET)}));
        }

        String message;
        String value;
        if (nameEnd <= nameStart) {
            if (valueStart == -1) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("parameters.emptyChunk"));
                }
            } else {
                Mode logMode = userDataLog.getNextMode();
                if (logMode != null) {
                    if (valueEnd > nameStart) {
                        value = new String(bytes, nameStart, valueEnd - nameStart, DEFAULT_BODY_CHARSET);
                    } else {
                        value = "";
                    }

                    message = sm.getString("parameters.invalidChunk", new Object[]{nameStart, valueEnd, value});
                    switch(logMode) {
                    case INFO_THEN_DEBUG:
                        message = message + sm.getString("parameters.fallToDebug");
                    case INFO:
                        log.info(message);
                        break;
                    case DEBUG:
                        log.debug(message);
                    }
                }

                this.setParseFailedReason(Parameters.FailReason.NO_NAME);
            }
        } else {
            this.tmpName.setBytes(bytes, nameStart, nameEnd - nameStart);
            if (valueStart >= 0) {
                this.tmpValue.setBytes(bytes, valueStart, valueEnd - valueStart);
            } else {
                this.tmpValue.setBytes(bytes, 0, 0);
            }

            if (log.isDebugEnabled()) {
                try {
                    this.origName.append(bytes, nameStart, nameEnd - nameStart);
                    if (valueStart >= 0) {
                        this.origValue.append(bytes, valueStart, valueEnd - valueStart);
                    } else {
                        this.origValue.append(bytes, 0, 0);
                    }
                } catch (IOException var21) {
                    log.error(sm.getString("parameters.copyFail"), var21);
                }
            }

            try {
                if (decodeName) {
                    this.urlDecode(this.tmpName);
                }

                this.tmpName.setCharset(charset);
                String name = this.tmpName.toString();
                if (valueStart >= 0) {
                    if (decodeValue) {
                        this.urlDecode(this.tmpValue);
                    }

                    this.tmpValue.setCharset(charset);
                    value = this.tmpValue.toString();
                } else {
                    value = "";
                }

                try {
                    this.addParameter(name, value);
                } catch (IllegalStateException var22) {
                    Mode logMode = maxParamCountLog.getNextMode();
                    if (logMode != null) {
                        String message = var22.getMessage();
                        switch(logMode) {
                        case INFO_THEN_DEBUG:
                            message = message + sm.getString("parameters.maxCountFail.fallToDebug");
                        case INFO:
                            log.info(message);
                            break label172;
                        case DEBUG:
                            log.debug(message);
                        }
                    }
                    break;
                }
            } catch (IOException var23) {
                this.setParseFailedReason(Parameters.FailReason.URL_DECODING);
                ++decodeFailCount;
                if (decodeFailCount == 1 || log.isDebugEnabled()) {
                    if (log.isDebugEnabled()) {
                        log.debug(sm.getString("parameters.decodeFail.debug", new Object[]{this.origName.toString(), this.origValue.toString()}), var23);
                    } else if (log.isInfoEnabled()) {
                        Mode logMode = userDataLog.getNextMode();
                        if (logMode != null) {
                            message = sm.getString("parameters.decodeFail.info", new Object[]{this.tmpName.toString(), this.tmpValue.toString()});
                            switch(logMode) {
                            case INFO_THEN_DEBUG:
                                message = message + sm.getString("parameters.fallToDebug");
                            case INFO:
                                log.info(message);
                                break;
                            case DEBUG:
                                log.debug(message);
                            }
                        }
                    }
                }
            }

            this.tmpName.recycle();
            this.tmpValue.recycle();
            if (log.isDebugEnabled()) {
                this.origName.recycle();
                this.origValue.recycle();
            }
        }
    }

    if (decodeFailCount > 1 && !log.isDebugEnabled()) {
        Mode logMode = userDataLog.getNextMode();
        if (logMode != null) {
            String message = sm.getString("parameters.multipleDecodingFail", new Object[]{decodeFailCount});
            switch(logMode) {
            case INFO_THEN_DEBUG:
                message = message + sm.getString("parameters.fallToDebug");
            case INFO:
                log.info(message);
                break;
            case DEBUG:
                log.debug(message);
            }
        }
    }

}

也是倍儿长的一段代码,其实逻辑并不复杂,改成伪代码大概是这样的:

while(遍历byte数组){
    int pos;  byte数组下标
    boolean parsingName;  正在处理name,默认true
    boolean parameterComplete; 表示一组name-value处理完毕,默认false
    int nameStart = pos;  name开始的下标
    int nameEnd = -1; name结束的下标
    int valueStart = -1; value开始的下标
    int valueEnd = -1;  value结束的下标

    while(从pos位置开始遍历byte数组,而且一组name-value没有处理完毕){
        如果pos位置是+,(byte=43),说明这个name或者value需要decode一下
            如果正在处理name,(parsingName==true),说明这个name需要decode一下
	    如果正在处理value,(parsingName==false),说明这个value需要decode一下
            下标后移
        如果pos位置是&,(byte=38),说明一组name-value处理完毕
	    如果正在处理name,(parsingName==true),则记录name结束的下标
	    如果正在处理value,(parsingName==false),则记录value结束的下标
            parameterComplete设为true
            下标后移
        如果pos位置是=,(byte=61)
            如果正在处理name,(parsingName==true),那么parsingName设为false,记录name结束的下标,下标后移,记录value开始的下标
            如果正在处理value,(parsingName==false),则不做处理,下标后移。(看起来是为了支持value中带等号的情况)
        如果post位置是其他字符
            不做处理,下标后移
    }
    
    代码能来到这说明一组name-value处理完毕了
    根据name开始和结束的下标获得name字符串,this.tmpName。
    根据value开始和结束的下标获得value字符串,this.tmpValue。
    如果需要decode一下,就先decode一下。
    String name=this.tmpName.toString();
    String value = this.tmpValue.toString();
    调用addParameter(name, value)添加到Map中
    清空this.tmpName和this.tmpValue。
}

其实就是在遍历byte数组的过程中用+=&等符号记录了name和value开始和结束的位置。

byte数组遍历结束,Map也就组装结束了。

 

另外有个小细节,这里的this.tmpName和this.tmpValue不是字符串类型,而是自定义的一个ByteChunk类,这个类的toString()方法是重写过的:

public String toString() {
    if (null == this.buff) {
        return null;
    } else {
        return this.end - this.start == 0 ? "" : StringCache.toString(this);
    }
}

可以看到end-start==0时,大概也就是请求中某个name对应的value为空时,返回的value是"",直接取自字符串的常量池,这大概是Spring处理请求的参数时唯一一次直接从常量池获取字符串的场景了,其他的字符串都是new String()的形式获取的。

虽然看起来没什么用,但是我们有以下推论:

当请求的ContentType是application/x-www-form-urlencoded时,如果某参数是空的,那么代码中用该参数和空字符串用==比较的结果是true。

也就是说,如果请求中para参数是空的,那么

@RequestMapping(value = "/testRequestMapping", method = RequestMethod.POST)
public void testRequestMapping (String para) {
    System.out.println(para=="");
}

输出的将会是true。

参数不为空时输出的会是false。

ContentType不是application/x-www-form-urlencoded时输出的也是false。

当然,作为遵纪守法的好码农,字符串比较请用equals()。这么好的装*机会怎么能错过!

 

以上

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: resttemplate form-urlencoded与form-data是两种不同的请求参数传递方式。 相同点: 1. 都可以用于传递表单数据。 2. 都是HTTP请求中的Content-Type的一种类型。 3. 都可以通过RestTemplate进行发送。 不同点: 1. form-urlencoded参数传递方式是将参数编码成key-value的形式,并用&符号连接起来,然后将参数放在请求的body中发送。而form-data参数传递方式是将参数以各个part的形式传输,每个part包含一个key-value对。 2. 在form-urlencoded方式中,参数会被URL编码,而在form-data方式中,参数不会被编码,会保留其原始格式。 3. form-urlencoded方式在处理参数键值对时只支持字符串类型的值,而form-data方式可以支持多种数据类型的值,比如文件等。 4. 在RestTemplate中,form-urlencoded方式通常使用MultiValueMap<String, String>来存储参数,而form-data方式则使用LinkedMultiValueMap<String, Object>来存储参数。 选择使用哪种方式取决于具体的需求。如果只是传输简单的键值对参数,而且参数值为字符串类型,可以选择form-urlencoded方式。如果需要传输复杂的参数,比如文件等,或者参数值类型多样,可以选择form-data方式。 ### 回答2: RestTemplate是Spring框架提供的用于发送HTTP请求的类库。而form-urlencoded和form-data都是HTTP请求中常见的两种请求数据格式。 相同点: 1. 都是用于在HTTP请求中传递表单数据的格式。 2. 都是将数据以键值对的形式传递。 3. 都可以通过RestTemplate的方法进行发送。 不同点: 1. 数据编码方式不同:form-urlencoded使用URL编码对数据进行编码,而form-data使用多部分(Multipart)编码。form-urlencoding将特殊字符进行URL编码,例如空格会被编码为%20;form-data则将数据以二进制形式发送,不对特殊字符进行编码。 2. 传递文件的能力不同:form-urlencoded只能用于传递文本数据,无法传递文件;而form-data可以传递文本数据、二进制文件等多种类型的数据。 3. 请求头不同:form-urlencoded的Content-Type为"application/x-www-form-urlencoded",而form-data的Content-Type为"multipart/form-data"。 4. 数据传输方式不同:form-urlencoded将数据以键值对的形式编码到请求的URL中的query string中,而form-data则将数据以多部分(Multipart)形式附加到请求的body中。 综上所述,form-urlencoded和form-data在数据编码方式、传递文件能力、请求头和数据传输方式等方面存在差异。在使用RestTemplate发送HTTP请求时,需要根据实际需求选择适合的数据格式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值