帮助网站网站做优化,广州网络营销外包团队,广西网络推广公司,ui设计培训班哪家好SpringMVC的底层原理 在前面我们学习了SpringMVC的使用#xff08;67章博客开始#xff09;#xff0c;现在开始说明他的原理#xff08;实际上更多的细节只存在67章博客中#xff0c;这篇博客只是讲一点深度#xff0c;重复的东西尽量少说明点#xff09;
MVC 体系结… SpringMVC的底层原理 在前面我们学习了SpringMVC的使用67章博客开始现在开始说明他的原理实际上更多的细节只存在67章博客中这篇博客只是讲一点深度重复的东西尽量少说明点
MVC 体系结构
三层架构
我们的开发架构一般都是基于两种形式一种是 C/S 架构也就是客户端/服务器另一种是 B/S 架构 也就是浏览器服务器在 JavaEE 开发中几乎全都是基于 B/S 架构的开发那么在 B/S 架构中系 统标准的三层架构包括表现层、业务层、持久层三层架构在我们的实际开发中使用的⾮常多所以我们的案例也都是基于三层架构设计的
三层架构中每一层各司其职接下来我们就说说每层都负责哪些方⾯
表现层 也就是我们常说的web 层它负责接收客户端请求向客户端响应结果通常客户端使⽤http 协 议请求web 层web 需要接收 http 请求完成 http 响应 表现层包括展示层和控制层控制层负责接收请求展示层负责结果的展示表现层依赖业务层接收到客户端请求一般会调用业务层进行业务处理并将处理结果响应给客户端表现层的设计一般都使用 MVC 模型MVC 是表现层的设计模型和其他层没有关系
业务层 也就是我们常说的 service 层它负责业务逻辑处理和我们开发项目的需求息息相关web 层依赖业 务层但是业务层不依赖 web 层业务层在业务处理时可能会依赖持久层如果要对数据持久化需要保证事务一致性也就是我们说的 事务应该放到业务层来控制这主要是保证持久层一个方法只干一件事情一般都会这样也是规范这样比较好维护否则持久层在一定程度也是业务层
持久层 也就是我们是常说的 dao 层负责数据持久化包括数据层即数据库和数据访问层数据库是对数据进 行持久化的载体数据访问层是业务层和持久层交互的接⼝合起来就是持久层业务层需要通过数据访问层将数据持久化 到数据库中通俗的讲持久层就是和数据库交互对数据库表进行增删改查的
MVC设计模式
MVC 全名是 Model View Controller是模型model视图view控制器controller的缩写是一 种用于设计创建 Web 应用程序表现层的模式MVC 中每个部分各司其职
Model模型模型包含业务模型和数据模型数据模型用于封装数据业务模型用于处理业 务实际上就是处理业务逻辑封装实体虽然大多数是这样的说明但是实际上M只是代表要返回的数据而已只是这个数据由业务逻辑等等产生的所以说成Service或者Dao层也行说成返回的数据也行但本质上是返回的数据而已只是我们通常会将生成的数据过程如业务逻辑也包括进去
View视图 通常指的就是我们的 jsp 或者 html作用一般就是展示数据的通常视图是依据 模型数据创建的
Controller控制器 是应用程序中处理用户交互的部分作用一般就是处理程序逻辑的
即数据Model视图View数据与视图的交互地方Controller简称为MVC
MVC提倡每一层只编写自己的东⻄不编写任何其他的代码分层是为了解耦降低联系解耦是为了维护方便和分工协作
Spring MVC 是什么
SpringMVC 全名叫 Spring Web MVC是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级Web 框架属于SpringFrameWork 的后续产品 SpringMVC 已经成为 目前最主流的 MVC 框架 之一并且 随着 Spring3.0 的发布全⾯超越 Struts2 成为最优秀的 MVC 框架
比如servlet、struts一般需要实现接⼝、而springmvc要让一个java类能够处理请求只需要添加注解就ok
它通过一套注解让一个简单的 Java 类成为处理请求的控制器⽽⽆须实现任何接⼝同时它还⽀持RESTful 编程⻛格的请求
总之Spring MVC和Struts2⼀样都是 为了解决表现层问题 的web框架它们都是基于 MVC 设计模 式的⽽这些表现层框架的主要职责就是处理前端HTTP请求只是SpringMVC以后简称MVC了更加的好而已对于现在来说并不保证以后的情况
Spring MVC 本质可以认为是对servlet的封装简化了我们serlvet的开发具体原生的servlet的开发可以到50博客学习虽然servlet也是封装的具体可以看27章博客的最后这里说明了最后那么就不用看整个博客了
如图 也就是说只是用一个servlet完成的用拦截确定类而非servlet这样也就完成了上面原生的处理了但是这样与其他多个servlet相比难道不会对该一个控制器造成负荷吗这其实也是一个问题假设有ab两个方法一个c类里面存放这ab两个方法和df这两个类分别都存放ab这两个方法其中100个线程同时调用一个c类里面的ab方法和50个线程调用d里面的ab方法和50个线程调用f里面的ab方法他们的资源利用是相同的吗就是这样的问题答不是相同的你这里可能会有疑惑为什么不是相同的大多数人可能会这样的认为既然是调用那么你都是拿取堆里面对象调用只是其中一个被多个线程拿取的多然而拿取是同时进行的自然速度一致实际上这个速度在大多数情况或者实际情况可能正确但是这里我并不这样的认为解释如下
/*
由于数据都是存在硬件层面的所以这里以硬件层面来进行说明
在硬件层面多个线程同时读取同一个数据时会存在电路访问冲突的问题既然都是拿取他总不能一条线走你们两个电吧这就是电路访问冲突这时当多个线程同时访问同一个数据时可能会出现竞争条件导致电路冲突和数据不一致的问题然而现代计算机在处理多线程并发读取时会采用各种优化措施来尽量提高并发性和效率以下是一些硬件层面上的优化技术使得多个线程可以在某种程度上同时读取同一个数据
处理器缓存现代处理器通常具有多级缓存包括L1、L2、L3等级别的缓存当多个线程同时访问同一个数据时处理器会尽量从缓存中读取数据而不是直接从主存中获取这样可以避免不必要的主存访问冲突提高读取速度
缓存一致性协议在多核处理器中各个核心的缓存之间需要保持一致性以确保读取到的数据是最新的常用的缓存一致性协议如MESI、MOESI等可以有效地解决缓存一致性问题使得多个核心可以同时读取同一个数据
数据预取处理器会根据访存模式和预测算法预取数据到缓存中以减少对主存的访问延迟这可以提前将数据加载到缓存中以备后续线程的读取操作从而减少冲突和等待时间
总结来说尽管在硬件层面上存在电路访问冲突的问题但现代计算机通过缓存、缓存一致性协议和数据预取等技术来优化并发读取以实现尽可能的同时读取性能这些技术可以减少冲突提高并发性使得多个线程可以近似同时读取同一个数据所以可以说读取根本来说并不是同时读取只是读取的速度够快所以看起来是同时读取的并且也足够快且不会造成数据的问题所以我们通常只会考虑并发情况下的写操作而非读操作不会改变数据不考虑因为读的操作并不会改变数据所以无论什么时候读都没有影响也就基本不会考虑多线程的问题同样的在硬件层面出现的问题在软件层面必然也会出现这是底层决定上层就算没有也只是降低这样的影响而非完全消除所以综上所述当多个线程访问同一个类里面的方法和分开访问时访问同一个类的两个方法需要的时间更加的多而不是同样的时间但是这些速度在考虑MVC的方便性上是微不足道的这也是为什么如果你自己写servlet和使用MVC时MVC虽然可能会慢点但是还是使用MVC的主要原因因为他提升的速度还不足以让我放弃这个方便性方便性有时也会称为维护性并非代码越少越好维护是需要考虑非常多的情况的MVC的内部代码可比你写的要多很多但我们还是使用MVC的这里也就提到了维护性和性能他们之间有个临界点使得我们要考虑谁了而MVC是考虑维护性这也是为什么大多数我们会写循环而非直接的都进行打印这就是考虑方便性在性能问题上循环虽然需要更多的时间循环自身需要操作自然绝对的比单纯打印慢循环打印!打印但是方便许多
*/大致流程如下 即数据到控制器到视图最终到响应给我们显示实际上控制器在一定程度上也可以和数据结合一起只是为了解耦合所以我们通常也分开变成数据业务了所以如果非要精准的说的话MVC只需要控制器包括数据以及视图就行所以才会说SpringMVC是表现层的框架而不包括数据之类的框架说明当然更加具体的在手写框架时会明白的现在可以大致了解
Spring Web MVC 工作流程
需求前端浏览器请求urlhttp://localhost:8080/xxx/demo/handle01xxx是项目名称8080是端口这里可以自行改变当然请求路径你也可以改变具体看你自己了前端⻚⾯显示后台服务器的时间具体看案例
开发过程
1配置DispatcherServlet前端控制器
2开发处理具体业务逻辑的HandlerController、RequestMapping
3xml配置⽂件配置controller扫描配置springmvc三⼤件
4将xml⽂件路径告诉springmvcDispatcherServlet
创建一个项目如图 对应的pom.xml上面的文件该创建创建当pom.xml刷新好后那么webapp文件会发生改变
packagingwar/packaging
!--使得webapp变成前端资源文件夹会改变颜色显示的--对应的web.xml
?xml version1.0 encodingUTF-8?
web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaeexmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsdversion4.0/web-app在pom.xml中加上如下依赖 dependenciesdependency!--mvc需要的依赖即有前端控制器DispatcherServlet--groupIdorg.springframework/groupIdartifactIdspring-webmvc/artifactIdversion5.1.5.RELEASE/version/dependency/dependencies然后再web.xml中加上如下 servletservlet-namedispatcherServlet/servlet-nameservlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class/servletservlet-mappingservlet-namedispatcherServlet/servlet-name!--方式一带后缀比如*.action找所有后缀为action的当然也存在*action即找所有以action结尾的可能还有其他方式具体可以百度或者自己测试方式二/拦截根目录下的一般不包括.jsp文件所以会拦截静态文件的因为他不是.jsp文件即通常只是不包括.jsp文件静态文件还是包括的这也是需要解决静态文件如cssjs等等拦截问题的原因方式三/*都拦截所以jsp也会看成拦截的参数所以就算是继续找视图拿取数据时也会进行拦截看成转发的所以一般我们都会使用/来免除jsp文件的拦截包括起始访问并不是浏览器访问可以看看50章博客中具体的对应说明了解即可实际上与其说是访问可能是里面某些操作触发了拦截而已如默认找起始页面就会触发和.jsp文件即起始的/默认会调用一次加上浏览器调用两次当然拦截的策略还是需要看MVC自身的而非我们主动的记住所以这里可能会随着版本而有所改变但是/一般不会一般我们只需要/即可--url-pattern//url-pattern/servlet-mapping
!--可以继续写一个/servlet-mapping来进行拦截补充看先后顺序如果前面一个没有拦截看后面一个以此类推直到都没有拦截当然他们的拦截不能是一样的所以没有存在上面是url-pattern//url-pattern下面也是url-pattern//url-pattern的情况就算是url-pattern/a/url-pattern都可以但不能继续是url-pattern//url-pattern了--!--通常我们都会使用/因为这样比较稳定且有些东西我们是需要可以直接访问的而不是拦截找方法比如jsp--!--
但是为什么/会拦截静态资源呢为什么不直接放行呢解释如下
其中tomcat一般也会存在web.xml他通常作为父而我们的web.xml一般作为子由于子会继承父所以我们会存在父中的操作并且也要注意他DispatcherServlet自身就是servlet的这里在后面的源码编写时会明白的
--一般tomcat的web.xml在其conf目录里面如图 我们继续说明
!--我们可以在该web.xml中找到如下
--
servlet
servlet-namedefault/servlet-name
servlet-classorg.apache.catalina.servlets.DefaultServlet/servlet-class
init-param
param-namedebug/param-name
param-value0/param-value
/init-param
init-param
param-namelistings/param-name
param-valuefalse/param-value
/init-param
load-on-startup1/load-on-startup
/servlet!--在后面可以找到这个--servlet-mappingservlet-namedefault/servlet-nameurl-pattern//url-pattern/servlet-mapping!--如果子配置也存在/那么子会覆盖掉父的/--!--
他上面存在如下的说明
The default servlet for all web applications, that serves static
resources. It processes all requests that are not mapped to other
servlets with servlet mappings (defined either here or in your own
web.xml file). This servlet supports the following initialization
parameters (default values are in square brackets):
翻译
所有web应用程序的默认servlet用于静态资源它处理所有未映射到其他的请求带有servlet映射的servlet在此处或您自己的中定义web.xml文件此servlet支持以下初始化参数默认值在方括号中从上面知道所有web应用程序的默认servlet用于静态资源即不会拦截静态资源
由于子配置覆盖了父配置那么子/基本会拦截静态简单来说对于资源来说父/自身并不拦截即静态资源的拦截与父的/没有关系但是子的/会所以总体形成了/除了.jsp文件外其他的基本都会拦截包括静态资源即这样的说明了这里也说明为什么.jsp也不拦截实际上在没有操作覆盖父xml时默认子xml优先处理由于父xml中存在如下
--
servletservlet-namejsp/servlet-nameservlet-classorg.apache.jasper.servlet.JspServlet/servlet-classinit-paramparam-namefork/param-nameparam-valuefalse/param-value/init-paraminit-paramparam-namexpoweredBy/param-nameparam-valuefalse/param-value/init-paramload-on-startup3/load-on-startup/servlet!--后面有--servlet-mappingservlet-namejsp/servlet-nameurl-pattern*.jsp/url-patternurl-pattern*.jspx/url-pattern/servlet-mapping!--他进行了处理拦截这里就需要有一个注意即子的/不会拦截所以当我们没有写上子配置时一般来说默认/是不会拦截静态和会拦截.jsp写上子配置时会拦截静态和不会拦截.jsp这是因为子优先或者覆盖的操作简单来说父不会拦截静态和拦截.jsp而写上子那么就反过来使得拦截静态和不拦截.jsp更加简单的就是子的/自身就是存在拦截静态和不拦截.jsp从而形成反过来的操作至于为什么父的/不会拦截静态和拦截.jsp子的拦截静态和不拦截.jsp那就需要看tomcat和springmvc自身是如何处理的了这里了解即可
--在java资源文件夹下创建com.controller包然后创建DisController类
package com.controller;import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;import java.util.Date;/****/
Controller
RequestMapping(/demo)
public class DisController {RequestMapping(handle01)public ModelAndView handle01() {Date date new Date(); //服务器时间//返回服务器时间到前端页面ModelAndView modelAndView new ModelAndView();modelAndView.addObject(date, date);modelAndView.setViewName(success);return modelAndView;}
}//也可以这样
//放入参数默认给你创建其中里面的Model也行虽然参数为Model时只会是数据而非视图public ModelAndView handle01(ModelAndView modelAndView) {Date date new Date(); //服务器时间//返回服务器时间到前端页面modelAndView.addObject(date, date);modelAndView.setViewName(success);return modelAndView; //手动的返回数据和视图给前端控制器来处理}
在WEB-INF文件夹下创建jsp目录然后创建如下success.jsp文件
% page contentTypetext/html;charsetUTF-8 languagejava %
htmlheadtitleTitle/title/headbody%-- ${ date }隔开也行单纯不在单纯之间的空格是不算的 --%跳转成功服务器时间是${date} /body
/html
在资源文件夹下加上springmvc.xml文件
beans xmlnshttp://www.springframework.org/schema/beansxmlns:mvchttp://www.springframework.org/schema/mvcxmlns:contexthttp://www.springframework.org/schema/contextxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsd!--扫描--context:component-scan base-packagecom.controller/!--配置视图解析器--bean idviewResolverclassorg.springframework.web.servlet.view.InternalResourceViewResolver!--没有在前面加上/那么就是考虑转发由于基本是直接的访问所以这里不会出现问题前提是没有多级的因为只是改变最后一个路径否则建议直接加上/到项目路径下的操作这本质上是转发形成的操作而已--property nameprefix value/WEB-INF/jsp//propertyproperty namesuffix value.jsp/property/bean!--处理器映射器-处理器适配器--!--可以省略不写但是最好写上因为有些版本可能不能省略且他可以操作对应的json--mvc:annotation-driven/mvc:annotation-driven!--前面我们说明了加上子配置会拦截静态和不会拦截.jsp现在我们来解决拦截静态的问题--mvc:default-servlet-handler/!--具体情况是添加该标签配置后会在SpringMVC上下文中定义一个DefaultServletHttpRequestHandler对象
这个对象如同一个检查人员对进入DispatcherServletorg.springframework.web.servlet.DispatcherServlet的url请求进行过滤筛查如果发现是一个静态资源请求那么会把请求转由web应用服务器(tomcat) 默认的DefaultServlet前面父的org.apache.catalina.servlets.DefaultServlet来处理也就会可以考虑不拦截静态了如果不是静态资源请求那么继续由SpringMVC框架处理所以简单来说就是换一个控制器只是大多数我们都会使用SpringMVC的控制器来处理的而非使用父xml的控制器当然有时候我们也可以在原来的控制器中进行处理比如在拦截静态到找方法的过程中进行拦截然后直接的帮助我们操作对应的资源并返回如操作对应路径的资源操作视图虽然这样并不友好所以父的配置也是控制器只是他们是默认的而非我们手动的操作的即DispatcherServlet但是该方式一般存在局限即在某些时候他可能并不能使得WEB-INF安全文件该名称就是安全其他名称一般可不是里面的资源被访问
并且经过测试一般只能放在其web资源文件下即不能放入资源文件里面即他也是不行的一般也访问不了即访问不到即如这里的webapp文件夹下且不能再其文件里面的WEB-INF里面其他的可以随便的放有目录也是可行的只是这个目录不要是WEB-INF一般可能也包括其他的安全文件如可能也包括META-INF虽然你可以创建该文件使得与war包里面的该文件重合使得其里面多出一些文件但是却可能也访问不了的就可--!--为了解决上面目录限制的问题可能需要如下的操作--!--
mapping:放行的映射路径location静态资源所在的目录该方式优先于上面的方式
一个*表示当前路径两个*表示当前路径及其子路径
--mvc:resources mapping/resources/** locationclasspath:// !--该方式与上面的一起时该方式优先的处理并不是覆盖优先使用方式一除非我没有拦截到当然如果是什么拦截链那么可能会继续处理但是到那个时候会特别的说明否则我们一般默认为覆盖在前端css的覆盖差不多也是这样很明显这里并不是覆盖所以是一个链那么当mvc:resources配置无法找到对应的静态资源或者请求不匹配该路径时那么mvc:default-servlet-handler/配置就会起作用这里也了解即可--!--这也就可以访问资源文件夹里面的了首先mapping匹配你的路径由于存在开头的/那么去项目/路径那么若你访问http://localhost:8081/springmvc/resources/s.html由于是开头的/即是resources/s.html那么就是匹配了resources/**即放行放行后就可以去对应的文件中找了由于指定的文件为classpath:/代表项目根目录所以再该根目录里查找resources/s.html正好是资源文件里面的s.html那么将他的结果给你看这就是全部的流程
当然大多数mapping默认是在项目/下的所以加不加好像并无所谓所以这里删除开头/照样可能找resources/s.html而不是s.html所以注意即可一般的上面的说明只是建立在起始条件下的大多数/的省略只是绝对和相对路径的说明所以我们最好使用/在前面的情况http://localhost:8081/springmvc/resources/s.html如果出现省略还是一样的大多数是因为请求就是resources/s.html而非s.html所以在起始条件下上面的说明都是正确的无论是否省略都是如此但是不是在起始条件下就不是了所以需要特别的注意所以这里可以解释如下比如如下
mvc:resources mapping/js/** location/js/WEB-INF//中的mapping属性省略第一个斜杠/如果省略了第一个斜杠路径模式将被解释为相对路径在这种情况下js/**将被解释为相对于当前请求上下文路径的路径例如如果当前请求上下文路径为/myapp那么js/**将解析为/myapp/js/**
mvc:resources mapping/js/** location/js/WEB-INF//中的location属性省略第一个斜杠/如果省略了第一个斜杠路径将被解释为相对于当前请求上下文的相对路径在这种情况下js/WEB-INF/将被解释为相对于当前请求上下文路径的路径。同样如果当前请求上下文路径为/myapp那么js/WEB-INF/将解析为/myapp/js/WEB-INF/
省略第一个斜杠可能导致资源路径解析错误因为最终的路径可能不是预期的路径主要他是可变的因此为了确保路径的正确解析和映射通常建议在mvc:resources标签的mapping属性和location属性中开头都明确地使用斜杠/那么结尾的/呢实际上结尾的可以加可以不加实际上大多数配置中路径只会分为相对和绝对的特别的开头加上斜杠大多数都会是绝对路径且相对于项目来说虽然有些加多了可能到起始浏览器如重定向所以需要这样说对于项目里面的路径大多数框架都只会分为相对和绝对项目路径的绝对这里了解即可
--
/beans补充web.xml servletservlet-namedispatcherServlet/servlet-nameservlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-classinit-paramparam-namecontextConfigLocation/param-nameparam-valueclasspath:springmvc.xml/param-value/init-param/servlet假设项目是springmvc这个名称端口是8080那么启动服务器访问http://localhost:8080/springmvc/demo/handle01若出现数据代表操作成功
Spring MVC 请求处理流程前端控制器是唯一的一个servlet所以自然都会经过他自己看他的类就知道他继承或者实现谁了会到HttpServlet再到Servlet其Servlet算是最终的虽然还有其他的最终 流程说明
第一步用户发送请求⾄前端控制器DispatcherServlet
第⼆步DispatcherServlet收到请求调⽤HandlerMapping处理器映射器一般是map保存的
第三步处理器映射器根据请求Url找到具体的Handler后端控制器可以根据xml配置、注解进行查找因为查找所以是映射⽣成处理器对象及处理器拦截器如果有则⽣成一并返回DispatcherServlet他负责创建
第四步DispatcherServlet调⽤HandlerAdapter处理器适配器去调⽤Handler
第五步处理器适配器执⾏Handlercontroller的方法生成对象了这里相当于调用前面的handle01方法他负责调用
第六步Handler执行完成给处理器适配器返回ModelAndView即处理器适配器得到返回的ModelAndView这也是为什么前面我们操作方法时是可以直接操作他并返回的而返回给的人就是处理器适配器就算你不返回那么处理器适配器或者在之前即他们两个中间可能会进行其他的处理来设置ModelAndView并给处理器适配器
第七步处理器适配器向前端控制器返回 ModelAndView因为适配或者返回数据所以是适配ModelAndView 是SpringMVC 框架的一个 底层对 象包括 Model 和 View
第⼋步前端控制器请求视图解析器去进行视图解析根据逻辑视图名来解析真正的视图加上前后的补充即前面的配置视图解析器
第九步视图解析器向前端控制器返回View
第⼗步前端控制器进行视图渲染就是将模型数据在 ModelAndView 对象中填充到 request 域改变了servlet最终操作servlet来进行返回
第⼗一步前端控制器向用户响应结果jsp的
即可以理解请求找路径并返回123给路径让其判断路径并返回且获得对应对象4567变成参数解析如拼接 进行转发89然后到jsp10最后渲染11
Spring MVC 九⼤组件
/*
1HandlerMapping处理器映射器
HandlerMapping 是用来查找 Handler 的也就是处理器具体的表现形式可以是类类的形式可以百度这里就不说明了虽然最终都是方法的执行只是类的形式可能需要多个类或者一个类具体情况看当时的操作方式只是现在这种一般不会使用了所以了解即可一般需要实现implements org.springframework.web.servlet.mvc.Controller接口他里面的那个方法一般就是操作的方法即类似于Handler也可以是方法⽐如标注了RequestMapping的每个方法都可以看成是一个HandlerHandler负责具体实际的请求处理在请求到达后HandlerMapping 的作用便是找到请求相应的处理器Handler 和 Interceptor或者拦截的东西虽然是对应的他主要的作用是找到具体的类并创建他的对象
2HandlerAdapter处理器适配器
HandlerAdapter 是一个适配器因为 Spring MVC 中 Handler 可以是任意形式的只要能处理请求即可但是把请求交给 Servlet 的时候由于 Servlet 的方法结构都是doService(HttpServletRequest req,HttpServletResponse resp)形式的这里的doService应该是doxxx要让固定的 Servlet 处理方法调用 Handler 来进行处理便是 HandlerAdapter 的职责他主要的作用是利用映射器创建的对象来进行调用大多数方法里面的参数设置的值就是他来完成的
3HandlerExceptionResolver
HandlerExceptionResolver 用于处理 Handler 产⽣的异常情况它的作用是根据异常设置ModelAndView之后交给渲染方法进行渲染渲染方法会将 ModelAndView 渲染成⻚⾯这也是为什么我们可以得到异常信息而非空的我们在浏览器上看到的异常信息都是处理好的否则没有任何数据出现主要看响应体当然除了一些框架或者软件自定义的处理浏览器或者协议之间也存在默认的处理比如在服务器发生报错时可能在浏览器中会出现无法访问此网站这个信息默认的
4ViewResolver
ViewResolver即视图解析器用于将String类型的视图名和Locale解析为View类型的视图只有一个resolveViewName()方法两个参数视图名和Locale从方法的定义可以看出Controller层返回的String类型视图名viewName 最终会在这⾥被解析成为ViewView是用来渲染⻚⾯的也就是说它会将程序返回的参数和数据填⼊模板中⽣成html⽂件如jsp的ViewResolver 在这个过程主要完成两件事情
ViewResolver 找到渲染所用的模板第一件⼤事和所用的技术第⼆件⼤事其实也就是找到视图的类型如JSP并填⼊参数默认情况下Spring MVC会⾃动为我们配置一个InternalResourceViewResolver是针对 JSP 类型视图的
5RequestToViewNameTranslator
RequestToViewNameTranslator 组件的作用是从请求中获取 ViewNameViewName就是值View是拼接后的视图因为 ViewResolver 根据ViewName 查找 View但有的 Handler 处理完成之后没有设置 View或者说也没有设置 ViewName便要通过这个组件从请求中查找ViewName默认情况下当我们没有给出视图名时会将请求参数一般是整个请求路径即RequestMapping对应的这个参数通常并不是方法的参数方法参数和请求参数是不同的并且这里也通常不代表给出的请求参数所以在某种情况下我们建议使用请求路径来代表这里即会将请求路径作为拼接参数作为拼接对象这个组件一般我们并没有使用通常使用在手动封装返回响应体时的处理具体可以百度这里我经过测试首先将视图解析器注释并且视图名也注释请求http://localhost:8080/springmvc/demo/handle01得到源服务器未能找到目标资源的表示或者是不愿公开一个已经存在的资源表示这是因为当没有视图操作时自然什么都不会出现自然会触发没找到浏览器与http中的处理中当没有数据返回时就会这样更何况这里SpringMVC进行处理了而不操作浏览器默认使得浏览器得到规定SpringMVC默认的错误页面这个时候你可以选择手动操作而不是进行拼接当然这里考虑的是拼接的当视图解析器不注释继续访问这个得到[/WEB-INF/jsp/demo/handle01.jsp]未找到也就是说他默认将请求的URL即demo/handle01作为拼接的视图名正好是找到该方法的具体URL而不是最后的handle01正好对应于当我们没有给出视图名时会将请求参数作为拼接对象
但是也存在这样的操作
如果是这样的
Controller
RequestMapping(/test)
public class upload {RequestMapping(handle11)public String handle11(ModelAndView modelAndView) {System.out.println(1);Date date new Date();modelAndView.addObject(date, date);return success.jsp;}}那么我们可以发现他有视图名称但是对应的结果却是在项目里面的xxx/test/success.jspxx代表项目名称当我们注释掉前面的test访问对应的url会得到在xxx/success.jsp所以实际上视图本身也会存在路径的问题在这里我们可以知道视图与类上的路径是有一定联系的而没有视图时则与全部路径有联系这里再67章博客我们并没有进行说明所以需要注意
6LocaleResolver
ViewResolver 组件的 resolveViewName 方法需要两个参数一个是视图名一个是 Locale中文意思一般是区域LocaleResolver 用于从请求中解析出 Locale⽐如中国 Locale 是 zh-CN用来表示一个区域这个组件也是 i18n 的基础一般操作默认所以前面的方法中我们并没有进行处理
7ThemeResolver
ThemeResolver 组件是用来解析主题的主题是样式、图⽚及它们所形成的显示效果的集合Spring MVC 中一套主题对应一个 properties⽂件⾥⾯存放着与当前主题相关的所有资源如图⽚、CSS样式等创建主题⾮常简单只需准备好资源然后新建一个主题名.properties并将资源设置进去放在classpath下之后便可以在⻚⾯中使用了SpringMVC中与主题相关的类有ThemeResolver、ThemeSource和ThemeThemeResolver负责从请求中解析出主题名ThemeSource根据主题名找到具体的主题其抽象也就是Theme可以通过Theme来获取主题和具体的资源这里可以选择百度查看所以了解即可
8MultipartResolver
MultipartResolver 用于上传请求通过将普通的请求包装成 MultipartHttpServletRequest 来实现MultipartHttpServletRequest 可以通过 getFile() 方法 直接获得⽂件如果上传多个⽂件还可以调用 getFileMap()方法得到MapFileNameFile这样的结构MultipartResolver 的作用就是封装普通的请求使其拥有⽂件上传的功能
9FlashMapManager
FlashMap 用于重定向时的参数传递⽐如在处理用户订单时候为了避免重复提交可以处理完post请求之后重定向到一个get请求这个get请求可以用来显示订单详情之类的信息这样做虽然可以规避用户重新提交订单的问题但是在这个⻚⾯上要显示订单的信息这些数据从哪⾥来获得呢因为重定向时没有传递参数这一功能的如果不想把参数写进URL不推荐写入URL那么就可以通过FlashMap来传递只需要在重定向之前将要传递的数据写⼊请求可以通过ServletRequestAttributes.getRequest()方法获得的属性OUTPUT_FLASH_MAP_ATTRIBUTE中这样在重定向之后的Handler中Spring就会⾃动将其设置到Model中在显示订单信息的⻚⾯上就可以直接从Model中获取数据FlashMapManager 就是用来管理 FalshMap 的
总结
1HandlerMapping处理器映射器创建对象
2HandlerAdapter处理器适配器使用创建的对象的方法也操作好了参数
3HandlerExceptionResolver处理异常的
4ViewResolver操作视图的解析即视图解析器需要两个参数视图名和Locale
5RequestToViewNameTranslator从请求中拿取视图名
6LocaleResolver给视图需要的Locale一般存在默认所以通常不处理
7ThemeResolver主题了解即可
8MultipartResolver文件上传的处理
9FlashMapManager给重定向时的参数数据
这些组件或多或少可以放入到参数列表中但是通常是不行的具体可以自己测试SpringMVC会自动进行处理给出对象的但是有些组件是可以的就如前面的public ModelAndView handle01(ModelAndView modelAndView) {中可以写上ModelAndView一样同样的上面的组件可能也会存在一些操作他们的参数写上比如文件上传的MultipartHttpServletRequest可以作为参数来操作MultipartResolver当然还存在一个组件DispatcherServlet前端控制器然而他是主要的中转所以一般是基础操作而非将他放入组件的行列当然你也可以放入因为他的确也操作了组件功能所以这里也可以称为十大组件
10DispatcherServlet前端控制器
用户请求到达前端控制器它就相当于 MVC 模式中的 C他代表C的根本处理所以说成C也不为过DispatcherServlet 是整个流程控制的中心由它调用其它组件处理用户的请求DispatcherServlet 的存在降低了组件之间的耦合性因为只需要与他建立关系即可而不用都进行建立关系这样就不用将所有的组件串起来了实际上也可以简便的说明即存在三大组件
处理器映射器HandlerMapping
处理器适配器HandlerAdapter
视图解析器ViewResolver
若是四大组件
那么在上述三大组件的基础上加上
前端控制器DispatcherServlet对于文件上传大多数情况下原生算是吧我们利用DiskFileItemFactory即可import org.apache.commons.fileupload.disk.DiskFileItemFactory;依赖一般是dependencygroupIdcommons-fileupload/groupIdartifactIdcommons-fileupload/artifactIdversion1.2.1/version/dependency
而框架如MVC的我们利用MultipartResolver即可当然里面也是利用了依赖再spring-web里面
那么真正的原生是怎么处理的实际上这是考虑服务器中对http请求的数据拿取了这里了解即可若需要知道可以选择去造一下轮子网上找资源吧学习指点利用网络编程来完成文件的操作自然也就完成图片的操作了
*/请求参数绑定
也就是SpringMVC如何接收请求参数在原来的servlet中是这样接收的
String ageStr request.getParameter(age);
Integer age Integer.parseInt(ageStr);然而SpringMVC框架对Servlet的封装简化了servlet的很多操作所以SpringMVC在接收整型参数的时候直接在Handler一般是对应Controller所操作的类里面的标注了RequestMapping的方法方法中声明形参即可如
RequestMapping(xxx)
public String handle(Integer age) { //这样就行了
System.out.println(age);
}
//那么他是怎么变成的呢这里再67章博客有细节的说明看看即可然而细节我们并不需要太死磕的因为技术会更新那么细节可能也会发生改变并且这也不需要记住大多数自动变化会合理的所以看看即可所以这里的参数绑定在一定程度上可以认为是取出参数值绑定到handler⽅法的形参上
在前面我们也可以这样在DisController类中加上如下
RequestMapping(handle11)public String handle11(ModelAndView modelAndView) {Date date new Date();modelAndView.addObject(date, date);return success; //不会设置因为他是modelAndView他一般是给你创建的而不是拿取固有的}RequestMapping(handle21)public String handle21() {Date date new Date();ModelAndView modelAndView new ModelAndView();modelAndView.addObject(date, date);return success; //没有数据date因为是我们创建的}//下面这三个才会可以RequestMapping(handle31)public String handle31(ModelMap modelMap) {Date date new Date();modelMap.addAttribute(date, date);return success;}RequestMapping(handle41)public String handle41(Model model) {Date date new Date();model.addAttribute(date, date);return success;}RequestMapping(handle51)public String handle51(MapString,Object map) {Date date new Date();map.put(date, date);return success;}/*
执行访问看看结果从结果可以看到若没有返回ModelAndView的话那么使用他是不能得到数据的只能使用他里面的成员变量ModelMap来处理即我们只会看返回值返回整体就是他返回部分需要特别的给出部分而不是整体对象即需要这些ModelModelMapMap等等即他们就是部分即一般情况下我们一个线程可能只能操作一个ModelAndView完整的且当操作完即返回后那么自然清除如果你直接的返回说明你操作了他里面的视图那么这里只能是ModelMap需要完整的才行且需要是同一个且根据返回值来决定的这可能是内部的操作方式有可能是他一般是给你创建的而不是拿取固有的其他三个是拿取固有的所以了解即可当然也存在Model和map来替换ModelMap操作且Model或者ModelMap中他们保存的数据最终通常都是保存在request域里面的所以简单来说ModelModelMap最终都是操作map的所以可以直接使用map作为参数并且他们map里面的值最终会保存到request中你可以打印他们因为他们的对象我框架给的你可以选择看一看经过测试打印后他们的对象是一样的所以他们基本都是同一个对象只是操作方式不同而已最终都是相同的操作即操作map的并且最终给到request域中至此我们说明完毕在对应的return前面加上如下即可
System.out.println(modelMap);
System.out.println(modelMap.getClass());System.out.println(model);
System.out.println(model.getClass());System.out.println(map);
System.out.println(map.getClass());
对应我的打印结果是
{dateThu Jul 06 17:07:24 CST 2023}
class org.springframework.validation.support.BindingAwareModelMap{dateThu Jul 06 17:07:26 CST 2023}
class org.springframework.validation.support.BindingAwareModelMap{dateThu Jul 06 17:07:45 CST 2023}
class org.springframework.validation.support.BindingAwareModelMap
即对象的确是一个对象
并且你查找这个对象在里面看看可以发现他是其他的子类其中ModelMap和Model同级看如下
public class BindingAwareModelMap extends ExtendedModelMap {public class ExtendedModelMap extends ModelMap implements Model {public interface Model {public class ModelMap extends LinkedHashMapString, Object {public class LinkedHashMapK,Vextends HashMapK,Vimplements MapK,V
{
所以前面的
modelMap.addAttribute(date, date);
model.addAttribute(date, date);
map.put(date, date);
最终都是操作BindingAwareModelMap对象里面的对应方法且最终的结果都是操作map并且最终给到request域一般称为请求域因为一般是保存请求的信息的中使得可以在前面jsp中使用${date}获得具体为什么可以获得可以去52章博客查看
*/默认⽀持 Servlet API 作为方法参数
当需要使⽤HttpServletRequest、HttpServletResponse、HttpSession等原⽣servlet对象时直 接在handler⽅法中形参声明使用即可
我们在前面的controller包下创建TestController类
在这之前首先需要加上如下依赖 !--servlet坐标若不使用对应的类如HttpServletRequest的话可以不加--dependencygroupIdjavax.servlet/groupIdartifactIdjavax.servlet-api/artifactIdversion3.1.0/versionscopeprovided/scope/dependencyTestController类
package com.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Date;Controller
RequestMapping(/test)
public class TestController {RequestMapping(a1)public ModelAndView a1(HttpServletRequest request, HttpServletResponse response, HttpSession session) {String id request.getParameter(id);System.out.println(id);Date date new Date();ModelAndView modelAndView new ModelAndView();modelAndView.addObject(date, date);modelAndView.setViewName(success);System.out.println(request);System.out.println(response);System.out.println(session);return modelAndView;}
}
这里我们假设项目是springmvc的名称或者说映射的所以我们访问http://localhost:8081/springmvc/test/a1?id2查看显示和打印的结果
当然还有很多种情况这些基础我们到67章博客回顾即可但是这里补充一个就是布尔类型的我们再TestController类中加上如下
RequestMapping(a2)public ModelAndView a2(boolean id) {System.out.println(id);Date date new Date();ModelAndView modelAndView new ModelAndView();modelAndView.addObject(date, date);modelAndView.setViewName(success);return modelAndView;}访问http://localhost:8081/springmvc/test/a2?idtrue一般来说可以是truefalse10由于对应“后面的作为一个字符串数所以若是id“true”那么他这个字符串是true”注意是这个
String id true;
System.out.println(id);
id \true\;
System.out.println(id);/*
即idtrue就是String id true;
而idtrue就是id \true\;
这里要注意
*/所以由于idtrue是不能变成boolean的就如上面的第二个id不能直接变成boolean一样但是第一个是可以的这里需要注意
经过测试boolean的值有truefalse1代表true0代表false当然也可以是包装类因为对于null基本类型可能不行会报错这个时候我们大多使用包装类的且他默认给出的绑定通常就是包装类的所以前面的boolean就是拆箱得到的就会考虑null的情况而导致是否报错虽然这个报错可能是捕获或者抛出来处理的所以通常并不是空指针异常这里可以自己测试这里也了解即可前端的id和id基本都会使得id作为null在前端如input中的name没有id或者id的value为空串即“”一般也是null
对 Restful ⻛格请求⽀持
虽然大多数67章博客或者从他开始基本都说明了但是有些是比较重要的需要特别的再说明一次比如这个Restful ⻛格请求大多数我们需要这个来统一规划而非按照某些自己定义的规划这里来说明一下为什么会存在该风格以及一定要使用该风格吗
/*
Restful 风格的请求是一种设计风格用于构建基于 Web 的应用程序接口API它强调使用统一的、无状态的、可扩展的和可缓存的方式进行通信
Restful 风格的请求具有以下优点
1可读性强Restful 风格的 API 设计具有清晰的结构使用直观的 HTTP 方法GET、POST、PUT、DELETE和资源路径易于理解和使用一般是如下的情况
GET读取Read
POST新建Create
PUT更新Update
DELETE删除Delete
2可扩展性好Restful 风格的请求允许通过添加新的资源路径来扩展 API使得系统可以更容易地支持新的功能和端点
3可移植性强Restful 风格的请求使用标准的 HTTP 方法和状态码使得 API 可以在不同的平台和技术之间进行移植和互操作
4缓存支持Restful 风格的请求利用 HTTP 协议的缓存机制可以有效地利用缓存来提高性能和减少服务器负载
然而使用 Restful 风格的请求并不是强制性的在某些情况下非 Restful 风格的请求也可以满足特定的需求例如当需要传输大量数据、进行复杂的操作或需要遵循特定的业务规则时可能需要使用非 Restful 风格的自定义请求具体可以百度因为并不是存在上面的四种情况只是存在共性而已比如要读取非常多的情况并且参数非常多这个时候利用get是否好呢很明显我们可能是使用post的
总而言之使用 Restful 风格的请求通常是一个良好的设计选择尤其适用于构建基于 Web 的 API但在实际开发中根据项目需求和特定情况选择合适的请求风格是很重要的因为该风格是参照URL的而URL一般也存在限制对于需要指定参数一般都不会使用这个风格并非标准可以选择可以不选所以自然可以共存多种风格只是不建议在大项目中共存而已因为在大项目中一般需要统一一种方式并且可以完成需求的方式所以越原始但是比较不原始的处理方法有时是比较好的之所以我完全使用原生是因为太麻烦即得到比付出的要少的所以大多数情况下该风格也只是适用于平常项目而非复杂的项目实际上越没有花里胡哨该风格可以认为是花里胡哨的针对大项目来说是越友好的因为能够更好的处理细节
*/即Restful 是一种 web 软件架构⻛格它不是标准也不是协议它倡导的是一个资源定位及资源操作的风格比如你走迷宫有10条道路可以走到终点那么Restful就是比较先到达终点的那条道路即他只是一个好的方式而已并非一定按照他也就不谈标准或者协议了协议一般也是一个标准标准代表很多的意思通常代表我们按照指定的规则或者双方按照指定的规则进行处理
什么是 REST
REST英⽂Representational State Transfer简称 REST描述了一个架构样式的⽹络系统 ⽐如web 应用程序它⾸次出现在 2000 年 Roy Fielding 的博⼠论⽂中他是 HTTP 规范的主要编写者之一在目前主流的三种 Web 服务交互方案中REST 相⽐于 SOAPSimple Object Access protocol 简单对象访问协议以及 XML-RPC 更加简单明了⽆论是对 URL 的处理还是对 Payload 的编码REST 都倾向于用更加简单轻量的方法设计和实现值得注意的是 REST 并没有一个明确的标准⽽更像 是一种设计的⻛格它本身并没有什么实用性其核⼼价值在于如何设计出符合 REST ⻛格的⽹络接⼝比如合理的请求合理的状态码等等甚至你可以加上返回一些提示信息这些请求和返回的数据设计过程就是该风格需要处理的而再这个基础之上我们使用GETPOSTPUTDELETE和来处理请求而REST风格基本就是上面的结合
Restful 的优点
它结构清晰、符合标准、易于理解、扩展方便所以正得到越来越多⽹站的采用
Restful 的特性
资源Resources⽹络上的一个实体或者说是⽹络上的一个具体信息它可以是一段⽂本、一张图⽚、一⾸歌曲、一种服务总之就是一个具体的存在可以用一个 URI统 一资源定位符指向它每种资源对应一个特定的 URI 要获取这个资源访问它的 URI 就可以因此URI 即为每一个资源的独一⽆⼆的识别符
表现层Representation把资源具体呈现出来的形式叫做它的表现层 Representation⽐ 如⽂本可以用 txt 格式表现也可以用 HTML 格式、XML 格式、JSON 格式表现甚⾄可以采用⼆进 制格式
状态转化State Transfer每发出一个请求就代表了客户端和服务器的一次交互过程比如HTTP 协议他是一个⽆状态协议即所有的状态都保存在服务器端因此如果客户端想要操作服务器 必须通过某种⼿段让服务器端发⽣状态转化State Transfer⽽这种转化是建⽴在表现层 之上的所以就是 “表现层状态转化” 具体说 就是 HTTP 协议⾥⾯四个表示操作方式的动词GET 、POST 、PUT 、DELETE它们分别对应四种基本操作GET 用来获取资源POST 用来新建资源PUT 用来更新资源DELETE 用来删除资源如get变成了获取资源这是一种状态转化一般我们常用的是get和post分别对访问url进行不同的处理即状态转化处理使得他们各有其职位即总体来说首先我们在访问url之前首先通过状态转化确定需要一些什么东西需要干什么特别的如加上参数然后进行表现层的访问最后拿取资源
RESTful 的示例
这里我们将RESTful简称为rest
没有rest的话原有的url设计一般是http://localhost:8080/user/queryUserById?id3
上面不难看出url中定义了动作操作因为queryUserById一看就知道是查询用户的id所以参数具体锁定到操作的是谁
有了rest⻛格之后那么设计应该是如下
由于rest中认为互联⽹中的所有东⻄都是资源既然是资源就会有一个唯一的uri标识它代表它如
http://localhost:8080/user/3 代表的是id为3的那个用户记录资源要注意由于侧重点代表的是资源所以这个整体就是这样的处理一般rest也默认这个3是查询id的主要是数据库默认主键基本都是id作为名称所以现在的规范基本都是如此
可以看出的确贯彻url指向资源这种情况由于默认的存在我们一般就省略了queryUserById?id3这就是rest风格的处理方式
既然锁定资源之后如何操作它呢常规操作就是增删改查
根据请求方式不同代表要做不同的操作
get查询获取资源
post增加新建资源
put更新
delete删除资源
在请求方式不同的情况之下rest⻛格带来的直观体现就是传递参数方式的变化参数可以在url中了以url作为资源指向并且自身作为参数作为其风格与传统的主要区别在于是url被作为参数的而不用操作放在?后面如queryUserById?id3
示例
/account/1 HTTP GET前面的url就不给出了虽然这个形式并非请求体的标准形式 得到 id 1 的 account
/account/1 HTTP DELETE删除 id 1 的 account
/account/1 HTTP PUT更新 id 1 的 account
请求头的标准形式http的协议标准一般是这样的POST /account/1 HTTP/1.1
总结
URL资源定位符通过URL地址去定位互联⽹中的资源抽象的概念⽐如图⽚、视频、app服务 等
RESTful ⻛格 URL互联⽹所有的事物都是资源要求URL中只有表示资源的名称没有动词
RESTful⻛格资源操作使⽤HTTP请求中的method⽅法put、delete、post、get来操作资源分别对 应添加、删除、修改、查询不过一般使用时还是 post 和 getput 和 delete⼏乎不使用在前端中提交表单时一般也只会设置get和post或者只能这样的设置具体解决方式在后面会给出的具体注意即可一般由于action中写上不存在的就如putdelete他们是不能设置的也就说明不存在像不存在的一律都会默认为get可以自己测试一下就知道了
虽然前端表单不能设置但并不意味着后端不能设置因为这些名称除了get其他的也只是名称上的不同而已存放的数据操作基本一致
RESTful ⻛格资源表述可以根据需求对URL定位的资源返回不同的表述也就是返回数据类型⽐如XML、JSON等数据格式
Spring MVC ⽀持 RESTful ⻛格请求具体讲的就是使用 PathVariable 注解获取 RESTful ⻛格的请求URL中的路径变量
从上面你可能会感受到post前面说明了是增加为什么这里说明是更新呢实际上post和put都是添加和更新的意思所以他们在该rest风格下基本只是逻辑的不同具体怎么使用看你自己但是一般情况下put是更新post是添加
现在我们来操作一下示例
先看一个图 首先在webapp目录下加上test.jsp
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
body%--查询指定id为15的数据--%
a hrefdemo/handle/15rest_get测试/a%--进行添加添加不需要指定什么--%
form methodpost actiondemo/handleinput typetext nameusername/input typesubmit value提交rest_post请求/
/form%--更新那么自然需要一个数据也就是list可以认为是姓名如改成这个姓名当然后面还可以继续的补充当然有时候并不操作put所以需要加上list才可来防止后面请求的冲突如后面的删除--%
form methodpost actiondemo/handle/15/lisiinput typehidden name_method valueput/input typesubmit value提交rest_put请求/
/form
%--删除指定的id的数据--%
form methodpost actiondemo/handle/15input typehidden name_method valuedelete/input typesubmit value提交rest_delete请求/
/form/body
/html
然后再controller包下添加DemoController类
package com.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;import java.util.Date;Controller
RequestMapping(/demo)
public class DemoController {//对应与查询指定id为15的数据RequestMapping(value /handle/{id}, method {RequestMethod.GET}) //后面的请求方式也会当成拦截成功的条件的否则相当于不存在但是若对应的路径存在只是请求方式不同那么会报错而非不存在这里需要注意的即总体流程中首先整体考虑路径加上请求都正确然后局部考虑路径正确请求不正确最后路径考虑路径不正确这个时候无论你请求是否正确都没有用的因为路径首先是需要满足的那么判断流程是首先查看路径是否存在若存在那么查看请求是否正确其中若路径没有那么没有找到若路径找到了但是请求不正确那么报错默认如果不写请求的话一般都是get的所以这就是为什么请求是正确而路径是存在的意思public ModelAndView handleGet(PathVariable(id) Integer id) {Date date new Date();ModelAndView modelAndView new ModelAndView();modelAndView.addObject(date, date);modelAndView.setViewName(success);return modelAndView;}//对应与进行添加添加不需要指定什么RequestMapping(value /handle, method {RequestMethod.POST})public ModelAndView handlePost(String username) {System.out.println(username);Date date new Date();ModelAndView modelAndView new ModelAndView();modelAndView.addObject(date, date);modelAndView.setViewName(success);return modelAndView;}//对应与更新那么自然需要一个数据也就是list可以认为是姓名如改成这个姓名当然后面还可以继续的补充当然有时候并不操作put所以需要加上list才可来防止后面请求的冲突如后面的删除RequestMapping(value /handle/{id}/{name}, method {RequestMethod.PUT}) //记得改成POSTpublic ModelAndView handlePut(PathVariable(id) Integer id, PathVariable(name) String username) {System.out.println(id);System.out.println(username);Date date new Date();ModelAndView modelAndView new ModelAndView();modelAndView.addObject(date, date);modelAndView.setViewName(success);return modelAndView;}//对应与删除指定的id的数据RequestMapping(value /handle/{id}, method {RequestMethod.DELETE}) //记得改成POSTpublic ModelAndView handleDelete(PathVariable(id) Integer id) {System.out.println(id);Date date new Date();ModelAndView modelAndView new ModelAndView();modelAndView.addObject(date, date);modelAndView.setViewName(success);return modelAndView;}}
当然为了保证编码的情况我们需要在web.xml中加上这个在67章博客说明了 !--配置中文过滤器--filterfilter-nameCharacterEncodingFilter/filter-namefilter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-classinit-paramparam-nameencoding/param-nameparam-valueUTF-8/param-value !--这个UTF-8一般不能写成/*因为他是需要将拦截的进行设置编码的--/init-param/filterfilter-mappingfilter-nameCharacterEncodingFilter/filter-nameurl-pattern/*/url-pattern/filter-mapping继续测试一下操作加上他和不加上他的情况看看输入中文后的结果当然确保重新启动的是改变的代码可以选择删除对应的编译后的处理来看看结果即可
当然若get乱码出现问题那么说明其tomcat版本比较低通常需要tomcat配置具体可以百度一般在tomcat的server.xml中进行修改而出现这种情况一般代表tomcat都存在默认编码编码是必然存在的只是有些默认并不是utf-8而已高版本中get一般都是utf-8而低版本一般就不是大多数都可能是iso8859具体名称可能不是这样这里只是简称
我们回到之前的jsp中可以看到后面的更新put和删除delete都有一个隐藏域并且name都是_method他一般是解决表单不能操作put和delete的一种方式因为我们可以通过他这个名称以及对应的值了判断进行某些处理但是一般需要在到达方法之前进行处理所以一般需要过滤器而我们并不需要监听什么所以过滤器即可并且这个过滤器springmvc已经提供给我们了所以我们使用即可
现在我们回到web.xml加上如下
!--配置springmvc请求方式转换过滤器会检查请求参数中是否有_method名称的参数如果有设置请求方式为其对应的值如果设置的是不存在的那么默认为get前面也提到过当然可能也会进行报错这可能会因为springmvc版本而发生不同的处理--filterfilter-namehiddenHttpMethodFilter/filter-namefilter-classorg.springframework.web.filter.HiddenHttpMethodFilter/filter-class/filterfilter-mappingfilter-namehiddenHttpMethodFilter/filter-nameurl-pattern/*/url-pattern/filter-mapping然后我们回到之前的DemoController类中将//记得改成POST中我们已经改变的改变回去然后操作会发现测试成功了说明我们配置成功并且解决了表单只能操作getpost不能操作put和delete的问题当然如果put操作delete自然与put操作get是类似的错误但是我们访问时他只是打印了并没有出现响应信息还是报错的首先是找到没有找到的话那么没有找到的报错先出现自然不会出现这个报错了报错在没有手动处理的情况下try可是不会操作后续的这是为什么因为只有当前的我们的请求是进行处理的而转发并不会进行处理但是他是在内部进行的所以错误信息也是不同的为了验证转发不行我们可以修改一下修改如下 //对应与删除指定的id的数据ResponseBodyRequestMapping(value /handle/{id}, method {RequestMethod.DELETE})public String handleDelete(PathVariable(id) Integer id) {System.out.println(id);Date date new Date();ModelAndView modelAndView new ModelAndView();modelAndView.addObject(date, date);modelAndView.setViewName(success);System.out.println(1);return 11;}这样就会操作直接的数据返回若你访问后出现了数据就说明的确是转发不能操作了即不会处理了
当然后面的一些知识可能也在对应67章博客开始到68章即可就大致学习过但是这里我们需要总结以及完成一些常用工具类的处理这个在以后开发中也是有巨大作用的
Ajax Json交互
交互两个方向
1前端到后台前端ajax发送json格式字符串后台直接接收为pojo即类的参数使用注解RequstBody
2后台到前端后台直接返回pojo对象前端直接接收为json对象或者字符串使用注解ResponseBody
什么是 Json
Json是一种与语⾔⽆关的数据交互格式就是一种字符串只是用特殊符号{}内表示对象、[]内表示数组内是属性或值表示后者是前者的值比如
{“name”: “Michael”}可以理解为是一个包含name为Michael的对象
[{“name”: “Michael”},{“name”: “Jerry”}]就表示包含两个对象的数组
ResponseBody注解
responseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后写⼊到response对象的body区通常用来返回JSON数据或者是XML数据注意在使用此注解之 后不会再⾛视图处理器⽽是直接将数据写⼊到输⼊流中他的效果等同于通过response对象输出指定 格式的数据这个在68章博客中有提到过
分析Spring MVC 使用 Json 交互
我们重新创建一个项目之前的不操作了创建如下 依赖如下
packagingwar/packagingdependenciesdependencygroupIdorg.springframework/groupIdartifactIdspring-webmvc/artifactIdversion5.1.5.RELEASE/version/dependency!--对应操作json需要的包如果不需要的话可以删除但是加上也没有关系--dependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-databind/artifactIdversion2.9.8/version!--这个必须要后面的可以不写后面两个但最好写上防止其他情况--/dependencydependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-core/artifactIdversion2.9.8/version/dependencydependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-annotations/artifactIdversion2.9.0/version/dependency/dependenciesweb.xml文件
?xml version1.0 encodingUTF-8?
web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaeexmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsdversion4.0servletservlet-namedispatcherServlet/servlet-nameservlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-classinit-paramparam-namecontextConfigLocation/param-nameparam-valueclasspath:spring-mvc.xml/param-value/init-paramload-on-startup2/load-on-startup/servletservlet-mappingservlet-namedispatcherServlet/servlet-nameurl-pattern//url-pattern/servlet-mapping
/web-appspring-mvc.xml
beans xmlnshttp://www.springframework.org/schema/beansxmlns:mvchttp://www.springframework.org/schema/mvcxmlns:contexthttp://www.springframework.org/schema/contextxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdcontext:component-scan base-packagecom.test/mvc:annotation-driven/mvc:annotation-driven
/beansJsonController
package com.test;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;Controller
public class JsonController {ResponseBodyRequestMapping(json)public String json() {return 1;}
}
启动服务器访问若出现数据代表操作成功
我们引入jq的js在webapp目录下的WEB-INF中创建js文件然后将下面下载的js放入进去
链接https://pan.baidu.com/s/1nRPfSHYOFx9pMHDMSsN0hg
提取码alsk
然后我们在mvc的xml中不是web.xml是前面的spring-mvc.xml加上如下
!--保证被访问即WEB-INF可以访问所以操作指定--mvc:resources mapping/js/** location/WEB-INF/js//然后在com目录下创建entity包然后在里面创建User类这里顺便将test目录修改成controller吧
package com.entity;public class User {String id;String username;public String getId() {return id;}public void setId(String id) {this.id id;}public String getUsername() {return username;}public void setUsername(String username) {this.username username;}Overridepublic String toString() {return User{ id id \ , username username \ };}
}
在test这里修改成了controller了加上如下 ResponseBodyRequestMapping(ajax)public ListUser ajax(RequestBody ListUser list){System.out.println(list);return list;}然后我们在webapp目录下创建如下的文件index.jsp
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
body!--这个路径可以选择远程的路径百度上查一查就行了
比如script srchttps://code.jquery.com/jquery-3.6.0.min.js/script
--
script srcjs/jquery-3.4.1.min.js/script
button idbtnajax提交/button
script$(#btn).click(function(){let url ajax;let data [{id:1,username:张三},{id:2,username:李四}]$.ajax({type:POST,//大小写可以忽略url:url,data:data,contentType:application/json;charsetutf-8, //如这里success:function (data) {console.log(data);}})})
/script
/body
/html
对应的路径我们不加上斜杠开头因为一般代表是端口开始的若测试后对应前端打印的信息得到了那么代表操作成功注意一般情况下默认ajax只会传递寻常get以及post的键值对而不会将其数据变成字符串这样会使得后端并不能进行解释如对应的注解来解释所以一般需要设置一些请求头“如上面的//如这里”
到这里我决定给出很多细节了因为这些细节大多数情况下可能并没有博客会选择将所有情况进行处理以及存在对应视频的说明所以在进行类似变化时大多数人是迷糊的而只是记住要这样做细节分别是两个方面
ajax或者说js前端访问后端除了标签一般就是使用ajax来访问了当然还有其他的即有其他的类似ajax的底层源码处理但是ajax我们使用的最多所以这里说明ajax以及文件的处理其中文件的处理我会编写几个工具类来给你进行使用当然可能也只是一个方法或者也不会进行提取编写所以了解即可并且文件也会通过ajax来进行处理的这里先进行注意
由于有很多的细节现在我决定将原生的servlet和现在的mvc框架一起来测试来使得更加的理解这些首先有多种情况这里我决定来测试测试
这里一般我们测试如下
首先我们会使用原生ajax以及框架ajax的请求来处理并且也会使用一些固定标签来处理某些文件的操作包括ajax即js来操作当然这里也会分成两个操作即获取和提交在后面会说明的
上面是前端需要注意的操作而后端需要注意的则是他们的后台完成分别又由原生servlet和mvc来进行处理
所以通过上面的操作我们应该存在如下
get请求操作加上参数这个处理完后那么后面的参数处理无论是get还是post都能明白了所以也基本只会测试一次get操作的请求头get的单文件多文件以及文件夹处理
post请求post操作的请求头post的单文件多文件以及文件夹处理
这10种情况分别由这四种组合分别处理原生ajax以及原生servlet原生ajax以及mvc框架ajax以及原生servlet框架ajax以及mvc
这加起来有40种操作方式这里都会进行给出其中会给出通过标签获取和提交通过标签获取和js提交通过js获取和标签提交通过js获取和提交等等对文件的细节处理由于这个获取提交只要我们操作一次就能明白了当然他们可能也会因为浏览器的原因比如安全或者说源码使得不能进行处理所以在后面说明时就会进行在过程中只会处理一下而不会多次处理或者只是单纯的说明一下
首先是原生的ajax的请求和原生servlet后台的处理学习并看后面的话这里建议先学习学习50章博客
这里我们将对应的10种情况都进行操作出来
这里我们需要知道ajax经常改变的有哪些首先是请求方式这里我们以getpost为主因为其他的put和delete本质上也是操作post的参数类型存放的虽然他们在后端都是一样的包括get还有ajax规定的url以及data等等当然他们并不是非常重要最重要并且没有系统学习的是请求体和响应体的设置这里就是大局的了而不是对数据这里我们将请求信息以请求体来进行说明因为大多数参数就是在请求体的具体自行辨别是整体还是局部这里需要重要的考虑这里也会顺便以后台的方式来说明get和post
由于测试非常多所以这里我们还是创建一个项目他用来操作原生ajax以及原生servlet的由于是原生的那么我们就操作创建如下当然如果你只是创建一个maven并且没有指定其他的组件那么一般需要你引入相关的servlet依赖这里我们就操作maven那么首先引入依赖
packagingwar/packaging
dependenciesdependency!--要记得Servlet一般也是框架需要jar包的只是我们创建web项目时自动给出对应的包了或者打包到tomcat中tomcat会隐藏的给出对应的这个包的内容虽然版本可能不同但是也基本包含了里面该有的内容所以如果是发版打包发布到服务器的话那么可以选择删除或者配置生效范围比如在开发环境使用等等所以看起来并没有我们手动的导入不是maven时servlet再怎么操作最终还是操作底层API的具体可以选择到27章博客中最后的地方查看不使用servlet实现的网页访问
虽然大多数情况下我们认为这个包是给httpHttpServlet但是其实还是Servlet注意即可
--groupIdjavax.servlet/groupIdartifactIdjavax.servlet-api/artifactIdversion4.0.1/version/dependency/dependencies创建的项目如下 其中GetRequest内容如下原生servlet当然这是相对原生的因为如果还要底层的话那么就不是servlet了所以原生servlet就是这里的代码那样的话你可以参照27章博客最后的内容
package com.test.controller;import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;public class GetRequest implements Servlet {//注意他们只是一个补充而言真正的初始化其实已经操作的所以你只需要知道他们只是一个执行顺序而言只是由于名称的缘故所以我们一般会将具体需求进行分开处理//大多数框架的什么初始化都是这样的说明是一样的意思//void init(ServletConfig config)由servlet容器调用以向servlet指示servlet正在被放入服务中Overridepublic void init(ServletConfig servletConfig) throws ServletException {System.out.println(初始化);}//ServletConfig getServletConfig()返回ServletConfig对象该对象包含此servlet的初始化和启动参数Overridepublic ServletConfig getServletConfig() {return null;}//void service(ServletRequest req,ServletResponse res)由servlet容器调用以允许servlet响应请求主要操作get和post的//这里才是post和get真正的调用者其实大多数我们在servlet中我们一般会继承过一个类HttpServlet他里面的doGet和doPost最终是这个执行的你可以看看他里面的方法就知道了后面会进行模拟的//这里我会进行模拟的根据这里的说明可以认为mvc也是如此只是在下层继续封装了后面手写时会知道的最终还是会到这里的Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {System.out.println(1);servletResponse.setContentType(text/html;charsetUTF-8);PrintWriter writer servletResponse.getWriter();writer.write(h1 11 /h1);}//String getServletInfo()返回有关servlet的信息如作者、版本和版权Overridepublic String getServletInfo() {return null;}//void destroy()由servlet容器调用以向servlet指示该servlet正在退出服务Overridepublic void destroy() {System.out.println(正在销毁中);}
}
web.xml如下
?xml version1.0 encodingUTF-8?
web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaeexmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsdversion4.0servlet!--对应大多数的参数值最好驼峰对于一些api来说都是会进行一些特别处理的所以idea一般也会存在这样api中有与idea进行关联的东西或者代码使得idea会操作提示但是大多数并非这样而是其分析引擎完成的这需要的技术非常有门槛所以了解即可一般编写一个高效准确的代码分析引擎需要全面的计算机科学知识包括编程语言、编译原理、静态分析、数据结构、算法、软件工程等idea会有特别的显示的虽然并不会影响运行--servlet-nameGetRequest/servlet-nameservlet-classcom.test.controller.GetRequest/servlet-class/servletservlet-mappingservlet-nameGetRequest/servlet-nameurl-pattern/get/url-pattern/servlet-mapping
/web-appindex.jsp谁说一定要分离的我就使用jsp因为只是测试而已具体的代码不还是与html一样的
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
body
1
/body
/html
在启动后在项目路径后面加上get访问后若出现对应的11h1标签形式的说明我们创建项目并测试成功
这下我们环境操作完毕现在来进行原生ajax的处理
这里我们修改index.jsp或者加上如下代码
input typebutton οnclickrun1() value原生js实现Ajax的get请求br
scriptfunction run1(){let x;//判断浏览器类型并创建核心对象if(window.XMLHttpRequest){x new XMLHttpRequest();}else{x new ActiveXObject(Microsoft.XMLHTTP);}//建立连接get方式资源路径是否异步//GET大小写忽略x.open(GET,get,false); //关于请求路径是否加上/可以选择参照67章博客中对/的说明可以全局搜索如action或者超链接或者类似的等等不使用时一般就是相action与这里是一样的或者说类似的这里是不加上/的//提交请求这里可以选择修改上面open的值再来访问x.send();//等待响应获取上面指定地址的响应结果x.onreadystatechangefunction (){//判断提交状态和响应状态码是否都ok//请求已完成且响应已就绪200响应状态完成if(x.readyState 4 x.status 200){console.log(第一个)//将响应的信息进行赋值let text x.responseText;console.log(text)}}console.log(下一个)x.open(geT,get?usernametom,false);x.send();x.onreadystatechangefunction (){if(x.readyState 4 x.status 200){console.log(第二个)let text x.responseText;console.log(text)}}}
/script启动点击访问出现如下前端的前端的出现了那么后端的就不用看了所以这里就不给出了后续也是如此
/*
下一个
第一个
h111/h1
*/为什么第二个没有显示这是因为当一个回调被设置后就不能继续被设置了并且在回调过程中自然解除了同步false所以根据顺序操作一般回调比较慢所以是下一个先打印这种细节是没有必要的所以我们再次的修改等待是send中进行的这个要注意所以我们修改如下
function run1(){let x;//判断浏览器类型并创建核心对象if(window.XMLHttpRequest){x new XMLHttpRequest();}else{x new ActiveXObject(Microsoft.XMLHTTP);}//建立连接.get方式资源路径是否异步//GET大小写忽略x.open(GET,get,false);//提交请求这里可以选择修改上面open的值再来访问x.send();let text x.responseText;console.log(text)}启动测试查看打印信息前端的显示如下
/*
h111/h1
*/现在我们给他加上参数分别需要测试如下几种情况这个只需要测试一次就行了前面也说明了这样的情况
1x.open(GET,get?,false);
2x.open(GET,get??,false);
3x.open(GET,get?,false);
4x.open(GET,get?,false);
5x.open(GET,get?,false);
6x.open(GET,get? 1,false);
7x.open(GET,get? ,false);
8x.open(GET,get?,false);
9x.open(GET,get?name,false);
10x.open(GET,get?name,false);
11x.open(GET,get?name ,false);
12x.open(GET,get?name,false);
13x.open(GET,get?name,false);
14x.open(GET,get?name1,false);
15x.open(GET,get?name1?,false);
16x.open(GET,get?name1,false);
17x.open(GET,get?name1pass,false);
18x.open(GET,get?name1pass,false);
19x.open(GET,get?nae1pass,false);
20x.open(GET,get,false);
21x.open(GET,getname,false);
22x.open(GET,get?name1name2,false);这22种情况是在前端进行处理的因为后端只是找参数得值那么后端则进行补充 Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {String name servletRequest.getParameter(name);String pass servletRequest.getParameter(pass);String p servletRequest.getParameter(?);String a servletRequest.getParameter();String b servletRequest.getParameter();String c servletRequest.getParameter( );System.out.println(name , pass , p , a , b , c);System.out.println(1);servletResponse.setContentType(text/html;charsetUTF-8);PrintWriter writer servletResponse.getWriter();writer.write(h1 11 /h1);}经过测试上面的22种情况分别是
/*
1null,null,null,null,null,null
2null,null,,null,null,null
3null,null,null,null,null,null
4null,null,null,null,null,null
5null,null,null,null,null,null
6null,null,null,null,null,1
7null,null,null,null,null,null
8null,null,null,null,null,null
9,null,null,null,null,null
10,null,null,null,null,null
11,null,null,null,null,null
12,null,null,null,null,null
13,null,null,null,null,null
141,null,null,null,null,null
151?,null,null,null,null,null
161,null,null,null,null,null
171,,null,null,null,null
181,,null,null,null,null
19null,,null,null,null,null
20访问失败么有找到资源前端访问后端报错了即没有找到后端后端将错误信息返回给前端打印出来了
21与20一样的错误
221,null,null,null,null,null这里很明显只是拿取第一个即写在前面的一个
*/总结
/*
1x.open(GET,get?,false);null,null,null,null,null,null
2x.open(GET,get??,false);null,null,,null,null,null
3x.open(GET,get?,false);null,null,null,null,null,null
4x.open(GET,get?,false);null,null,null,null,null,null
5x.open(GET,get?,false);null,null,null,null,null,null
6x.open(GET,get? 1,false);null,null,null,null,null,1
7x.open(GET,get? ,false);null,null,null,null,null,null
8x.open(GET,get?,false);null,null,null,null,null,null
9x.open(GET,get?name,false);,null,null,null,null,null
10x.open(GET,get?name,false);,null,null,null,null,null
11x.open(GET,get?name ,false);,null,null,null,null,null
12x.open(GET,get?name,false);,null,null,null,null,null
13x.open(GET,get?name,false);,null,null,null,null,null
14x.open(GET,get?name1,false);1,null,null,null,null,null
15x.open(GET,get?name1?,false);1?,null,null,null,null,null
16x.open(GET,get?name1,false);1,null,null,null,null,null
17x.open(GET,get?name1pass,false);1,,null,null,null,null
18x.open(GET,get?name1pass,false);1,,null,null,null,null
19x.open(GET,get?nae1pass,false);null,,null,null,null,null
20x.open(GET,get,false);报错
21x.open(GET,getname,false);报错
22x.open(GET,get?name1name2,false);1,null,null,null,null,null这里很明显只是拿取第一个即写在前面的一个结论如果一个请求没有找到?那么他整体就是一个请求即当成值来使用当找到第一个?时这个时候后面的?作为正常值来使用而作为连接不参与值来使用了在考虑这种情况时值和值代表这个值的value为比如1011的结果而和 中后端接收的是接收不到的只有 可以所以6存在值即和在后端是接收不到了单纯的接收不到转义也是没有用的在url中\也就是所以没有用的当然了在不考虑转义的替换的情况下\都是将对应的值直接放入所以\\就是放入\那么这个时候后?后面又有\会导致语法错误?后面是参数处理了其中不能存在\\是不会允许的考虑到转义所以才会这样的但是/可以/会作为值你可以测试get?/a或者get?\a就知道了
当然上面是浏览器自身的规则处理以及报错的处理与后端关系不大最终还是需要看浏览器给后端的值对于后端来说只会存在和其他的值处理还是null对于url中就是是否存在参数如namename不存在name以及name1等等当然name1 1中 是为1的因为前端的才是代表没有参数而 代表 这里了解即可简单来说如果存在name那么在某个地方这里针对post来说就是
name:这样的get也是如此只是他是解析url到后端而post则是直接的设置这里但是他们的结果都是一样的比如表单中写name那么相当于?1中1前面的不写他们最终都是一样的所以get和post其实是一样的只是这个参数和值的存放不同导致get和post分成了一些操作这里再后面会说明的2222x.open(GET,get?name1name2,false);1,null,null,null,null,null 这里很明显只是拿取第一个即写在前面的一个当然22这里我应该还需要测试拿取两个在后端的String name servletRequest.getParameter(name);中后面可以继续加上
String[] names servletRequest.getParameterValues(name);for (int i 0; i names.length; i) {if (i names.length - 1) {System.out.println(names[i]);continue;}System.out.print(names[i] ,);}最终打印出1,2
*/我们也可以通过标签来处理在学习前端表单时应该会非常清除实际上form的请求方式也只是将表单内容变成对应的请求参数的而已所以这里我们测试一下即可修改index.jsp
form actionget methodgetinput typetext namenameinput typetext namepassinput typesubmit value提交
/form
!--
输入12get?name1pass12
输入1不输入get?name1pass
输入不输入不输入get?namepass
很明显去掉自然也就不存在了
都去掉get?即get?是基础
--我们继续操作这个get请求这个时候我们可以加上一些请求头比如
x.setRequestHeader(Content-Type, application/json); // 设置JSON请求头加上请求头自然需要在send发送请求之前加上的然后执行访问因为send是最终处理这时发现结果是一样的那么他有什么用呢实际上get请求在操作过程中并不会使用他或者说只会使用一些请求头你就访问百度https://www.baidu.com/查看网络中对应的请求看看是否有请求标头就知道了get也是有的因为一个完整的请求是基本必须存在其请求信息和响应信息但是Content-Type是忽略的实际上是设置的之所以忽略是因为几乎用不上这些或者给出的api中或多或少可能会判断一下比如后面的multi-part的对应的错误因为其url的保存基本只能由默认请求处理所以这在一些版本中可能并不会进行显示所以说成是忽略也是可以的为什么这里要说明这个在后面你就会知道了
现在我们操作了get请求和get操作的请求头那么现在来完成get的单文件处理在这之前有个问题get存在单文件的处理吗答并没有或者很少为什么说很少这是因为get数据的保存是在url中的url中加入的数据是有限的所以如果是小文件的话或者说某些符合字符的文件get是可以完成的现在我们来进行测试
在真正测试get请求文件之前首先我们要来确认get请求文件的流程思路是怎么来的或者为什么只能将文件数据放在url中现在来让你好好的理解get请求文件为什么要这样做以及如果不这样做会出现什么
在测试之前我们必须要明白get的作用就是在url中进行添加而post则不是他们是不同的操作方式自然其对应的需求的请求也是不同的
在测试之前我们还需要明白一件事情前端要上传文件一般就是上传二进制的代码然后操作一些文件信息无论你前端如何变化本质也是如此所以只需要掌握了拿取二进制信息发生的流程那么无论什么情况下文件上传的操作你就不会出现问题了这里建议参考这个视频https://v.douyin.com/iJ8YRXGw/这个视频我感觉还是很好的虽然没有从0到有的代码的说明
那么现在有一个问题由于文件是从磁盘文件系统里面拿取的我们并不能很好的手动的写上这些数据到url中特别是图片等等那么就需要一些操作来读取比如通过标签或者通过js手动的拿取等等因为最终他们的基础代码是一致的通过标签获取一般是如下的操作这里我们完全改变之前的index.jsp了且记得放在body标签里面
input typefile idfileInput /
button οnclickuploadFile()上传/buttonscriptfunction uploadFile() {let fileInput document.getElementById(fileInput);let file fileInput.files[0]; // 获取文件选择框中的文件sendFileUsingGET(file); // 调用发送文件的函数}function sendFileUsingGET(file) {let xhr new XMLHttpRequest();//原生中需要这个对象或者说他也是相对原生的因为你也会看XMLHttpRequest对象里面的源码的这些源码相当于操作系统的接口一样是提供的//所以说一般情况下原生的意思代表是整个语言操作编写时自带的一些API或者固定语法意思否则就应该说成是底层原理考虑操作系统接口的处理这不是单纯程序员所要理解和操作的东西了原生也会称为底层源码let formData new FormData();// 将文件添加到FormData相当于表单操作的name和对应的值了formData.append(file, file);// 构建GET请求URL将FormData中的数据作为查询参数let url get? new URLSearchParams(formData).toString();console.log(new URLSearchParams(formData).toString()) //一般是这样的形式file%5BobjectFile%5D// 打开并发送GET请求xhr.open(GET, url, false);xhr.send();//后面的就不操作打印了因为只是打印返回值而已没有必要}
/script上面我们并没有通过标签提交而是通过标签获取后通过js提交等下我们会说明其他三种情况现在我们来看如下
首先我们需要注意基础代码即底层原理上面有注释说明基本代码即底层源码
在操作文件上传时需要说明他为什么需要一些固定的操作
在这里需要说明一下FormData对象FormData是一个内置的 JavaScript API所以可以将他看成原生它用于创建关于文件的表单数据前面说过操作文件我们会使用标签的方式其实标签的方式一般就是表单而表单的基础代码就是这个的基础代码html和css可以看成先变成js然后变成基础代码或者直接由其独有的与js的不同解释的器进行变成对应的基础代码所以简单来说该对象可以认为是文件操作的底层源码注意是文件在后面会说明为什么当然这样的源码还有很多但是他们的基础代码都是一样的并将其用于 AJAX 请求中一般情况下我们使用这个对象即可因为既然基础代码是一样的那么其他的类你学会也没有什么帮助只是一个换了名字的对象而已它允许你构建以 multipart/form-data格式编码的数据也就是表单对文件的处理这种数据格式通常用于发送文件和其他类型的二进制数据或者说可以发送二进制的数据所以简单来说他可以用来保存二进制并进行发送出去到请求头而不是只保存具体数据再保存但是这样的对multipart/form-data的解释是无力的为什么
实际上任何数据都是二进制只是再查看的时候会以当前查看的编码进行解析查看而已所以这里需要注意一个地方即为什么我们要用multipart/form-data来发送文件解释如下
因为在 HTTP 协议中数据的传输有多种编码方式而multipart/form-data是专门用于上传文件的一种编码类型
其中HTTP 协议规定了多种数据传输的编码类型常见的有application/x-www-form-urlencoded和multipart/form-data这两种编码类型都是 POST 请求中用于向服务器传递数据的方式而这里我们在尝试get具体结果请看后面测试的结果
application/x-www-form-urlencoded是默认的表单数据编码类型他是用来提交的编码注意是表单的大多数比较原始的需要加上他来操作否则可能是什么都没有设置更加的一般默认的可能只是一个纯文本也就是text/plain;charsetUTF-8这才是底层的默认处理一般来说post才会考虑默认加上该Content-Type没有加那么没有显示只是post不会再浏览器显示而已内部是处理的而get没有这是体现在浏览器的了解即可在这种编码类型下基本上没有变化表单数据会被转换成 URL 查询参数的形式如果是post则会同样以对应的形式放在post对应的参数域中这里的域对应与get来说基本只是存在表面的说明即没有更加深入说明建议全局搜索这个实际上存在一个空间get和post的数据都是放在这个空间的可以让你更加的理解post和get的存在方式只是可能并不存在url那样的连贯如例如get请求的key1value1key2value2这种编码方式适合传输普通的键值对数据但对于文件数据在不考虑其他判断的情况下由于文件内容可能包含特殊字符如特别的可能还存在那么在后端得到的数据就算结合也可能得不到完整的文件因为自然会省略一个那么一个文件可能会变成多个键值对并且由于特别的的存在所以文件不适合直接编码在 URL中否则的话可能导致读取的文件信息错误不能进行没有完整的处理因为可能也存在所以浏览器提交或者后端接收时他们可能都会进行判断是什么类型以及对应的编码类型来使得是否报错最后不让你进行传递一般在前端和后端基本都会进行判断的底层源码中的处理是底层源码的源码因为对于底层原理应该是最底层的除非前端是自行写的原生js或者后端也是自行写的原生servlet这也是我们需要改变类型的一个原因当然就算是自己写的原生js或者原生servlet前端和后端可能底层源码也判断了但是这种情况我们看后面就知道了
multipart/form-data这种编码类型用于传输二进制数据如果是变量赋值的话保存的也是编码后的二进制这里在后面会说明的包括文件数据在这种编码类型下表单数据会被分割成多个部分每个部分都包含一个 Content-Disposition头和其他相关信息以及对应的数据内容其中文件数据会以二进制的形式编码而不会被转换成 URL 查询参数相当于完整存放了自然不会出现数据丢失的错误get的是会造成丢失的这也导致get一般并不能操作他也同样的由于get主要是操作url的也导致了并不能很好的操作请求头使得忽略一些东西或者说避免无意义的赋值可能get和post都可以操作对应的域只是分工导致并不会操作对方或者只能操作某些东西所以get可能会判断忽略请求头
对于后端代码修改多余的注释删掉了
package com.test.controller;import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;public class GetRequest implements Servlet {Overridepublic void init(ServletConfig servletConfig) throws ServletException {System.out.println(初始化);}Overridepublic ServletConfig getServletConfig() {return null;}Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {System.out.println(1);System.out.println(servletRequest); //org.apache.catalina.connector.RequestFacade5b7236d6//可以强转并且可以赋值必然存在上下级关系而非隔代的关系并且打印出现的是org.apache.catalina.connector.RequestFacade5b7236d6//但是在当前idea中并不能直接的查看为什么因为idea中或者说maven中只是管理他导入的依赖操作而自身的或者服务器等等并不会给出所以需要自行去找//我们可以到服务器目录中如我的就是C:\apache-tomcat-8.5.55\lib找到catalina.jar然后解压根据目录找到如下//比如我的就是C:\ceshi\catalina\org\apache\catalina\connector然后找到RequestFacade.class//那么如何查看呢实际上idea就有这样的方式我们复制这个class粘贴到当前项目的target需要编译目录中点击查看即可当然由于他的反编译并不能保证100%正确所以有些与源码可能是不同的但是大致相似//这个时候我们并不能在idea中进行删除他不能局部删除所以也就是说可以删除target目录包括class他所在的目录原本编译的进行删除时与你复制粘贴的是不同的一般可能是因为你使用他了当然如果你没有使用过那么他与你复制粘贴的出现的不能删除的错误是一样的或者类似的这是idea的判断规定所以需要自行到文件系统比如在c盘你手动到c盘中删除而非在idea中这些盘简称为操作系统的文件系统的中进行删除或者删除目录//这个时候你点击进去可以看到public class RequestFacade implements HttpServletRequest {即他的确是实现的所以也证明了可以进行赋值//当然由于服务器或者说tomcat的类很多所以这里我们并不能将里面进行学习完毕但是idea关联了tomcat那么中间操作的类加载的过程肯定是加载的所以你不用想他是怎么处理并且操作的任何的类都是加载才会操作的包括jdk的自带的类只是我们不知道而已这里可以选择到104章博客进行学习HttpServletRequest h (HttpServletRequest) servletRequest;System.out.println(h.getMethod()); //可以查看请求方式servletResponse.setContentType(text/html;charsetUTF-8);PrintWriter writer servletResponse.getWriter();writer.write(h1 11 /h1);}Overridepublic String getServletInfo() {return null;}Overridepublic void destroy() {System.out.println(正在销毁中);}
}
现在我们启动访问一下看看后端可以发现打印的请求方式是get这个时候我们后端并没有接收文件的信息这需要特别的处理即后端文件的接收后端怎么接收文件的信息呢在这之前我们需要先修改index.jsp因为在let url ‘get?’ new URLSearchParams(formData).toString();中后面只是作为值也就是说像这样的形式file%5BobjectFile%5D只会得到file后面的字符串值所以我们需要修改修改如下
function sendFileUsingGET(file) {let xhr new XMLHttpRequest();let formData new FormData();formData.append(file, file);xhr.open(GET, get, false);xhr.send(formData); /*当然在get请求中给加上请求参数是无意义的也就是说相当于send()get请求基本只会加载url后面如果存在某些框架是给出对应的一个对象的那么不要怀疑底层也是进行解析放在url中的由于前面对get的请求操作我们已经给出了22种那么就不考虑在url后面添加信息了所以对应的需求中我们才会有层级即get请求操作加上参数这个处理完后那么后面的参数处理无论是get还是post都能明白了所以也基本只会测试一次get操作的请求头get的单文件多文件以及文件夹处理这里已经操作参数了现在是get的单文件*/}我们继续修改service方法
Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {HttpServletRequest h (HttpServletRequest) servletRequest;if (h.getMethod().equals(GET)) {System.out.println(GET);}if (h.getMethod().equals(POST)) {System.out.println(POST);}try {//你看到上面的处理应该就会知道哦原来get和post并没有区别上面的判断就能判断保证执行谁了在前面也说过了get和post其实是一样的只是这个参数和值的存放不同导致get和post分成了一些操作如文件的处理等等//同样的如果我们没有上面的判断的话那么他们的操作就是一样的自然会导致出现问题这里我们就不进行操作就只是打印到底看看什么情况下必须需要进行分开Part filePart h.getPart(file); // 获取名为 file 的文件Part对象String fileName filePart.getSubmittedFileName(); // 获取上传的文件名System.out.println(文件名 fileName);} catch (Exception e) {e.printStackTrace();System.out.println(2);}System.out.println(1);}进行访问记得点击上传文件当然你也可以不上传甚至没有这个文件参数可以发现报错了并打印信息出现了两个GET且没有返回值返回为什么我们首先看错误
/*
打印21后GET也会的错误出现因为他打印是比较慢的但是由于打印自身也不是异步那么只能说明是在打印准备显示或者赋值时是异步的也就是说的大多了比较慢即打印也需要时间
错误由于没有提供multi-part配置无法处理parts
*/这个错误的出现是这Part filePart h.getPart(“file”);一行的原因这个时候如果你修改成了POST请求那么他的错误也是这个地方即这个错误与get和post无关那么这个错误怎么解决我们可以分析这个错误没有提供multi-part配置那么这个配置是什么
Multi-Part 请求是一种在 HTTP 协议中用于传输二进制数据和文件数据的请求类型在 Multi-Part 请求中请求信息的数据被分割成多个部分Part每个部分都有自己的头部信息以及对应的数据内容这种格式允许将不同类型的数据比如文本、二进制数据、文件等同时包含在一个 HTTP 请求中通常情况下我们在前端上传文件或者提交包含文件的表单数据时后端会接收到一个 Multi-Part 请求Multi-Part 请求的内容类型Content-Type通常为 multipart/form-data用于标识请求信息中的数据是 Multi-Part 格式需要配置 Multi-Part 是因为在后端处理 Multi-Part 请求时需要对请求信息进行解析以提取其中的数据并正确处理文件上传等操作对于某些后端框架或服务或者服务器本身它们可能默认不支持解析 Multi-Part 请求因此需要进行相应的配置告知后端如何处理 Multi-Part 数据那么很明显由于前面默认的编码格式是application/x-www-form-urlencoded前面我们说明了这个操作文件的坏处而引出了前后端会判断的处理这种判断是建立在默认的情况下所以也给出了我们可以通过其他方式来使得get进行处理文件所以这里才会报错也就是说后端原生的也操作了判断那么前端原生有没有判断答没有但是由于前端必须设置multipart/form-data他是一个请求头信息的而get对他Content-Type是忽略的也就造成了get并不能操作这个而get不能操作这个后端也判断报错所以导致前端get不能操作文件的上传即这条路被堵死了但是这里我说了我们需要进行操作get文件所以我们应该这样的处理修改index.jsp在修改之前我们应该要考虑一个问题既然get操作不了对应的编码且只能操作url那么如果我们将对应的二进制文件放入到url中即可并且通过编码来解决原来使得默认报错的问题就行了最终通过这个编码提交然后后端接收解码这个时候是根据默认处理的编码来解决的因为前面的编码只是对文件的一个保存而已即两种一种是上层另外一种下层是交互后端的这个几乎不会改变进行接收文件就行而不用操作总体的multipart/form-data的解码了简单来说无论是get还是post都是操作文件的解码和编码而已只是其中一个是multipart/form-data另外一个是我们自定义的加上默认的而get之所以需要定义还是因为对应编码的并不存在根本原因是get是只能操作url导致并不会操作对应的请求头而使得他自身并不能进行其他操作即需要我们手动的处理其中这个url得到的二进制的数据自然是操作了编码的为什么实际上大多数语言中其变量并不能直接的指向原始二进制必然是通过一些编码而进行间接指向这是因为二进制的处理过于底层也只能在底层中进行计算而不能在上层计算所以说你得到文件信息也是通过对二进制进行编码来得到的那么根据这个考虑我们看如下修改的index.jsp我们也自然不会引入一些js都是使用自带的js的即原生的
input typefile idfileInput/
button οnclickuploadFile()上传/buttonscriptfunction uploadFile() {//前面没有说明这些是因为他们是不会成功的其实这个代表得到文件对应的信息由于存在文件和多文件所以下面才会存在数组的形式的let fileInput document.getElementById(fileInput);console.log(fileInput)let file fileInput.files[0]; // 获取文件选择框中的文件由于只是单文件所以只需要拿取下标为0的值即可因为也只会是下标为0开始因为只有一个console.log(file)//读取文件内容并进行 Base64 编码注意是文件也就是包括图片或者视频都可以即将文件信息从二进制进行该编码处理该编码对文件的操作使用的比较多所以这里使用这个编码也就是说虽然我们不能使用get的multipart/form-data编码方式但是我们可以直接来解决这个特殊字符的问题如因为后端之所以默认判断报错无非就是防止这种问题即不能让你单纯的操作文件但是他的判断一直存在所以我们需要进行跳过即按照其他方式的数据进行传递也就只能将文件信息放在url中了所以通过上面的说也就是为什么get也只能将数据放在url的原因根本原因还是get只能操作url是否感觉上面的测试是多余的但是也不要忘记了前面也说了以及如果不这样做会出现什么这种情况所以我也应该进行测试出来//一般使用该编码的他的好处可以降低数据大小的存在那么变量就不会占用很多内存了否则可能使用默认的变量给变量会造成比较大的内存占用文件中的处理是操作系统的处理而编码只是一种表现形式的指向而已//创建了一个新的 FileReader 对象用于读取文件内容FileReader 是一个可以异步读取文件的 API即你可以执行其他的任务并且他是原生中js读取文件的操作即也是原生的我可没有引入一些js要知道原生js的api可是有很多的他们一般也是使用这些原生js组成的而已比如vuelet reader new FileReader();//设置了 FileReader 的 onload 事件处理函数当文件读取完成时该函数将会被触发reader.onload function (e) {/*你可以将该e的数据复制下来操作打印即可即console.log(e);然后记得两边没有分号然后在url中粘贴若出现你上传的图片代表是真的获取了他就是编码后的数据*/let fileData e.target.result.split(,)[1]; // 获取Base64编码后的内容因为这个时候他才算是真正的编码后面的数据而前面的前缀只是一个标识而已而非数据所以通常并不影响后端的解码处理sendFileUsingGET(fileData); // 调用发送文件的函数};//开始读取指定的文件内容并以 Base64 编码的形式返回结果首先判断是否传递了文件if (file ! undefined) {reader.readAsDataURL(file); //对应的异步出现一般体现于这里并且他内部操作了编码然后变成上面的e参数同样的reader.onload 也是异步的处理可以说reader基本都是异步的处理这是保证再处理多个文件时不会浪费很多时间这也是没有办法的事情}}function sendFileUsingGET(fileData) {let xhr new XMLHttpRequest();// 添加文件信息到 URL 中这里之所以需要encodeURIComponent方法是因为与默认的后端需要对应的编码一样防止对应一些字符的处理如等等由于get必然会在url中进行操作那么如果不这样处理的话可能会造成数据丢失let url get?file encodeURIComponent(fileData);console.log(encodeURIComponent(fileData))// 打开并发送 GET 请求xhr.open(GET, url, false);xhr.send();}
/script前端代码编写完毕可以发现get和post一个体现在url一个体现在域中在这种情况下get是存在局限的而post则没有前面的测试则多是体现在get的局限也就是默认报错的原因因为post可以解决而get不能由于get并不能操作对应的请求头url导致实际上是分工所以导致get在某些操作情况下需要进行手动处理post可以设置来处理而get不能特别是文件的处理即文件需要我们手动的处理现在我们来从后端接收该文件信息然后保存到本地所以post是可以完成get的功能这里特别的需要注意在后面也会提到且可以更好的完成其他的功能但是也由于域的存在导致可能会比get慢这里需要考虑很多的问题了
至此get的测试我们说明完毕即get的url自身的特性导致get的文件上传需要我们手动处理
简单来说就是认为get有两条路一个是与post一样的设置编码另外一个是自身的url很明显设置编码的不行那么自然只能操作自身url了而post确存在这两种形式只是post的url的形式在一个参数域中而已虽然其两种都在该域中
同样的由于分工不同操作get只针对url而post只针对域从而造成方案的不同并且很明显大多数后端代码对文件的处理是操作multipart/form-data的即是默认的判断处理从而建议我们使用post操作文件这也同样是在考虑url添加数据少的情况下后面也会说明一下也验证了分工不同的最终后续的影响分工不同导致默认不同存在判断导致方案不同所以get在没有考虑对url的自定义编码的情况下报错是正常的因为你并不是按照正常的流程来处理而使用了自身缺点并且url还不能很好的分开数据或者需要更多操作来进行处理如自定义的编码使得还要编码一次所以这也是建议get不操作文件的一个原因特别如操作多文件因为每个文件都需要进行编码处理来使得数据是正常的甚至还有考虑大小的限制问题
也就是说若不考虑其他的因素那么get和post其实是完全一样的只是由于存放形式的不同导致有所限制所以不考虑这些限制get也可以完成所有post的操作而这些限制的出现只不过是人为规定让我们可以方便的选择使用那一种所以才会出现get和post或者其他的请求形式
后端代码如下
Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {HttpServletRequest h (HttpServletRequest) servletRequest;if (h.getMethod().equals(GET)) {System.out.println(GET);}if (h.getMethod().equals(POST)) {System.out.println(POST);}String file servletRequest.getParameter(file);if (file ! null) {// 解码 Base64 数据Base64也是java中原生的代码因为我并没有引入任何相关的依赖//Base64.getDecoder()得到一个对象static final Decoder RFC4648 new Decoder(false, false);//即Decoder对象他是 Java 提供的用于解码 Base64 编码数据的类//而其方法decode(String base64)则是将 Base64 编码的字符串解码为字节数组byte[] decodedBytes Base64.getDecoder().decode(file); //一般情况下我们是需要解码encodeURIComponent的数据的但是getParameter中内部通常解决了这样的情况所以我们并不需要手动的解码// 根据实际需求这里可以对解码后的数据进行进一步处理// 例如保存文件到服务器等操作//这里我们试着将文件保存到本地如果可以自然也能操作保存到服务器了FileOutputStream fileWriter new FileOutputStream(F:/in.jpg);fileWriter.write(decodedBytes);} else {}}我们找一个文件上传然后你可能会出现如下的错误也是不建议使用url或者说get操作文件的情况
/*
java.lang.IllegalArgumentException: Request header is too large
翻译java.lang.IollegalArgumentException请求标头太大
他一般代表通常发生在HTTP请求头过大时服务器无法处理该请求这种情况可能发生在GET请求中特别是在您尝试在URL中传递大量数据或参数时即get请求长度是有限的
*/一般情况下get的上限是与浏览器相关在后端是与post一样的在同一个地方操作的如前面的service方法只是因为浏览器前的分工导致后端某些处理或者前端的处理发生一些变化比如不同的浏览器对GET请求的URL长度有不同的限制这些限制通常在2KB到8KB之间例如对于Internet ExplorerURL长度的限制可能较低而对于现代的浏览器如Chrome、Firefox和Edge通常会更大所以现在我们随便创建一个txt文件加上1这个数字然后将后端对应的FileOutputStream fileWriter new FileOutputStream(“F:/in.jpg”);的后缀jpg修改成txt继续进行测试这个时候可以发现上传成功了并且对应的F:/in.txt的数据与上传的一致至此我们的get上传文件操作完毕
那么为什么浏览器要限制url的长度呢后端出现报错是判断请求浏览器类型而进行处理的前端还是发送过去的实际上是提供传输速率的这里了解即可因为这是规定
现在我们改造后端和前端将后端代码变成一个方法
package com.test.controller;import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.util.Base64;public class GetRequest implements Servlet {Overridepublic void init(ServletConfig servletConfig) throws ServletException {System.out.println(初始化);}Overridepublic ServletConfig getServletConfig() {return null;}Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {HttpServletRequest h (HttpServletRequest) servletRequest;if (h.getMethod().equals(GET)) {System.out.println(GET);getFile(h, servletRequest, servletResponse);}if (h.getMethod().equals(POST)) {System.out.println(POST);}}//提取一下吧在后面你可以选择用这个方法来放入一个工具类这里我就不操作了private static void getFile(HttpServletRequest h, ServletRequest servletRequest, ServletResponse servletResponse) {servletResponse.setContentType(text/html;charsetUTF-8);try {PrintWriter writer servletResponse.getWriter();String file servletRequest.getParameter(file);String filename servletRequest.getParameter(filename);if (file ! null) {String[] split filename.split(\\.);byte[] decodedBytes Base64.getDecoder().decode(file);FileOutputStream fileWriter new FileOutputStream(F:/in. split[1]);fileWriter.write(decodedBytes);writer.write(h1 上传文件成功 /h1);return; //提前结束吧执行完结束和现在结束差不多这是独有的void的return结束方式不能加上其他信息哦可以加空格}writer.write(h1 上传的文件为空 /h1);return;} catch (Exception e) {e.printStackTrace();}}Overridepublic String getServletInfo() {return null;}Overridepublic void destroy() {System.out.println(正在销毁中);}
}
前端
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
body
input typefile idfileInput/
button οnclickuploadFile()上传/buttonscriptfunction uploadFile() {let fileInput document.getElementById(fileInput);let file fileInput.files[0];let reader new FileReader();reader.onload function (e) {console.log(e);let fileData e.target.result.split(,)[1];sendFileUsingGET(fileData, file.name);};if (file ! undefined) {reader.readAsDataURL(file);}}function sendFileUsingGET(fileData, name) {let xhr new XMLHttpRequest();let url file?file encodeURIComponent(fileData) filename name;xhr.open(GET, url, false);xhr.send();console.log(xhr.responseText);}
/script
/body
/html
上面我们操作了一下后缀我们看看即可
一般情况下你选择的文件可能与之前的文件有所联系可能是日期可能是唯一内容或者id也就是说如果你选择后修改文件系统的对应文件那么可能上传不了一般存在reader.onload里面即原来的信息具体可以认为一个文件里面存在是否改变的信息即会再次的进行处理从getElementById获取虽然他也可以获取文本的但是内容可能还操作了对应指向的类型方法所以了解即可相当于操作了return;即会使得当前方法直接停止不操作了一般并不包括里面的异步处理所以只是停止当前线程异步是新开线程的但也只是对该文件而言如果是多个文件那么没有改变的就不会操作return;也就不会结束调用他的方法return;可不是程序结束的意思所以其他的还是会执行的
但是这里大多数人会存在疑惑js是单线程的为什么存在新开线程这里就要说明一个问题你认为页面渲染主要只由js来处理吗实际上是浏览器来处理的所以js只是一个重要的组件而已而非全部那么其他的线程可能并不是js来新开的可以认为是浏览器或者其他操作系统来新开的就如浏览器存在js组件自然会与他交互浏览器新开一个线程与你交互有问题吗没有问题所以这也是js也是单线程但是确存在异步根本原因但是随着时间的发展js可能也会诞生时间片的概念使得js在单线程的情况下与java一样的进行切片处理多个线程的这里了解即可
改造web.xml
?xml version1.0 encodingUTF-8?
web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaeexmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsdversion4.0servletservlet-nameGetRequest/servlet-nameservlet-classcom.test.controller.GetRequest/servlet-class/servletservlet-mappingservlet-nameGetRequest/servlet-nameurl-pattern/file/url-pattern/servlet-mapping
/web-app继续测试吧上面我们操作了get的单文件的处理并且是通过标签获取js提交现在我们来完成js获取js提交那么js可以完成获取文件吗实际上js并不能获取我们系统的文件信息这是防止浏览器访问本地文件的安全策略特别的如果对方网页中的js是删除你文件系统的所有文件你怎么防止你也就是说只能通过上面的表单交互来进行文件的上传处理也是浏览器唯一或者少的与文件交互的处理并且也是需要用户与他进行处理的但是我们可以选择不手动点击具体上传文件的按钮操作如下
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
body
button idopenFileButton打开文件选择器/button
scriptlet openFileButton document.getElementById(openFileButton);openFileButton.addEventListener(click, () {let fileInput document.createElement(input);fileInput.type file;fileInput.click();fileInput.addEventListener(change, (event) {let file event.target.files[0];let reader new FileReader();reader.onload function (e) {console.log(e);let fileData e.target.result.split(,)[1];sendFileUsingGET(fileData, file.name);};if (file ! undefined) {reader.readAsDataURL(file);}});});function sendFileUsingGET(fileData, name) {let xhr new XMLHttpRequest();let url file?file encodeURIComponent(fileData) filename name;xhr.open(GET, url, false);xhr.send();console.log(xhr.responseText);}
/script
/body
/html
但是这里为什么还要加上标签实际上浏览器并不允许单纯的没有与用户交互过的自动处理所以如果你去掉了这里的用户点击的交互那么后面的fileInput.click();不会进行
至此js获取和js提交也可以认为是完成了那么js获取和标签提交呢实际上更加的复杂并且标签的获取和提交也是如此但是这个标签获取和提交只是针对get来说是比较的复杂而对post来说甚至由于浏览器存在自带的处理所以导致他可能是比标签获取js提交还要好的特别是多文件的处理以后会说明的后面既然会说明所以这里就不进行提示再哪个地方了按照顺序看下去即可因为这样的话语还有很多的那么是为什么呢这都是需要满足其标签以及js安全考虑的所以如果需要进行改变那么首先就是修改浏览器源码否则是无能为力的这里给出这些说明是为了让你知道标签获取js提交是最好的处理方式也是最安全的方式即对应的三种我们说明完毕这具体的实现那么等你解决浏览器源码后去了关于js的获取那么需要解决浏览器js可以获取文件系统的安全问题而标签提交则需要修改源码对get的处理而不是默认按照名称而不是内容加在url中所以说我们也只是在浏览器允许的情况下进行的处理那么文件上传本质上也是这样的所以你获取不了什么参数或者可以获取什么参数都是浏览器造成的当然他通常也会有原因但是并不完美而已就如get没有一个好的方法变成post虽然并没有什么必要
至此我们四种情况说明完毕现在将前面的坑都填完了接下来是正事也就是get的多文件上传
get的多文件上传与单文件上传基本的类似的但是还是有一点前面的操作一般只能选择一个文件也是浏览器的限制前面我们说过了我们也只是在浏览器允许的情况下进行的处理所以我们需要进行特别的改变我们继续修改index.jsp文件上面的基本都是这个
但是在修改之前首先需要考虑一个事情是前端访问一次后端进行多文件的处理还是将多个文件分开处理呢这里我就不选择一个了而是都进行考虑首先是多个文件统一处理但是这里就需要考虑很多问题了虽然比较复杂但是确只需要一次的访问即可但在一定程度上会占用url如果是post我们建议统一放入post也的确是希望这样处理的当然这是后面需要考虑的了现在我们来完成get的多文件统一处理但是由于前面我们使用new FileReader();时基本是异步的前面说明了他是异步的原因考虑文件传递速率的所以我们需要一下前端的异步和同步的知识怎么学习呢一般情况下我们学习这三个asyncawaitPromise他们有什么用
/*
async 和 await 是 JavaScript 中用于处理异步操作的关键字它们使异步编程更加清晰和易于管理在传统的异步编程中只使用回调函数和 Promise 用于处理异步操作时往往会导致嵌套深度增加、回调地狱等问题而取代回调函数的async 和 await 通过使用更直观的语法使异步代码看起来更像是同步代码从而提高了代码的可读性和维护性
asyncasync 关键字用于标记一个函数是异步的特别注意是函数哦这意味着函数内部可以包含 await 关键字并且在该函数内部使用的任何 await 表达式都会暂停函数的执行等待 Promise 解决并在解决后恢复执行
awaitawait 关键字只能在使用了 async 关键字标记的函数内部使用它用于等待一个 Promise 解决并返回 Promise 的解决值在 await 表达式后面的代码会在 Promise 解决后继续执行使用 await 可以让异步代码看起来像同步代码避免了回调函数的嵌套
但是要注意async并不是整体函数进行异步他只是标记而不是使得是异步的标记可不是改变哦但是也正是标记所以才会与await以及Promise进行处理而这个标记存在的异步意义是
使用了 async 关键字只有在函数内部使用了 await 或者返回了一个 Promise 的情况下async 才会影响函数的执行方式如果函数内部没有使用 await也没有返回一个 Promise那么函数的行为仍然是同步的所以简单来说await之前同步只会异步中间可以选择操作一下Promise所以上面才会说是标记哦并且如果返回的Promise是没有被await修饰的那么返回的值就是Promise对象而不是其对应的返回值这里需要注意
*/接下来我来举个例子并在这个例子中进行学习一下规定的api你可以选择到vscode这里可以选择看看44章博客中进行处理 function fetchUserData() {console.log(9)return new Promise((resolve, reject) {setTimeout(() {console.log(1)resolve(5);}, 1000);});}async function getUserInfo() {console.log(4)let userData await fetchUserData();console.log(userData)}console.log(2)getUserInfo();console.log(3)/*流程我们只是定义给fetchUserData函数但是可没有执行他首先给getUserInfo加上异步那么当后面的2打印后执行他注意这个时候还是同步的因为只有在函数内部使用了 await 或者返回了一个 Promise 的情况下async 才会影响函数的执行方式而由于Promise的返回是有await处理的所以也就真正使得异步的是await即有await那么前面同步后面异步否则都是同步这个时候还是同步的那么打印4然后经过await fetchUserData();现在由于使用了await那么是异步了即直接使得改方法变成异步但是使用了await的证明是对应的方法执行完毕也就是说fetchUserData方法执行完才算使用所以这个时候会打印9并且由于await的使用会导致获取Promise的值所以里面的打印会慢一点而方法是异步自然使得外面的3进行打印因为他只是给函数方法的内部还是一个单独的线程处理所以局部还是同步的但是由于是等待1秒执行并且是需要接收resolve的值所以1秒后打印15所以打印结果是249315执行一下看看结果是否正确吧*/
修改一下
function fetchUserData() {let currentTimeStamp Date.now();console.log(9)return new Promise((resolve, reject) {console.log(1)resolve(5);});}async function getUserInfo() {console.log(4)let userData await fetchUserData();console.log(userData)}console.log(2)getUserInfo();console.log(3)/*打印的是
2
4
9
1
3
5
为什么1也打印了我前面说明了由于await的使用会导致获取Promise的值而获取的值的主要时间是resolve所以在同时进行异步处理时异步信息给方法和异步信息给当前是慢一点的所以1会先打印但是由于resolve的时间较多所以他是后打印的
*/上面在很大程度上解释了三个关键字的说明上面存在同步异步等待获取的处理这三个在异步编程中是最为重要的形式在java中一般也需要如此即同步异步以及在其中相应的等待处理且包括数据的处理当然等待处理可以认为是数据的处理只是java更加的好因为异步编程无非就是同步异步以及其中出现的数据处理所以js和java的异步编程都是非常灵活的虽然在java中外面一般会称为并发编程也是归于他异步编程的优秀处理他比js更加的灵活的当然这些规定的知识层面了解即可深入java的就行js的也可以顺序深入一下他们归根揭底是不同的预言所以请不要找共同点相似的也最好不要因为他们本来就不同只是由于英文造成的某些关键字相同而已如intString等等
现在我们使用这些知识来解决前面的多文件统一处理现在修改index.jsp
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
body
input typefile idfileInput multiple/ !--multiple可以选择多个文件而也有webkitdirectory可以选择文件夹都写上那么可以选择文件夹和文件并且可以多选因为multiple他们都是一个补充当然补充之间也存在覆盖关系多文件包含单文件即覆盖掉文件夹也是如此也就是说加上了webkitdirectory那么只能操作文件夹了--
button οnclickuploadFile()上传/buttonscriptasync function uploadFile() {let fileInput document.getElementById(fileInput);let fileData [];let fileDatename [];if (fileInput.files.length 0) {return;}let readFilePromises [];for (let y 0; y fileInput.files.length; y) {if (fileInput.files[y] ! undefined) {let reader new FileReader();let readFilePromise new Promise((resolve, reject) {reader.onload function (e) {console.log(e);fileData.push(e.target.result.split(,)[1]);fileDatename.push(fileInput.files[y].name);resolve(); // 标记异步操作完成};reader.readAsDataURL(fileInput.files[y]);});//保存Promise准备一起处理readFilePromises.push(readFilePromise);}}// 等待所有异步操作完成all方法必须先执行完毕才会考虑异步所以后面的并不会进行处理在前面我们也说明了哦必须等方法执行完毕去了//而all就是处理所有的Promise的结果后才会进行完毕并且是按照添加顺序的这样就使得我们可以得到正常数据的结果//且由于Promise的特性必须是resolve()才会进行结束且保证了顺序所以无论你是否异步最终的结果都会正确因为只需要给最后的处理进行resolve()即可await Promise.all(readFilePromises);//完成后进行处理sendFileUsingGET(fileData, fileDatename);/*上面的操作是否看起来进行了同步呢实际上从上往下看的确是但是从内部看只是一个被等待造成的同步而已*/}function sendFileUsingGET(fileData, name) {console.log(fileData)console.log(name)//拿取了总共的数据现在我们来处理一下urllet xhr new XMLHttpRequest();let url file?;for (let h 0; h fileData.length; h) {if (h fileData.length - 1) {url file encodeURIComponent(fileData[h]) filename name[h];continue;}url file encodeURIComponent(fileData[h]) filename name[h] ;}console.log(url)xhr.open(GET, url, false);xhr.send();console.log(xhr.responseText);}
/script
/body
/html
后端代码如下
package com.test.controller;import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.util.Base64;public class GetRequest implements Servlet {Overridepublic void init(ServletConfig servletConfig) throws ServletException {System.out.println(初始化);}Overridepublic ServletConfig getServletConfig() {return null;}Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {HttpServletRequest h (HttpServletRequest) servletRequest;if (h.getMethod().equals(GET)) {System.out.println(GET);getFile(h, servletRequest, servletResponse);}if (h.getMethod().equals(POST)) {System.out.println(POST);}}private static void getFile(HttpServletRequest h, ServletRequest servletRequest, ServletResponse servletResponse) {servletResponse.setContentType(text/html;charsetUTF-8);try {PrintWriter writer servletResponse.getWriter();String[] file servletRequest.getParameterValues(file);String[] filename servletRequest.getParameterValues(filename);if (file ! null) {for (int i 0; i file.length; i) {String[] split filename[i].split(\\.);byte[] decodedBytes Base64.getDecoder().decode(file[i]);FileOutputStream fileWriter new FileOutputStream(F:/ split[0] . split[1]);fileWriter.write(decodedBytes);writer.write(h1 上传一个文件成功 /h1);}return;}writer.write(h1 上传的文件为空 /h1);return;} catch (Exception e) {e.printStackTrace();}}Overridepublic String getServletInfo() {return null;}Overridepublic void destroy() {System.out.println(正在销毁中);}
}
测试一下吧
当然了上面前端的三个关键字姑且认为Promise也是吧的灵活使用需要更多的练习所以不用着急先多看看思路
现在操作分开处理当然分开处理建议还是一样使用Promise因为他是保证顺序的当然原来的异步由于执行先后的时间原因大多数基本都会存在好的顺序但是还是存在风险因为如果其中一个在某个时候变快如某个耗费性能的进程在其操作过程中突然的关闭使得变快了那么顺序就不一致了所以建议使用Promise现在我们修改前端和后端代码 function sendFileUsingGET(fileData, name) {console.log(fileData)console.log(name)//拿取了总共的数据现在我们来处理一下urllet xhr new XMLHttpRequest();let url;for (let h 0; h fileData.length; h) {url file?file encodeURIComponent(fileData[h]) filename name[h];console.log(url)xhr.open(GET, url, false);xhr.send();console.log(xhr.responseText);}}当然前端只需要改一下上面的即可而后端可以不用动因为数组的形式差不多是一样的操作即下标为0的处理至此我们的多文件操作完毕现在我们来完成文件夹的处理
文件夹的处理其实并不难他与文件的处理只是多一个路径的处理而已然而文件的操作也通常没有保存了路径因为我们并不能通过浏览器访问文件系统在前面有说明所以文件夹的特别处理只是在后端根据自定义的路径进行创建文件夹的操作大多数的操作都是如此其他的与文件的处理完全一致并且这里也会留下一个问题等下进行给出我们先改变一下前端也就是index.jsp
input typefile idfileInput multiple webkitdirectory/
!--虽然是补充但是补充之间也有覆盖所以如果选择了文件夹的处理那么你就只能选择文件夹了--
button οnclickuploadFile()上传/button改变上面即可因为文件夹处理相当于自动多选了里面的所有文件只是对比多文件的选择我们只需要选择文件夹而已所以我们直接的访问看看对应后端的路径里面是否存在对应的文件吧这里选择统一处理还是分开处理都行建议分开处理因为需要考虑get的长度虽然现在影响不大这里看你自己了
至此我们的文件夹处理也操作完毕即原生js原生servlet的get请求带参数get请求头处理get单文件多文件文件夹的处理都操作完毕了但是上面的文件夹处理的时候说过了留下了一个问题假设如果我非要获取文件路径呢我虽然不能读取或者修改你的文件内容安全文件我可以读取如代码的内容找到破解方式修改的话自然也不能修改这是最危险的地方路径总能读取吧经过我的思考实际上路径的读取好像的确也不能因为浏览器就是不能你可能会有疑惑为什么我们选择文件中弹出的框框有呢要知道弹出这个框框的处理虽然是浏览器但并不是浏览器自身打开的而是浏览器调用文件系统打开的框框所以他只是引用可能会有参数改变对方的样式这是文件系统的扩展内容而非访问出来的
即get的相关处理都操作完毕其实你现在只需要改变如下
input typefile idfileInput multiple webkitdirectory/即后面的multiple或者webkitdirectory直接访问即可这样可以完成单文件多文件文件夹的处理了这是通用的操作这很重要哦现在我们来完成原生js和原生servlet的post请求post操作的请求头post的单文件多文件以及文件夹处理
现在我们将前面的处理中的get直接修改成post你也可以就改变当前前端中的index.jsp的get请求即可因为前面的都是一样的看看结果是否相同并且在后端中也进行相应的代码改变当然这里我给你测试完毕了你就不要测试了因为没有必要经过大量的测试发现将get修改成post结果都可以处理并且结果也一模一样这也就证明了前面说过了所以post是可以完成get的功能这里特别的需要注意在后面也会提到其中虽然有时候url是get形式的但是当请求方式是post时他会存在get形式的转换也是post完成get请求的一个重要因素这也给出我们在写某些函数时可以反过来进行处理即将给post的参数变成get形式
然而直接的说明并不好因为并没有示例代码所以我还是决定将测试结果写在这里
首先是post的请求
我们修改前端代码index.jsp
input typebutton οnclickrun1() value原生js实现Ajax的post请求br
scriptfunction run1() {let x;if (window.XMLHttpRequest) {x new XMLHttpRequest();} else {x new ActiveXObject(Microsoft.XMLHTTP);}x.open(POST, post?name1pass, false);x.send();let text x.responseText;console.log(text)}
/script后端代码创建PostRequest类
package com.test.controller;import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;public class PostRequest implements Servlet {Overridepublic void init(ServletConfig servletConfig) throws ServletException {System.out.println(初始化);}Overridepublic ServletConfig getServletConfig() {return null;}Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {String name servletRequest.getParameter(name);String pass servletRequest.getParameter(pass);String p servletRequest.getParameter(?);String a servletRequest.getParameter();String b servletRequest.getParameter();String c servletRequest.getParameter( );System.out.println(name , pass , p , a , b , c);System.out.println(1);servletResponse.setContentType(text/html;charsetUTF-8);PrintWriter writer servletResponse.getWriter();writer.write(h1 11 /h1);}Overridepublic String getServletInfo() {return null;}Overridepublic void destroy() {System.out.println(正在销毁中);}
}
web.xml加上如下 servletservlet-namePostRequest/servlet-nameservlet-classcom.test.controller.PostRequest/servlet-class/servletservlet-mappingservlet-namePostRequest/servlet-nameurl-pattern/post/url-pattern/servlet-mapping进行执行看看结果发现对应的结果与get是一样的证明了get请求转换了post的方式实际上post域中标准应该是这样写的
我们继续修改index.jsp
input typebutton οnclickrun1() value原生js实现Ajax的post请求br
scriptfunction run1() {let x;if (window.XMLHttpRequest) {x new XMLHttpRequest();} else {x new ActiveXObject(Microsoft.XMLHTTP);}x.open(POST, post?ff3, false);let body {name: 1,pass: ,}console.log(body)console.log(JSON.stringify(body))x.send(JSON.stringify(body));let text x.responseText;console.log(text)}
/script后端代码
package com.test.controller;import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;public class PostRequest implements Servlet {Overridepublic void init(ServletConfig servletConfig) throws ServletException {System.out.println(初始化);}Overridepublic ServletConfig getServletConfig() {return null;}Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {String name servletRequest.getParameter(name);String pass servletRequest.getParameter(pass);String p servletRequest.getParameter(ff);System.out.println(name , pass , p); //null,null,3servletResponse.setContentType(text/html;charsetUTF-8);PrintWriter writer servletResponse.getWriter();writer.write(h1 11 /h1);}Overridepublic String getServletInfo() {return null;}Overridepublic void destroy() {System.out.println(正在销毁中);}
}//上面打印了3也就是说get的url确会进行转换但是其他两个是null说明JSON.stringify(body)并不行所以去掉JSON.stringify()函数的处理看看当我们去掉了JSON.stringify()会发现也并没有打印对应的都是null为什么实际上这里我需要注意一下你认为getParameter方式的获取有没有条件实际上是有的在前面我们知道默认的处理是application/x-www-form-urlencoded当对应的请求头中存在这个那么对应的getParameter就可以进行处理无论你是在url中还是域中都是如此当然了后端中该代码是会判断请求信息中是get还是post而进行选择url处理还是域处理的这也是分工的判断要不然你写了就会进行分工呢所以肯定还是操作了判断的而get的处理一般都是操作这个的这没有问题而post对这个来说有点不同情况如下的操作
假设你post是get的url转换的那么默认会加上application/x-www-form-urlencoded使得后端的getParameter可以接收get本身就会加上但是有些东西在请求头中可能并不会直接的进行显示比如application/x-www-form-urlencoded在请求标头中可能并不会直接的显示不显示他而已具体情况可能是其他纯文本的方式而这个时候显示与不显示就需要看浏览器版本了包括get和post只是post如果没有进行get的转换那么即没有显示也没有进行设置所以这个时候前端代码应该是如此的两种都可以测试 x.open(POST, post?ff3, false);let body {name: 1,pass: ,}console.log(body)console.log(JSON.stringify(body))x.setRequestHeader(Content-Type, application/x-www-form-urlencoded);x.send(JSON.stringify(body));let text x.responseText;console.log(text)x.open(POST, post?ff3, false);let body {name: 1,pass: ,}console.log(body)console.log(JSON.stringify(body))x.setRequestHeader(Content-Type, application/x-www-form-urlencoded);x.send(body);let text x.responseText;console.log(text)然而上面的结果还是null,null,3这是因为你虽然设置了请求头但是他的处理方式并不是处理某个对象或者一些字符串的操作即需要是get的形式的所以我们应该这样写
x.open(POST, post?ff3, false);let body {name: 1,pass: ,}console.log(body)console.log(JSON.stringify(body))x.setRequestHeader(Content-Type, application/x-www-form-urlencoded);x.send(name1pass);
//这个时候你可以选择去掉x.setRequestHeader(Content-Type, application/x-www-form-urlencoded);可以发现没有获取即对应的值为null也可以更加的验证getParameter是靠对应请求头获取let text x.responseText;console.log(text)打印了1,3你可以继续修改 x.open(POST, post, false); //可以写成post?也可以不写let body {ff:3,name: 1,pass: ,}console.log(body)console.log(JSON.stringify(body))x.setRequestHeader(Content-Type, application/x-www-form-urlencoded);x.send(name1pass);let text x.responseText;console.log(text)打印的信息是1,null可以发现ff是null说明send最终的拼接是与get方式的url拼接的或者他们最终都是操作一个域里面所以ff直接没写时那么他就没有
我们可以发现实际上之所以浏览器默认对应的请求头是因为后端的操作或者每个操作都是默认了处理请求头来完成数据的获取的否则虽然你在网络上查看了他有参数但是也并非获取当然这种操作也是由于原始处理造成的原始处理在后面会说明的
从上面的测试来看我们测试了post的请求以及post的请求头的处理其中Content-Type是请求头的处理
好了post的请求和请求头的处理我们初步完毕在后面我们可能还会继续进行说明所以先了解
现在我们来完成post的单文件处理实际上单文件多文件和文件夹都可以使用前面的代码这里我们可以给出
前端
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
body
input typefile idfileInput multiple/
button οnclickuploadFile()上传/buttonscriptasync function uploadFile() {let fileInput document.getElementById(fileInput);let fileData [];let fileDatename [];if (fileInput.files.length 0) {return;}let readFilePromises [];for (let y 0; y fileInput.files.length; y) {if (fileInput.files[y] ! undefined) {let reader new FileReader();let readFilePromise new Promise((resolve, reject) {reader.onload function (e) {console.log(e);fileData.push(e.target.result.split(,)[1]);fileDatename.push(fileInput.files[y].name);resolve();};reader.readAsDataURL(fileInput.files[y]);});readFilePromises.push(readFilePromise);}}await Promise.all(readFilePromises);sendFileUsingGET(fileData, fileDatename);}function sendFileUsingGET(fileData, name) {let xhr new XMLHttpRequest();let url;for (let h 0; h fileData.length; h) {url file?file encodeURIComponent(fileData[h]) filename name[h];xhr.open(POST, url, false);xhr.send();}}
/script
/body
/html
我们只是将GET变成了POST即xhr.open(‘POST’, url, false);修改web.xml servletservlet-namePostRequest/servlet-nameservlet-classcom.test.controller.PostRequest/servlet-class/servletservlet-mappingservlet-namePostRequest/servlet-nameurl-pattern/file/url-pattern !--原来post的变成file--/servlet-mapping后端代码
package com.test.controller;import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Base64;public class PostRequest implements Servlet {Overridepublic void init(ServletConfig servletConfig) throws ServletException {System.out.println(初始化);}Overridepublic ServletConfig getServletConfig() {return null;}Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {HttpServletRequest h (HttpServletRequest) servletRequest;if (h.getMethod().equals(GET)) {System.out.println(GET);}if (h.getMethod().equals(POST)) {System.out.println(POST);getFile(h, servletRequest, servletResponse);}}private static void getFile(HttpServletRequest h, ServletRequest servletRequest, ServletResponse servletResponse) {servletResponse.setContentType(text/html;charsetUTF-8);try {PrintWriter writer servletResponse.getWriter();String[] file servletRequest.getParameterValues(file);String[] filename servletRequest.getParameterValues(filename);if (file ! null) {for (int i 0; i file.length; i) {String[] split filename[i].split(\\.);byte[] decodedBytes Base64.getDecoder().decode(file[i]);FileOutputStream fileWriter new FileOutputStream(F:/ split[0] . split[1]);fileWriter.write(decodedBytes);writer.write(h1 上传一个文件成功 /h1);}return;}writer.write(h1 上传的文件为空 /h1);return;} catch (Exception e) {e.printStackTrace();}}Overridepublic String getServletInfo() {return null;}Overridepublic void destroy() {System.out.println(正在销毁中);}
}
选择测试者三个
input typefile idfileInput/
button onclickuploadFile()上传/buttoninput typefile idfileInput multiple/
button onclickuploadFile()上传/buttoninput typefile idfileInput webkitdirectory/
button onclickuploadFile()上传/button经过测试发现都可以完成即post完成了单文件多文件文件夹的处理但是实际上前面的代码中我们还不够优化我们优化一下相应的后端代码
if (file ! null) {for (int i 0; i file.length; i) {String[] split filename[i].split(\\.);byte[] decodedBytes Base64.getDecoder().decode(file[i]);FileOutputStream fileWriter new FileOutputStream(F:/ split[0] . split[1]);fileWriter.write(decodedBytes);writer.write(h1 上传一个文件成功 /h1);fileWriter.close(); //操作关闭}writer.close(); //操作关闭return;}post我们也操作完毕但是还为之过早我们知道使用get的时候如果超过了url的限制那么会报错那如果post超过呢他是不是不会报错了所以我们先来测试一下
首先修改前端代码
function sendFileUsingGET(fileData, name) {let xhr new XMLHttpRequest();let url;for (let h 0; h fileData.length; h) {url file?file encodeURIComponent(fileData[h]) filename name[h];xhr.open(GET, url, false);xhr.send();}}相应的标签
input typefile idfileInput /
button onclickuploadFile()上传/button重新启动服务器然后上传一个图片文件看看检查里面的网络信息有没有错误发现还是出现如下
java.lang.IllegalArgumentException: Request header is too large一样的错误现在我们将GET修改成POST修改回来xhr.open(‘POST’, url, false);
我们继续执行会发现
java.lang.IllegalArgumentException: Request header is too large注意在看下面时不得不提到一点在讨论时确实需要澄清一些术语在HTTP协议中请求头这个词通常具体指代请求中的请求头字段而不包括请求行请求行和请求头构成了HTTP请求的头部区域而我们这里指的就是请求区域只不过用请求头来表示因为在学习时我们也基本认为请求行与请求头合称为请求头但是也要注意如果非要具体一点请说成请求区域或者请求头区域
会发现是一样的错误为什么你认为url是属于请求信息中的哪个地方实际上url属于请求头区域也就是说get出现这样的原因虽然我们说是url的限制即后端的限制但是实际上还有一个说法就是请求头区域过大因为url是请求头区域的也就是说url中添加的数据就算少于最大限制可能也会出现这样的错误因为请求头区域中并不是只包含他所以大多数博客说明的url过大并不准确真正的应该是请求头区域过大只是url基本在请求行中而已
如
/*
POST /task01_demo01/demo1.html HTTP/1.1 请求行包括请求类型请求路径协议版本
Host: localhost:8088 请求头 主机主机地址请求的服务器的地址
Content-Length: 21 最下面的请求体的长度
Cache-Control: max-age0 浏览器相关信息在服务器上修改代码可以进行实施更新
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) 浏览器相关信息//空白行
namescottpwd123456 请求体即请求数据不是请求过去的内容这是添加的数据
*//*
响应行用来说明HTTP协议版本号和状态码以及状态消息格式如下协议的版本(1.0 1.1) 状态码 (200 成功 404 路径错误 500 服务错误) 状态信息
响应头用来说明客户端要使用的一些附加信息格式key:value
空白行就是响应头部的空行即使后面的请求数据为空则必须有空行
响应体用来服务器返回给客户端的文本信息
举例如下
HTTP/1.1 200 OK 响应行 协议版本 状态码 很成功
Content-Type: text/html 响应头 这里是我要给你的的内容类型文本的html类型
Content-Length: 588 我要给你的内容长度
Date: Thu, 08 Sep 2021 12:59:54 GMT 日期//空白行
htmlheadtitle示例1/title/head 响应体 服务器给你的信息内容
bodyh1这是一个HTML页面/h1/body 这里省略了很多只给你html标签里的东西
/html
*/一般情况下如果粗略的说的话我们会将请求体或者响应体称为他们的整体所以存在两个意思对数据来说那么他们就是里面的体对一种大局来说就是一个整体这个在前面说明时就有类似的体会是请求体和响应体的设置这里就是大局的了而不是对数据可以全局搜索查看
很明显虽然post是保存在域中但是请求信息中也由于保证数据可见则必然会写上对应的拼接的整个url随着过程会进行get转换但是这个值还是设置的我们也会进行可见数据的处理所以post若要解决这样的问题我们应该要操作如下
function sendFileUsingGET(fileData, name) {let xhr new XMLHttpRequest();let url;for (let h 0; h fileData.length; h) {url file;xhr.open(POST, url, false);xhr.setRequestHeader(Content-Type, application/x-www-form-urlencoded);xhr.send(file encodeURIComponent(fileData[h]) filename name[h]);}}我们执行测试后可以发现文件处理完毕错误已经解决这里也能说明之前get也是可以操作图片的因为我们只是修改参数存放位置对应的值没有改变只是一般由于url限制所以操作不了但是又有一个问题我们发现生成的文件中名称可能存在乱码你可以测试一下中文经过测试在前端name[h]还是正确的所以是后端的问题当然中文的处理可以看看50章博客中的内容
private static void getFile(HttpServletRequest h, ServletRequest servletRequest, ServletResponse servletResponse) {servletResponse.setContentType(text/html;charsetUTF-8);try {//这里加上即可解决中文问题h.setCharacterEncoding(utf-8);PrintWriter writer servletResponse.getWriter();String[] file servletRequest.getParameterValues(file);String[] filename servletRequest.getParameterValues(filename);if (file ! null) {for (int i 0; i file.length; i) {String[] split filename[i].split(\\.);byte[] decodedBytes Base64.getDecoder().decode(file[i]);FileOutputStream fileWriter new FileOutputStream(F:/ split[0] . split[1]);fileWriter.write(decodedBytes);writer.write(h1 上传一个文件成功 /h1);fileWriter.close();}writer.close();return;}writer.write(h1 上传的文件为空 /h1);return;} catch (Exception e) {e.printStackTrace();}}当然post可不比get他的处理方式有非常多也正是因为post是主要操作文件的所以在前端存在多种方式来完成文件的处理但是也正是因为这些方式的处理才会造成大多数人并不会原始的文件操作方式如上面的前端传递文件信息的操作在前面我们知道只需要将get修改成post一样可以完成文件上传因为我并没有使用这些自然也没有这些的限制而是比较原始的处理所以当你改变后也只是h.getMethod()的值改变而已其他的一模一样当然由于上面的是原生js的处理所以这些方式你可以选择不用但是也可以用一用首先是标签的处理与get不同的是他可以进行直接的处理所以我们修改jsp文件操作如下
form actionfile methodpost enctypemultipart/form-datainput typefile namefileinput typetext namefilenameinput typesubmit value文件上传
/form这个标签进行提交的处理与前面的这个js是类似的我们也操作过甚至可以说是一样的
input typefile idfileInput/
button οnclickuploadFile()上传/buttonscriptfunction uploadFile() {let fileInput document.getElementById(fileInput);let file fileInput.files[0];sendFileUsingGET(file);}function sendFileUsingGET(file) {let xhr new XMLHttpRequest();let formData new FormData();formData.append(file, file);xhr.open(POST, get, false);xhr.send(formData);}
/script然而我们需要先通过formData来完成表单的操作这个时候我们不操作文件之前我们只是说明他的作用并没有真正的使用他完成过某些操作而是错误出现因为之前操作的是get所以我们来处理一下这个由于在前面我们说了formData主要是操作文件的为什么这是因为他自动携带了请求头信息multipart/form-data也就是说他相当于表单加上了multipart/form-data在请求头中可以看到所以他才是一个操作了文件的一个api但是也正是因为该请求头所以单纯的如前面的getParameterValues并不能进行处理他getParameter的数组方式与他getParameter是一样的需要对应的相同请求头也自然是application/x-www-form-urlencoded这个时候我们只能使用原始的处理的但是原始的处理我们并没有学习过所以在进行测试之前我们先学习一下这个原始处理首先是get的处理我们改变index.jsp
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
body
button οnclickuploadFile()上传/buttonscriptfunction uploadFile() {let xhr new XMLHttpRequest();xhr.open(get, get?name1, false);xhr.send();}/script
/body
/html
web.xml记得存在这个 servletservlet-nameGetRequest/servlet-nameservlet-classcom.test.controller.GetRequest/servlet-class/servletservlet-mappingservlet-nameGetRequest/servlet-nameurl-pattern/get/url-pattern/servlet-mapping后端的代码 Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {String name servletRequest.getParameter(name);System.out.println(name);servletResponse.setContentType(text/html;charsetUTF-8);PrintWriter writer servletResponse.getWriter();writer.write(h1 11 /h1);}至此看看打印结果是否出现出现后我们修改后端代码
Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {String name servletRequest.getParameter(name);System.out.println(name);//这样也可以BufferedInputStream fileInputStream new BufferedInputStream(servletRequest.getInputStream());他一般是用来指定获取的当然他是获取字节流的而下面是字符流的对文件来操作时建议操作字节流比如写入当然这里获取由于需要判断所以需要字符这里就这样操作了BufferedReader br servletRequest.getReader();BufferedWriter bw new BufferedWriter(new FileWriter(F:/in.txt));String str null;while ((str br.readLine()) ! null) {System.out.println(str);bw.write(str);bw.newLine();}bw.close();br.close();servletResponse.setContentType(text/html;charsetUTF-8);PrintWriter writer servletResponse.getWriter();writer.write(h1 11 /h1);}我们可以发现str是null也就是说在url中设置并不能获取对应请求体的数据当我们修改post时也是如此那么可以说他只是获取对应请求信息中的multipart/form-data处理所以我们修改前端 function uploadFile() {let xhr new XMLHttpRequest();let formData new FormData();formData.append(name, 1);xhr.open(POST, get?name1, false); //虽然请求的url是get名称但是也只是一个名称而已所以并不需要完美在某些方面他的名称只是一个获取而已而非代表是get请求所以注意即可xhr.send(formData);}查看网络可以发现有两个地方他们是互不影响的 注意上图中的显示是get和post共用的因为也只是显示而已并不代表存放的地方
第二个就是multipart/form-data的处理在请求方式发生改变后对应的send的参数可以接收这个对象从而出现表单数据这与对应的请求头application/x-www-form-urlencoded使得在send中加上对应url参数形式是一样的但也只是针对后端数据的获取也验证了formData操作了该请求头信息你可以点击查看源代码看看内容等下看看执行后生成文件的内容即可当然在某些情况下他可能是隐藏的可能的原因之一是防止你赋值拿取信息当然了如果你能够破解浏览器那么也行但是如果可以破解了你也大概率不会到这里了
现在我们访问后端可以发现str不为null了对应的处理就是专门操作对应表单的信息的所以不为null了经过测试之所以可以得到是因为servletRequest相关的获取servletRequest.getInputStream()或者servletRequest.getReader()只能操作表单并不绝对看后面就知道了所以当是表单时那么就能操作否则不会有即不会有那么基本是空数据了自然得不到了那么看看对应的生成的文件内容是什么这个时候可以发现与对应的上面的查看源代码内容一致说明我们获取的就是对应传递的信息要注意对应的信息是分割的我们可以通过分割的字符串来进行处理当然这是比较麻烦的即原始操作为了证明原始操作可以进行处理所以我们来手动写一个现在我们修改前端代码
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
body
input typefile idfileInput/
button onclickuploadFile()上传/buttonscriptfunction uploadFile() {let fileInput document.getElementById(fileInput);let file fileInput.files[0];sendFileUsingGET(file);}function sendFileUsingGET(file) {let xhr new XMLHttpRequest();let formData new FormData();formData.append(file, file);formData.append(name, 22);xhr.open(POST, get, false);xhr.send(formData);}
/script
/body
/html
当然通过前面的说明我们知道了原始操作实际上原始操作最终还是服务器自身的api而服务器的api也是根据网络编程来处理的最终的最终都只是操作请求信息和响应信息所以这个原始操作只是在利用比较原始的api来进行处理的不是真正的底层因为服务器也是编写的要知道下层还有网络编程呢所以存在后面的代码
我们编写后端代码就以这个为主上面前面的检查元素里面的下面是我上传图片后的其中一个结果 后端代码是
package com.test.controller;import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;public class GetRequest implements Servlet {Overridepublic void init(ServletConfig servletConfig) throws ServletException {System.out.println(初始化);}Overridepublic ServletConfig getServletConfig() {return null;}Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) {try {HttpServletRequest h (HttpServletRequest) servletRequest;String contentType h.getHeader(Content-Type);String boundary null;//boolean startsWith(String prefix)判断字符串是否以参数字符串开头if (contentType ! null contentType.startsWith(multipart/form-data)) {//找到下标int boundaryIndex contentType.indexOf(boundary);if (boundaryIndex ! -1) {//获取他后面的字符串boundary contentType.substring(boundaryIndex boundary.length());}}//得到的是分割符一般来说分割符是唯一的这是算法形成的可以认为会判断上下文来确定唯一所以不用考虑他的重复性if (boundary ! null) {MapInteger, MapString, String mapMap new HashMap();MapString, String map null;//我们需要这样操作因为我们并不能使用除了对应二进制的如ascii的编码操作图片二进制的数据因为由于并不识别找相似的原因他们并不会对应所以可能会造成文件变大或者变小所以我们需要进行另外的方式//并且也不能操作多次因为对应的流是同一个容易出现下标的问题所以我们需要这样的处理即一起处理//但是与之前的servletRequest.getReader()还是有区别之前的servletRequest.getReader()是直接拿取对应文件的信息的ServletInputStream inputStream servletRequest.getInputStream();FileOutputStream fileWriter null;int hh 0;int re 0;byte[] fd new byte[1024];int hkl 0;int u 0;int bh 0;//默认这个名称String name 333.png;int ghk 0;int count 0;int jj 4;int uh 0;//为了解决中文的问题不得不这样操作否则根本并不需要这样的操作了直接的强转变成字符即可while ((re inputStream.read()) ! -1) {if (hkl fd.length) {byte[] ll fd;fd new byte[fd.length * 2];for (int g 0; g ll.length; g) {fd[g] ll[g];}}fd[hkl] (byte) re;hkl;//这里代入了空白行所以这里可能会判断多次而ghk就是防止加入了空白行的信息的并且如果以后也存在//那么由于这个的原因会导致必然存在一个正确的数才会进行处理if ((char) re \r || (char) re \n) {ghk;if (count 1) {if (ghk 4) {if (ghk % 2 0) {uh;}fd new byte[1024];hkl 0;}}if (count 0) {if (ghk jj) {count 1;fd new byte[1024];hkl 0;}}if ((char) re \n ghk 2) {if (u 0) {u;byte[] ii new byte[hkl];for (int yun 0; yun hkl; yun) {ii[yun] fd[yun];}byte[] iii new byte[ii.length - 2];for (int b 0; b iii.length; b) {iii[b] ii[b];}//得到对应的一行数据//对字符处理即将读取的字节按照UTF-8变成字符由于大多数表单处理的都是UTF-8所以根据这样的可以不会出现编码问题如文件名称String str new String(iii, UTF-8);//防止对应的文件里面最后存在空格导致判断出现问题str str.replace( , );byte[] iij new byte[ii.length uh * 2];for (int hkk uh * 2, kl 0; hkk iij.length; hkk) {iij[hkk] ii[kl];kl;}int q 0;int w 1;for (int gh 0; gh uh * 2; gh) {if (q 0 w 1) {iij[gh] \r;w 0;q 1;continue;}if (q 1 w 0) {iij[gh] \n;w 1;q 0;}}//判断第一行的内容是否是这个由于在每个数据开始都会存在--的添加所以这里是--boundaryif (str.equals(-- boundary)) {if (uh 0) {byte[] hj new byte[uh * 2];int ujg 0;for (int i 0; i hj.length; i) {if (ujg 0) {hj[i] \r;ujg 1;continue;}if (ujg 1) {hj[i] \n;ujg 0;}}uh 0;fileWriter.write(hj);}bh 0;if (fileWriter ! null) {count 0;fileWriter.close();//去掉最后两个字节dern(name);}map new HashMap();mapMap.put(hh, map);hh;}if (str.equals(-- boundary --)) {if (uh 0) {byte[] hj new byte[uh * 2];int ujg 0;for (int i 0; i hj.length; i) {if (ujg 0) {hj[i] \r;ujg 1;continue;}if (ujg 1) {hj[i] \n;ujg 0;}}uh 0;fileWriter.write(hj);fileWriter.close();}break;}uh 0;if (str.indexOf(Content-Disposition) 0) {int i str.indexOf(;);String substring str.substring(i 1, str.length());String s ;char[] chars substring.toCharArray();for (int o 0; o chars.length; o) {if (chars[o] ) {continue;}s chars[o];}String[] split s.split(;);for (int k 0; k split.length; k) {String[] split1 split[k].split();String replace split1[1].replace(\, );map.put(split1[0], replace);}}//这个是文件的类型如果是需要传递pdf的话那么这里通常需要判断是否是application/pdf具体可以在网络的请求的载荷也可以说是请求信息或者参数信息中看到if (str.indexOf(Content-Type) 0) {if (str.indexOf(image/png) 0||str.indexOf(text/plain)0){name map.get(filename);if (name ! null) {fileWriter new FileOutputStream(F:/ name);}bh 1;}fd new byte[1024];hkl 0;continue;}//其他的可能也要处理但是我测试的数据一般是没有的所以就不进行处理了//并且上面的都只是根据已经存在的数据进行处理的所以只是一个临时的处理对真正的处理可能还需要进行判断这里了解即可//一般来说服务器也存在调试只是他的功能更加的多将下面的窗口往上拉即可if (bh 1) {//前面是需要解决中文问题才需要的而这里直接给出字节数组即可fileWriter.write(iij);}fd new byte[1024];hkl 0;}}continue;}ghk 0;u 0;}inputStream.close();for (int hhh 0; hhh mapMap.size(); hhh) {MapString, String mapp mapMap.get(hhh);SetMap.EntryString, String entries mapp.entrySet();System.out.println(hhh :);for (Map.Entry e : entries) {System.out.println({ e.getKey() : e.getValue() });}}servletResponse.setContentType(text/html;charsetUTF-8);PrintWriter writer servletResponse.getWriter();writer.write(h1 11 /h1);System.out.println(操作完毕);}} catch (Exception e) {e.printStackTrace();}}Overridepublic String getServletInfo() {return null;}Overridepublic void destroy() {System.out.println(正在销毁中);}private void dern(String name) {try {FileInputStream fileInputStream new FileInputStream(F:/ name);int re 0;byte[] fd new byte[1024];int hkl 0;while ((re fileInputStream.read()) ! -1) {if (hkl fd.length) {byte[] ll fd;fd new byte[fd.length * 2];for (int g 0; g ll.length; g) {fd[g] ll[g];}}fd[hkl] (byte) re;hkl;}FileOutputStream fileWriter new FileOutputStream(F:/ name);byte[] fdd new byte[hkl - 2];for (int u 0; u fdd.length; u) {fdd[u] fd[u];}fileWriter.write(fdd);fileWriter.close();fileInputStream.close();} catch (Exception e) {e.printStackTrace();}}
}
这个代码是我临时编写大概率还可以进行优化作为临时的就不考虑优化了可以用即可单纯的利用比较原始api来完成最终结果当然没有一定的编程功底是很难编写出来所以为了方便使用servlet实际上也给出了对应的api来给我们来使用就不需要自行编写了后面会说明的
现在我们上传执行上传一个图片看看图片信息是否生成如果对应的在指定盘中出现了图片那么说明操作完毕上面默认在F盘下的且名称是文件名称当然我们需要考虑一个点对应文件的内容存在浏览器对应的区域时是如何处理的特别是换行符怎么处理我们看这个
假设这个是文件信息
1111111114444444
5555221
那么对应再前端的区域也是这个
分割符
1111111114444444
5555221分割符也就是说绝对的一致但是由于最后的值指的是空需要换行到分割符中所以上面的代码由于考虑到了这样的情况所以默认将后面的换行符都进行加上所以我们最后的处理需要这样的存在
//去掉最后两个字节
dern(name);也就是说实际上如果对应的文件的一行中存在换行那么对应的区域也存在若没有那么也不存在但是需要换行到分割符中所以我们需要特别的处理最后两个字节也就是说我们确定原来的文件末尾是否存在换行符他会影响上面的值使得1后面多出几个换行所以文件末尾是否存在换行符并不会影响数据的操作只有到分割符的换行会影响整体性但并不影响文件的显示但是无论是否存在在前面对应的地方上面写的代码都会加上换行符并且最后去掉并且实际上不考虑去掉最后两个字节也没有问题因为换行符并不会影响图片的显示但是也只是末尾头部可不要这样哦而正是如此如果非常的细节的话实际上浏览器的文件上传在末尾的情况有些时候可能会出现一些问题面临整体性的问题然而上面的解释与实际情况是相同的我们可以观察生成的文件会发现字节数是一样的注意需要看属性因为kb比较大并不会细节的显示这个一样通常在很多方面如上面解决了//去掉最后两个字节否则会导致生成的可能会多出几个字节如换行符实际上由于默认添加换行符所以我们生成的文件一般会多出两个字节也就是\r\n你可以选择在生成的文件内容最后去掉最后一个的换行符可以发现他们的字节数量是一样的了
从这里我们也可以发现关于IO流无非就是字符和字节的保存和转换的变化实际上无论api是如何操作最终都是字符变成字节保存的
当然上面换行符的出现是因为浏览器在文件上传的过程中多部分表单数据格式会引入一些额外的字符包括换行符甚至会对某些字符进行统一的处理但这些通常不会对上传的文件本身产生实质性的影响而正是如此所以一般情况下内容是完全一致的但是可能如换行符的存在会导致出现问题虽然只是一个完整性的问题也就是说如果解决了这个问题那么文件就完全的一样了而不会有任何的不同但是这里我为了完整性所以才操作了最后去掉字节
至此我们的后端代码操作完毕当然这也只是极小的部分代码实际上情况会更加的复杂正如代码注释中所说其他的可能也要处理但是我测试的数据一般是没有的所以就不进行处理了
至此原始处理我可以说操作完毕
之前我们操作了post的多种方式将get变成post的的文件处理但是他们都只是操作key-value的处理也就是传统的处理post有其他的处理也就是multipart/form-data而不是application/x-www-form-urlencoded他的优势在于我们不需要操作继续的编码如base64上面是按照传统的multipart/form-data处理来的并且使用比较原始的api
实际上post可以完成get的处理前面说过了但是也正是他的域存在所以post存在其他的处理也是正常的也有属于操作他专门的方式也就是之前默认的存在可以操作请求头需要请求头需要multipart/form-data解决Multi-Part错误几乎没有需要的文件上限即没有限制大小get的url有限制
上面我们了解即可但是为了验证上面的原始处理比较正确所以现在我们来操作多文件的处理也是使用上面的代码总需要都试一下
修改前端代码
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
body
input typefile idfileInput multiple/
button οnclickuploadFile()上传/buttonscriptfunction uploadFile() {let fileInput document.getElementById(fileInput);let file fileInput.files;sendFileUsingGET(file);}function sendFileUsingGET(file) {let xhr new XMLHttpRequest();let formData new FormData();for (let y 0; y file.length; y) {formData.append(file, file[y]);}formData.append(name, 22);xhr.open(POST, get, false);xhr.send(formData);}
/script
/body
/html
!--上面的情况可以解决多个文件的处理以后建议操作文件的时候最好这样因为他无论是选择多个还是一个都可以完成而最好不用操作默认为0的处理即let file fileInput.files[0];当然前面我们只是测试测试而已所以这样也没事因为我们只是一个测试操作而非正式的写代码--
虽然对应的在检查中比如说 存在两个name但是他并不会是覆盖的形式因为对于域来说他只负责存放数据而数据的分别则是由分割符来完成即或者说实际上我们并没有具体参照过name而是参照分割符因为他们是一个整体这个分割符到空白行之间的整个信息
也就是说实际上无论是多文件还是问文件夹都是如此文件夹只是一个选择了一个文件里面的所有数据的多文件而已还是属于多文件的范畴实际上多文件可以那么文件夹也可以更甚至文件可以多文件也就可以经过测试文件夹也是可以的但是域即multipart/form-data与之前的处理有点不同前面是最原始的操作而这个是经过中间处理的我们可以发现存在这个 也就是说带上了文件夹的名称了因为他这个操作是封装的要不然分割符怎么来的呢或者说这也是浏览器给出的一个操作所以在multipart/form-data的情况下是存在对于文件夹名称的实际上前面的操作中即不是这样的方式下当然post可不比get他的处理方式有非常多也正是因为post是主要操作文件的所以在前端存在多种方式来完成文件的处理也就是前面第一次使用get的时候拿取的这个 let fileInput document.getElementById(fileInput);let file fileInput.files;console.log(file)实际上里面的file中就存在对于的文件夹信息前提是文件夹的处理否则是对应的文件信息也就是说这些处理实际上还是浏览器自身的处理而非multipart/form-data只不过在过程中拿取了这个数据来操作了之前我们操作get时只是操作文件的内容而并没有操作文件夹的处理实际上我们还应该传递这个路径信息来进行处理的经过测试每个文件中存在webkitRelativePath参数前端如果是文件夹那么他就不是那么前面操作文件时需要判断一下这个的值是否为空即可从而赋值这个值到后端然后后端直接执行创建目录的方法来保证目录的创建当然如果没有目录创建目录基本不会进行任何操作的这也是一种优化可以选择试一下或者了解即可
当然这里也可以给出一个优化即创建文件的优化考虑文件目录的创建也可以给前面操作get时的处理的
代码如下
if (str.indexOf(Content-Type) 0) {if (str.indexOf(image/png) 0||str.indexOf(text/plain)0){name map.get(filename);if (name ! null) {String pa F:/;File file new File(pa name);if (file.exists()) {fileWriter new FileOutputStream(pa name);} else {//默认是\开始的在windows中一般是\linux中一般是///当然这也只是文件系统的处理或者显示在其他操作或者显示的处理上大多数都是/String name1 file.getPath();int i name1.lastIndexOf(\\);String substring name1.substring(0, i);new File(substring).mkdirs();fileWriter new FileOutputStream(pa name);}}bh 1;}fd new byte[1024];hkl 0;continue;}重启测试文件夹的处理发现操作成功了
至此原始处理我们操作完毕实际上上面在并发情况下容易出现问题这是因为可能同时操作对应的相同文件或者操作相同文件所以我们可以在很多文件上传的处理中基本都会给文件名称进行处理比如加上UUID等等的处理来保证唯一就是防止并发情况下的问题
当然服务器一般存在写好的原始处理的api而不用我们自己来写比如我们可以这样处理
修改后端代码实际上将上面我写好的代码封装一下也可以使用的前端代码操作单文件处理即可
这里决定给出传统形式的处理当然给出的是get和post的判断看如下
HttpServletRequest h (HttpServletRequest) servletRequest;if (h.getMethod().equals(GET)) {System.out.println(GET);}if (h.getMethod().equals(POST)) {System.out.println(POST);}前面我们操作过这个实际上由于某些东西是需要一些对应的请求的所以在这之前我们通常需要判断对应于框架来说就是判断你的请求是get还是post了实际上由于服务器原本的处理是service方法那么在考虑传统的处理比如50章博客中的dopost和doget实际上都只是在service里面进行的处理的或者说也是根据这个判断来执行谁所以实际上上面写的是最原始的但是我们也知道其实我们也利用了HttpServletRequest来操作方法实际上只是因为用来接收的而已还存在更加原始的只是这里我们操作比较原始的否则岂不是还要到网络编程里面去了
当然有些依赖也存在这样的处理相当于也封装了上面的原始操作实际上上面的原始操作中我最后是操作打印信息的这个信息是否可以认为是一个依赖里面给与的方法的返回值呢
现在我们来操作一些自带的api来处理文件
我们创建re类然后写上如下复刻一下对应的50章博客的处理实际上对应的配置或者代码可能还有更多的操作当然这里我们就不考虑复杂的兜底操作了所以直接的简单处理便可
package com.test.controller;import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class re implements Servlet {Overridepublic void init(ServletConfig servletConfig) throws ServletException {System.out.println(初始化);}Overridepublic ServletConfig getServletConfig() {return null;}Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) {HttpServletRequest h (HttpServletRequest) servletRequest;if (h.getMethod().equals(GET)) {System.out.println(GET);try {doGet((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);} catch (Exception e) {e.printStackTrace();}}if (h.getMethod().equals(POST)) {System.out.println(POST);try {doPost((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);} catch (Exception e) {e.printStackTrace();}}}protected void doPost(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {}protected void doGet(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {}Overridepublic String getServletInfo() {return null;}Overridepublic void destroy() {System.out.println(正在销毁中);}}/*
我们可以查询对应的类HttpServlet是否与这个re一样的结构即servicedoPostdoGet所以说如果我们继承了re可以操作那么自然继承了HttpServlet一样可以操作与对应的50章博客类似了即复现
*/然后创建一个reen类
package com.test.controller;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class reen extends re {Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) {System.out.println(1);}Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) {System.out.println(2);}
}
在web.xml中进行编写
servletservlet-namereen/servlet-nameservlet-classcom.test.controller.reen/servlet-class/servletservlet-mappingservlet-namereen/servlet-nameurl-pattern/reen/url-pattern/servlet-mapping这样就会定位到reen的方法因为他也是对应接口的子类然后执行reen的service方法由于service是其父类的那么使用其父类的并且由于重写了对应的doPost和doGet那么执行自己的版本我们在前面进行访问
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
body
button οnclickuploadFile()访问/buttonscriptfunction uploadFile() {sendFileUsingGET();}function sendFileUsingGET(file) {let xhr new XMLHttpRequest();xhr.open(POST, reen, false);xhr.send();xhr.open(GET, reen, false);xhr.send();}
/script
/body
/html
点击访问访问两次若后端打印了对应的12那么说明操作成功我的打印信息是
初始化
POST
2
GET
1正好对应即我们操作完毕在这种情况下我们开始使用服务器一般存在写好的原始处理的api来完成我们的文件处理了
后端代码修改如下我们创建一个FileUploadServlet类
package com.test.controller;import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;WebServlet(/upload) //这样不用在配置文件中处理了当然经过前面几章博客的学习那么这个注解自然存在初始化操作就处理的所以了解即可
MultipartConfig( //配置这个除了对应的请求头需要multipart/form-data一级post请求外在前面我们也提到过对于某些后端框架或服务或者服务器本身它们可能默认不支持解析 Multi-Part 请求//而这个就是进行配置否则还是会报对应的错误fileSizeThreshold 1024 * 1024 * 2, // 2MB指定了一个文件大小的阈值以字节为单位如果上传的文件大小小于这个阈值文件将存储在内存中如果文件大小超过这个阈值文件将存储在磁盘上这里理解成io流的刷新机制即可默认为0即读取一个操作一个到磁盘文件里面maxFileSize 1024 * 1024 * 10, // 10MB用于限制单个上传文件的最大大小以字节为单位在这个示例中最大文件大小被设置为 10MB这意味着用户无法上传超过 10MB 大小的文件实际上这个我们也可以在前面操作的代码中来计算从而完成比如判断总字节如果超过删除对应文件便可默认是无限大maxRequestSize 1024 * 1024 * 50 // 50MB配置用于限制整个请求的最大大小包括所有上传文件和其他请求参数在这个示例中最大请求大小被设置为 50MB这意味着整个请求不能超过 50MB类似于get中的请求体大小虽然那里说明的是请求头区域get有限制我们post拥有他的能力自然也可以进行设置默认是无限的
)
public class FileUploadServlet extends HttpServlet { //前面我们复现了一个简易版的所以我们直接使用他即可//这样就不用操作强转了使用上面的继承判断post自然到这里来protected void doPost(HttpServletRequest request, HttpServletResponse response) {try {// 获取文件Part这里相当于之前我们代码的处理只是他里面封装了关键的信息又或者说将关键信息放在临时文件里面最后拿取进行处理所以存在filePart.getInputStream();Part filePart request.getPart(file);// 获取上传的文件名String fileName filePart.getSubmittedFileName();// 获取文件输入流InputStream fileContent filePart.getInputStream();// 将文件保存到服务器上的目标位置Path targetPath Paths.get(F:/ fileName);//StandardCopyOption.REPLACE_EXISTING 选项用于在目标文件已存在时覆盖它//这意味着如果目标文件已经存在它将被新上传的文件内容替换//将左边给右边大多数移动数据的比如文件类集合等等基本都是左边移动右边但是还是需要看实际操作来判断要不然为什么是大多数呢//所以建议在操作对应的代码时请给出注释Files.copy(fileContent, targetPath, StandardCopyOption.REPLACE_EXISTING);// 可以在这里对文件进行进一步处理或响应客户端response.getWriter().write(文件上传成功 fileName);} catch (Exception e) {e.printStackTrace();}}
}前端代码
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
body
input typefile idfileInput/
button οnclickuploadFile()上传/buttonscriptfunction uploadFile() {let fileInput document.getElementById(fileInput);let file fileInput.files;console.log(file)sendFileUsingGET(file);}function sendFileUsingGET(file) {let xhr new XMLHttpRequest();let formData new FormData();for (let y 0; y file.length; y) {formData.append(file, file[y]);}formData.append(name, 22);xhr.open(POST, upload, false);xhr.send(formData);}
/script
/body
/html
开始文件处理又何尝不是文件上传的处理呢我们可以发现文件的信息被保存了并且内容一模一样说明他也是解决了换行或者空白符的处理那多文件呢那么需要这样的处理
package com.test.controller;import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Collection;WebServlet(/upload)
MultipartConfig(fileSizeThreshold 1024 * 1024 * 2,maxFileSize 1024 * 1024 * 10,maxRequestSize 1024 * 1024 * 50)
public class FileUploadServlet extends HttpServlet {protected void doPost(HttpServletRequest request, HttpServletResponse response) {try {CollectionPart parts request.getParts();for (Part part : request.getParts()) {//与我们的代码一样存在多个相同的name因为对于域来说他只负责存放数据而数据的分别则是由分割符来完成//或者说这里前面说明过了实际上我们并没有参照name而是参照分割符因为他们是一个整体即part用part存放整体信息if (file.equals(part.getName())) {String fileName part.getSubmittedFileName();InputStream fileContent part.getInputStream();Path targetPath Paths.get(F:/ fileName);Files.copy(fileContent, targetPath, StandardCopyOption.REPLACE_EXISTING);response.getWriter().write(文件上传成功 fileName);}}} catch (Exception e) {e.printStackTrace();}}
}那么文件夹呢实际上如果是文件夹那么名称可能需要处理一下如创建目录当然前面我们也处理过这个我们只给出这个名称的获取方式就不操作具体的目录创建了
String fileName part.getSubmittedFileName();
//实际上这个就是一般来说Part对文件来说单纯的就是文件名称如果是文件夹那么这个值会加上目录的但是确好像并没有具体的文件名称这是因为在域中也没有之间的文件名称信息对文件夹来说所以传递时需要考虑目录的创建当然如果前端真的考虑的话实际上可以传递具体文件名的因为在前端是可以得到这个名称虽然他可能也是拿取对应参数来得到的如查找字符串或者根据/或者\来区分的实际上对于陌生的默认接收信息的类如Part需要知道他的具体的结构最好调试一下看看的的信息也可以更好的知道其方法的作用如大多数信息如变量对于的方法名称可能与他对应的或者查看对应的方法是否是操作或者得到他即可
至此我们利用服务器自带的编写好或者封装好的api里面存在原始api原始代办最底层的意思即没有其他api操作获取了完成了这些操作至此post请求还剩下最后一个处理即标签处理我们修改前端
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
body
form actionupload methodpost enctypemultipart/form-datainput typefile namefileinput typefile namefileinput typefile namefileinput typetext namefilenameinput typesubmit value文件上传
/form
/body
/html
检查中的截图如下 也就是说结果是一样的当然上面的顺序是根据标签顺序来的并且如果一个文件存在多文件选择那么按照多文件选择来多文件选择时可能是按照某种编码来进行排序比如ascii这在某种情况下无论你多文件如果选择都是2.txt在qq截图前面这里可以了解因为并没有很重要所以将上面标签处理说成formData也不为过无论是多文件和单文件还是文件夹的一起操作都只是对应的参数值不同而已其中文件夹多一个目录结构都是一样的
至此我们的post操作就已经完美完成
经过上面的处理完美原生的js原生的servlet完成了get和post的所有操作了
实际上对原生的处理完成后其他的任何变化我们都基本很容易的理解那么看如下
现在操作框架js和原生servlet的操作框架js我们可以选择jq当然经过前面的原生的处理我们在考虑操作框架时或多或少觉得没有必要因为我们很容易的理解完毕所以我们选择的进行处理那么我们操作jq的多文件的处理
修改前端这里的jq很容易的引入因为不是mvc的相对全部拦截
input typefile idfileInput/
button onclickuploadFile()上传/button
script srchttps://code.jquery.com/jquery-3.6.0.min.js/script
scriptfunction uploadFile() {let fileInput document.getElementById(fileInput);let file fileInput.files;console.log(file)sendFileUsingGET(file);}function sendFileUsingGET(file) {let formData new FormData();for (let y 0; y file.length; y) {formData.append(file, file[y]);}formData.append(name, 22);$.ajax({url: upload, // 提交到的URLtype: POST,data: formData,processData: false, // 不处理数据contentType: false, // 不设置内容类型success: function (data) {// 成功回调函数console.log(data)},error: function (error) {console.log(data)}});}
/script我们只是将ajax进行了改变而已一般在前端中ajax一般都是封装好的而基本都不是原生的处理
后端代码还是之前的代码使用了Part类的代码也就是前面我们操作了集合的处理
package com.test.controller;import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Collection;WebServlet(/upload)
MultipartConfig(fileSizeThreshold 1024 * 1024 * 2,maxFileSize 1024 * 1024 * 10,maxRequestSize 1024 * 1024 * 50)
public class FileUploadServlet extends HttpServlet {protected void doPost(HttpServletRequest request, HttpServletResponse response) {try {CollectionPart parts request.getParts();for (Part part : request.getParts()) {if (file.equals(part.getName())) {String name part.getName(); //只是一个类型比如是Content-Disposition: form-data; namefile; filenameQQ截图20230816093813.png中的name而下面的则是filenameString fileName part.getSubmittedFileName();InputStream fileContent part.getInputStream();Path targetPath Paths.get(F:/ fileName);Files.copy(fileContent, targetPath, StandardCopyOption.REPLACE_EXISTING);response.getWriter().write(文件上传成功 fileName);}}} catch (Exception e) {e.printStackTrace();}}
}我们执行后可以发现操作成功这里我们需要提一下由于使用对应的api里面可能是处理默认编码的而不是与我们自行编写的代码一样前面字符处理中文的操作操作指定的解决但是他自然也会考虑这样的情况所以存在如下
后面拿取中文名称的文件时是一个乱码的实际上我们只需要这样就能解决
request.setCharacterEncoding(utf-8); //加上这个一般情况下Part的编码是操作这个的所以设置这个即可而前面我们由于基本是最原始的处理所以只能指定了
CollectionPart parts request.getParts();现在我们继续测试如果编码正确那么操作成功
现在我们可以选择看看ajax的一些参数
url: upload, // 提交到的URL
type: POST,
data: formData,
processData: false, // 不处理数据
contentType: false, // 不设置内容类型前面三个很好理解其中url和type我们就不做说明但是data需要因为他的类型决定了是操作什么样的参数并且也会决定请求头在前面我们也知道这样的处理
function sendFileUsingGET(fileData, name) {let xhr new XMLHttpRequest();let url;for (let h 0; h fileData.length; h) {url file;xhr.open(POST, url, false);xhr.setRequestHeader(Content-Type, application/x-www-form-urlencoded);xhr.send(file encodeURIComponent(fileData[h]) filename name[h]);}}send也可以存在对应的值当然如果要设置请求头那么jq的ajax应该是如此
type: GET, // HTTP请求方法headers: {Header-Name1: Header-Value1, // 设置请求头的键值对Header-Name2: Header-Value2,// 可以添加更多的请求头},在继续说明之前我们需要一下对应的一些请求头对应的信息我们看看这个前端
let xhr new XMLHttpRequest();xhr.open(POST, upload?k4, false);xhr.setRequestHeader(Content-Type, application/json);xhr.send(nn3);得到的信息是这样的字符串一般都是载荷无论是否设置了上面的请求头 他也是一个新的请求头处理在后面会说明的在mvc中存在对应的注解如ResponseBody进行处理这个在68章博客有说明这里就不操作了实际上他只是操作一个特殊变化这个在操作完这些原生和框架的说明后单独的进行处理说明因为他基本是最为重要的当然get是操作不了的忽略前面只能操作上面的查询字符串的信息因为他是键值对这个get和post是同一个地方虽然一个是域一个是url但是最终的操作是一样的处理这里只是显示即这个整体域
现在我们继续来说jq的ajax的参数主要说明这两个地方
processData: false, // 不处理数据不进行中途改变数据
contentType: false, // 不设置内容类型自动设置请求头实际上这些参数可有可无因为这都是js框架补充的而已只需要前面三个正确基本就行了一般需要加上请求头除非有自动的处理如formData所以我们删除这两个然后再来测试发现不行前端报错加上了processData和contentType就行了也就是说jq为了严谨必须需要设置contentType和processData这个虽然存在自动的处理即formData这个时候你可以选择设置也可以不设置设置相同的请求头是操作覆盖的所以建议加上这些即可当然在不同的版本中可能并不需要他们这都是需要注意的chatgpt是一个很好的工具
当然上面的代码还有一个地方有问题返回信息也需要进行处理编码因为他可能也是处理默认的所以需要加上这个
response.setContentType(text/html;charsetUTF-8); /*之前的我们基本都操作了当然一般如果是直接到jsp通常由于jsp存在对应编码所以我们可能并不需要设置这里可以选择在50章博客里或者其后面了解*/
response.getWriter().write(文件上传成功 fileName);再来测试然后看返回信息上面图片检查中的预览就可以看到如果正确那么我们操作完毕
至此我们操作框架js原始servlet已经完毕当然对应的get请求我们就不测试了具体流程与前面是一样的底层都是原生的处理只是可能存在一些判断而这些判断也会随着版本而发生改变所以我们并不需要特别的了解如果以后出现了什么问题或者你不知道他存在什么判断你完全可以网上找资料当然chatgpt是很好的选择他的意义很大程度上帮助我们程序员并不需要记住一些容易变化的框架或者代码只需要知道原理即可也让我们可以学习更多的知识而不用反复的记住一些重复的代码了
现在我们来操作原生js和框架servlet这个博客主要说明的是mvc也是我们进行这些测试的原因没有之前的测试mvc的一些api是难以懂得其原理的所以现在开始操作前面的项目主要是操作原生servlet的现在我们重写来一个项目
对应的依赖
mvc的依赖封装了很多的东西无非就是一些配置处理以及拦截的处理拦截的处理实际上可以看成Spring中类似的拦截无非就是拿取信息对比而已 packagingwar/packagingdependenciesdependency!--mvc需要的依赖即有前端控制器DispatcherServlet--groupIdorg.springframework/groupIdartifactIdspring-webmvc/artifactIdversion5.1.5.RELEASE/version/dependency!--servlet坐标若不使用对应的类如HttpServletRequest的话可以不加
具体为什么可以看67章博客
--dependencygroupIdjavax.servlet/groupIdartifactIdjavax.servlet-api/artifactIdversion3.1.0/version/dependencydependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-databind/artifactIdversion2.9.8/version!--这个必须要后面的可以不写后面两个但最好写上防止其他情况--/dependencydependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-core/artifactIdversion2.9.8/version/dependencydependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-annotations/artifactIdversion2.9.0/version/dependency/dependenciesweb.xml
?xml version1.0 encodingUTF-8?
web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaeexmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsdversion4.0servletservlet-namedispatcherServlet/servlet-nameservlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class/servletservlet-mappingservlet-namedispatcherServlet/servlet-nameurl-pattern//url-pattern/servlet-mapping/web-app对应的在java资源文件夹下创建controller包然后创建upload类
package controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;Controller
RequestMapping(/test)
public class upload {RequestMapping(a)public void handle01(HttpServletRequest request, HttpServletResponse response) {try {response.setContentType(text/html;charsetUTF-8);response.getWriter().write(操作);} catch (Exception e) {e.printStackTrace();}}
}
然后在资源文件夹下创建springmvc.xml文件
beans xmlnshttp://www.springframework.org/schema/beansxmlns:contexthttp://www.springframework.org/schema/contextxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdcontext:component-scan base-packagecontroller//beans修改web.xml
?xml version1.0 encodingUTF-8?
web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaeexmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsdversion4.0servletservlet-namedispatcherServlet/servlet-nameservlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-classinit-paramparam-namecontextConfigLocation/param-nameparam-valueclasspath:springmvc.xml/param-value/init-param/servletservlet-mappingservlet-namedispatcherServlet/servlet-nameurl-pattern//url-pattern/servlet-mapping/web-app因为这里是起始需要操作扫描的然后操作处理拦截
然后我们启动项目直接访问http://localhost:8080/mvcservlet/test/a如果出现了操作说明操作完毕当然返回值是void的情况在67章博客也有说明具体可以去看看
接下来我们来进行修改了这里我们有点不同的操作即请求头多一种方式在后面我们就知道了
首先他也有10种情况大多数对前端框架的变化也只有一点点都是通过原生js来改变的而原生js我们已经完全的写出来了所以就不多说但是后端的我们只是部分写出因为有很多情况需要判断而我们只判断了一点在这种情况下后端就应该多说明一下的所以前面的jq我们基本知识粗略的说明但是后端框架由于设计的东西比较多所以这里需要着重说明但是一眼看出来的就没有必要了比如get或者带参数post或者也带参数这里再方法里面处理时与原生的servlet是一模一样的处理而get对文件的操作其实也是如此再不考虑请求头的情况下处理完全一样我们可以复刻一下
前端代码创建index.jsp
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
body
input typefile idfileInput multiple/
button οnclickuploadFile()上传/buttonscriptasync function uploadFile() {let fileInput document.getElementById(fileInput);let fileData [];let fileDatename [];if (fileInput.files.length 0) {return;}let readFilePromises [];for (let y 0; y fileInput.files.length; y) {if (fileInput.files[y] ! undefined) {let reader new FileReader();let readFilePromise new Promise((resolve, reject) {reader.onload function (e) {console.log(e);fileData.push(e.target.result.split(,)[1]);fileDatename.push(fileInput.files[y].name);resolve();};reader.readAsDataURL(fileInput.files[y]);});readFilePromises.push(readFilePromise);}}await Promise.all(readFilePromises);sendFileUsingGET(fileData, fileDatename);}function sendFileUsingGET(fileData, name) {console.log(fileData)console.log(name)let xhr new XMLHttpRequest();let url test/a?;for (let h 0; h fileData.length; h) {if (h fileData.length - 1) {url file encodeURIComponent(fileData[h]) filename name[h];continue;}url file encodeURIComponent(fileData[h]) filename name[h] ;}console.log(url)xhr.open(GET, url, false);xhr.send();console.log(xhr.responseText);}
/script
/body
/html
后端代码前面我们操作过了
package controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.Base64;Controller
RequestMapping(/test)
public class upload {RequestMapping(a)public void handle01(HttpServletRequest request, HttpServletResponse response) {response.setContentType(text/html;charsetUTF-8);try {PrintWriter writer response.getWriter();String[] file request.getParameterValues(file);String[] filename request.getParameterValues(filename);if (file ! null) {for (int i 0; i file.length; i) {String[] split filename[i].split(\\.);byte[] decodedBytes Base64.getDecoder().decode(file[i]);FileOutputStream fileWriter new FileOutputStream(F:/ split[0] . split[1]);fileWriter.write(decodedBytes);writer.write(h1 上传一个文件成功 /h1);}return;}writer.write(h1 上传的文件为空 /h1);return;} catch (Exception e) {e.printStackTrace();}}
}
执行一下然后修改
xhr.open(GET, url, false);
到
xhr.open(POST, url, false);发现结果是一样的在没有考虑其他操作时基本上是不会出现什么判断错误当然这里还是建议使用小文件具体原因看前面就知道了那么既然mvc也一样的是操作了servlet但是他也存在对应的设置或者对前面的配置进行了某些处理比如
MultipartConfig(fileSizeThreshold 1024 * 1024 * 2,maxFileSize 1024 * 1024 * 10,maxRequestSize 1024 * 1024 * 50)
//这里补充一下一般情况下他们的值若为-1那么则是默认的同理相关mvc处理时若值也是-1那么也是默认的如果存在不支持-1的设置那么一般可能是版本问题同理mvc也是如此这里需要注意哦他可能是一个设置了默认的值而不是根据servlet的默认当然我们可以通过配置文件进行处理这些我们了解即可一般情况下mvc并不会默认的进行设置通常需要我们手动的处理所以我们主要学习的就是其api了也就是说mvc实际上也只是多出了几个api或者内部通过某些操作也进行了封装像自动保存了Collection Part parts request.getParts();的值一样其他的基本上不用进行什么说明这里我们前端使用标签表单mvc使用api来进行处理
前端是
form actiontest/a methodpost enctypemultipart/form-datainput typefile namefileinput typetext namefilenameinput typesubmit value提交
/form后端在mvc是进行封装的在前面我们以及了解了这个
/*
8MultipartResolver
MultipartResolver 用于上传请求通过将普通的请求包装成 MultipartHttpServletRequest 来实现MultipartHttpServletRequest 可以通过 getFile() 方法 直接获得⽂件如果上传多个⽂件还可以调用 getFileMap()方法得到MapFileNameFile这样的结构MultipartResolver 的作用就是封装普通的请求使其拥有⽂件上传的功能
*/他就相当于操作了原生api的处理后的结果进行保存的或者可以说成是像自动保存了Collection Part parts request.getParts();的值一样所以他自然也会存在一些api来给我们使用
后端代码
package controller;import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.Date;/****/
Controller
RequestMapping(/test)
public class upload {PostMapping(a)public void handleFormUpload(RequestParam(file) MultipartFile file, RequestParam(filename) String filename, HttpServletRequest request, HttpServletResponse response) {if (!file.isEmpty()) {try {//没什么用因为他只是在得到之前进行的处理而getOriginalFilename的操作是在操作之前就已经处理了因为上面的优先注入所以这个并没有什么对他的作用request.setCharacterEncoding(utf-8);//用一个文件试一下System.out.println(文件名称: filename);String name file.getOriginalFilename(); //一般是获取文件的原名与前面的String fileName part.getSubmittedFileName();基本一样的结果当然他们都是封装了对应的原始处理操作的信息所以可以直接的拿取//我们手动的操作一下先用对应的编码处理回来然后用原来传递过来的编码进行处理name new String(name.getBytes(iso-8859-1), utf-8);byte[] bytes file.getBytes();FileOutputStream fileOutputStream new FileOutputStream(F:/ name);fileOutputStream.write(bytes);response.setContentType(text/html;charsetUTF-8);PrintWriter writer response.getWriter();writer.write(h1 上传一个文件成功 /h1);} catch (Exception e) {e.printStackTrace();}}}
}
我们需要配置对应的multi-part配置因为一般mvc并不会默认处理没有这个自然也会报前面的那个Multi-Part错误对于框架来说可能会捕获这个错误然后报其他错误所以我们操作如下否则的话可能操作不了相关文件的组件或者说赋值或者说某些处理导致报错等等
在springmvc.xml中加上如下 !--配置文件上传解析器
一般情况下这个id名称必须是multipartResolver否则不会处理相当于没有写在spring中是存在拦截的
--bean idmultipartResolver classorg.springframework.web.multipart.commons.CommonsMultipartResolver!-- 设定文件上传的最大值为5MB5*1024*1024 --!--这里一般不配置默认没有上限那这时就看对方服务器的空间了若他没有这么多自然可能会使得连接断开或者终止传输通常服务器在只有固定的空间后就会使得终止这是对于连接来说的在满之前通常只能手动补充满但实际上连接也会可能不会--property namemaxUploadSize value5242880/property!--设定文件上传时写入内存的最大值因为有临时文件存放而不是读取就写入如果小于maxInMemorySize这个参数值则不会生成临时文件而是保留在内存里否则就会在硬盘上创建临时文件下面的配置大小不设置的话默认为1024当全部读取完后才会进行写入操作这是为了数据完整性防止中途中断而读取中断则会全部删除
实际上这个临时文件一般情况下是真实文件只是一般情况下他可能存在多种情况比如临时文件到真实就是真实--property namemaxInMemorySize value40960/property/bean上面的属性在一定程度上对应与这个
/*
fileSizeThreshold 1024 * 1024 * 2, // 2MB指定了一个文件大小的阈值以字节为单位如果上传的文件大小小于这个阈值文件将存储在内存中如果文件大小超过这个阈值文件将存储在磁盘上这里理解成io流的刷新机制即可默认为0即读取一个操作一个到磁盘文件里面maxFileSize 1024 * 1024 * 10, // 10MB用于限制单个上传文件的最大大小以字节为单位在这个示例中最大文件大小被设置为 10MB这意味着用户无法上传超过 10MB 大小的文件实际上这个我们也可以在前面操作的代码中来计算从而完成比如判断总字节如果超过删除对应文件便可默认是无限大maxRequestSize 1024 * 1024 * 50 // 50MB配置用于限制整个请求的最大大小包括所有上传文件和其他请求参数在这个示例中最大请求大小被设置为 50MB这意味着整个请求不能超过 50MB类似于get中的请求区域大小get有限制我们post拥有他的能力自然也可以进行设置默认是无限的上面的三个配置中其中maxUploadSize maxFileSizemaxInMemorySize fileSizeThreshold他们后面的数都是代表字节即property namemaxUploadSize value5242880/property等价于maxFileSize 1024 * 1024 * 5因为1024 * 1024 * 5 5242880property namemaxInMemorySize value40960/property等价于fileSizeThreshold 1024 * 40因为1024 * 40 40960当然前面的MultipartConfig(fileSizeThreshold 1024 * 1024 * 2,maxFileSize 1024 * 1024 * 10,maxRequestSize 1024 * 1024 * 50)并不只是这三个属性并且mvc的文件上传解析器也同样如此因为他们基本是完全对应的就算不对应也必然存在MultipartConfig中有的因为mvc只是以他为基准的只能小于等于他而不能大于他的配置如果配置数量多那么不用思考一定是有几个配置是需要一起影响或者关联MultipartConfig中的
*/现在我们执行看看结果然而还不行这是mvc由于主要操作拦截他只是提供了对应的关联处理一般我们需要如下的依赖 !--
文件上传 没有指定的那么就需要一起即这里一般必须一起
因为他们需要结合实际上只需要第一个即可即commons-fileupload但通常都是结合的--
dependencygroupIdcommons-fileupload/groupIdartifactIdcommons-fileupload/artifactId!--初始化操作--version1.3.3/version
/dependency
dependencygroupIdcommons-io/groupIdartifactIdcommons-io/artifactId!--io对应操作--version2.6/version
/dependency
!--
上面两个虽然我们并没有操作但上传解析器需要上面这其中的commons-fileupload否则报错
在需要上面两个时我们一般要导入这里就需要导入第二个一般也要导入当然若不是必须可以不导入
--现在我们再来进行操作可以发现文件上传成功一般来说图片的资源被占用时打开后通常会使得文件感觉变大这是图片与文件系统的关系了解即可如果是多文件那么我们应该如此
首先是对应前端修改一下
input typefile namefile multiple后端也修改一下因为对应的MultipartFile file只能得到第一个文件在多文件时也是按照第一个的自己测试就知道了
package controller;import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.Date;
import java.util.List;/****/
Controller
RequestMapping(/test)
public class upload {PostMapping(a)public void handleFormUpload(RequestParam(file) ListMultipartFile fileList, RequestParam(filename) String filename, HttpServletRequest request, HttpServletResponse response) {if (fileList.size() 0) {try {for (MultipartFile file : fileList) {request.setCharacterEncoding(utf-8);System.out.println(文件名称: filename);String name file.getOriginalFilename();name new String(name.getBytes(iso-8859-1), utf-8);byte[] bytes file.getBytes();FileOutputStream fileOutputStream new FileOutputStream(F:/ name);fileOutputStream.write(bytes);response.setContentType(text/html;charsetUTF-8);PrintWriter writer response.getWriter();writer.write(h1 上传一个文件成功 /h1);}} catch (Exception e) {e.printStackTrace();}}}
}
也就是说本来就已经都处理好变成List的如果你的参数只是一个那么给List的第一个否则都给你
我们可以发现使用框架的mvc是比较容易的虽然前面我们的处理封装一下也行但是有现成的为什么不用呢至此原生js和框架servlet我们操作完毕现在我们来操作框架js和框架servlet由于js无论是否框架对代码封装影响不大即在前面我们也说明了变化也只有一点点所以这里的我们也只给出一个测试结果吧
只需要修改前端即可
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
body
input typefile idfileInput/
button οnclickuploadFile()上传/button
script srchttps://code.jquery.com/jquery-3.6.0.min.js/script
scriptfunction uploadFile() {let fileInput document.getElementById(fileInput);let file fileInput.files;console.log(file)sendFileUsingGET(file);}function sendFileUsingGET(file) {let formData new FormData();for (let y 0; y file.length; y) {formData.append(file, file[y]);}formData.append(filename, 22);$.ajax({url: test/a, // 提交到的URLtype: POST,data: formData,processData: false, // 不处理数据contentType: false, // 不设置内容类型success: function (data) {// 成功回调函数console.log(data)},error: function (error) {console.log(data)}});}
/script
/body
/html
上传一个文件试一下吧至此我们的框架js和框架servlet操作完毕
至此我们的所有请求说明基本操作完毕当然前面也说过了需要说明一下这个
xhr.setRequestHeader(Content-Type, application/json);现在我们来操作这个如果说
xhr.setRequestHeader(Content-Type, application/x-www-form-urlencoded);
或者
multipart/form-data都有原生处理来进行比如默认的可以存在getParameter进行获取而multipart/form-data可以通过读取io流来处理那么application/json呢我们可以来看看
首先说明一下这个请求头在前面我们基本只是了解一下并没有使用他操作过其实该请求与multipart/form-data基本类似的
所以他一般是不会放在url中我们来试一下首先修改前端index.jsp
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
body
button οnclickuploadFile()访问/buttonscriptfunction uploadFile() {sendFileUsingGET();}function sendFileUsingGET() {let xhr new XMLHttpRequest();xhr.open(POST, te/u, false);//可以选择加上看看结果一般是一样的xhr.setRequestHeader(Content-Type, application/json);xhr.send(22);}
/script
/body
/html
创建一个类代码如下
package controller;import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.Date;
import java.util.List;/****/
Controller
RequestMapping(/te)
public class json {PostMapping(u)public void handleFormUpload(HttpServletRequest request, HttpServletResponse response) {// 设置响应的内容类型为 JSONresponse.setContentType(application/json);// 从请求中读取 JSON 数据StringBuilder requestBody new StringBuilder();try {BufferedReader reader request.getReader();String line;while ((line reader.readLine()) ! null) {requestBody.append(line);}String jsonPayload requestBody.toString();response.getWriter().write(jsonPayload);} catch (Exception e) {e.printStackTrace();}}
}
我们发现响应得到了22这个数据并且他也可以操作getReader为什么不是说前面说他只能操作表单吗实际上除了默认的头一般其他的信息都会在域中但是前面也说明过了由于分工不同所以导致也可以说他们都操作同一个域所以这里也只是一个判断而已判断他不是默认所以可以除了或者说默认的存在在另外一个敌方所以这里我应该需要对get和post的存放需要进行一个统一说明处理实际上存在一个空间get和post的数据都是放在这个空间的其中get需要在url中拿取而post是直接存在所以在这个处理上说成是不同的域也没有问题或者说一个是url一个是域也行而这个空间存在两个地方一个是key-value一个是直接的数据存在默认get只能存放在key-value区域而post则都可以前提是请求头的改变很明显这个json的相关请求头就是第二个直接的数据存在的区域所以可以与表单一样的操作通过这样的说明也就能更好的理解无论你是在url中还是域中都是如此在前面的某个地方说明了这个可以全局搜索即可
那么我们对这个请求头进行说明
一般来说他这个请求头只是一个传递字符串信息的请求而不是一些如二进制或者key-value形式的数据就单纯的是一个字符串所以也决定了他是被大量使用的原因或者是当前主流使用的原因因为任何的数据基本上都可以使用字符串来进行获取或者操作文件也是如此get的url操作文件方式不就是一个例子吗
所以当我们进行读取时就是得到一个字符串也就是前面的22但是前面的代码中我们设置了
response.setContentType(application/json);我们也见过这样的处理
response.setContentType(text/html;charsetUTF-8);那么他们有什么不同呢
在这之前我们在前端中加上如下
xhr.send(22);
console.log(xhr.responseText); //这里多加一行代码基础api基本是最底层的了还要底层的是没有必要继续学习的除非学习浏览器的源码或者感兴趣一般我们说到的原始基本就是直接的最底层而不是直接到二进制的处理/*response.setContentType(application/json);
和
response.setContentType(text/html;charsetUTF-8);
他们是两种不同的HTTP响应内容类型设置它们用于告诉浏览器或客户端接收到的数据的格式和编码方式响应体信息请求体给服务器响应体给客户端注意这里对请求体和响应体的说明是请求信息和响应信息一般我们并不会直接的指向说明请求信息里面的请求体和响应信息里面的响应体这里注意即可response.setContentType(application/json);:
这行代码设置了响应的内容类型为JSONJavaScript Object Notation这意味着服务器将返回JSON格式的数据JSON是一种轻量级的数据交换格式常用于前端与后端之间的数据交互浏览器或客户端通常会解析JSON数据以在页面上渲染或使用这些数据那么假设你传递的数据是22那么通过这个设置后这个22在响应信息的请求体之前会进行一些处理当然了单纯的22与response.setContentType(text/html;charsetUTF-8); 基本没有关系所以你将前面修改成response.setContentType(text/html;charsetUTF-8); 结果也是一样的response.setContentType(text/html;charsetUTF-8);:
这行代码设置了响应的内容类型为HTML并指定了字符编码为UTF-8这意味着服务器将返回HTML格式的数据并且该HTML数据使用UTF-8字符编码进行编码HTML是用于构建网页的标记语言而UTF-8是一种广泛使用的字符编码支持多种语言的字符集
总结
application/json 适用于返回JSON数据通常用于API响应
text/html;charsetUTF-8 适用于返回HTML数据通常用于构建网页字符编码的设置确保了文本能够正确地显示特殊字符和多语言字符集
响应的内容类型设置应根据服务器返回的数据类型来选择如果您的服务器端点返回JSON数据那么您应该使用 application/json如果返回HTML页面那么您应该使用 text/html;charsetUTF-8 或类似的内容类型这有助于浏览器或客户端正确解释和渲染响应数据*/当然由于html自身在响应体中的数据也基本都是字符串所以response.setContentType(“application/json”);也意味着可以一样的操作对应的html信息与对应设置的是一样的只是他好像并不能设置连接设置编码一般需要通用的编码设置如response.setCharacterEncoding(“UTF-8”);
那么这个编码设置和response.setContentType(“text/html;charsetUTF-8”);编码设置有什么区别实际上只是先后处理顺序而已先操作这个通用的然后操作这个response.setContentType(“text/html;charsetUTF-8”);的
所以在以后我们一般也会使用response.setContentType(“application/json”);来代替这个html的设置当然可能response.setContentType(“text/html;charsetUTF-8”);是存在其他作用的这里我们选择忽略了解所以现在可以确定
我们基本使用json来处理那么json到底进行了json的什么处理呢还是说他其实什么都没有做呢我们看如下的例子
前提实际上从上面可以知道编码的设置只是请求到响应直接的处理而jsp其实他自身也存在这样的编码设置你可以看看jsp是否存在% page contentType“text/html;charsetUTF-8” language“java” %他相当于操作了response.setContentType(“text/html;charsetUTF-8”);这里我们了解即可其中如果单纯的什么都没有加好像也就是什么都没有操作就是把这个字符串给出去就如response.getWriter().write(“22”);当然json进行了什么设置我们还不清楚但是可以这样的认为
单纯的给数如字符串设置jsonhtml以及什么都没有设置结果一样
给出某些特定的数设置jsonhtml以及什么都没有设置结果不一样当然这里我们选择考虑html与什么都没有设置是一样的不考虑编码所以按照这样的考虑那么json是进行操作的其他的基本没有但是真的是这样的吗所以我们来测试测试看看他进行了什么操作
考虑到json可能会操作改变的处理所以我们操作一下数据因为字符串他们基本不会改变所以我们测试如下的几种如
map集合list集合类作为返回值进行处理而由于你设置了一些响应设置那么前端可能会判断这个设置而进行某些处理当然后端可能也会进行某些处理基本都是围绕着这个设置来处理的所以我们应该有如下的后端代码来测试一下他应该有的作用这里我们需要考虑到对应的流只会操作字符串所以对应的集合一般是如此的
/*
map集合
{\message\: \Hello, world!\}
list集合
[1,2,3]
类
{\message\: \Hello, world!\}*/首先我们操作map // 设置响应的内容类型为 JSONresponse.setContentType(application/json);try {response.getWriter().write({\message\: \Hello, world!\});} catch (Exception e) {e.printStackTrace();}设置list // 设置响应的内容类型为 JSONresponse.setContentType(application/json);try {response.getWriter().write([1,2,3]);} catch (Exception e) {e.printStackTrace();}
设置类
首先创建test类
package controller;public class test {String message;Overridepublic String toString() {return test{ message message \ };}
} // 设置响应的内容类型为 JSONresponse.setContentType(application/json);try {test test new test();test.message Hello, world!;response.getWriter().write(test.toString());} catch (Exception e) {e.printStackTrace();}结果分别是
{message: Hello, world!}
[1,2,3]
test{messageHello, world!}所以我们可以得出结论这个响应头无任何作用因为由于响应信息的字符串是绝对的所以这个响应头是没有作用的如果说html的头可能服务器会自动的进行某些处理甚至也只是操作了编码而已同理这个json作为请求头时也是如此本质上这些都只是一个请求吧标识服务器读取这些标识而产生一些处理如文件中的某些api需要对应的文件请求标识头那么这个json则没有任何操作所以我们应该明白一个问题
响应头信息基本只是一个提示具体作用由当前代码的判断判断响应头或者服务器自身又或者前端的某些处理来进行的比如请求网络中载荷的展示效果而非自身的某些处理实际上任何头都是如此包括请求头就如前面的请求头的默认格式和文件请求头那里的说明也就是说json的响应头没有任何处理那么总结如下
单纯的给数或者给其他的如字符串设置jsonhtml以及什么都没有设置结果基本一样不考虑其他的处理如编码
那么这个设置也就是一个提示但是也由于是提示所以一般的后端或者前端可能会判断然后操作大多数后端或者前端框架基本都是如此所以这里我们了解即可因为他是根据当前情况来分析的简单来说前端和后端都会处理请求头和响应头的对应标识而决定前端和后端的请求发送前端请求接收后端响应发送后端响应接收前端等等的一致问题比如看对应的框架包括前端和后端是如何的处理比如对应的注解ResponseBody 当然知道原理并非意味着对框架熟悉也就是说会报错或者会犯错因为是需要遵循框架来的但是原理有利于自行编写框架和理解框架一般mvc中ResponseBody只能有一个且他是操作json而由于操作json导致寻常的通过key-value来获取的处理是操作不了的因为他只是传递字符串也只能根据字符串来进行接收也就是请求载荷对浏览器来说他们是不同的处理的
到这里我们基本说明完毕简单来说请求头和响应头的信息都只是一个信息然后这些信息会使得浏览器以及服务器进行某些判断处理也会由框架也进行某些补充判断处理最终这些信息决定了返回和接收的信息处理方式如文件通常需要读取其请求体信息
所以前面所说明的总结论和所有的测试的结果最后得到如此由各种请求信息和响应信息组成的一系列的操作方式由这些操作方式来操作具体的数据当然如果需要修改这些方式自然需要对协议方面进行修改这就需要看更加原始的操作了在27章博客最后就有一个小例子
你可能会有疑惑直接说出这句话就可以了为什么需要前面的测试或者铺垫其实在以后你就算知道原理但是原理只是让你知道为什么而实际操作中的细节是体现不出来的而上面基本上几乎将所有细节进行了说明那么以后出现问题可以选择参照这里所以说原理需要和实际情况来结合你知道原理了但是你的原理只是知道他是这样的但是中间的其他限制如需要这样写需要写在这里你也并不明白或者说你只是一个大致的原理因为你完全没有全部揉碎只是一个总结的原理而已而这个原理虽然到达高本质但是对实现的细节的其他低本质并不清楚而实践是清楚低本质的操作所以原理需要结合实践
至此我们的请求才算完美的说明完毕那么就以现在的知识看后面的内容吧
这个时候我们回到之前的操作也就是引出我们说明请求的哪个
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
body
script srcjs/jquery-3.4.1.min.js/script!--script srchttps://code.jquery.com/jquery-3.6.0.min.js/script--
button idbtnajax提交/button
script$(#btn).click(function(){let url ajax;let data [{id:1,username:张三},{id:2,username:李四}]$.ajax({type:POST,//大小写可以忽略url:url,data:data,contentType:application/json;charsetutf-8, //如这里success:function (data) {console.log(data);}})})
/script
/body
/html
在前面我们提到了需要如下
processData: false, // 不处理数据不进行中途改变数据
contentType: false, // 不设置内容类型自动设置请求头为什么processData没有呢实际上contentType:application/json;charsetutf-8’中或者jq的该框架处理了这个请求头所以默认的处理了processData即按照这个编码来进行了改变数据我们可以复现一下
在前面我们只是说明他会自动的处理但是原因是什么我们并没有操作过
按照前面的代码我们修改前端
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
body
input typefile idfileInput/
button onclickuploadFile()上传/button
script srchttps://code.jquery.com/jquery-3.6.0.min.js/script
scriptfunction uploadFile() {let fileInput document.getElementById(fileInput);let file fileInput.files;console.log(file)sendFileUsingGET(file);}function sendFileUsingGET(file) {let formData new FormData();for (let y 0; y file.length; y) {formData.append(file, file[y]);}formData.append(name, 22);$.ajax({url: upload, // 提交到的URLtype: POST,data: formData,processData: false, // 不处理数据contentType: false, // 不设置内容类型success: function (data) {// 成功回调函数console.log(data)},error: function (error) {console.log(data)}});}
/script
/body
/html
后端如下
package controller;import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Collection;WebServlet(/upload)
MultipartConfig(fileSizeThreshold 1024 * 1024 * 2,maxFileSize 1024 * 1024 * 10,maxRequestSize 1024 * 1024 * 50)
public class FileUploadServlet extends HttpServlet {protected void doPost(HttpServletRequest request, HttpServletResponse response) {try {System.out.println(1);request.setCharacterEncoding(utf-8);CollectionPart parts request.getParts();for (Part part : request.getParts()) {if (file.equals(part.getName())) {String name part.getName();String fileName part.getSubmittedFileName();InputStream fileContent part.getInputStream();Path targetPath Paths.get(F:/ fileName);Files.copy(fileContent, targetPath, StandardCopyOption.REPLACE_EXISTING);response.setContentType(text/html;charsetUTF-8);response.getWriter().write(文件上传成功 fileName);}}} catch (Exception e) {e.printStackTrace();}}
}在mvc中处理原始的时候一般情况下会按照原始的优先处理比如你可以选择再次的创建一个类
package controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/****/
Controller
RequestMapping(/upload)
public class up {PostMapping(u)public void handleFormUpload(HttpServletRequest request, HttpServletResponse response) {try {System.out.println(2);response.getWriter().write(Hello, world!);} catch (Exception e) {e.printStackTrace();}}
}
这个时候打印了1而不会打印2所以原始的优先处理这个优先只是一次也就是说不会执行mvc的了
但是实际上只是由于WebServlet的配置大于xml的优先等级这是servlet的作用他规定的否则如果都是配置文件一般需要看先后顺序了一般写在前面的是优先的这里具体需要看版本说明因为随着版本的改变可能后写的优先但一般是写在前面的优先所以在一个博客说明了谁优先时建议自己测试一遍
现在我们去掉一个代码我们看前端 $.ajax({url: upload, // 提交到的URLtype: POST,data: formData,
//这里去掉了processData: falsecontentType: false, // 不设置内容类型success: function (data) {// 成功回调函数console.log(data)},error: function (error) {console.log(data)}});然后我们继续访问出现了之前的问题即jq为了严谨必须需要设置contentType和processData这个对应的错误这个报错可以看这是jq的提示报错会随着时间而发生改变的所以最好自己看这里就不给出了
很明显前面我们说明了存在自动的处理说明的就是他们各自的自动处理如果都是false那么内容的类型和数据的处理等等会根据值来自动处理如果不写那么直接报错一般必须写的而基本没有什么默认的处理认为是防止文件的错误操作当数据的处理不加时并且类型是自动时那么说明数据的处理不会进行所以报错但是由于类型通常是数据的处理的前提所以jq存在若你指定了类型那么会按照对应类型来自动处理数据这里有待疑问后面会揭晓而不是只是判断类型所以存在这样的操作了
contentType:application/json;charsetutf-8, //如这里修改后我们继续执行发现报错因为对应类型与自动处理的数据类型不对要知道自动处理时是需要根据内容的而你的类型与内容不同自然会报错所以在某种程度上contentType只是一个定义了数据处理的方式的自动处理而已因为jq为了防止特别的情况还会判断你设置的类型是否一致的实际上数据也会判断我们看后面的总结即可所以我们需要这样的修改
contentType:multipart/form-data, //如这里现在我们执行发现还是执行错误出现了没有分割符的操作说明jq的操作在某种程度上使用了某种原始api设置了分割符所以我们需要这样
contentType: multipart/form-data;boundary----WebKitFormBoundary85LOXlPgPrx4eO, 这个分割符一般情况下还是建议是很多的处理或者使用某些原始api生成这里我们使用以前操作过的分割符来操作的可以测试一下自定义的虽然可能会出现问题如与内容出现一样的默认情况下大多数分割符与multipart/form-data;是空格隔开但是并不影响对应的获取信息所以并不需要注意
执行还是报错加上processData: false,即可在这里也就说明了processData在没有设置时其实只是按照字符串来处理的前提是设置了类型这里解释contentType:‘application/json;charsetutf-8’,可以单独操作的原因设置了false才会按照contentType指定的处理来操作这里也就又解释了这里有待疑问后面会揭晓即默认不写时是字符串否则按照指定的处理如果没有指定那么都自动处理
总结
processData和contentType都不加报错在jq的不同版本下有些版本不加可能默认为false具体可以百度可能现在不会了无论是否单独不加都是如此那么这里就需要考虑为false的情况了我们注意即可
processData不加contentType加falsecontentType没有指定所以processData没有操作那么必须指定数据处理
processData不加contentType加指定类型contentType有指定所以processData默认操作字符串这个时候判断指定类型是否与内容一致否则报错若一致那么判断类型对应的数据处理是否也与字符串的处理一致否则也报错
processData加falsecontentType不加报错必须指定类型
processData加falsecontentType加false类型和数据都按照自动来处理即按照内容来处理
processData加falsecontentType加指定类型判断内容的类型与指定类似是否一致否则报错如果一致那么由于processData加false所以数据处理与指定类型的数据处理一致
而大多数我们基本在ajax中使用的都是contentType:‘application/json;charsetutf-8’,所以jq大多数只会操作contentType就行了其他的一般不会处理因为存在自动的或者默认的处理
所以可以知道其实无论你是否设置jq都会进行判断所以有时候写了比不写还是不好的处理只是不写的话我们并不能知道他是怎么处理的不利于维护并且他也存在判断所以你可以放心的写而不会出现数据的问题如防止文件的错误操作所以jq的ajax考虑的比较多但是非常安全且也存在提示或者说维护的操作
到此我们也可以知道前端框架也会存在各种的判断在以后学习新的处理时一般来说考虑到请求头即类型处理以及处理的数据即可因为其他的参数一般都会很直观的给出的
所以我们继续回到这里
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
body
script srcjs/jquery-3.4.1.min.js/script!--script srchttps://code.jquery.com/jquery-3.6.0.min.js/script--
button idbtnajax提交/button
script$(#btn).click(function(){let url ajax;let data [{id:1,username:张三},{id:2,username:李四}]$.ajax({type:POST,//大小写可以忽略url:url,data:data,contentType:application/json;charsetutf-8, //如这里success:function (data) {console.log(data);}})})
/script
/body
/html
然后后端代码如下User类 String id;String username;public String getId() {return id;}public void setId(String id) {this.id id;}public String getUsername() {return username;}public void setUsername(String username) {this.username username;}Overridepublic String toString() {return User{ id id \ , username username \ };}package controller;import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import java.util.List;Controller
public class ajax {ResponseBodyRequestMapping(ajax)//依赖什么的以及配置什么的根据前面的说明自己加上前面操作过一次了public ListUser ajax(RequestBody ListUser list) {System.out.println(list);return list;}
}
执行访问一下前端的ajax提交若出现了对应的数据然后看看后端的数据是否是对的如果是那么我们操作成功
这个时候我们需要说明一下RequestBody和ResponseBody了根据前面的说明很明显RequestBody是框架或者说依赖的处理他判断了对应的请求头来进行一下数据的处理使得可以被list接收而ResponseBody同样如此他也操作了对应的请求处理只是他默认处理响应头我们可以在检查元素中的响应标头中看到这个
Content-Type:application/json;charsetUTF-8也就是说他也操作了对应的设置然后也根据框架或者依赖进行初级的反向的处理要知道在前面我们说过了实际上任何头都是如此包括请求头所以他们的设置只是一个值具体操作都是有代码来处理的而这个代码一般就是框架或者依赖来完成的所以在mvc中存在这样的数据转换也只是根据请求信息来完成的也就是处理了原生字符串这里在67章博客可以知道所以我们了解即可而在原生中一般并没有就如我们前面操作时对应的值只能是字符串在中间你可以选择处理就是这样的情况
为了给出mvc的转换所以这里给出全部可能当然由于只是对参数的变化所以与文件并不同即并不会操作问题由于只有框架会处理所以这里考虑以下的情况
原生js和mvc框架和mvc当然我们也会加上标签和mvc的相关处理的
比如使用标签或者js来说明全部的转换
首先我们需要创建一个项目
给出对应的依赖 packagingwar/packagingdependenciesdependency!--mvc需要的依赖即有前端控制器DispatcherServlet--groupIdorg.springframework/groupIdartifactIdspring-webmvc/artifactIdversion5.1.5.RELEASE/version/dependency!--servlet坐标若不使用对应的类如HttpServletRequest的话可以不加--dependencygroupIdjavax.servlet/groupIdartifactIdjavax.servlet-api/artifactIdversion3.1.0/version/dependencydependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-databind/artifactIdversion2.9.8/version!--这个必须要后面的可以不写后面两个但最好写上防止其他情况--/dependencydependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-core/artifactIdversion2.9.8/version/dependencydependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-annotations/artifactIdversion2.9.0/version/dependency/dependencies目录如下 对应的web.xml是如下的
?xml version1.0 encodingUTF-8?
web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaeexmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsdversion4.0servletservlet-namedispatcherServlet/servlet-nameservlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class/servletservlet-mappingservlet-namedispatcherServlet/servlet-nameurl-pattern//url-pattern/servlet-mapping/web-app创建controller包然后创建test类
package controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
Controller
RequestMapping(/test)
public class test {RequestMapping(a)public void a() {System.out.println(1);}
}
在资源文件中加上springmvc.xml
beans xmlnshttp://www.springframework.org/schema/beansxmlns:contexthttp://www.springframework.org/schema/contextxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdcontext:component-scan base-packagecontroller//beans在补充web.xml
servletservlet-namedispatcherServlet/servlet-nameservlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-classinit-paramparam-namecontextConfigLocation/param-nameparam-valueclasspath:springmvc.xml/param-value/init-param/servlet先执行访问这个路径看看是否打印若打印了那么我们操作如下
首先创建index.jsp
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
bodyform actiontest/a methodpostinput typetext nametext1input typetext nametext2input typetext nametext3input typesubmit value提交
/form/body
/html
现在我们修改这样的
package controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
Controller
RequestMapping(/test)
public class test {RequestMapping(a)public void a(String text1) {System.out.println(text1);System.out.println(1);}
}
使用post和get来访问后面都这样处理如果出现对应的数据代表操作完成如果是这样呢
package controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;import java.util.Arrays;Controller
RequestMapping(/test)
public class test {RequestMapping(a)public void a(String[] text1) {System.out.println(Arrays.toString(text1));System.out.println(1);}
}
前端则需要这样
form actiontest/a methodgetinput typetext nametext1input typetext nametext1input typetext nametext1input typesubmit value提交
/form
或者
form actiontest/a methodgetinput typetext nametext1input typetext nametext2input typetext nametext3input typesubmit value提交
/form
分别得到1,2,3和1get和post都是如此
如果后端是这样呢创建一个类
package controller;public class User {private String id;private String name;private String pass;Overridepublic String toString() {return User{ id id \ , name name \ , pass pass \ };}public String getId() {return id;}public void setId(String id) {this.id id;}public String getName() {return name;}public void setName(String name) {this.name name;}public String getPass() {return pass;}public void setPass(String pass) {this.pass pass;}
}
然后对应的controller修改如下
package controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
Controller
RequestMapping(/test)
public class test {RequestMapping(a)public void a(User text1) {System.out.println(text1);System.out.println(1);}
}
那么前端需要这样get和post都是如此
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
bodyform actiontest/a methodpost!--name属性而不是属性值nameid中name是属性说明的就是这个中的大小写一般是忽略的--input typetext nameidinput typetext namenameinput typetext namepassinput typesubmit value提交
/form/body
/html
如果是类数组呢也就是这样的
package controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
Controller
RequestMapping(/test)
public class test {RequestMapping(a)public void a(User[] text1) {System.out.println(text1);System.out.println(1);}
}
实际上mvc并不支持将对象数组在标签中进行处理这是为什么假设有数组Object[]你怎么处理呢所以大多数对象数组一般并不会直接的自动处理即需要手动的处理当然了基础类型可以是IntegerString等等的数组是可以的所以一般来说如果没有手动处理的话那么自动的处理就会报错即这种情况我们忽略当然这种情况是可以判断解决的只是mvc没有进行处理因为手动处理是最好的方式特别的因为出现这种情况的代码一般是复杂的即一般会手动那么mvc一般也不会这样的判断处理的
如果是这样的呢
package controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;import java.util.Arrays;
import java.util.List;Controller
RequestMapping(/test)
public class test {RequestMapping(a)public void a(ListString text1) {System.out.println(text1.toArray());System.out.println(Arrays.toString(text1.toArray()));System.out.println(1);}
}
实际上mvc也不支持List在标签中的处理因为同样的List中也会存在Object的情况
如果后端是如下呢
package controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;import java.util.Arrays;
import java.util.List;
import java.util.Map;Controller
RequestMapping(/test)
public class test {RequestMapping(a)public void a(MapString, String text1) {System.out.println(text1);System.out.println(1);}
}
同理由于里面也会存在Object所以也不支持但是如果是这样呢
创建一个类
package controller;import java.util.List;
import java.util.Map;public class QueryVo {String id;User user;ListUser userList;MapString, User userMap;Overridepublic String toString() {return QueryVo{ id id \ , user user , userList userList , userMap userMap };}public String getId() {return id;}public void setId(String id) {this.id id;}public User getUser() {return user;}public void setUser(User user) {this.user user;}public ListUser getUserList() {return userList;}public void setUserList(ListUser userList) {this.userList userList;}public MapString, User getUserMap() {return userMap;}public void setUserMap(MapString, User userMap) {this.userMap userMap;}
}
前端需要这样写
% page contentTypetext/html;charsetUTF-8 languagejava %
html
headtitleTitle/title
/head
bodyform actiontest/a methodpost搜索关键字input typetext namekeyword bruser对象input typetext nameuser.id placeholder编号input typetext nameuser.username placeholder姓名brlist集合br第一个元素input typetext nameuserList[0].id placeholder编号!--可以写0或者不写当然有些版本可能必须要写0了--input typetext nameuserList[0].username placeholder姓名br第二个元素input typetext nameuserList[1].id placeholder编号input typetext nameuserList[1].username placeholder姓名brmap集合br第一个元素input typetext nameuserMap[u1].id placeholder编号input typetext nameuserMap[u1].username placeholder姓名br第二个元素input typetext nameuserMap[u1].id placeholder编号input typetext nameuserMap[u1].username placeholder姓名brinput typesubmit value复杂类型
/form/body
/html
当你写上后把数据都加上只有匹配的才会进行处理这里其实在67章博客就有说明的
当然如果你将对应的集合的User修改成了String那么就需要这样 list集合br第一个元素input typetext nameuserList[0] placeholder编号!--可以写0或者不写当然有些版本可能必须要写0了--input typetext nameuserList[0] placeholder姓名br第二个元素input typetext nameuserList[1] placeholder编号input typetext nameuserList[1] placeholder姓名brmap集合br第一个元素input typetext nameuserMap[u1] placeholder编号input typetext nameuserMap[u1] placeholder姓名br第二个元素input typetext nameuserMap[u1] placeholder编号input typetext nameuserMap[u1] placeholder姓名br他们是会处理合并的在67章博客有具体说明的哦并且前面的关于这样的操作其实get和post都是一样的处理为什么这里支持了这是因为有了选择之前的直接的list和map我们是难以确定的就如list和数组有相似的处理比较难以分辨而map与集合也有相似的所以导致只能在其他的类里面才能进行处理了实际上这些基本在请求字符串前面或多或少可以知道中操作的所以关于js的处理就不说明了只操作标签的处理了然而在某种情况前端如果直接传递对象的话那么后端是怎么处理的实际上是前端怎么处理的在前面我们知道是操作了类型最终处理的一般情况下对象会考虑变成键值对使得是我们正常的情况其他的一般不会处理也就是说至此我们应该操作完毕了而对字符串的处理我们实际上只需要看68章博客即可而70章博客中有具体的变化情况的说明这里唯一需要的是知道直接的List和Map和对象数组不能处理当然也不一定在某种程度上可能是需要一些操作或者需要其他版本的所以一般情况下我们基本只会使用字符串来进行数据的交互的而不是操作自动的处理的因为他们的变化最终还是字符串的倒不如直接回到原来的地方呢没有必要多次一举所以我们操作json就行了
由于博客字数限制其他内容请到下一篇博客111章博客去看