Servlet2

前言(Preface):回顾Servlet1

  • 在Sevlet1中,对Web项目做了简单的介绍,那web项目的演变规则,它是有两条主线,一条是由单机向网络程序演变,第二个是由CS向BS演变,其中又对CS和BS也做了介绍, 所谓的CS指的是Client Server,客户端服务器程序,比如说一些单机游戏、QQ等等,那么,我们要访问服务器需要通过单独安装个客户端来访问,这样的程序就叫CS。那这种程序开发人员比较麻烦,需要专门的开发一个客户端,开发的难度也是比较大的,再一个,客户在使用时也麻烦,需要下载客户端,再安装一下,总之就是两边都麻烦;
  • 那相对来说BS就比较容易,BS指的是Broswer Server,浏览器和服务器程序,这里面它没有单独安装客户端,它是用浏览器来充当客户端,而浏览器每台电脑上都有,所以开发人员不用单独开发,客户也不用单独安装啊,比较方便。当然,这个浏览器里所运行的网页,还是要写的。不过,网页开发的难度要远远低于客户端。总之,BS比CS,它具有天生的优越性,所以BS是更为流行的这个趋势,尤其是做一些这个办公软件,那更多的就是采用BS。
  • 那么下一个话题,则通过服务器,它怎么去给浏览器返回一个网页,从而引出了Servlet这个话题,那服务器,它给浏览器返回的网页,一种是静态网页,一种是动态网页,那所谓的静态网页指的是,我们无论谁去看,结果都一样,比如说,新闻,小说,百科,开发手册等等,那么静态网页,服务器则直接保存一份HTML就可以了,它会把这个HTML发送给浏览器。那第二个是动态网页,像淘宝,微博等等,每个人去看这个网页,结果是不同的,是有差异的,那像这种情况,服务器就不能存HTML了,它要存的是一个对象,然后用这个对象,来拼一个网页,来动态拼一个网页发送给浏览器,所以,我们要想解决动态网页的问题,就必须得有那个对象,而不同的语言当中,这个对象不同,在java语言里,这个能够拼动态网页的对象,叫Servlet。
  • 通过服务器如何给浏览器返回一个网页这个话题,概括归纳了Servlet的几个特点,第一点,它是一个对象,一个满足规范的对象,不是说随便创建一个对象,就能够拼动态网页,是要有规范的。那么满足规范的对象,我们通常也叫组件,术语叫组件;第二点,这个对象,它存到服务器上,因为是服务器端,要给浏览器返回数据时,返回网页时,才会用这个对象。第3点,那这个对象的作用,它能够动态的拼资源,它能够拼网页,能够拼图片,能够拼各种各样你想要的内容,那么,这件事它的术语,叫做处理HTTP协议。那经过这些特点归纳了一下什么是Servlet,概况一下 ,Servlet是Sun推出的,用来在服务器上,处理HTTP协议的组件,通俗理解就是说这个对象,它能够拼动态网页。
  • 然后,下一个话题,又解释了一下这个服务器,首先,做为一个名字解释,那服务器,有的人喜欢叫java服务器,有的人喜欢叫web服务器,有的人喜欢叫JavaWeb服务器,还可以叫Servlet容器,它有这么多名字,叫哪个都行,都有道理。然后这个服务器的本质是一个软件,并不是硬件,那这个软件可以运行java项目,比较特殊,然后,市场上有很多服务器,像TomcatJBossWebLogicWebSphere等等,那其中咱们开发时,平时用的都是Tomcat,因为这是一个开源的产品,不用付费,再一个我们在将来,项目上线时,很多企业也会选择使用Tomcat,因为毕竟是免费的便宜,像这个WebLogic啊,动不动就几十万,所以它成本太高了,然后,这个之后,又说了Tomcat的使用方式,有两种情况,一种是当我们软件开发完了,要上线的时候,我们要安装软件的时候,我们需要单独安装Tomcat,Tomcat单独使用就可以,一般的企业,在软件上线时,都是有专门的运维,或实施去处理,一般不会由开发人员去管。那第二种场景是,我们平时开发的时候,是通过Eclipse去管理Tomcat,首先是在Eclipse中,怎么去配置Tomcat,在配置的过程中如果出现任何问题,我们最好重新配置,那么重新配置的话,需要把Eclipse之前配置的Tomcat删了,再打开Window,首选项,选择Server下的Runtime Envieronment,然后选择Tomcat,把它移除,另外,在Eclipse左边有个Servers项目,这个项目是自动生成的,也要把它移除, 把这两个内容移除,然后再重新去配置,最后,配置tomcat的一个注意的地方是,当把tomcat配好了,一看我这有tomcat了就直接启动就不管了,往往会遗漏最后一步,那最后一步是要双击Servers视图里面的那个Tomcat,打开它的配置的那个界面,然后勾选一个选项Use Tomcat installation(takes control of Tomcat installation),它那是单选,默认选第一个,你要选第二个,那当然,其实你不勾选,也不代表说它不能用,也能用,但问题是,如果你不做这一步,你部署的项目代码,就指不定跑哪去了,找不着了,它隐藏到了一个非常深的路径下,那我们最好还是选第二个选项,然后的话,我们好找部署好的项目,有问题好调错,调试。
  • 那这些内容说完以后又说了,那Servlet怎么开发,开发步骤第一步,创建web项目,要注意的是创建好的web项目以后它报错,我们点右键,Generate...的目的是什么,目的是补全,这个web项目必须的这个目录结构,补全这些内容,如果你不点那个东西的话,这个内容就缺失了,就报错了。第二步,创建项目以后要导包,那导包的话,咱们要导的是JavaEE的开发包,企业级的开发包,那企业级开发包的导入方式有两种,一种是通过maven搜javaee,就是从maven服务器下载这个包,那还有一个简化的方式,我们可以让我们的项目直接依赖Tomcat包啊,因为Tomcat有能力运行Servlet,它就有能力,它里面自带那些包,那么依赖的方式,右键项目,选择Properties,首选项,然后Target Runtimes,勾选Tomcat,再Apply
  • 那以上,我们就做了很多配置,包括配置Tomcat,包括建项目,包括导包,这些都是常识性的基本操作。那导完包以后就可以写代码了,所以下一步就开始开发了Servlet,那开发Servlet,首先需要创建一个类,那这个类,Sun的要求是要实现这个Servlet接口,但是它又给我们提供了一个默认的实现类,叫HttpServlet,它间接的实现了Servlet这个接口,那我们通常是继承于HttpServlet这个父类,因为如果你直接实现接口的话,需要实现的内容太多,比较麻烦,我们继承于父类,父类毕竟帮我们默认做了一些事情,这些事情,我们就省事了,所以,通常我们都是去继承的。创建了这个类以后,那么我们在这个类当中主要的目的,是要向浏览器拼网页,那在哪里拼网页,需要重写service方法,在这个service方法里拼,然后,这个类写完以后,还需要配置,配置Servlet,那么配置的时候,就在web.xml这个配置文件里,通过两步配置好这个类,那个语法结构是固定的,两组标签,第一组标签声明这个类名,通过类名声明它的别名,第二套标签,通过别名引用这个类,然后,声明网络访问路径,声明网名,那个结构要背下来;
  • 然后,配置完以后,这个并不能立刻运行,还需要把它部署到Tomcat里,这一步叫部署项目,那什么叫部署,部署是拷贝的术语,就是拷贝的意思,但我们不要手动拷贝,我们是通过Eclipse,具体的操作方式,首先要打开这个Servers视图,选择这个Tomcat,然后右键点击Tomcat,点Add and Remove,然后在弹出框里,把左侧的项目移到右侧就部署了,启动完之后就可以了。部署完以后只要启动Tomcat就得访问了,我们启动Tomcat,通过这样的路径,http://localhost:8080/servlet1/ts,来访问Servlet,访问的时候,需要注意,端口之后先写的是项目名,项目名之后写的是Servlet访问路径,网名,固定的格式,那在开发时,也经常会修改代码,修改代码时要重新部署,重新部署的方式,就是选Tomcat,选Publish。以上就是这个开发Servlet主要的步骤。
  • 最后开发完以后,测试时可能会报一些错误,要想理解这个错误的本质,首先需要理解我们所写的代码,它的执行的过程,在正式的web项目中,并不是只调用一个main方法就可以执行了,而是通过浏览器发出访问的,那我们在浏览器上输入一个路径,一回车,就会访问服务器,那当我们的请求到达服务器以后, 服务器是个软件,这个软件之内专门有一个对象,专门的处理这个请求,它处理请求,怎么处理呢,主要是分析请求的路径,在发送完成之后,首先它会看,发现有两级,第一级/servlet1,这是项目名,那么它会根据项目名,在Tomcat之下找同名的文件夹,并且,它会在这文件夹之内,去寻找web.xml配置文件,如果你的项目满足规范,就一定会有,一定会找到,找到以后,最终的目的,它是要帮我们去调用Servlet,所以下一步,Tomcat里面的这个对象,它会通过第二级路径,就是网名,去配置文件里,找对应的网名<url-pattern>/ts</url-pattern>,找到以后,根据网名,能得到别名<servlet-name>time</servlet-name>,根据别名,能找到类<servlet-class>web.TimeServlet</servlet-class>,所以,经过一系列的动作,Tomcat最终能找到这个类,找到这个类以后,Tomcat想帮我们去调用它,帮我们去实例化这个类,并且帮我们调那个service方法,那这个方法是我们写的,向浏览器输出了内容,输出什么,由我们决定,经过这几步,浏览器访问服务器,服务器就返回了一个动态网页,就是这么个流程。
  • 那么在这个过程当中,如果说你的路径写的不对,和你所配置的内容不匹配,比如说,项目名写错了,比如说网名写错了,那么会报一个404的错误,表示找不到资源;那还有,如果你在写配置文件的时候,这个类名写的不对,或者是,我们再写这个类的时候,类的内部有一些问题,那么最终会报出500的错误;还有如果说,你类中的方法,声明的不对,会报405,那么绝大部分的情况, 一般都是404和500,那404和500,意味着什么问题,要根据这个问题和它对应的关系去调试。

1.HTTP协议

引言

  • Servlet1 中已经知道,Servlet是Sun推出的,用来在服务器端处理HTTP协议的组件,本质上就是个对象,有这个规范的对象,然后它的作用,往大了说,是处理这个协议,往小了说,是拼一个动态的网页,拼一个东西。对于Servlet的概念,既要通俗的去理解它 ,也要它这个学术化的东西有所了解,像这个HTTP协议。所以,下面的内容就去了解一下这个协议,关于这个协议本身,已经由人家给实现完了,我们不用写任何代码去实现它,它已经实现好了,但是正是因为人家实现完了,我们都是拣现成的,所以我们需要对人家做的工作,底层有一定的理解。

What is the HTTP?

  • 那什么是HTTP协议,书面上的定义是这样说的,HTTP是一组单词的缩写,叫HyperText Transfer Protocol,超文本传输协议,是由w3c制定的一种应用层协议,用来定义浏览器与web服务器之间如何通信,以及通信的数据格式,这个比较学术化的这个语言呢,主要还是理解。那这个协议是什么呢,协议本质上,就是一种规定,规定了一些事情,我们在做开发时,要遵守这个基本规定,就这个意思;如果你不遵守这个规定的话呢,这个程序就玩不转了,因为浏览器和服务器都是,按照这个规定去写的代码,它所能够处理的这个程序,是满足规定的,这个协议,如果你不满足规定,服务器,浏览器,执行不了你的程序,是这个意思。这件事也好理解,你像任何行业都有它的这个规则,比如说,快递行业,我要给别人邮个快递,我怎么邮快递,我得找快递公司,找到快递公司的话,我还得添个单子,那个单子都是是有规定的,所以说一样的,那快递公司邮个包裹,它也有规定,说你要怎么填单子,我才能给你邮过去;然后的话呢,我们邮封信怎么样怎么样,也是一样的套路,就总之,你邮个什么东西有规定,其实网络上,这个浏览器和服务器之间的这个互相访问,和邮快递也差不多,不就是浏览器给服务器传一份东西,比如我登录时,我传的是账号密码,相当于邮了一个包裹一样,服务器不就是返回一个结果么,给我一个比如说,返回一个成功,还是失败,也相当于返回了一个包裹一样。

HTTP的具体内容

  • 所以说,这个互联网上,浏览器和服务器之间的交互,其实和我们互相的发信,发快递,大同小异,只不过邮的内容不一样而已,一个是实体内容,一个是虚的,那么,总之,什么是HTTP协议,或者说,我们在互联网上,用浏览器访问服务器,服务器给我返回数据,那么它之间的这个交互的规定,HTTP协议是对这件事的规定,是由w3c呢,制定的;那么它规定了两件事,第一件事,浏览器和服务器之间怎么去通信;第二件事,通信时,这个数据格式是什么,归纳一下这个主要的内容就是,HTTP协议,第一件事,什么是HTTP协议,它就是w3c制定的一个规范,这个规范它规定了浏览器和服务器如何通信,还有通信的数据格式,那当然有人可能不理解,那为什么w3c就这么事,事这么多,还得要制定个规范,凭什么别人就遵守它的规范呢,这个得解释一下,咱们互联网是开放的,任何企业,任何 人, 都有能力,或者都有这个权利,去开发一个浏览器,或开发一个服务器,而事实上也是这样,这个市场上目前有很多浏览器,很多服务器,比如说,浏览器有这个IE,还有这个Chrome,还有这个FireFox等等,市场上有很多很多这个浏览器,然后,还有很多服务器,比如说这个Tomcat,这个WebLogic等等,还有别的,那么,市场上有这么多浏览器,这么多服务器,比如说,我也要开发一个浏览器,我开发一个浏览器,叫MyBroswer,那我开发一个浏览器的话,我这个浏览器是不是要考虑,我得支持Tomcat,我得支持WebLogic,以及其他的服务器,都得支持。那么,如果市场上没有规范就乱了,我要想支持Tomcat,我得问问Tomcat,你们是怎么处理这个网页的,我怎么向你这个传数据,你才能支持我,你得跟它聊一下,如果市场上没有规范,你势必要找到这个服务器的提供商,和他聊一下说,你看我怎么写代码,你能支持我,或者我能支持你,咱们互相支持,好,Tomcat你跟它聊完了,WebLogic也得聊一遍,因为这是另一家公司,它的代码和Tomcat又不一样,所以,你和他也得 聊一下,我怎么写代码能支持你,然后,如果它俩的规则都不一样的话,可能是我要写出两份代码才能都支持,以此类推,市场上有十个浏览器厂商,我得跟10个浏览器厂商,挨个聊一遍,搞不好,得写出10套代码来,然后的话,如果说将来再有服务器厂商,我还要支持的话,那还得跟他聊,总之,非常的麻烦。所以,如果说没有一个统一的规则,没有一个统一的这个规范的话,那每一家服务器,自己干自己的,自己按照自己的方式去写代码,那么我想支持它就难了,同样的 道理,比如说,我现在我想开发一个服务器,MyServer,那我这个服务器,它肯定得支持各种浏览器,不然的话,就没法玩了,我要支持IE,我得跟微软聊一下,我要支持Chrome,得跟谷歌聊一下,我想支持谁得跟谁聊一下,我得知道,你们这是怎么处理的这个网页,我怎么才能支持你,我好写代码,那如果说,规范不统一的话,也是得写出好几份代码,才能都支持,总之很麻烦。所以呢,为了让这个互联网,能够更加开放,更加标准化,那么w3c,万维网联盟,它出面呢,制定了这么一套标准,就是不管是哪家浏览器,哪家服务器,只要你想交互,按照这个标准来,那如果所有人都遵守这个标准了,就好了,就容易了。那我想开发一个自己的浏览器,我知道这个标准,我只要遵守这个标准,我还用跟别的公司去聊么,不用了,同理,我开发服务器,我只要遵守一个标准就可以,它一定支持所有浏览器的,所以有标准,是有绝对的好处的,没有标准是不行的。总而言之,什么是HTTP协议,它是w3c给我们制定的一个规范,任何人想开发任何浏览器,任何服务器,都遵守这个规范,你一遵守这个规范,你就可以支持其他的任何的服务器 ,或者是其他的任何浏览器,你这个软件才能玩的转,如果你不守这规范,你去它服务器,去它浏览器,是不支持的,你玩不转,所以说,任何人都一定会准守这个规范,是这样的。
  • 那这个规范,规定了两件事,一个是浏览器和服务器怎么通信,通信的方式,一个是通信时的这个格式,数据格式是什么,所以下面的话题,探讨一下这两者之间怎么通信,格式又是什么,如下图:
    在这里插入图片描述
  • 左边是浏览器,右边是服务器,浏览器和服务器之间的通信是基于4步,当然,这个是概括来讲是4步,那么在实现的时候,详细来讲,这个步骤是很多的,是很繁琐的,是10多步,甚至更多的步骤,但是我们大概,就是记个概括的就可以了。4大步基本上是这样的,首先,浏览器和服务器建立连接,先连上,连上以后,浏览器把要发送的数据打包,然后发送请求,就请求服务器给我点什么,服务器接收到请求以后,处理请求,比如你输入的是账号密码,它就判断账号密码对不对,比如你输入的是注册信息,它就把这个信息存到数据库里,总之,它做出了判断,做出了处理,处理完以后,又给你一个反馈,这个反馈叫做响应,那服务器会把反馈的数据打包,然后,发送响应数据,发送给浏览器,浏览器得到以后,它会把连接关闭,这请求就结束了,整个过程就结束了。总而言之,浏览器和服务器的通信是基于4步,建立连接,就简单说,建立连接,发送请求,发送响应,和关闭连接,那么这4步,它和那个jdbc有点相似,jdbc基本上也是这样,jdbc要访问数据库,首先得创建连接啊,创建连接就是建立连接,然后呢,你要访问个数据库不就想执行个sql么,所以就发送一个sql,发送完sql以后,数据库就会执行这个sql,给你返回结果,返回完结果以后,我们会把连接关闭,也是这样。经过这个分析发现,jdbc访问数据库,和浏览器访问服务器,就这两个环节,就是交互的环节,它们都不约而同的,采用同样的方式,为什么,肯定是这种方式有优点,有什么优点呢,也很容易理解,它这种方式是什么呢,每次访问,都先建个连接,结束以后连接断开,它的特点是一次请求就要创建一次连接,连完之后,断开,下次请求再创建,它的特点就是,一次请求,一次连接,那这样做的目的是什么呢,是为了降低服务器的压力,和它相对的方式是什么呢,就是我们连上就不断了,连上不断的方式,对服务器会造成持久性的压力,为什么呢,因为服务器也好,还是数据库也好,它们所能够处理的这个请求,是有限的,那这个数据库,几百个,这个服务器呢,也是一两千个,当然,牛的服务器可能稍微多一点,所以,它处理能力有限,如果这个客户呢,都连着不放的话,那么很快这个资源就会被消耗光了,很快就会崩溃了,所以最好是什么呢,你有用,就连上,没有用就断开,有用就连上,没有用就断开,这样的话,是尽量降低服务器和数据库的压力,你可以想一下,就是咱们这个双十一,同时很多人访问淘宝,那是数以上亿的人同时访问淘宝,如果说每个人都和淘宝的服务器,连上不断的话 ,它有上亿个连接,很快的话,服务器早就崩溃了,所以它一定是连完,处理完你的请求,然后赶紧断开,再处理别人的,释放这个资源,当然了,那浏览器怎么和服务器建连接,浏览器怎么给服务器发请求,服务器怎么给浏览器发响应,连接怎么关闭,这4步,我们只要了解,我们不用写代码,这个代码,浏览器,服务器,早就写好了,我们甚至都不用学那个代码是怎么写的,因为不同的浏览器,不同的服务器,它的底层的代码还有区别,区别还挺大,你学也学不过来,但是呢,方式都是这4步,规则是一样的,所以我们只需要了解这个规则,这就是第二件事,如果以浏览器的角度来阐述这4步,就是如何通信基本上分为4步,第一步建立连接,浏览器和服务器建立连接,第二步发送请求,浏览器向服务器发送请求,第3步接收响应,浏览器接收服务器返回的响应,然后,关闭连接 ,连接断开,请求结束,那总体而言,这4步的特点概括一下是,一次请求,每有一个请求,一个请求就一次连接,那么,可以尽量降低服务器的压力,这是它的特点。那么这4个步骤,4大步是这个w3c的规定,就是协议要求的,那任何浏览器,任何服务器,都是这么干的,啊,至于说底层的代码,到底怎么写,那个是有差异的,但是步骤,和基本的这个方式是一样的。HTTP这个协议,一个是规定了怎么通信,大概4步,第二个又规定了通信时,这个数据格式是什么,如下图:
    在这里插入图片描述
  • 左边是浏览器,右边是服务器 ,那么浏览器向服务器发送的数据,Request Message,服务器向浏览器返回的数据,Response Message,图中只是一小部分,还没有写全,这些数据并不需要看懂每一句话,但如果有必要确实需要理解每一句话的意思,就可以去查一下那个协议,去看那篇文章,那个协议就是一个文档,写的很详细,去w3c官网看,那总之,这个数据的格式都是协议规定的,那每一个单词代表什么意思,那个文档上有说明,那么,而我们不用了解那么细,我们就了解一个大概,了解到什么程度呢,你需要知道这个请求的数据,它分为几部分,每一部分代表什么含义,响应数据又分为几部分,每部分又代表什么含义,把这个整体结构了解就可以了。请求数据包括3部分,请求行,消息头,实体内容:
    在这里插入图片描述
  • 响应部分也包括3部分,状态行,消息头,实体内容:
    在这里插入图片描述
  • 那我们需要了解每一部分,它到底有什么东西,大概概括一下是,请求数据和响应数据的结构,那请求数据,包括请求行,消息头,实体内容;响应数据包括,状态行,消息头,实体内容。然后,下面写一段程序,通过代码,来观察一下这两部分内容,这样的话,就可能直观一点了。打开Eclipse,通过对 Servlet1 中的TimeServlet类进行扩展,是写代码观察一下,这两部分的内容。首先,利用servlet1的这个项目中TimeServlet类,演示一下浏览器给服务器发送的请求数据,请求行,消息头,实体内容,这3部分数据分别都有什么;服务器给浏览器发送的响应数据,这3部分都有什么。在这里演示。那我们当前所看到的这个TimeServlet类代码是运行在服务器上的,因为这个代码执行在服务器端,所以我们现在是站在服务器的角度去看待这个问题,那么站在服务器的角度来说,浏览器发送过来的数据,我是要接收的,我要接收请求数据,那么,站在服务器这个角度,它发送给浏览器其的数据是要输出的,输出响应数据,是接收和输出的关系。对于响应数据一定不是接收的,是发送的,是输出的,对于请求数据是接收的。

Servlet如何处理HTTP协议

  • 在TimeServlet这个类中,因为是在service这个方法里,才能够处理请求,拼动态网页,那在这个方法里要,接收浏览器发送过来的数据,要给浏览器返回数据,用谁呢,request,response,显然是这两者,然后request是请求的意思,response是响应的意思,所以,我们想接收请求数据是用你谁接收呢,request ,我们想输出响应数据用谁输出呢,response,这很显然,很直观。那怎么利用request 接收请求数据,第一个话就是,使用request接收请求数据。这个用来处理请求数据的对象,我们叫request,参数名简写为req,就是浏览器发送过来的请求数据,它分为3部分,分别是请求行,消息头,还有实体内容3部分,总而言之就是,浏览器給我们服务器发送过来的是3部分数据,而每一份数据不是一个数据,是好几个,请求行有好几个数据构成,消息头有好几个数据构成,它是由多个数据构成的,是一堆,摆成3块。首先,用request先接收请求行的数据,请求行的数据固定,它只有3个数据:

    System.out.println(“协议类型:”+req.getProtocol());
    System.out.println(“访问路径:”+req.getServletPath());
    System.out.println(“请求方式:”+req.getMethod());

    那么浏览器发送给服务器的数据当中的请求行的这一部分,包括3个数据,一个数据是协议类型,如果你想获取的话,调req.getProtocol()这个方法,这个方法里封装的就是协议类型;第二个呢是访问路径,那么这个req.getServletPath()方法,得到的是访问路径;第3个方法是请求方式,那么这个req.getMethod()方法得到的是请求方式。当然了,这些内容,通常我们开发普通的案例中用不上,一般用不上,如果你开发一些特殊的请求,特殊的功能时,如果要用上时,如果想得到这些东西怎么得,就调用这些方法,一般用不上。我们平时做的,什么注册,登录,保存一份数据,什么查询点什么东西,这些东西一般都用上,那么什么时候可能会用到协议类型呢,什么时候可能会用到请求方式呢,这个得看特殊需求。比如做一个特殊的需求,刷票,做一个投票的一个功能,它要模拟投票,还怕人看出来,怎么办呢,它伪装,伪装这个不同的版本的协议,伪装不同的请求方式,甚至伪装IP地址,伪装什么端口,然后就去访问那个服务器投票,这种情况下,就像做这样的不正经案例的时候,可能会用上这些内容,一般做普通的案例底层都封装好了,也用不上。

  • 那写完这几句话以后,把修改后的代码重新部署到服务器上并运行,输出看一下它得到的到底是什麽东西,控制台输出的内容如下:

    协议类型:HTTP/1.1
    访问路径:/ts
    请求方式:GET

    协议类型输出的是HTTP/1.1,HTTP是协议的名字 ,1.1是版本,连起来就是协议类型,这个HTTP协议呢,发展的比较稳健,它到目前为止,就两个版本,一个是1.0,一个是1.1,目前的话,基本上所有浏览器,所有的服务器,都已经支持1.1了,这是最新的版本;然后呢,访问路径,你看当前我们所访问的路径,它的路径是不是/ts啊,就是/ts;还有一个请求方式,叫GET,那除了GET之外,还有POST等等等。

  • 再去看第二个是消息头,消息头中的数据很多,这个我也记不住有多少个,就N个吧,不确定个数,比较多,这个消息头中的数据,在存储的时候,是按照key-value来存的,就是,消息头该数据是按照键值对的方式存储,按照key-value存储的,和map有点相似,然后它里面的key有什么,这个我也记不住,因为太多了,而且它的key呢,很长,那如果说我要是这个数据中的这个key-value的这个key,我没记住的话,我还想把它都输出一下,那得怎么办呢,你比如说,我有一个map,map中有一大堆数据,那个key,我们没有记住,我想把它们都输出一下,怎么办,你得遍历对吧,是不是啊,遍历,那我们遍历map的话,有几种方式呢,3种方式,分别是,遍历它的key,遍历它的value,遍历它的key-value,3种方式,这个消息头也是类似的,就是说,它用键值对方式来存储,那这个数据,是可以遍历的,那这样,但是它的,遍历的方式比较少,它只能按照key来遍历,那我们遍历一下它的key,看一下有多少个key,同时,再输出它每一个key的值,那我们用这个方法,叫req.getHeaderNames(),它会返回一个特殊的数据类型Enumeration,这个getHeaderNames(),返回的这个叫Enumeration,这是啥东西呢,这也是一个迭代器,JavaSE部分有一个迭代器叫Iterator,这个Enumeration也是,Enumeration这个迭代器呢,比Iterator还早,后来的话,就感觉它这个名字太长,不好记,慢慢就不用了,就被换成了Iterator,就总之,这是一个更早的迭代器,它的用法和Iterator是 一样的,是相似的。

  • 这个Enumeration是一个古老的迭代器,其用法和Iterator相似,那么说到迭代器,其实目前,已经知道了好几个迭代器了,最早在SE时的Iterator是一个,现在的Enumeration又是一个,还有一个是JDBC时的ResultSet,jdbc中的结果集,这些接口都是按照迭代器模式设计的,迭代器模式我们怎么用,通常是while遍历。那么迭代器模式怎用,一般都是用while去遍历,这个Enumeration也是,我们while遍历一下。这个Enumeration,它是一个迭代器,它里面呢,封装了我们所得到的,这是getHeaderNames(),得到的这是key,所有的key,所有数据的名字,所以呢,我遍历它,会得到每一个key,然后,我调用request,根据key得到每一个value,这就是一组数据,那我们把每一组数据呢,输出一下看看:

    Enumeration e = req.getHeaderNames();
    while(e.hasMoreElements()) {
    String key = e.nextElement();
    String value = req.getHeader(key);
    System.out.println(key+":"+value);
    }

    代码写完以后,再把这个项目呢,重新部署一下,然后启动Tomcat,再访问一下这个对象,看一看控制台,输出的这部分内容都有什么,重新部署项目,选择Tomcat,右键publish,点播放键,启动它,启动以后呢,那打开浏览器,再次访问这个组件,这个对象,http://localhost:8080/servlet/ts,然后,再看控制台,那这回输出的数据就多了:

    host:localhost:8080
    connection:keep-alive
    upgrade-insecure-requests:1
    user-agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36
    accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9
    sec-fetch-site:none
    sec-fetch-mode:navigate
    sec-fetch-user:?1
    sec-fetch-dest:document
    accept-encoding:gzip, deflate, br
    accept-language:zh-CN,zh;q=0.9

    有好几个,是具体几个你也不用记,没关系。先看它先输出的什么呢,host : localhost:8080,这是咱们访问的IP加端口,这个内容,我们可以通过这个方法得到;还有什么,connection : keep-alive,连接是否活跃,是否可用;然后这个,cache-control : max-age=0,这个缓存机制是什么;然后这个,upgrade-insecure-requests : 1,我也没太用过,不太清楚,你就必须得看那手册,才能了解;然后呢,这个,user-agent : Mozilla/5.0(Windows NT 6.1; WOW64... ...)user-agent这个词熟悉么,在JS部分的web对象的navigator里,有个user-agent属性,返回的不也是这个内容么,它返回的是客户端,浏览器的版本,内核,是这样的内容,我们通过它呢,能知道浏览器是什么;然后还有什么,它希望返回的数据的格式,accept : text/html...;这个编码,accept-encoding : gzip, deflate, sdch;语言,accept-language: zh-CN,zh;q=0.8,等内容。还是那句话,这些内容,我们平时普通案例也用不上,如果比如说你以后想做一个类似投票的功能,需要伪装的话,可能是,需要用的上,总之,特殊案例可能会用上,后面的这个做的案例中,基本上这些参数都没有用上,就是了解一下 。这是第二个内容。

  • HTTP协议就是个规范,规定了浏览器和服务器如何通信、通信的数据格式。那通信的话,有4个步骤,建立连接,发送请求,接收响应和关闭连接。浏览器和服务器呢,在实现这个功能的时候,按照这种方式来做;再一个它规定了,它的请求的数据的结构是什么,请求行,消息头,实体内容,请求行和消息头的这个数据呢,通常,一般的案例,它用不上,那这个数据,往往是谁要用上呢,是这个服务器和浏览器自己要用的,这些数据是浏览器和服务器自身要用的,那哪个数据是我们要用的呢,这个实体内容,其实是我们要用的。那么下面这个实体内容,我们也可以呢,用request接收实体内容的数据,那什么是实体内容呢,就是浏览器给服务器发送的详细内容,比如说,我注册时浏览器向服务器发送的什么帐号密码,这个邮箱,手机号,这样的数据,那比如说登录时,账号密码,验证码,这样的数据,这个详细的内容叫实体内容,那TimeServlet这个案例,浏览器直接访服务器,浏览器给服务器发送了详细的内容么,详细的数据么,发了么,或者说,浏览器给服务器发送这个业务数据了么,没有,就没有账号密码,我们填的数据,没有客户提填的数据, 它没有业务数据,所以,本案例其实没有实体内容,实体内容是一份数据,是一套数据。那再归纳一下,这个请求的数据结构包括请求行,这个协议的类型,请求的方式,还有访问路径,这是什么呢,其实,是请求的基本信息,请求行中包括请求的最最最基本的信息;那消息头,它里面存的什么,这个浏览器的版本,这个连接是否活跃,这个数据的什么这个格式,等等,它是什么呢,是对实体内容的描述,就消息头这份数据,它是对实体内容的一个描述,概括。那最后实体内容是什么呢,实体内容是浏览器向服务器发送的具体的业务数据,就是用户呢,输入的数据,比如说,我做个财务软件,浏览器向服务器,发送财务数据,我做的这个HR软件,发送的可能是人力相关的数据,你是什么行业就得发送什么数据,这得看情况。总之,数据分为这3部分,然后的话呢,它各有区别。

  • 那么响应数据结构也是3部分,状态行,消息头,实体内容,都包括什么呢,响应数据的状态行中,存的是协议类型,状态码,还有这个状态,像程序报了个404,报了个500,甚至有405的,这个404,500,405,以及200,是什么呢,状态码,我们看到的那个404,就是状态码,表示找不到东西;那500也是个状态码,表示说程序有问题;不同的状态码,表示不同的意思,200表示成功,其他的表示各种各样的问题。然后这份数据不用我们去填,这个服务器向浏览器发送数据,服务器本身知道,我支持的是什么协议,它也知道,程序对不对,这个数据完全由服务器自动生成,自动填写。

  • 上述已经知道如何使用request接收请求数据,那如何使用response发送响应数据,服务器向浏览器发送响应数据,也包括3部分,第一部分是状态行,状态行里面有3份数据,这3份数据,我们不是读取,是书写,是设置,因为服务器向浏览器发送数据,它是设置,它得写这份数据,那这个数据,平时我们不用手写,就是此数据通常由服务器自动填写,我们基本上从来都不用写。总之,状态行这份数据,由服务器自动填写,一般不用我们去写,如果你有特殊情况,需要去写的话,调set方法,response点set,但是它的set方法有很多,它会涉及方方面面,那至于哪个方法是设置,比如说这个状态,状态码啊,哪个方法是设置这个协议类型啊,可以去查一下那个官方文档,查文档查手册,那一般用不上,了解就可以了,这是第一份内容。

  • 那第二份内容,那么响应数据当中,也有消息头,消息头呢,有很多数据,比如说响应的时间,日期,响应的服务器是什么服务器,这份数据最后修改的时间是什么,数据长度是什么,数据的类型是什么,等等,那这些内容也是,不用每一个都我们去set,那太麻烦了,像这个当前的时间,服务器自己就知道,服务器版本,它自己知道,那么,这份数据什么时候产生的,它知道,数据长度,它也知道,所以,基本上呢,消息头中的这些数据,服务器默认都知道,它只有一个内容默认不知道,数据的类型,所以我们平时给这个响应时,我们要设置的这些消息头的数据,我们只要设置数据类型,这一个就够了,其他的一般不用写,不用改。我们想设置数据的内容的类型,其实就是这句话,res.setContentType("text/html");,这就是一个set方法,就是修改,或者说,设置响应的消息头的内容,只不过这个ContentType,只是消息头中的一部分,一个数据,还有很多其他的数据,也是set,就不挨个演示了,因为没有必要,我们平时不用去set,就这一个需要,这是第二件事,就是消息头,大部分的消息头的数据由服务器自动填写 ,只有ContentType必须由我们填写,那么响应数据,除了有这个状态行,消息头,还有什么呢,最后一部分,也是实体内容,实体内容是什么呢,响应时实体内容就是一个网页,实体内容就是我们服务器向浏览器发送的具体的数据,数据就是网页就是在TimeServlet中print的这个网页,w.println("<p>"+ now+"</p>");, 这个就是实体内容 ,这是第3个实体内容,什么叫实体内容,实体内容就是服务器向浏览器发送的具体内容,那么本案例输出的网页就是实体内容。关于这个响应数据也再归纳一下,总结一下,关于响应数据状态行,它封装的是什么呢,响应就是服务器向浏览器发送的响应的这个基本信息,它包括什么呢,这个服务器的类型,协议什么的,然后呢,这个消息头也是对实体内容的描述,它包括什么,服务器这个是什么服务器啊,内容多长啊,什么格式啊,是对实体内容的描述,那实体内容是服务器向浏览器发送的具体数据,或者说业务数据,是和业务相关的数据。

Servlet处理HTTP的代码实现

package web;

import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TimeServlet extends HttpServlet {

	@Override
	protected void service(
			HttpServletRequest req, 
			HttpServletResponse res) throws ServletException, IOException {
		//1.使用request接受请求数据
		//1)请求行(3)
		System.out.println("协议类型:"+req.getProtocol());
		System.out.println("访问路径:"+req.getServletPath());
		System.out.println("请求方式:"+req.getMethod());
		//2)消息头(N)
		//该数据是按照键值对的方式存储的
		//Enumeration是一个古老的迭代器,其用法和Iterator相似.
		Enumeration<String> e = req.getHeaderNames();
		while(e.hasMoreElements()) {
			String key = e.nextElement();
			String value = req.getHeader(key);
			System.out.println(key+":"+value);
		}
		//3)实体内容(一套)
		//本案例浏览器没有向服务器发送这样的数据,我们在后面案例中演示。
		
		//2.使用response发送响应数据
		//1)状态行
		//此数据通常由服务器自动填写
		//获取服务器的时间
		Date date = new Date();
		SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
		String now = sdf.format(date);
		//2)消息头
		//大部分的消息头数据由服务器自动填写,只有contentType必须由我们填写
		//将时间拼到一个网页里给浏览器返回
		//告诉浏览器向它发送的是什么类型的内容
		res.setContentType("text/html"); 
		//获取输出流,该流指向的目标是浏览器
		PrintWriter w = res.getWriter();
		//服务器向浏览器发送的具体内容,本案例输出的网页就是实体内容.
		//此处偷懒,拼一个简化不版的网页
		w.println("<p>"+now+"</p>");
		//关闭输出流
		w.close();	
	}
}

小总结

  • 那总体来讲,这个协议是规定,规定了两件事告诉你怎么通信,这件事,只能体会,咱们看不到,由程序来保障,然后呢,数据结构很直观,能看到。大概是这样,大概了解一下,这个并不用背,归纳一下,或者说注意一下,我们在开发时,很多地方是不需要我们去自己处理的,浏览器和服务器通信的过程中,很多是由浏览器和服务器自动实现,我们不用管,只要了解就可以了,关于这个数据,哪些由浏览器和服务器实现,哪些由我们实现呢,请求行,请求数据中的请求行,消息头是由这个浏览器自动填写,这些内容不用我们去写,然后,响应数据中的状态行,还有消息头,通常由服务器自动填写,但不是绝对,所以这件事我们不用管,那需要我们处理的是什么呢,我们仅仅是需要处理什么呢,就是请求数据中的实体内容由我们提供,然后,响应数据中的实体内容由我们提供,说白了,我们主要是提供这个实体内容。总之,这个http协议非常复杂,关于http呢,有专门学习的一本书,那书你去看的话,那老复杂了,老抽象了(学一年都不一定能学完),所以说,这个协议本身呢,是要求很高,很复杂的,但是我们需要处理的,仅仅是一点点内容,那我们怎么处理那个内容呢,那概括一下,就是我们只要学会呢,通过request处理请求数据,通过response处理响应数据,只要会这两个数据的使用,就可以了。所以,就关于这个协议呢,就看起来,挺这个麻烦,但落实到我们头上,我们只需要知道这俩对象怎么用,request和response,总而言之,这个http协议,它是一个规范,规定了一些事情,这个规定中所涵盖的大部分内容,由浏览器服务器已经实现了,我们只是需要提供一些实体内容而已,仅此而已。那怎么提供呢,request和response,所以我们关于协议只要知道两个东西就可以了。
  • 从应用层面而言,不去深刻理解这个HTTP协议背后的一些原则,这个也很正常,知道一些概括的东西就可以了。但结合HTTP协议,可以对Servlet的本质有了一个更深层次的理解。Servlet是什么呢,它是服务器上用来处理HTTP协议的一个对象,一个组件,所谓的处理HTTP协议,就是浏览器访问我,我给你返回一个动态的东西,那我去处理这件事,利用什么来处理呢,利用的是这个service()方法的参数,request和response。

2.通过HTTP深入理解Servlet开发过程

  • 虽然说,说起来很麻烦,但我们这个Servlet,在整个这个HTTP协议的过程中,只是一个小小的环节,我们需要掌握的只是两个参数,以及程序执行的完整流程,仅此而已。这只是Servlet的一个概括性的描述,那下面再去深入了解它的每一个细节。首先,就是Servlet,它怎么具体的获取到那个业务数据,就是浏览器向服务器发送具体的业务数据,我们怎么得到,那这个数据也可以叫参数,我们怎么得到那个参数,Servlet如何获取请求参数。同样,我们得有一个案例,通过在案例中的演示来理解技术的本质,所以下面做一个简化的版注册,做出注册吧,因为登录的数据少,注册稍微多一点点,做一个注册功能。那做注册这个功能的流程是什么,先把需求搞清楚。

注册案例

需求分析与设计
  • 我们的开发站在web的角度来分析,web项目,它一定会有浏览器,也一定会有服务器,有这两者,所以呢,想都别想,左浏览器,右服务器,那么我们要注册的话,首先呢,一定要先打开注册网页,那我们在正式的环境下打开一个页面,一定是你敲一个网站,敲网址以后,一回车访问服务器,服务器给我返回一个注册页面,一定是这样的,就是,浏览器敲网址,访问服务器,服务器给我返回一个网页。那注册网页,是动态的还是静态的,注册网页应该是静态的,因为我们每个人打开它,它都是空的,啥都没有,每个人看都一样,没有差异,我们任何人打开注册页面都是一样的,都是空白的,需要去填写的。所以说呢,那当然了,你输入信息以后就有差别了,但我们打开那一刻,是不是都一样呢,是的,所以注册网页是静态的,静态的话,服务器存一个啥呢,静态网页,就HTML呗。

  • 那这个静态网页,我们怎么存到服务器上,其实很容易,我们把这个网页放到我们项目里一部署,它是不是就进去了。我们一部署项目,项目一拷过去,静态网页就过去了,所以这个部署很容易,跟着项目一起部署就可以了,那静态网页怎么访问呢,直接访问,静态网页不像Servlet,还得写个网名,不用,你直接敲什么呢,reg.html就可以了。注册的话,这个单词叫register,简写一下,就reg.html,服务器就存这么一个静态网页,然后呢,这个浏览器访问服务器,服务器把这个网页呢,返回给它,然后呢,浏览器得到这个网页以后,它需要去加载网页,显示其内容,那我的要求这网页上有什么呢,总之呢,我敲一个网页的名字访问它,它呢,返回一个网页,然后呢,浏览器显示其内容,那注册页面显然就是一个表单呗,就是表单,表单里有什么呢,我有要求,我要求它有这个账号,然后,再来个框,账号一个框,然后再来密码,那当然应该还有这个验证码,一般不会有,登录才有验证码,还要有确认密码,确认密码咱们就不写了,省点事,还可以有性别,填你的性别,性别以什么形式来体现呢,单选radio,性别得有男,有女,除了性别之外,再来一个兴趣,比如注册时,填写你的兴趣,兴趣里得可以多选,多选是什么,那checkbox,比如说美食,还有啥呢,竞技,社交,就写3个得了,你可以写更多的,那都没关系。我的表单里就有这些内容,然后你最终的话,填完这些数据是不是得提交,那提交的话,这里有按钮,这个按钮叫什么呢,就叫注册。

  • 然后呢,用户打开这个注册网页,它就按照我的要求填上这些数据,填上以后,他就可以点注册,提交数据,一点注册表单会提交给服务器,那你注意,咱们这个注册功能,它不是只是一个请求,很多时候呢,我们一个功能包含多个请求,输入网址发送给服务器,是第一个请求,这个请求的作用是我们得到一个注册网页,静态的,然后呢,我去填数据,填完数据以后,一点注册,是不是把数据发送给了服务器,这是第2个请求,是浏览器向服务器发送的第2个请求,那这个请求返回的结果是动态的,还是静态的,动态的,为什么呢,因为每个人都有区别,你注册成功,我注册失败了,有没有可能我注册时,它提示我账号已被占用,所以说,它有结果可能是不一样,有区别,所以是动态的,再一个,我们把一个数据提交给这个服务器,我们不写一个Servlet,我们写一个HTML,这个html能处理这个数据么,它没有能力,我们想处理这份数据得用什么处理呢,这个http协议,我们想要处理这个浏览器发送过来的数据,用什么处理呢,这个浏览器发送给服务器,对于服务器而言,这个数据是请求数据,我要处理的话,一定是request,你现在有数据了,我要处理它,不得用request么,如果你用html,有request这个东西么,html里没有,没法处理,所以我这里一定得是用Servlet,所以,无论从哪个角度,我这得写一个Servlet。

  • 那写一个比如说叫,RegServlet,然后呢,由这个RegServlet,它最终给这个浏览器发送一个响应,当然这个响应,要做完整的话,你要判断账号密码,账号存不存在,然后的话,还得去保存这份数据,然后呢,有可能成功,有可能失败,会有不同的结论,但我们这是模拟的,咱们不真的存这份数据,所以我们就给一个固定的返回提示吧,提示什么,OK吧,现在呢,还不能写成功俩字,我们返回的时候,输出了汉字,就乱码了,现在呢,还不能返回汉字啊,那汉字怎么办,这是另一个话题,反正我们现在先返回一个OK,就这样。那么,在整个这个案例当中,重点是第2个请求,因为第一个请求,写个静态网页,这个很容易,把静态网页部署,我们部署项目它就自动部署了,也很容易,访问它只要知道这个访问路径的规则,也很容易,所以第一个请求,其实不是什么重点,就很容易,然后呢,重点是第二个请求,那浏览器怎么给服务器发送这份数据,那服务器怎么接收这份数据,那浏览器向服务器发送这些数据,这个对浏览器和服务器都有一些要求,首先,那浏览器要发送这个数据,它的要求是什么,那这个黄色区域是不是个表单,它肯定得有表单,就是对表单的要求是什么,就是发送请求数据时对表单的要求,对表单的要求第一点,我要想向服务器发送数据,那个表单上,form上得不得做一些什么配置,你得写个啥啊,得写一个action,action里写什么,写我们要提交的目标,我要提交给谁,就是通过表单的action属性,声明提交的目标,这是第一个话题。

  • 那么,我们要提交数据,账号,密码,性别,兴趣,这么多数据,你想是不是得给每一份数据起个名字,账号得不得有个名字,密码需不需要有个名字, 应该是有的,如果你没有名字,你把账号密码全都给我,我能知道哪个是账号,哪个是密码么,我能识别么,识别不了,所以你想传数据的前提,一定要给每一份数据命名,要怎么命名呢,通过控件的name属性,声明数据的名称,这个表单控件都可以有name,但在此之前,我们只是在radio加了name,其他的还没有写,但其他的其实也可以有name,这个name,它有一层含义就是,它是数据的名字,你账号可以叫code,密码可以叫password,性别可以叫sex,兴趣可以叫interest,你得取一个名字,如果你不取名的话,就乱了,就服务器没法识别了,一定要有名字。

  • 第3个要求,那账号,我输入数据以后,我向服务器发送的就是这里面的这个值, 我输入aaa,我传给服务器就是aaa,这3个字母,密码也是,我输入123,我们向服务器,传的就是123,这个数据,你写啥,它传什么,但是性别,兴趣,它会传什么过去呢,它有可能会传男女这俩字么,有可能会传美食社交,这个字么,有可能么,没有可能,默认没有可能,为啥呢,咱们可以想一下,我们写这个单选或多选,我们是这样写,<input type="radio"/>男,后面写男,然后啊,多选,<input type="checkbox"/>美食,那这个男字和这个radio,有关系么,这个美食和checkbox有联系么,所以,你勾选这个checkbox,它一定不会发送美食这俩字,它俩没什么关系,那它会发送什么呢,它默认发送的是一个状态,这个状态叫on,比如说我勾选美食,发送给服务器on,我勾选竞技,发送的状态还是on,它认为勾选的状态是on,发送的是on的一个单词,那我勾选美食竞技社交,你给我来3个on,你觉得这样合适么,识别不了,我也不知道这on是啥意思,所以这个默认的发送的数据时不行的。

  • 那怎么去改变发送的这个值呢,也可以,那这是第3个要求,对于单选或多选,我们需要通过控件的value属性,然后呢,声明发送的值,我们通过value来声明发送的值,比如说,我在这个radio,写了value=“M”,<input type="radio" value="M"/>男那我一提交,这个浏览器发送的是M这个字母,value写什么就发送什么,如果我想发送男这个字,怎么办呢,value=“男”,<input type="radio" value="男"/>男,就是我想发送具体什么内容,通过value来声明,如果你不写value,它永远都是发送on。这3点呢,是对表单的要求,我们在写表单时,就遵守这3点,做这样的一个归纳,通过案例去练就可以了。那么,浏览器只要你表单做出这样的声明以后,就能发送数据给服务器,那服务器怎么接收数据呢,这个,通过哪个对象来接收呢,通过哪个对象来接收这个数据,request,还是response,那浏览器给服务器发送的是请求数据,比如我访问淘宝,我请求淘宝給我返回一个网页,请求数据,request,这个单词就是请求的意思,是非常直观的。

  • 那我们如何通过request去得到这个数据呢,它給我们提供了两个方法,一个方法是这样的,req.getParameter("code"),code是什么呢,就是这个name属性,就这数据的名字code,那这个方法呢,会返回一个String,字符串,我们从网页上得到的都是字符串,那还有一种情况,那有的数据呢,它不是单个值,这个账号是一个数据,密码是一个数据,性别有两个选项,但只能选一个,还是一个数据,是但兴趣就不是了,兴趣是3个选项我都能选吧,它是一组数据,那我们怎么获得一组数据呢,这样,req.getParameterValues("interest"),里面写上参数名,比如说interest,这个方法会得到一个数组。总之,这是Servlet当中,我们需要做的事情 ,或者说对Servlet的要求,它需要做的事情。 前者是对表单的要求,后者是对Servlet的要求,我们这个案例,主要就是演示这两个地方,其他的是次要的。
    在这里插入图片描述

  • 那这两个要求说清楚以后,下面我们来写这个例子,来体验一下,浏览器向这个服务器传数据的这个方式,那写这个功能的话,是按照请求来写,按照功能来写,那第一步,要得到一个静态页面,首先得有这个静态页面,先得得到它,打开开发工具Eclipse,再新建一个项目,项目叫Servlet2, 也是web项目,也要导 入javaee那个包(下图项目名Artifact Id : servlet):
    在这里插入图片描述

  • 因为项目中要写Servlet,它是JavaEE企业级开发当中的一部分,所以我们需要引入javaee那个包,javaee的包默认在jdk里没有,那引入方式我们能不用Maven,就不用了,因为Maven得下载,我们直接引用本地的更方便,所以呢我们选择这个项目servlet2,右键Properties,然后左边选什么呢,Targeted Runtimes,然后呢,勾选Tomcat,勾选tomcat的意思是什么呢,让Eclipse去依赖Tomcat下的包,Eclipse很聪明,然后呢,Apply,ok ,这个包就依赖了,那想知道它是否依赖成功,可以在Java Resources/Libraries里看,具体操作详见 Servlet1/Servlet开发步骤/需求实现/创建项目

  • 项目建好了,包也导进来了,那下面呢,就要开始正式的写代码,第一步要先写这个注册网页,这个网页我们把它放到哪去呢,放到webapp下,网页就放到webapp下,它不是java,这样,我就直接在webapp下创建这个网页,就不建文件夹了,咱们案例小,不用建文件夹了,那我们选择webapp,然后呢,右键,然后呢,New/Html File,给它取名叫reg.html
    在这里插入图片描述
    在这里插入图片描述

  • 那reg.html这个网页里,我要求有这样的表单,表单里有什么呢,账号密码性别兴趣,先把这个网页的标题改一下,这个稍微正式一点,这个标题不好看,改标题就叫注册吧。然后呢,body里面,我们写上一个表单,先不用做任何配置,把内容写出来就行了,写了账号,文本框,密码,密码框,性别,俩单选,还有兴趣,还有按钮,能够提交数据的按钮是什么,submit,按钮的名字叫注册,什么功能,就叫什么:
    在这里插入图片描述

  • 那这个网页就写完了,总之就是个静态页面,连css都没有,非常简单,就是一些标签,那注意,写完以后不要习惯性的,我打开workspace双击一下,看它对不对,不是了,咱们现在是有了服务器,我们需要把这个网页,把它部署到服务器上,我们远程访问服务区这个网页,听明白了么,当然了,我们当前的服务器就在我们本地,所以,ip地址写个localhost,那正式的环境,这个服务器可能装在上海,装在天津,我们在北京可以访问,就像我们访问百度,访问淘宝一样,只要你把这个服务器放到了一个公IP的服务器上,一个电脑上,这是可以访问的,那有人说,那我还得敲IP地址,我怎么才能不敲IP地址,敲那个网站呢,你买个域名,和那个IP绑定,那你敲域名,和敲IP一样,就是我们访问taobao.com,这个taobao.com,绑定了一个IP地址和端口号,上哪买啊,上那个阿里云,阿里云上有,你真要买啊,没必要,你要想试,也可以买,然后还得备案,麻烦死了,就总之呢,就是说这个项目如果你做好以后,他真的可以放到互联网上,远程访问,正式访问,外地的人都可以访问,前提是什么呢,你得有一个,有公有IP的服务器,你可以去阿里云买一个,租个服务器,一个月几十块钱,一个月20块钱左右,一年也就是七八百块钱,有优惠,然后呢,再买一个域名一挂,那就可以了,是可以的。但我们现在不具备这个条件,我们现在本地能访问明白就可以了,至于说你怎么把这个软件做好以后,装到互联网上被别人访问 ,那是运维,他们做的事情,你要想感兴趣,自己研究去吧。

  • 那写完以后,现在就把它部署到服务器上,通过浏览器远程访问它,那我们部署这个项目,在Servers视图下面,选择Tomcat,然后右键,点Add and Remove...,弹出框里选择我们要部署的项目,Add,挪到右侧,然后完成,然后呢,启动Tomcat,启动以后,打开tomcat,在D盘,tts9之下,apache-tomcat-7.0.67, 然后呢,看wtpwebapp那个部署的项目就在这里,有了servlet2,打开servlet2,看一下是不是立刻就有这个reg.html网页,它就这样,立刻就有了这个网页,它这个部署以后的项目结构和我们所写的代码结构不一样,是有所调整的,就是这样,这是一个结论。那这个reg.html网页,直接在servlet2之下,我们可以直接访问它,可以怎么访问呢, 打开浏览器,这样输入,http://localhost:8080/servlet2/reg.html,打开项目servlet2,再写那个网页reg.html,就可以了,就是静态网页可以按照它的路径结构直接访问,那如果你出现404,表示这东西找不着,找不着的话,那检查一下,是不是部署的不对,或者是,项目名,文件名,对应写错了呢,检查检查。

  • 这个案例的静态网页就写好了,那静态网页,它不需要像Servlet那样去配置,只有Servlet需要配置,它不需要,直接就可以访问,当然了,关于说这个静态网页,我们部署的话和动态的Servlet部署的时候,也有一些规则上的差别,这是后话,那然后呢,再看第二个环节,就是这个网页写好以后也能访问了,那第二个环节,我们得在网页上能填数据,能提交给服务器,那网页上要想填数据,还提交给服务器,这个表单需要有设置,前述的3点要求,那我们下面遵循这3点来写这个配置。打开Eclipse对reg.html这个静态页面,从上往下逐项来配,那第一项配置,是要配置表单是要提交给谁,表单提交的目标,那么,我们需要在这个form上写一个action,然后,在action里面写上访问路径,这个访问路径,如果你写完整的话,应该是要写完整的,但是有简化的方式,那这个action我们要访问谁呢,我们要访问的是,这个RegServlet,那我们要访问它的话,那是不是得访问它的网名,网络访问路径是这样的,你不能写类名,但这个类还没写网名,那我预期一下,我预计它的网络访问路径,叫/reg,那我要用/reg这个网名来访问RegServlet这个对象,那我得怎么写呢,按理说应该这样写,action="http://localhost:8080/servlet2/reg",按理说你得这么写,这太麻烦了,太长了,所以一般我们是不会这样写的,就这种写法,这叫完整路径。

  • 那关于这个action中的这个目标的访问路径,我们需要加以解释,第一个是完整路径,http://ip:port/project-name/network-name,那完整路径就是从头,从http开始写,一直写到最后,就是非常完整的把这个网址写出来就可以了,这太麻烦了,一般我们不会这样写;那么还有第二种方式,第二种方式叫做绝对路径,那绝对路径,它的格式是固定的,格式:/项目名/网名,那么绝对路径就是我们以斜线开头,然后写项目名,再写斜线,网名,这是固定的要求,就这样写。举例子啊,比如说当前案例,绝对路径的例子,我们可以写成这样,/servlet2/reg,那么在本案例中,我们发现,绝对路径已经比较简单了,已经挺简单了,但其实呢,还有种方式比它更简单,叫什么呢,相对路径,这个相对路径没有什么固定的格式,相对路径写出来,就是当前我们正访问的,它是什么路径,我们将要访问的目标,又是什么路径,两者求一个相对关系,所以这个,没有一个固定的格式,得看情况。那么举个例子吧,就是说,当前是什么,当前指的就是当前这个网页,那当前这个网页的访问路径是什么呢,是http://local:8080/servlet2/reg.html,这是当前的网页路径,从项目名开始写,当前就是,/servlet2/reg.html,这是我们当前正在访问的路径,那我们要以,在此基础上访问谁呢,访问目标,我们将要访问的目标是,http://localhost:8080/servlet2/reg,简写其中一部分,目标是它,/servlet2/reg,那么访问的话,得看它的关系,如果是平级,直接写目标的名字,如果是下级,下级目录名字,上级,点点杠,规则是这样的,那当前情况下显然是平级,所以,这个相对路径是多少呢,就是reg

  • 然后要注意的是这个完整路径,以http开头,当然了,有的时候,你不写浏览器会自动补充上,补全。然后,这个绝对路径,要以斜线开头,以斜线开头就是绝对路径,那相对路径是没有斜线的,所以,要区分好情况,看开头,那这个就用相对路径,尽量去用相对路径,它简单,所以action里面些什么呢,就写个reg就可以了。这是表单配置的第一个方面,在表单元素上声明提交的目标,这还不够,除此以外,我们还需要配置这项内容,我们需要给每一个控件,加name,这个name就是数据的名字,必须得有name,首先是账号,name="username",这个数据的名字,由你来取,叫什么都行,通常,我们可能账号的名字更习惯于叫username,然后密码,叫什么都行,我叫pwd,password缩写,然后单选,单选已经有name,就叫sex,因为单选必须得有name,哪怕不提交,也得有name,为了互斥,它必须有,那么name对单选来说有两层含义,那第一层含义,是为了互斥,第二层含义,它也是数据的名,就叫sex;然后呢,多选,也得有name,那注意,多选是一组选项,同一组的内容,name相同,同一个name,name等于兴趣,兴趣一般就叫interest,这3个多选同名,都一样。

  • 那这些个控件,name都有了,这是第二部分内容。还有,关于表单的第3项配置,是针对这个单选,多选而言的,那么单选,多选,我们需要加value,那value是我们发送数据时候的那个具体的值,如果你没有value,它发送的是一种状态,是个on,是个单词 ,不是我们想要的,区分不了,那回到reg.html这个网页上,给单选,多选加value,那性别男女,value等于什么呢 ,这个value等于什么都行,但是呢,这两个value不能重复,因为两个数据,值得不同,能够区分开来,通常呢,是我们数据库里这个表里,想存什么,我们这value就写什么,比如说,数据库的这个字段,我想存M,F,这个value就等于M,F,我想存0,1,这就写0,1,我想存什么boy,girl,就存boy,girl,这就写boy,girl,反正,数据想存什么,你就往value里写什么,这里简单点,我们就写个M和F吧,咱们这个行别一般存的话,都一般习惯于存M,F,一看就懂,M就是male,男性,F,就是female,女性,然后呢,还有兴趣,这有3个兴趣,3个value,美食,value我叫food,竞技,value等于game,社交,value等于friend,就大概这个意思。

  • 那表单就配置完了,表单要求要记住,那表单呢要配,表单的数据,要提交给Servlet,我们还需要写这个Servlet,要写Servlet首先要建个包,我们在src/main/java的下面创建个包,包名呢,我习惯于叫web,web之下,我们创建这个Servlet,这个类名叫RegServlet,啊,要继承于谁呢,继承于谁,这得点Browser…,选谁,搜索,父类叫HttpServlet,你搜索的到这个父类,OK,反正你手敲也行,但手敲麻烦,还是搜比较方便,搜索到这个父类,然后呢,一确定,这就有了。然后完成,完成以后,就有了这个类RegServlet,继承于HttpServlet,那么,要想处理浏览器的请求,要想返回动态网页,我们得重写哪个方法来着,service,所以,在这重写父类的service()方法,我们可以通过右键选择,在代码空白处,右键/Source,然后呢,选Override重写,右键/Source/Override,在弹出框里啊,我们选择父类的倒数第二个方法,黄色的那个service(),然后呢,ok,就重写了黄色的service,service中,默认的这个代码没有用,super.service(arg0, arg1);,把它删掉,不要留着,留着会有问题,删掉,然后呢,这个方法有两个参数啊,一个叫request,一个叫response,参数名,我们可以改一下,我不习惯叫这个名字,先改一下,反正改叫啥都行,叫ab啊,mn啊,随便,我喜欢叫req和res,那这个方法呢,经过整理以后,那么,我们这个方法当前呢,是要处理,这个注册的请求,那其实呢,这个方法无论处理什么请求,它大概的流程是一样的,处理请求的一般流程,第一步就是啊,前面先提前写一下,比如说啊,处理请求的一般流程,1.接收参数,2.处理业务,3.发送响应,就通常都是这样,你别管是注册啊,登录啊,保存数据啊,等等,一般都是这样,当然我说的是一般流程,可能有的业务当中,没有什么参数,第一步可以省略,甚至呢,有的流程当中呢,连业务都没有,第二步也可以省略,但基本上呢,第3步应该是差不多会有,就总而言之呢,一般流程大概3步,有可能某些流程会省略某些步骤,不是说绝对是这样的。

  • 那第一步接收参数,本案例是有参数传过来的,有什么呢,账号密码,性别兴趣,那我要接收参数,我用哪个对象来接收呢,request,因为这个,接收的是浏览器发送来的请求,request才是请求的意思,它有能力处理请求的东西,一定不是response,response是响应了,那就接收吧,

    String name = req.getParameter(“username”);
    String pwd = req.getParameter(“pwd”);
    String sex = req.getParameter(“sex”);
    String[] interests = req.getParameterValues(“interest”);

    总之,我们从浏览器得到的,就是获取它的所有的数据都是字符串,那么,如果是一个数据,就是一个字符串,如果是一组数据,就是一个数组,字符串数组,如果有需要的话,可以自己转型,但我们得到的,都是字符串,然后呢,getParameter()getParameterValues里面呢,写的是数据的名字,就是那个控件的name的值,所以这个字符串,一定要和你的控件的name相对应,如果你控件name跟它不一致,肯定是得不到,有对应关系。

  • 注册案例,在这个服务器端,在这个RegServlet里,通过这个数据的名字得到了这个数据,即getParameter和getParameterValues,这里面的字符串参数和网页上,就是reg.html里,这个name是对应的,两者必须一致才能得到这个数据,那么如果说,我们得到这个数据是空的,应该是不对应;然后呢,得到这个数据以后,声明的变量保存这个数据,变量名无所谓,变量名叫什么都行,这都没关系。那得到这个数据以后,如果是正式的这个注册的业务,我们应该把这个数据进行验证,看帐号是否已存在,这个数据对不对,如果没有问题的话,我们把数据存到数据库里,那当前,我们只是一个模拟,就只是,主要就是演示服务器怎么接收到这个参数,所以,现在得到以后;那第二项,处理业务,我们就简化 ,就把这数据输出一下,直接把这数据打印到控制台,把每一个数据输出看一下,那最后一个数据是个数组,所以就把它遍历一下,然后输出每一个值,那么输出以后啊,这个业务就算处理完了,这只是一个模拟,就相当于模拟这个业务,就是意思一下。

  • 还有第3步,就是你处理完业务以后,要给浏览器一个反馈,不然浏览器不知道,你到底怎么样了,它还等着呢,所以呢,再给它发送响应,那么,再这个正式的这个注册的业务中,我们要判断,比如说账号,它的这个存不存在,然后呢,我们保存这个数据,看看是成不成功,然后,根据这个结果,我们发送响应,提示用户说成功了,或者是失败了,等等,那目前的话呢,是模拟的业务,所以 这一步还没法动态的判断,我们就写死了,就直接呢,给浏览器输出一个ok,表示成功了。那我们向浏览器输出一个动态网页,动态网页之内,给它拼一个段落 ,段落中拼一句话ok,当然,这个可以拼任意的一句话,ok也好,error也好,拼什么都行,目前只是模拟,先就写死了。那我向浏览器呢,发送响应,主要是这样几句话,首先呢,写这几句话,res.setContentType("text/html");,这句话的意思,是告诉浏览器,我向你输出的是什么格式,数据的格式,那么,如果我们想输出一个网页,就必须得写text/html,必须这样写,两个单词不能颠倒,两个单词呢,不能写错字母,一定要写对了,然后呢,我们需要向浏览器输出东西,需要获取这个输出流,PrintWriter w = res.getWriter();,那按理来讲,应该给浏览器输出一个完整的网页,那很麻烦,这里简化一点,偷懒,只输出网页中的那个段落,那个核心的那个内容,那其他内容省略了。那这不止输出ok,因为输出ok呢,太死了,我们可以这样,ok,然后后面再加上什么呢,这个名字,比如张三注册了,我就输出ok,张三,李四注册了,输出ok,李四,这样就动态了,返回动态结果么。写上段落,把这个ok包含在段落里,w.println("<p>ok,"+name+"</p>");,那输出完以后,别忘了关闭这个流close,w.close();,到这,这个案例就完成了。

  • 那么通过这个案例,可以说明两个内容,第一点,Servlet怎么开发,第二点,怎么传参,这个Servlet就写完了,当然想访问Servlet怎么办呢,还得配置,Servlet需要配置,不然的话,直接访问不了,打开这个servlet2这个项目下的配置文件,web.xml,配置这个RegServlet,配置时需要注意3个地方,完整的包名加类名,对应的别名要一致,网名必须以斜线开头,可以与别名相同,也可以不同,这里,我是把它弄成相同的了。

完整代码
RegServlet
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>注册</title>
</head>
<body>
	<!-- 
		1.完整路径
			http://ip:port/项目名/网名
		2.绝对路径
			格式:/项目名/网名
			举例:/servlet2/reg
		3.相对路径
			当前:/servlet2/reg.html
			目标:/servlet2/reg
			相对路径:reg
	 -->
	<!-- form action="http://localhost:8080/servlet2/reg"-->
	<form action="reg" method="post">
		<p>
			账号:<input type="text"  name="username"/>
		</p>
		<p>
			密码:<input type="password"  name="pwd"/>
		</p>
		<p>
			性别:
			<input type="radio" name="sex"  value="M"/>男
			<input type="radio" name="sex"  value="F"/>女
		</p>
		<p>
			兴趣:
			<input type="checkbox"  name="interest"  value="food"/>美食
			<input type="checkbox"  name="interest"  value="game"/>竞技
			<input type="checkbox"  name="interest"  value="friend"/>社交
		</p>
		<p>
			<input type="submit"  value="注册"/>
		</p>
	</form>
</body>
</html>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
  <display-name>servlet2</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  
  <servlet>
  	<servlet-name>reg</servlet-name>
  	<servlet-class>web.RegServlet</servlet-class>
  </servlet>
  <servlet-mapping>
  	<servlet-name>reg</servlet-name>
  	<url-pattern>/reg</url-pattern>
  </servlet-mapping>
</web-app>
测试
  • 那么写完以后,可以测试,把这个项目重新部署一下,部署完以后,打开那个注册网页,可以填数据,点注册,看一下,能不能注册成功,控制台能不能输出相关的内容。测试如下:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

3.请求方式

  • 这个注册案例,主要演示的是浏览器怎么向服务器传参数,服务器怎么接收参数,那么服务器从浏览器接收到的是请求的内容,所以,用request来接收,来获取,那么我们getParameter,或者是getParameterValues这样的方法,那我们得到的数据不假,那这个数据它是从哪得到的呢,请求的数据当中,有3部分,请求数据有请求行,消息头,实体内容,那我们得到的这个参数,它是属于哪一块呢,显然是实体内容,因为取请求行,消息头的时候,是没有看到这样的东西的,所以,这些这个数据,就是从实体内容取的值,案例当中也没有获取请求行和消息头的数据,用不上,一般的项目中 ,都是从实体内容中先得到它的参数,然后,加以处理,而那个请求行,消息头是自带的参数,是服务器要用的,我们一般用不上,但是,你想用也可以,一般用不上;响应时,那么响应数据也是3部分,状态行,消息头,实体内容,那么状态行,包括什么状态码,状态值,还有这个协议,这些内容是由服务器自动填写的,我们没有填写,没有set,没有写,那么因为我们当前这个对象是由服务器调用的,那这个对象是否调用成功了,它报没报异常,服务器知道,说白了,服务器在调用这个Servlet时,它也try-catch,有异常它立刻就知道了 ,所以呢,它根据这个是否抛异常,给你返回一个状态码,总之呢,第一个内容,就是状态行由服务器自动填写的。
  • 那这个响应数据包,响应数据,它的第二部分是消息头,消息头包括很多内容,包括什么,这个内容的长度,服务器的版本,等等,基本上我都没有设置,我们只设置其中一个,就这句话,res.setContentType("text/html");,那么,这句话就是设置消息头,只是设置其中一个值,格式,然后呢,响应时,服务器给浏览器发送的结果,叫响应数据,这个数据由我们来写,我们每一句print,就是输出响应数据的一部分,当然,我们这个案例很简单,我么只输出了一句话而已;总之,通过这个案例就了解了怎么去传参,从而也熟悉了http协议所规定的这个请求和响应的数据的数据的格式,可以对应到案例中看一下每一部,是用哪句话处理的。
  • 以上是从HTTP的角度去理解Servlet,那下一个话题是请求方式,请求方式是请求数据中,请求行的的内容,用request可以接收请求数据,那么接收到的请求行数据有3个,其中有一个是请求方式,我们通过req.getMethod()方法,得到的就是请求方式,那请求方式是什么,它有什么用,在这里介绍一下,归纳一下,概括一下,第3个话题请求方式,那什么是请求方式,所谓的请求方式就是浏览器向服务器发送请求时,传递数据的方式,那么浏览器向服务器传递数据,其实有好几种方式,每一种方式有所区别,那它这个发送数据的方法,方案,我们就叫请求方式,那请求方式有几种呢,可不只是两种,请求方式有很多种,有GET,有POST,有HEAD,有PUT,有DELETE,等等,不过呢,咱们通常只用两种,通常只用GET和POST,那么这两种请求方式能够解决一切问题,其他的可以不用,事实上是这样的,在HTTP1.0的时候,只有GET和POST,没有别的,后来1.1中有了其他的,但是呢,1.0的时候,这两种请求方式能解决一切问题,虽然说1.1加上了,除GET(请求指定的资源)和POST(向指定的资源提交需要处理的数据)之外的,HEAD(要求响应与相应的GET一样,但没有响应体),PUT(上传指定资源),DELETE(删除指定资源),等这些方式,但是呢,它们没有什么颠覆,只是一个,怎么说呢,一个补充而已,用不用都行,那么程序员呢,一旦养成习惯了,就都用GET,POST,习惯了,不喜欢用其他的了,因为你学其他的,还需要成本,反正这两都是万能的,都能解决问题,所以,我们通常就掌握GET和POST这两个就可以了,那么归纳而言,什么是请求方式,就是发送数据的方式, 第二个,常用请求方式有哪些,主要有这个GET,还有POST。
  • 那么GET和POST,这两个请求有什么区别呢,那第3个话题是,这个GET和POST的区别(常见的面试题FAQ)。先说这个GET,再说POST,在那个注册案例说,我们得到的数据,是从实体内容中得到的,其实并不准确,那么不同的请求方式当中,我们得到的数据,它存放的位置其实不太一样,所以,这里再纠正一下,因为之前默认忽略了请求方式,就大概笼统说,我们得到的业务数据,这是从实体内容当中取的,一般是这样,但是呢,其实有特殊情况,那GET请求,它的特点是什么呢,这个参数可见,第一个特点,参数可见。从注册案例来看,我在注册网页上输入数据,当我点注册时,其实呢,这个点注册,向服务器发送的请求,它就是GET请求,为啥说它是GET请求呢,默认一切请求 都是GET请求,所以注册是GET请求这种方式,那这种方式,我们传的参是可见的:
    在这里插入图片描述
    在注册页面点注册时,点完以后,我们访问的路径是/reg,http://localhost:8080/servlet2/reg,路径就变了,变成了http://localhost:8080/servlet2/reg?username=zhangsan&pwd=123&sex=M&interset=food&interest=game,有很多参数,用户号,密码等这些参数,在地址栏都能看见,这就是GET请求的特点,GET请求,我们传的参数可见。
  • 那再归纳一下就是说,GET请求在请求过程中传递的参数可见, 那么参数可见,这是好事,还是坏事呢,坏事,为啥是坏事,因为那个密码,不在那摆着么,账号也在那摆着,如果你后面站个人,哎,你就可想而知了,没了是吧,容易被盗,所以呢,请求过程中,传递的参数可见,那么它就有一个明显的缺点,就是说,隐私性差,不能保护隐私。这是它的第一个特点,也是第一个缺点,隐私性差。还有第2个,那么为什么参数在传递过程中可见呢,其实,因为什么呢,它采用路径传参,就是GET请求,它不用实体内容传参,它是用请求路径,或者说那个ServletPath来传参,它很懒,它直接把参数呢,附加在那个路径上传过去,就啥意思呢,浏览器访问服务器的话,那浏览器上肯定得写一个网址,比如,http://localhost:8080/servlet2/reg,我想访问的是它,本来这样就可以了,但是呢,我们如果传参的话,它就利用这个路径传参,它把路径又拼了一部分内容,又拼了什么呢,比如说username,等于啥啥啥,然后and pwd等于啥啥啥,http://localhost:8080/servlet2/reg?username=xxx&pwd=xxx,就是说在传参时,它直接就把参数就拼到了路径的后面,那其实我们要访问到Tomcat那个项目 ,http://localhost:8080/servlet2/reg,这些就够了,但我们写多了,写多了,那Tomcat既然能够读取,能够识别,http://localhost:8080/servlet2/reg,这些内容,它能不能进而识别后面的参数呢,显然也能,因为Tomcat既然能解析,http://localhost:8080/servlet2/reg这一部分,自然也能解析后面?username=xxx&pwd=xxx,这一部分,也可以解析,所以它也能得到这个参数,这是一种比较懒的方式,就直接用路径传参,省事倒是。就是说采用路径传参,参数在请求过程中可见,就是参数在传递过程中可见,所以呢,它的特点是隐私性差,这是第一个特点,就再重新归纳一下,就算为一点吧。
  • 那么还有第二点,正是因为呢,它采用路径传参,那你想啊,这个路径,它可能无限的长么,这个路径不可能无限长,因为地址栏空间是有限的,不让你无限的长度,所以 它传的参数大小受限,就是GET请求的特点是采用路径传参,然后呢,参数在传递过程中可见,隐私性差。另外一点,那么路径的大小有限制,所以传递的参数大小受限,你不能传无限多个参数,大概是多少,通常呢是2k,一般浏览器限制,这个地址栏那个路径顶多是2k,再多不行了,不让了,它有规定。所以呢,GET请求有两个缺点,隐私性差,参数大小受限,然后呢,那什么样的请求是GET请求呢,归纳一下,所有的请求,默认都是GET请求;比如说,我在地址栏,敲个地址,访问服务器 ,这就是GET请求,我点个按钮,点注册是GET请求,就默认,一切请求 都是GET,是有缺点的。
  • 那么与之相对的是POST请求 ,那POST请求就,把它那个缺点呢,规避了,或者说解决了,那POST请求的特点,POST请求,它不是采用路径传参,它是采用实体内容传参,实体内容是什么呢,请求数据有3部分,请求行,消息头,实体内容,它是采用这个请求数据当中的实体内容传参,那么实体内容是专门用来传递一些数据的,所以呢,它的大小不受限,再一个呢,这个内容因为它不是,参数没有加到路径上,传递过程中,看不到,就是参数在传递过程中不可见,隐私性好,然后再有,实体内容专门用来传参,大小不受限制。那么什么样的请求是POST呢,我们在表单上加上一句话method,method是请求方式, 因为你像getMethod是获取请求方式,所以请求方式,它是用method这个单词来表达,method=“post”,如果我们在表单上写上这句话,那么这次的请求就是POST请求,是这样的,那既然这两者有这样的一个特点,那什么的情况用GET,什么请求下用POST,从隐私性角度考虑,我们什么样的东西暴露了才不安全呢,是密码,账号到没关系,因为你在这个,这个框里输入账号时,别人能看见么,也能看见,所以它本来就是暴露的,密码是不允许暴露的,所以说,我们应该保护密码的隐私,那我们就是提交的数据当中,如果有密码就一定得用POST,就绝不能用GET的,是这一点,从隐私性来讲,只要你数据当中包含了密码,这样的比较隐私性的数据,你就必须用POST,不要管数据多与少,哪怕只有一个数据,你也要保证隐私性,这是第一点;第二点,从大小上来看,如果我们传的数据多的话,你用什么呢,POST,所以,总之就是说,如果传的的数据多,要用POST,如果需要保护隐私,就用POST,其他的可以用GET,是这样的。归纳一下就是,建议,参数需要保密时用POST,比如带有密码时,那么参数较多时,用POST,其他的用GET就可以了。
  • 那么大概介绍完GET和POST的区别以后,回到注册的案例中去看 一下GET和POST的两者区别,通过浏览器是能看到它们的区别的,好,打开浏览器,然后回到这个注册的页面,那当前点注册按钮,是GET请求,那怎么能看出来是不是GET呢,看路径当然可以看,那路径里有参,就一定是GET么,这样确认,按一下F12,打开F12那个插件,F12顶上有一大排的那个选项,其中Elements,它能调试网页结构;Console控制台,能够调试JS,那Network里头,在每次请求时,都出现了一行东西,一行描述,这就是一个请求,在Network里,我们能够观察到,我们发的请求的详细内容,可以点进去细看,比如案例中的注册请求:
    在这里插入图片描述
  • 点进去以后,它有几个内容,一个是Headers,一个是Preview,一个是Response,Response呢,是响应,我们响应的内容是个段落,<p>ok,zhangsan</p>,这是我们看到响应的内容,就是这个段落的这句话;Headers,是其他的一些内容:
    在这里插入图片描述
  • 首先,它的General当中有一句话,叫Request Method: GET,这句话就证明了这个请求方式 为GET,这是实实在在的,然后呢,看状态码,Status Code:200 OK,响应状态码,还有呢,请求的路径,Request URL:http://localhost:8080/servlet2/reg?username=zhangsan&pwd=123&sex=M&interset=food&interest=game,路径后面带了参数,等等,这都能看到,包括响应的消息头,也能看到,什么Content-Length: 20,Content-Type是什么什么,Response Headers,这是响应消息头,然后,在Response Headers里面的Content-Type,这个Content-Type是我用set设置的,我写了res.setContentType("text/html");,是这样写的,但是写了后半句话么,charset=ISO-8859-1,没有,后半句是服务器自做主张加上去的,我们如果没有声明编码的话,它给我们加了一个编码,它默认认为是charset=ISO-8859-1,服务器默认编码是这个,所以有中文的话,它就一定会乱码,乱码的根源在于此。所以,我们通过F12这个插件,我们看这个请求的过程,有的时候能看出一些问题来,有的地方不正常,能看出来,还包括什么,日期啊,服务器的版本啊,等等内容。
  • 后面还有请求的消息头,Request Headers
    在这里插入图片描述
    其实呢,这些内容,在服务器端,也可以通过request输出获取,见Servlet处理HTTP的代码实现TimeServlet,也可以看到,但我们在浏览器上看呢,其实更方便,总之两种途径,我们都可以看到这些内容,在浏览器这里看更方便,那在开发的时候,如果说遇到什么问题,有问题怎么办呢,多看看浏览器F12插件这个地方,因为有可能有些数据不正常,会解决你的问题。然后,还有最后一个地方,最后一个选项,它叫Query String Parameters
    在这里插入图片描述
    这里面就是得到的具体参数,叫Query String Parameters。这是GET请求,是这样。
  • 那下面,我们再把这个请求改为POST,然后再观察一遍,做一个对比,看有没有区别,那什么样的请求才是POST呢,回到那个注册的网页,reg.html那个代码当中去,我想把这个表单提交呢,改为POST,怎么办呢,在form上加一个属性叫method,它默认值是GET提交,那把它改为post,method="post"
    在这里插入图片描述
  • 改完代码以后保存,编译,重新部署,如果Tomcat没关,它会自动的重新部署,当然这个部署的时机,你把握不了,它可能有点时候慢,有点时候快,所以为了保险起见呢,把它关掉重新部署,手动部署重启一遍,可能会更好一点。那么当把这个请求方式改为post以后,再打开浏览器注册页面上,退回刷新,因为这个网页改了,最好多刷几遍,因为有的时候有缓存,刷新完之后,填上账号密码,点注册,然后,看一下这个Network里输出的内容,点完以后,Network里有了这个请求:
    在这里插入图片描述点一下这个请求,去看有变化么,这回的请求方式就为POST,请求路径没有参数:
    在这里插入图片描述
    那参数存在哪去了呢,往后找,当然这个消息头,这些内容都差不多,都一样,那最后有一个叫Form Data
    在这里插入图片描述这就是实体内容,之前get请求,这个地方也有一个名字,那不叫Form Data,叫Query String Parameters,那个不是实体内容,这个Form Data,这才是实体内容。所以,POST请求采用实体内容传的参,传递过程中看不到,隐私性好,再一个呢,这个地方能存任何多个数据,没有限制,大小不受限。

4.Servlet的运行原理

  • 上述对请求方式,以及GET和POST的区别做了介绍,并通过注册案例进行了对应的演示,那这两种请求,它能解决一切问题,默认请求都是GET,当我们数据需要保密,或数据大的时候,就用POST,就可以了,其他的不用也行,但是现在呢,还有一个问题要解决,就是我们在提交数据的时候,如果说你账号是中文,会有乱码,我们返回响应时,如果说你输出的不是OK,输汉字你好,会有乱码,那总之,目前浏览器服务器之间交换数据,有中文会乱码,那这个东西也需要解决,但是在解决这个问题之前还得归纳一下,通过这个注册案例再归纳一下,请求执行的完整过程,或者说,Servlet运行的根本原理,我们理解这个原理之后,在这个原理基础上,再去理解,为什么会有乱码,怎么解决就好了,所以直接去讲,乱码解决方案,这个不好理解,它不是那么容易理解。
  • 所以下面的内容,就是Servlet运行过程,或者说它的运行原理,在这个原理基础上,再去探讨乱码的解决方案。这个Servlet运行步骤用文字来表述,不是很直观,所以画个图,把这件事说清楚,这个Servlet运行的过程,那么Servlet,它是整个web项目中的一个环节,它是服务器端最主要的一个组件,一个对象,那我们想探讨它的这个运行原理,我得把浏览器画出来,那当然了,我还得把服务器也画出来,那这两者之间的交互,我们要说清楚,先把它俩画出来,那浏览器和服务器之间的交互的方式,或者说通信的方式,大概是4步,哪4步呢,建立连接,浏览器和服务器之间建立连接,发送请求,返回响应,关闭连接,大概是这样,但是呢,详细来说呢,要比这细的多了,我们说的是详细的过程。
  • 那么浏览器和服务器之间呢,通信,它怎通信的,它靠谁通信呢,靠的是一个组件,组件是什么,是满足规范的对象,那么浏览器也好,服务器也罢,它都是软件,这个软件也是,采用面向对象的方式去开发的,那么浏览器端有一个对象,这个对象呢,专门用来通信的,用来和服务器交互的,我画一个方块代表这个,专门用来通信的对象,那这个对象呢,它是谁,那这个对象的名字叫什么,你就不用管了,为什么呢,因为不同的浏览器有区别,但底层的实现的方式是一样的,那我们就笼统的给它命名,笼统的管它叫什么呢,就叫通信组件,因为这个组件,或者说这个对象,它是专门负责和服务器进行通信的,我们给它命个名叫通信组件。那浏览器端,浏览器是个软件,它里面有这么一个组件,这么一个对象,负责和服务器通信,服务器,也有这么一个组件,这么一个对象,负责和浏览器通信,就浏览器和服务器,就是这两端各有一个对象负责呢,彼此之间的联系,负责通信,两个对象,组件。那么通信的完整过程是这样的,我们浏览器能访问服务器,往往是我们要点个什么东西,点个按钮,点个超链接,回车一下,你总得点点啥,你不可能平白无故就访问服务器,那像前述的注册案例,我们点了个按钮。比如说,我在浏览器上点了个按钮,触发了对服务器的访问,那么按钮会通知这个浏览器的通信组件,说我要访问服务器了,底层是怎么干的,那么浏览器的通信组件,就要访问服务器,它怎么访问呢,基于以下的步骤。
  • 第一步,建立连接,那浏览器要访问服务器,是不是得先和服务器连接起来啊,这是第一步建立连接,标一下,那就连接吧,建立连接以后,第二步,那浏览器建立连接之后,是要向服务器发送数据,发送请求,那发送数据的话,请求的数据包分为3部分,请求行,消息头,实体内容,那么这个请求数据这3块,由浏览器来组织,由它来设置,很多内容它都写好了,它都组装好了 ,所以,这个连接之后下一步,是浏览器呢,对数据进行组装,或者说准备数据,那这个过程呢,我们有个名字,叫做打包,打包数据,就是浏览器向服务器 发送的数据,它不是一个,是一堆,是一包,所以,第二步呢,是打包数据,一下,整体直接发送给服务器,这是第二步,打包数据。那浏览器呢,把数据打包以后,有了请求行,消息头,实体内容,它一下,把整个一包数据发送给服务器,发送给服务器的谁呢,通信组件,因为这两者负责通信,数据给通信组件,所以第3步,是浏览器把数据发送给服务器,第3步呢是发送具体的数据。
  • 然后呢,数据发送给服务器以后,服务器的通信组件可以接收到这个数据,那么接收到数据以后,它需要呢,做出一些处理,那服务器怎么处理数据呢,你给的我的是一包数据,我要读取的话,是不是得拆开啊,这个事就像我们就像你给对方,发一个邮件,快递一样,我是把我要发的东西打包好,发送给对方,对方得拆开才能看,其实差不多啊,一个意思。所以,服务器端呢,它要做一件事,浏览器这面打包,服务器这面就拆包,拆包得到具体数据,当然了,我们所讲的这些规则,这些步骤啊,它都是由这个w3c规定的,其实w3c规定的,大概是4步,但详细来说呢,是这么多,更详细的步骤,那第4步呢,是拆包数据,把数据拆开,拆开以后呢,服务器得到了所有数据,那么对于数据怎么处理呢,这个我们发送给服务器的数据,服务器,它也不会处理,它得给我们来处理,它要给我们,给谁呢,给Servlet,因为我们写Servlet,它把数据给Servlet处理,怎么给呢,是这样的,那服务器呢,在接收,在拆完包以后,它会把数据呢,存到哪里去呢,存到request里,就是你想啊,我们最终是不是通过request,得到这个数据的,这说明肯定在此之前,服务器悄悄的,把数据存进去了,肯定是这样,对吧,不然凭啥能,从这里得到数据呢,所以在此之前,有这么一步。所以,下一步呢,是服务器,它自动的帮我们创建出来了,request和response,就是下一步是服务器帮我们去实例化,request和response, 实例化就是new,这没什么好说的,下一步是new,就是服务器,它得到数据以后,它赶紧,帮我们new,request和response,new出来,那new完request和response以后,这是第5步。
  • 那么,它会把这个数据,就是存到request里面去,我们才能够从这里得到数据,所以第6步,是把数据存入request,是把这个数据啊,存入request,这是第6步,那这是服务器的一些准备工作,它拆包数据,创建request和response,然后呢,把数据存入request,存入以后,它也不管这数据怎么处理,它会把request和response给我,那怎么给我呢,是把request和response给这个,我们所写的Servlet,所以下一步呢,是服务器,它把创建好的request和response,传给Servlet,并调用它,那在调用它之前,这Servlet是不是得被实例化呢,所以下一步,第7步,实例化new,实例化完调用,调用时传入 request和response,所以下一步,第7步,是new Servlet,然后呢,第8步是,调用Servlet,调用时,它会传入之前,创建好的request和 response。然后呢,这Servlet我们写的,我们在这里呢,就是,写了一些输出的语句,用来向浏览器输出一些内容,输出响应的内容,所以第9步,是这个对象执行了,执行了输出语句,所以第9步,就输出吧,输出语句是我们所写的。
  • 然后呢,我们输出的内容,由谁输出给浏览器呢,也是由通信组件啊,就是我写了一封信,我要求这封信给浏览器,谁给呢,通信组件,我们只是提供了内容而已,所以通信组件,再把这个我输出的内容,发送给浏览器,怎么发送呢,当初浏览器怎么给它发数据,它就怎么把数据还给浏览器,再还回去,还是一个相反的过程,那过程是这样的,服务器要对数据怎么样呢,打包,它要对数据呢,打包,为啥要打包呢,因为响应数据也是3块,得保证那个结构,打完包以后啊,数据呢,要发送给浏览器,浏览器呢,拆包,完全相反的过程,然后呢,发送,就这样,具体标一下,第9步是我们,我们的代码println调用了,输出了,我们输出了提供了内容,这个内容被通信组件输出给浏览器,打包,打包以后呢,发送给浏览器,然后呢,浏览器得到数据以后,会拆包,就得到具体内容,然后呢,最终,处理完以后,连接怎么样呢,关闭,这标一下。
  • 第10步,这个是打包,第12步,这个是拆包,然后呢,第11步,这个是发送,打包以后要发送,第13步是什么呢,关闭,完成以后,最终连接呢,关掉,整个事就结束了。那么我们访问服务器,由Servlet处理请求,它一个完整的流程,就是这样,那么w3c呢,大概规定,大概的步骤是4步,但详细来说有10几步,当然我这画的还不够细,其实,还可以再细,但是我们没有必要搞的那么细了,你再细的话,就得去看它的详细的文档说明了,就到这种程度就可以了。总之呢,这个图形,就能解释,我们访问服务器时,那么浏览器和服务器之间,它到底干了什么事,我们又干了什么事,是这么复杂的一个过程。那么,在这么复杂的一套流程里,很多事都是由服务器,浏览器自己完成的,你像,什么建立连接,打包数据,发送数据,拆包数据,创建request和response,存数据,前6步,我们没有参与吧,不用我们做,后面呢,打包数据,发送响应,拆包数据,关闭连接,这几步,也是由浏览器,服务器完成的,我们也不用参与,那么在这么复杂的一个环节之内,我们需要做的是什么呢,我们需要干什么呢,其实我们我们什么都不用干,我们只需要写出Servlet这个类来,连这个类的实例化,也不需要我们,我们只需要写出Servlet这个类就可以了。所以我们需要做的,仅仅是写这个类,这个类在整个13步当中,它处于这样的一个环节,就这样。
  • 那通过这个案例呢,你也能理解,就为什么我们,这个Servlet,我们不自己去调用,因为tomcat帮我们去调用了。那这个类,这个方法中的参数从哪来,由tomcat传入,那这个参数,它怎么传入呢,它事先先new,new好以后,存入数据,传入,这个关键的环节,就串起来了,尽量去体会吧。然后呢,整个13步,整个流程,它是基于一个原则,基于一个规范,这个规范叫什么呢,http协议。整个13步,这个流程,这个http协议,是w3c所规定的,不是我们所发明的,是人家规定的,这是核心,是基础。那这个图呢,就是这个Servlet的运行原理。
  • 再概括两句,加深体会,就是说浏览器和服务器,这两者都是软件,软件内部是由对象构成的,那么它们之间的通信,由通信组件,或者说通信对象来负责完成,所以,这两个软件里,都有一个对象,我们称之为通信组件,负责彼此的通信,那么通信的方式,有连接,打包数据,发送,拆包,基本就是这样,反过来就是,打包,发送,拆包,关闭,就这个,是对称的,它们的这个发送数据的方式,完全是一样的其实,只不过呢,你输入数据发送给服务器以后,服务器需要处理的,怎么处理呢,服务器要把数据封装到对象里,然后给我们,它封装的对象呢,是request,当然还有一个response,response呢,不是用来存数据,是给我们的一个工具,这个工具,我们输出时要用,提前给我们准备好了,这都是人家做的,还有,那么我们向浏览器,输出的往往是网页,或图片,浏览器得到网页和图片以后,会直接显示,那这个细节我们就不用管了,反正浏览器会加载网页,显示出内容,这个人家都做好了,也不用我们做任何处理啊。所以呢,整体来说,是这么复杂的流程啊,在这么多的环节之内,我们只需要处理,Servlet这么一点点。
  • 那么,如果你想知道细节,这个通信组件到底是怎么连接的,怎么打包的,怎么拆包的,怎么发送的,如果你想知道细节,那只能去看它的源码了,但是呢,这些框架啊,这些底层的技术源码,是非常的复杂的,它想做一件事,可不像我们简单,new个对象,就new,它考虑的特别多,所以你会发现,我们看任何一个类,它都会引用其他的几十个类,那你要是不会看的话,你看这个类引用几十个类,我得把那几十个类看明白,才能看这个类,那好你去看吧,你看其中任何一个类,它又引入几十个类,海量的,你说你怎么看,无从看起。所以说呢,把这个大概的思路了解就可以了。那么后面的很多内容,在这个体系内,在这个思路内,好解释,把这个思路理解了,有些事就很好理解,到这就可以了。
    在这里插入图片描述
  • 这是第4大内容,是Servlet运行原理,或者说运行的过程,尽量去理解。那现在请求方式知道了,在这个注册案例中呢,也体会了,也看了,然后Servlet运行原理也知道了,那下面,就可以用这个原理,去解释一些事情,解释什么呢,解释一下我们在GET请求当中,还有POST请求当中,它出现乱码,这乱码怎么解决。

5.乱码问题

回顾

  • 下一个话题,乱码解决方案,仅仅通过文字描述,并也不好记忆,如能理解透彻它背后的原理,就好记了。那么要想理解这个事,还是想画个图,就画前述那个注册案例的那个图,Servlet的原理图,那是个泛泛的原理,现在画的是,注册的时候, 我点注册按钮那一刻,发出的请求,在这个过程中出现乱码,怎么解决,是这个原理图。那么,注册功能,它也是web项目中的一个功能,也是通过浏览器访问的服务器,所以我先画个浏览器,然后呢,我再画一个服务器,那么,浏览器和服务器之间的通信,它是依赖于通信组件,就随便画一个红色方块,代表通信组件,当然,浏览器上有通信组件,服务器端也有,浏览器和服务器端呢,各有一个通信组件,然后呢,彼此之间呢,负责这个通信。
  • 那么,首先呢,在注册的时候,是点了个注册按钮,才发起注册的,点按钮以后,然后呢,浏览器才开始访问服务器,把我写的注册的数据发送给服务器,就是从这开始说起,那浏览器还是,首先建立连接,然后呢,打包数据,打包数据以后,发送给服务器,服务器拆包,把这个标出来就,第一步,建立连接,第二步,打包数据,第3步,发送这个数据,第4步,拆包,大概是这样,这个步骤,我也不标了,这序号一会要给别的地方用,反正就是这个意思,就浏览器访问服务器,就这么几步,然后呢,这个服务器得到数据以后,它会把数据呢,存入request,刚才不是说了么,它会把数据存入request,它会实例化request,把数据存入request,最终它把request给谁了呢,给了Servlet,就画一个大概的步骤,不是那么细,然后呢,由Servlet向这个浏览器做出响应,然后浏览器才显示出了我们想要看的内容,当然,在响应的时候,也是啊,服务器需要对数据打包,然后呢,浏览器需要对数据拆包,这中间有发送的环节,发送完以后呢,会关闭,是这样一个步骤,大概是这样。

乱码原因

  • 那为什么说会有乱码呢,首先,哪个地方会有乱码,我们向服务器提交数据时,如果请求数据中有中文,会不会有乱码呢,会有的,试一下,打开之前这个注册页面刷新一下,比如说,账号填中文,张三,密码随便填,性别,兴趣随便勾选,只有账号,是中文,我点注册,一注册,数据发送给服务器:
    在这里插入图片描述我们看看,服务器收到的结果,是不是乱码,是乱码,ok,å¼ ä¸‰,所以,首先在这就会有乱码,就是浏览器发送 请求给服务器,服务器接收到的数据有可能就会乱码。那我们探讨一下,那这个乱码是怎么产生的,为什么会乱,因为数据要在网络上传输,浏览器访问服务器是通过网络访问的,我们目前访问的是localhost本机,但将来把这个项目做好以后,把它放到了互联网上,我就远程访问,是可以的,所以你是在网上访问服务器,那网上访问服务器,打包以后的数据以什么形式发送给服务器呢,二进制的数据,你是不能发送字符串的,我们发送的数据呢,都是byte,这个数据说白了,是要进行序列化,然后才能发送给服务器的,它发送的是byte,然后,我们在网页上得到的数据,打包的是字符串,得到的是字符串,把字符串转为byte,把byte发送给服务器,服务器拆包以后,给我们是不是还是字符串啊,它还会把这个byte再转为字符串,这里有个转换,浏览器打包这个地方,是把字符串转为了byte,而服务器拆包时又把byte还原为字符串,如果两端编码不一致,在转换时编码不一致,会不会乱码呢,一定会,所以 ,像这种情况,就是前面编码,后面解码,用相反的方式来转换的时候,编码不一致就会有乱码。
  • 那浏览器,它采用什么方式去编码的呢,这得看网页,看一下这个注册网页,一看就知道了,它这个网页是自动创建的,带有模板的这个网页,那它head标签里,不是有这句话么,<meta charset="UTF-8">,就这句话声明了,这个网页的内容是UTF-8,所以我点按钮时,它提交的数据就是按这个方式编码的,UTF8。浏览器采用的是UTF-8进行编码,那服务器是按照这个方式解码的么,显然不是,那服务器 是按照什么方式解码的呢,那注意,服务器啊,它的编码默认是ISO-8859-1,因为服务器是老外发明的,它默认,它不支持中文的,默认的编码为ISO-8859-1,简写标记iso,那浏览器用utf8这个方式编码,用iso这个方式解码,显然不对,就乱了,那么,我们想解决乱码怎么办,最核心的是把这个,两端的编码方式,改成它们一致,但是我们将浏览器端编码改为和服务器端解码一致,这个方式有3种方式,第一种方式,第一种方式是这样的,就是你乱就乱,我无视,但我想办法修复。

乱码解决方案

  • 第一种方式,我们就修复,怎么修复呢,当Servlet接收到了乱码以后,那么,我们想办法把这个乱码,再还原回去,怎么还原呢,就是将乱码后的String,乱码以后的字符串先还原为byte,你不是乱码了么,你是用iso把它变乱的,我再按照iso把它还原回原来的byte,可不可以呢,再把它退回到byte的状态,再把它还原为byte,还原为byte以后,然后呢,我再按照utf-8将byte,编为String,浏览器打包这是采用utf8编码,服务器拆包这按照iso解码,我们Servlet这里得到的是乱码以后的字符串,我把这个字符串原路退回,原路把它转换回去,我按照iso把它再还原回byte,我自己对byte编为,按照这个utf8编码编为String,它就解决了。所以这是修复,我们修复乱码以后的数据,这样去解决。那这种方式有什么优缺点,这个概况一下,它的优点是万能的 ,就是对GET或POST都有效,这是它的优点,因为你看这种方式,无论是什么请求方式,它都可以,反正你乱就乱,我就还原回去再编一下,这是都有效,但是也有缺点,缺点是什么呢,太麻烦了,如果说呢,我账号乱了,我要修复账号,密码乱了,要修复密码,任何数据乱了,都要修复一遍,你得逐个修复,如果说我们当前呢,提交的表单有100个数据,其中呢,有80个中文,那你得修复80个数据,总之很繁琐,很麻烦,这是第一种方式。第一种方式就是,我们修复得到的乱码数据,把它修正确了,但是太麻烦,我们一般不会这样用的,一般不用。

  • 那么,再说第二种方式。第二种方式呢,是配置,改配置文件,怎么改呢,我们修改Tomcat的配置文件,那个文件叫做什么呢,叫做server.xml,这个文件有印象么,我们可以在里面改什么呢,可以改Tomcat的访问端口,这里可以改端口,在哪一行呢,65行改端口,还是这一行,那么在大约65行加一句话,哪句话呢,这样写叫URIEncoding="utf-8",加上这样一句话,那注意,这句话中的URI是什么意思,URI是路径的意思,那总体来说,这句话的意思是,路径的编码为utf-8,所以,这个编码影响到的是路径,那我们传数据的时候,有中文的时候,如果用这种方式配了,它对哪种情况下的乱码会有效呢,GET,因GET才会用路径传参,它只对GET有效,是这样,概括一下,这是它的优点,就是优点是简单,因为你看就加一句配置,就算有100个数据,100个中文,都是这一段配置,就一段配置就搞定了,简单。那缺点呢,缺点是只对GET有效,它对POST没什么效果,因为POST不用路径传参,影响不到POST,那它是针对这个地方的编码,设置的改动,这是第二种方式。

  • 然后还有第3种方案,第3种方案呢,大概的思路是这样的,就是声明吧,第3种是声明,那么在获取参数之前,加上一句话,加上哪句话呢,这句话比较长,是req.setCharacterEncoding("utf-8");,写上这么一句话,那这句话是什么意思呢,是声明实体内容的编码为UTF-8,是声明实体内容编码,那它对谁有效呢,POST,所以,它的优点是什么呢,优点也是,还是比较简单的,因为就一句话,你不管有多少个这个数据,都是这一句话,你传了100个数据,我只要对这一个声明就搞定了,这还是比较简单的,那缺点呢,还是局限,它只对POST有效,所以一共是3种方案,那每一种方案都是有利有弊,这句话是在我们获取参数之前,我们在这里,Servlet从request获取参数之前,一开始的时候去声明,3种方式呢,各有利弊。

  • 那分析一下,我们平时开发时,我们怎么用,用哪一种,第3种,那你用第3种的话,如果是GET请求,你也解决不了啊,怎么办,用第一种,我刚才都说了,这太麻烦了,一般不会用,你还往上说。我们可以怎么办呢,2加3,结合在一起,如果是GET,我们把这个配置文件一改,GET请求都搞定对吧,然后呢POST请求,是不是写这句话 ,request.setCharacterEncoding("utf-8");,就搞定了呢,所以说,你把2和3结合在一起就可以了,第二种方式我们会用,那么第3种方式,咱们也会用。那第一种太麻烦,我们通常就不用了。那回到之前的注册案例中来,传中文时是乱码,通过这个案例把3种方式都演示一下,尽管说第一种方式用的少,也要知道怎么用,打开这个RegServlet.java,打开这个注册的Servlet类,演示一下怎么用第一种方式解决乱码问题,那接收到的这个name是乱码,在这个地方,处理业务的地方(接收name之前)修复,这是采用方案1解决乱码问题,

  • 那么方案一是什么,是修复,我得到的是乱码以后的字符串,我把它还原为byte,因为当时你使用iso编成为String才乱的,我就再用iso把它还原回去,采用同样的方式给它还原回去,byte[] bs = name.getBytes("iso8859-1");,声明它的编码是iso8859-1。这句话我得到的这个byte是乱码以前的byte,还原了,那下面我手动再把这个byte编为我想要的字符串,就可以了,那我们就自己new吧,new String,new的时候,把byte传入,然后呢,再指定我们的编码,我们就得到了这个新的字符串,name = new String(bs,"utf-8");,那总之,这种方式比较麻烦,name乱码这样解决,别的字段乱码也是这样解决,就很麻烦,但优点是万能的,不管是什么请求都没问题:

    //采用方案一解决乱码问题
    byte[] bs = name.getBytes(“iso8859-1”);
    name = new String(bs,“utf-8”);

  • 那写完以后,把这个项目重新部署,重启Tomcat,打开浏览器,退回到注册页面刷新,多刷几遍,然后呢,填中文张三,密码,性别兴趣,点注册,没用,这还乱了:
    在这里插入图片描述
    在这里插入图片描述

  • 那注意浏览器得到的是响应的内容,我当前是请求乱码,是一回事么,我们现在讲的是请求的过程,要看服务器得到的请求参数是不是乱的,不要看响应,还没到那个时候,所以不用着急,Eclipse的控制台,它输出的这个,获取到的请求的内容,有没有乱码,这已经不乱了,可以解决问题:
    在这里插入图片描述

  • 那么接下来,演示其他的方案,为了避免和第一种方案冲突,把第一种方案先注掉,这种方案了解就可以了,一般不用啊,注释掉。那第2种方案是修改配置文件,而这种方案只对GET请求有效,那把当前的请求改为GET,method=“get”,或者删了,默认为GET,它就用路径传参了。那改Tomcat的配置文件,Tomcat配置文件在哪改呢,可以打开tomcat那个config目录,能找到配置文件,或者在Servers项目下,改这个项目的配置文件也可以,这两个算是一套的,两者是一致的,那打开这个Servers项目里改更方便,打开以后,点开这个server.xml,然后呢,在这个文件里找到第65行,当前的这个版本是65行,如果换一个版本就未必,反正大概是65行,65行之上,可以改改端口,那现在再加一句话,叫URIEncoding,那URIEncoding是标签的一个属性,这个属性我是写到哪呢,写哪都行,因为元素的属性没有顺序要求,无所谓,写在前面吧,URIEncoding="utf-8"<Connector URIEncoding="utf-8"...>注意大小写。这个改完以后,需要把Tomcat关掉,重新部署项目,重启一下,因为改的是它配置文件,它得重新加载,重新读取,当然这个项目,因为改了代码也要重新部署一下,然后再打开这个注册页面,注意刷新,因为你改了代码,刷新,然后填数据,填完数据以后,点注册,点完以后不要看网页,看控制台,因为是请求乱码,和响应无关,也是可以的,这是第2种方案。那平时啊,我们就把URIEncoding="utf-8",加上就可以了,就别动了,反正所有GET请求就都解决了。

  • 还有第3种方案,我们再演示第3种方案,那么要演示第3种方案与第2种方案没有什么冲突,它们是互补的,没有冲突的,URIEncoding="utf-8"也不用删掉,保留。那么要演示第3种方案,得把那个网页上的,method,再改为post,因为它只对post有效,打开那个注册网页reg.html,把这个message,再改回post,那改回来以后,这回传中文就会乱码,怎么解决,我们是在获取请求参数之前加req.setCharacterEncoding("utf-8");这句话,一定是在getParameter之前加上,你要在get之后再加就晚了,这要注意顺序,那打开这个RegServlet,我们是在service方法一开头接收参数的,那我应该在哪去设置呢,是在接收参数之前,就是采用方案三解决post乱码问题。那那注意req.setCharacterEncoding("utf-8");,与res.setCharacterEncoding("utf-8");,这两句话的参数不同,一个是request,一个是response,request和response都有这个方法,一样的作用,所以一定不要写串了,我们写的是什么呢,请求乱码,request,这句话写完以后,还要重新部署项目,再去测试,然后呢,还是看控制台,也是可以的。

  • 那以上这3种方式,是解决请求过程中的乱码问题,和响应无关,所以目前,我们响应时还是有乱码的,还没有解决啊,那响应时的乱码,那怎么解决,那响应时的乱码产生的原因,其实和请求时是差不多,大同小异,响应时服务器给浏览器返回数据,它返回数据的时候,是不是也得用byte呢,很显然也得是byte,那既然是byte,那么服务器,它在这个打包时,要把数据呢,转成byte,浏览器拆包时要把byte再还原为String,又有一个还原的过程,那么这个现在我们响应的时候,浏览器上已经有乱码了,那乱码产生原因是什么呢,编码解码不一致,那服务器它会用什么方式编码呢,它还是万年不变的iso,服务器默认就只认这个东西,所以,服务器是按照iso-8859-1,进行了编码,不但如此,服务器呢,还非常的勤快,挺欠的,它呢在,向浏览器输出那个响应格式的时候,还声明了,我的编码是iso,你要按照iso去解码哟,在浏览器的network里头可以看到响应的消息头Response HeadersContent-Type: text/html;charset=ISO-8859-1,我们只写了前半句text/html,后半句ISO-8859-1是服务器自动加上去的,它告诉浏览器,我是这个编码,你要按照这个编码去解码,它告诉浏览器了,浏览器它很听话,浏览器呢,告诉它用什么解码,它就用什么解码,浏览器确实用iso解的码,结论是什么呢,其实浏览器和服务器的编码解码,在响应时是一致的,那一致的话,它凭什么就乱码了呢。

  • 我们给浏览器返回的,就拼了个段落,浏览器得到了什么呢,在network里能看到,就这句话,<p>ok,??</p>,我们没写全,写了一部分,浏览器得到的就是这东西,然后,这上面没有声明编码, 没有,那浏览器是按照什么方式解码呢,就是iso,服务器告诉它按这个解码,它很听话,是一致的,浏览器真的是用iso去解的码,没有一个别的过程了,没有了,而之前请求时的那个utf-8呢,这个utf-8,是在哪reg.html上,是那个静态网页上,而这个响应时的iso,是我们的动态网页,把之前的那个静态网页覆盖了,和之前的那个没有关系了,确实一致了,为什么还会乱码呢,虽然响应时编解码是一致的,但是iso-8859-1,这个编码本来就不支持中文,它不支持中文,你说 你一致有用么,没有用,那怎么办呢,我们需要把这两个编码改为支持中文的编码,比如说gbk,比如说utf-8,我们一般用utf-8,因为它字符集大,中文也支持,什么都支持,都有了,方便。

  • 所以我们需要改,怎么改呢,在这个响应打包时,我们想改这个位置,需要做出一段声明,res.setCharacterEncoding("utf-8");,那这句话是声明响应的时候,这个编码是什么,做出这个声明,那么,把这句话写上,它在打包时,就按照这个utf-8进行编码了,这还不够,浏览器还要解码,你是不是还得声明这个解码,还要声明解码方案,那解码方案怎么声明呢,这样声明,res.setContentType("text/html;charset=utf-8");,这句话以前只写了半句,写了什么呢,text/html,然后啊,服务器会自动补上后半句,它补上charset="iso-8859-1",这回不用它补,我们主动加上,就写,charset="utf-8",这样写是告诉浏览器,我给你发送的数据是html,我的编码是utf-8,写上这句话,res.setContentType("text/html;charset=utf-8");,就可以了,这句话管的是响应时浏览器的拆包,之前的,res.setCharacterEncoding("utf-8");这句话,管的是响应时服务器的打包,它们这个目标不一样,然后呢,还要再强调一句,那么这两个地方,任选其一,写一处就可以,或者写打包,或者写拆包,另外一处,以此为默认值,那我们写哪个地方更好呢,浏览器的这个,res.setContentType("text/html;charset=utf-8");,为啥啊,因为本来这句话就有,它无论如何都得有,所以一般我们写这句,就是这两个地方二选一,那我们通常呢就写浏览器拆包时这个,另一个就不用写了。

  • 最后进行测试,把项目重新部署一下,重启Tomcat,然后再试,这回就不乱了:
    在这里插入图片描述

乱码概况原理图

在这里插入图片描述

6.关于乱码的几点探究

问题

经过试验,发现了一些有关乱码的小结论,但并未深究其原因:

  • 1.为什么通过IO流输出的字符常量,无论是utf8编码还是gbk编码,Eclipse控制台和浏览器都能原封不动的将其输出?
  • 2.UTF8和GBK是两种不同的可以表示中文的字符编码集,不能相互转换。
  • 3.UTF8和GBK表示的统一中文字符,通过UTF8/GBK 进行编码,再通过ISO-8859-1进行解码后,最后由UTF8/GBK的编码方式进行读取的结果(乱码形式)相同。比如,

举例1

  • UTF8的中文字符张三,通过UTF8进行编码,再通过ISO-8859-1进行解码,最后由UTF8编码方式进行读取的结果为å¼ ä¸‰
  • GBK的中文字符张三,通过UTF8进行编码,再通过ISO-8859-1进行解码,最后由UTF8编码方式进行读取的结果为å¼ ä¸‰
    在这里插入图片描述
  • UTF8的中文字符张三,通过UTF8进行编码,再通过ISO-8859-1进行解码,最后由GBK编码方式进行读取的结果为??????
  • GBK的中文字符张三,通过UTF8进行编码,再通过ISO-8859-1进行解码,最后由UTF8编码方式进行读取的结果为??????

举例2

  • UTF8的中文字符张三,通过UTF8进行编码,再通过ISO-8859-1进行解码,最后由UTF8编码方式进行读取的结果为ÕÅÈý
  • GBK的中文字符张三,通过UTF8进行编码,再通过ISO-8859-1进行解码,最后由UTF8编码方式进行读取的结果为ÕÅÈý
    在这里插入图片描述
  • UTF8的中文字符张三,通过GBK进行编码,再通过ISO-8859-1进行解码,最后由GBK编码方式进行读取的结果为????
  • GBK的中文字符张三,通过GBK进行编码,再通过ISO-8859-1进行解码,最后由GBK编码方式进行读取的结果为????
  • 上面对Servlet运行的原理介绍,在理解这个原理之后,又在此基础上,去阐述了请求或响应时乱码,它产生乱码的原因,以及乱码的解决方案,第一个,Tomcat要改这个server.xml配置文件,加URIEncoding="utf-8",这句话,一切GET请求乱码都解决,其次呢,要在获取参数前,加这句话,req.setCharacterEncoding("utf-8");,一切POST请求,乱码都解决,还有一种修复的方式就忽视;而响应时,我们一定要在输出的这个格式后面,加上utf-8这个编码,res.setContentType("text/html;charset=utf-8");来搞定,这个解决的方案,说的比较啰嗦,比较全面,但是选择的时候,主要就是3个地方,配置文件,一句话声明,还有呢,响应时编码设置utf-8,这个地方,这3个地方就可以了,然后以后开发时,逐渐的,把这个东西养成习惯,慢慢就好了,当然了,那么我们在开发web项目的时候,任何语言的时候,咱们这个是中国,我们用的是中文,都会有乱码的问题,所以对于中国的程序员,解决乱码就是一个必要的一个话题,所以这个话题是很被常问到,所以这个要懂,要是这个外国,欧洲啊,美国 啊,它本来就英文,它没有这个问题,它不需要解决,通常不需要解决。

7.员工查询案例

案例需求分析与设计

  • 关于Servlet,这个Servlet的开发步骤,怎么用,它背后的协议,大概是什么意思,Servlet的原理,请求方式,以及如何传参,乱码等问题都有了了解,那么通过以上的内容,下面就可以做一个小案例,做一个查询员工的案例,要求很简单啊,就是我在浏览器上,输出一个地址,访问到服务器,然后呢,服务器给我返回一个网页,网页上显示出某张表里,员工表里所有员工数据,就这个意思,浏览器访问服务器,服务器给我返回什么呢,员工表里存的所有数据,那它给我返回一个网页,网页里显示所有员工的话,我们用什么元素来表现更好呢,用什么元素,来表现所有员工更好呢,table是吧,因为我们在那个sql里面,我们查询一个员工,不也得到一个表格么,所以网页也用表格来表现更好,所以最终,我要求服务器,返回的网页里,就一个表格,表格里有多行员工,表里存了多少个,你就给我显示多少个,就可以了。但这个案例,涉及到数据库,存储数据,查询数据,我们是一笔带过,是模拟实现,我们的重点是在于Servlet,它是怎么样的开发步骤,它怎么配,参数怎么接收,然后,遇到乱码怎么解决,重点在于这,因为目前主要是对Servlet的内容进行应用和巩固,至于Servlet如何访问数据库,这个跨度太大了,在当前阶段,把Sevlet的相关的内容,都巩固完了,再去访问数据库,要分清楚这侧重点,不要着急,否则这个跨度太大,侧重于Servlet的内容,而简化数据库的访问环节。
  • 那这个详细的思路,当前做的肯定是这个web项目,这个不用想,web项目它就必须有浏览器,它必然要有服务器,两者之间呢,产生了一定的交互,或者通信,两者之间呢要通信,当然了,我们通信时,这个员工的数据来源,按理来说应该从数据库里获得,应该还有数据库的这事,当然了,这件事尽量的简化,或者说,就是模拟一下这是数据库,数据库的员工表emp。
    在这里插入图片描述
  • 那么要想开发这个员工的查询案例,首先浏览器要访问服务器,服务器要返回一个包含员工的网页来,那这个网页是动态的还是静态的,静态的,为什么是静态的呢,谁访问都一样,行,你把这句话理解的,怎么说呢,出神入化,比较死,我想说,还出神入化呢,看把你美的,出神入化了。它不是静态的,为啥呢,比如说,我这个软件用户上线了,里边存了比如说10个员工,今天我们访问,谁访问都10个员工,没问题,当明天我们访问的时候,有没有可能变成20个员工,有没有可能变成8个员工,这个员工的数据有没有可能会变,我们在一定的时间跨度之内得到的结果会不会有变化呢,肯定会,因为员工今天可能来一个,明天走两个,有变化的,所以说我们把时间的长度拉长以后,它会有所变化,所以呢,这是一个动态的请求,动态的网页,不是静态的,是啊,我们今天此时此刻,100个人访问,它都是10个员工,但明天就变了,所以,你不能光想着什么呢,人多,你还得想着,这个时间,两个线索,一条线索呢,是人,一条线索呢,是时间,在一个较长的时间范围内访问,会不会有变化,也会,所以是动态的,再一个,咱们这个数据,最终是要访问数据库,要从表里查出数据来,那我们写个网页,网页有能力从表里取出数据来么,没有,肯定是java去处理,那不可能是网页处理,所以,还是动态的。
  • 那么既然是动态的,就在服务器端写一个Servlet,现在做动态网页,不就是会写Servlet么,没有别的东西了,那么这个类的命名呢,最好是直观一点,给它取名叫什么呢,叫FindEmpServlet,看到名能够直观的感受到它的意思,就叫这个名字,FindEmpServlet,由它给浏览器拼一个网页,还有,我希望呢,这个类,这个对象,它的访问路径是/findEmp,那既然它的网名是findEmp,我们在访问时,是不是得敲这个地址,然后还有,显然我们这个还得建一个新项目,这个新项目的名字,叫EmpManager,员工管理,假设做一个员工管理的项目,当然这是杜撰,就是假设是这样,我希望呢,我们在浏览器的地址栏,输入这样的一个地址/EmpManager/findEmp,这样的一个路径访问服务器,服务器呢,给我返回一个网页,这个网页中呢,主要包含一个内容,就是一个包含员工的table就行了,就这么一个请求 啊,非常简单。
  • 那当然了,我们在Servlet里,要访问emp这个表,我们怎么去访问这个表呢,怎么通过这个emp表查出数据来呢,怎么办,用jdbc,那具体怎么去实现,怎么去操作呢,去访问这个emp表,当然用的是jdbc这项技术,怎么用,加载驱动,建立连接,创建一个Statement,执行一个sql,关闭连接,这是jdbc的流程,但是我们在正式项目中使用jdbc的时候,是一定会对这项内容加以封装的,封装的话,我们是用什么来封装这项技术呢,有什么呢(是不是得有个线程池,是这样吧,我们用什么来封装呢,连接池,唉,我说最后封装的那项技术),是Dao,不就Dao么,那么Dao里应该封装的是,对某张表的,就是处理的逻辑,增删改查,等等等,它里面封装的是逻辑。
  • 那么通常呢,一个表只有一个Dao,员工表,我就有一个EmpDao, 然后呢,Dao呢,是封装了增删改查的一些方法,那当前我们说的是查询,所以这个Dao里呢,我new一个方法EmpDao.findAll(),查询全部员工,那么这个findAll(),这个方法,是不是Servlet可以调用呢,可以调用,当然了,如果你不调Dao,我们就在这个service()方法之内去创建连接,去执行个sql,是不是也可以呢,也可以但,肯定是这种Dao的方式更好,那封装调用,可以复用。那service()方法,调这个Dao,Dao呢,返回数据给它,它利用得到的数据,然后呢,把数据拼成一个动态网页,table,就可以了。
  • 那这个Dao的findAll()方法,它个别人返回的是什么类型的数据,比较合理,返回什么数据,字符串,返回啥,返回集合,为啥呢,因为有多个员工,List,所以是个集合,那集合里装的是什么呢,Emp对象,一个对象对应一条数据(不是让你复习了吗,你嘿嘿一笑,什么意思,想让我复习,门都没有啊,是么,是这意思么),多条数据,一条数据是一个对象,封装,多条是个集合,这个对象有个名字,叫什么来着,叫entity,中文叫实体,实体类,实体对象,这是我们这个案例的开发的一个这个思路。
  • 那我们写代码时,需要写几个地方呢,一个是Servlet得写吧,List,Emp这个实体类,要不要写啊,得写,Dao得写,这个emp表,我就不建了,访问数据库这个事,我们先简化一下,模拟一下,不是正式去完成。那Servlet,Emp实体类,和Dao,这3个地方要写的话,先写谁后写谁,比较合适,先写Emp,先写实体类,为啥先写实体类呢,因为你在写Dao,声明findAll()这个方法的时候,这个方法的返回值就得用上,用上Emp实体类,先写Emp实体类,再写Dao的这个findAll()方法就方便了,先写实体类Emp,再写Dao,然后再写Servlet,这样比较顺,说白了,我我们写代码的顺序,最好是按照它们的依赖顺序来写,Servlet依赖于Dao,我就先写Dao,Dao依赖于实体,我就先写实体,就有依赖。

案例代码实现

  • 那么下面就来写这个代码,首先写这个Emp实体类,写一个简化的版本,要模拟访问数据库,写个简化版的,就写4个属性,而这个表里应该有更多的属性,就是把这个思路了解清楚就可以了。那我们打开Eclipse,需要新建一个项目,项目名,要求叫EmpManager,Maven Project,Group Id:cn.company,Artifact Id:EmpManager,Packaging:war,然后完成,完成以后项目中报错,点Generate... ,除此以外呢,我们要写Servlet,写之前,得导包,还是把这个包导好,依赖于Tomcat自带的包,(建项目这玩意,就操作啊,没什么好练的,当然你回去真的是练操作了是吧,练走位了是吧,那都会走呢,哈哈,千锤百炼,你能不能把这个建项目的过程,练两遍,是吧)。

  • 项目建好以后,下面我们要写的是这个实体类,那我们先给实体类建个包,现在呢,我们这个项目,内容还比较少,只有一个实体类,那这个项目丰富起来之后,可能会有更多的实体类,单独给它来个包,然后呢,这都是正式代码,不是测试。所以回到开发工具当中,我在src/main/java下,建一个包,实体类包名,一般我习惯于叫entity,建完包以后呢,我们需要写这个类,那我在这个包下创建一个类,一般表名叫什么,类名就叫什么,员工表,类名就叫Emp,Emp啊,它是一个javabean,要满足javabean规范,那什么是javabean,满足规范的类就是JavaBean,什么规范呢,这个类必须得有包,它必须得有无参构造器,它必须得实现序列化接口,而最后一点get/set方法,通常有,没有也允许,所以,我们写的这个实体类,它就应该满足这个javaBean规范。

  • 可以这么讲吧,一般呢,用来封装数据,用来存数据的对象,都应该满足这个规范,不满足规范,系统上线以后,高并发时,会有问题,这个问题的产生呢,和一个知识点,叫Session有关系,它不止是这一个地方,就是很多地方都会有, 类似的问题,总之你要遵守,还是那句话,那当前呢,自己写代码太少了,所以呢,你很难体会到,哪些类需要满足规范,哪些不需要满足,如果你不知道的话,最笨的一办法,也是最万能的一个办法,怎么办呢,都满足,都满足一定没有问题,但不满足,可能会有问题。那这个问题呢,平时做小案例,自己试着玩,一般不会出现,一般都是上线以后,数据量大了,并发高了会出现,那出现的时候呢,就晚了,就这个造成了一个比较大的影响,所以一开始一定要遵守这个规范。

  • 那目前这个类满Emp足了几个规范,第一个有包,它有了么,有了web,第2个无参构造器有没有,默认是不是有,我要不加构造器,默认就有,就无参的,然后第3个,序列化接口有么,没有怎么办,在创建这个类时,Add添加Interfaces,序列化接口,点Add弹出窗,然后呢,搜索那个接口,然后的话,确定,点Add,弹出窗口内,Choose interfaces:搜索那个序列化接口叫Serializable,选中,java.io.Serializable,完成以后,这个类就有了。类有了以后呢,我们想用它封装表里数据,所以,我需要在类中加几个属性,每个属性对应一个字段,因为当前是想模拟,不是真的要访问数据库,所以呢,这个属性就少写几个,意思一下,不写全了,我就写4个吧,这样我们模拟案例,就是效率高一点,先写一个id,就empno,再来一个名字,ename,再来一个工作岗位,job,再来一个工资,sal,那这个员工表按理说有8个字段,这里是模拟省点事,就只写4个,id,name,job,sal。

  • 那么,写完以后,还得生成get/set,再生成get/set,然后呢,还要补充一点,实现序列化接口以后,它会有提示,有黄色提示,提示你要生成一个序列化的id,这是为了避免将来我们这个软件有多个版本之间,有冲突导致的,但这个呢,因为做的小案例,我们也没有版本,我们不生成也没关系,什么样的项目需要生成呢,我这个项目持续升级,今年我开发第一版,上线了,明年开发第二版,怕第一版,第二版对象有冲突,才生成id,有个区别,那么,因为我们是做一个小案例,咱们也不会有版本升级,所以你不写,也问题不大,工作时一般是要生成的,工作时你不用担心啊,你看同事生成,你就生成,它不生成,你就不生成,跟他学就可以了。

  • 这个实体类呢,就写完了,非常简单,没什么好说的。然后呢,进行下一步,下一步呢,我们要写Dao,这个Dao啊,这里就模拟,不是真的访问数据库,重点不在于这,所以简单的模拟就算了,,我们再回到开发工具当中,要写Dao呢,我给Dao也创建一个包,那么,这个包呢,就叫dao,然后在这个包下,创建一个类,类的名字呢,叫EmpDao:那么,如果呢,我们不知道哪个类需要满足javabean规范,我们干脆就都满足,就没有问题,所以,当前这个类也实现序列化接口,也满足就得了,所以呢,我在Interfaces的后面,点Add,搜索Serializable,序列化接口,让当前这个Dao也实现序列化接口,然后呢完成,那这个EmpDao,这个类就有了:
    在这里插入图片描述

  • 那么因为当前呢,我们做的是查询功能,所以呢,我们在这个类当中呢,写一个查询方法,用来支持这个功能,那这个方法呢,是被别人调用的,它将来是要被Servlet调用,所以它必须是公有的,然后呢,这个方法需要给调用者返回的是多个员工,多个用集合来表达,或者数组也可以,那么集合或数组中,封装的是每一个员工对象Emp,方法名我叫findAll(),public List<Emp> findAll(){},就查询全部的数据,这个很直观了,那么这个方法内部,如果真的想写jdbc实现的话,这个流程,就先DBUtil创建连接,得到一个连接,然后呢,我们写一个sql,然后,创建Statement对象,来执行这个sql,得到结果集resultSet以后,我们遍历它,将结果封装成Emp,放到集合里返回,大概就这个流程。那这里这个流程就不写了,因为很罗嗦,重点就不在于这,我们就模拟,怎么模拟呢,我干脆就手动new几个Emp,把它放到集合里,一返回。那我要模拟数据,模拟查询所有的员工,List<Emp> list = new ArrayList<Emp>();,那么因为呢,最终,我们要返回的是集合,所以呢,我就直接实例化一个集合,并且return list;,省着忘,然后,集合中存的数据是什么呢,我们去模拟,那模拟的话,就是自己new几个Emp,Emp e1吧,随便呢,编点数据,存进去就可以了:

    Emp e1 = new Emp();
    e1.setEmpno(1);
    e1.setEname(“唐僧”);
    e1.setJob(“领导”);
    e1.setSal(9000.0);
    list.add(e1);
    Emp e2 = new Emp();
    e2.setEmpno(2);
    e2.setEname(“悟空”);
    e2.setJob(“职员”);
    e2.setSal(9000.0);
    list.add(e2);
    Emp e3 = new Emp();
    e3.setEmpno(3);
    e3.setEname(“八戒”);
    e3.setJob(“职员”);
    e3.setSal(6000.0);
    list.add(e3);

  • 那我一共是模拟了,3份数据, 假设就3个员工,啊,就这样了,那么写的时候,有的人会感觉,我们一个一个set数据,有点麻烦,有人就会想,我能不能写一个全参的构造器,我new的时候把参数传进去不就省事了么,是不是这样,可不可以呢,可以是可以,但我实话告诉你,工作时从来都不会这么干,为啥不会这么干呢,就是我们写实体类不会这么干,为啥呢,因为我们现在做案例,表里字段还少,我们不会选一个很复杂的表去演示,都是简单的表,那工作的时候,那个表里字段就很多了,少的也得二三十个,多的百八十个, 甚至一两百个,以前我遇到的表,最大的有两百多个字段,两百多字段,你想啊,你加个全参构造器,我new的时候,要传入200多个参数,能传的对么,够呛吧,那肯定是不太合适,那你这个new,那得多长啊,反正呢,这个工作时,咱们这个表里字段多了,我们在写这个实体类的时候,基本上不会写全参构造器,没什么用,字段太多了,看花眼了,还不如set呢,当然了,这个问题在使用框架以后就好了,这个实体类,可以由框架帮我们去new,实体类当中的数据,可以由框架帮我们set,以后的话,这几句话,甚至连list.add(...);这几句话,不用写了,自动就完成,但现在还是得写。

  • 那这Dao呢,就写完了,因为这里是模拟,也没什么逻辑,只要你new,set的没错,就没什么错,就不用测了,不测了,如果是用jdbc真的实现的话,需要测。那实体类,Dao,都有了,两步完成了,第3步就写这个FindEmpServlet,那么它在处理请求时,要调Dao,得到数据,然后呢,要把数据拼到网页里,给浏览器返回。那再创建一个包,src/main/java下面,再建一个包,叫web,这个包下面呢,我们再创建一个Servlet,叫FindEmpServlet,继承于javax.servlet.http.HttpServlet,这个父类,那我们所创建出来的这个类FindEmpServlet,它是不是满足javabean规范呢,它满不满足规范,不满足,差在哪呢,序列化接口,其实呢,它满足,第一点,包有了,第2点,这个类我们不写构造器,默认就有无参构造器,两点满足,第4点,有无get/set无所谓,不强求,第3点有无序列化接口,我们是没有实现,但你注意,javax.servlet.http.HttpServlet它的父类,还是FindEmpServlet它的爷爷,已经实现了,所以它继承过来了,这3点都满足,那你可以去它的父类中去看一下,所以它是满足javabean规范的,然后呢完成。

  • 如果你不信的话,咱们去父类中看一眼,信也看一眼,非看不可啊,省得你忘了,我们在这个FindEmpServlet类当中,ctrl点这个父类HttpServlet,能进去,然后当然,它这个父类没有源码,我们看到的是Class,没关系,我们在这能够看到这个类的声明,这个父类的名字,叫HttpServlet,继承于javax.servlet.GenericServlet这个,HttpServlet extends javax.servlet.GenericServlet,也没有实现序列化接口,没关系,GenericServlet这个类中实现了,那我想看这个类,现在ctrl不好使了,不行了,那怎么办呢,你把这个GenericServlet类名copy一下,可以利用开发工具搜一下这个类,怎么搜,工具栏上,手电筒左边的左边,这个文件夹,快捷键是ctrl+shift+t,然后呢,粘贴过来,就搜到了GenericServlet这个类,没有,那你是copy错了吧,肯定是有的啊,如果没有的话,刚才你创建这个Servlet时,就报错了,连父类都没有,如果这个没有,父类肯定也没有,就报错了,早就报错了。找到它以后,OK,打开了它爷爷的这个Class,就当前类的爷爷,FindEmpServlet的爷爷,它实现了序列化接口么,GenericServlet,它实现了Servlet接口,还实现了序列化接口,public abstract class javax.servlet.GenericServlet implements javax.servlet.Servlet, javax.servlet.ServletConfig, java.io.Serializable{...},所以,当前所写的FindEmpServlet,它就实现了序列化接口,它是满足javabean规范的,一定满足。

  • 然后,我们在这个类当中,要处理请求,要给浏览器,返回它想要的那个网页,带有table的网页,那我们需要重写父类的service()方法,那我们在这个类当中,右键,Source,Override,从父类中选择倒数第二个service,然后呢,ok 。那么Override时,开发工具自动的帮我们生成了这个方法super.service(arg0,arg1);,这个方法内部要清空,自带代码没用,参数需要调整,我喜欢呢改个名,参数调整一下:

    public class EmpServlet extends HttpServlet {
    @Override
    protected void service(
    HttpServletRequest req,
    HttpServletResponse res) throws ServletException, IOException {
    super.service(arg0,arg1);
    }
    }

  • 那么,和注册的案例一样,几乎所有的这个请求,我们处理的方式,大概都是3步,第一步,接收参数,第二步,处理业务,第三步,发送响应,基本都这3步。那这个第一步,接收参数,这个案例有没有案例需要接收呢,我的操作是什么呢,在地址栏直接敲个网址,访问服务器,有参数传过来么,有表单么,你要传参,不是得有表单么,啥都没有,有什么参数呢,没有,那本案例是查询全部员工,没有查询条件,无条件查询全部员工,没有条件,没有参数,所以呢,第一步省略了,第1步不用了,省略了;直接第2步,处理业务,我们的业务就是什么呢,就查询,调Dao查询就可以了。

    EmpDao dao = new EmpDao();
    List<Emp> list = dao.findAll();

  • 那么我要使用这个Dao,我先对Dao进行了实例化,然后调它的findAll方法,得到了一个集合,那有人就有想法了,你这个实例化这个Dao,那以后的话,我们的业务会很多,增加员工,修改员工,删除员工,还有别的业务,那每个业务我们都实例化Dao,是不是太麻烦了呢,这个Dao中的方法,是不是可以写成静态的呢,其实是可以的,但是呢,为什么我要new呢,因为我们实际工作时,一般这个对象,不写成静态的,为啥呢,因为将来我们会用Spring框架,我们的项目一般都会用这个框架,这个框架能够帮我们管理对象,那将来,这个框架由框架来实例化,所以我们创建对象,现在是new,将来是由框架帮我们去new,所以按照这个习惯,是没有把它写成静态的 ,写成静态的话,这个Spring去管理的话,也比较麻烦,那对象更方便于管理,总之,将来是这么干的,这么用的,那得到数据以后,最后要发送响应,我们要给浏览器拼一个网页,网页中查到几个员工就拼几个,动态的拼,那主要是在网页内拼一个table,那这个案例,还是偷点懒,这个什么doctype,html,head,body,我就省略了,就直接捞干的,拼个table,就不写样式了,不写那些个啰里吧嗦的东西,要不然太麻烦了,后面还会有一个更友好的,更方便的技术JSP,把网页拼完整。现在暂时就是处理一下。

  • 那么,我们要向浏览器输出东西,得先声明,text/html,我要输出的是html,res.setCharacterEncoding("text/html");,那么,我们输出的网页当中,有没有可能有中文呢,有,那你还要加上后半句话,就以后,你都加上,别管有没有中文,都加上就OK了,res.setCharacterEncoding("text/html;charset=utf-8");。那么,要拼网页,得获取输出流,通过response获取,PrintWriter w = res.getWriter(); 那么在我们获取到流以后,在流关闭之前,别忘了关闭,先写上,w.close();,省得以后忘,在此之前,输出想要的东西,就捞干的,直接输出个table,w.println("<table>"),w.println("</table>"),然后呢,table开头,table结尾,要保障我们这个程序的这个结构,不要横着print,table之中呢,首先有一行标题,什么编号,姓名,职位,薪资,有一行标题,我再输出一行,println(" <tr>"),println(" </tr>"),就tr,tr开头,tr结尾,要保证格式,要有必要的缩进,以免将来看不懂。然后呢,tr里面,要有td,有4列,这是标题行,当然了,这个table,默认的话,没有边框,没有宽度,不好看,我希望它稍微好看那么一点点,想让它好看的话,第一种方式,我们写样式,但我想还是别写样式了,就是你想想,写样式得多恶心对吧,就不想写;那不想写样式呢,我有个权益之计,怎么办呢,在table标签上,写几个属性,也可以有边框,也可以有宽度,还是按照那种方式做吧,写几个属性,得了,w.println("<table border='1' cellspacing='0' width='30%'>");,那border='1' ,那边框是1,cellspacing='0',是边框的间距为0,边框合并,width='30%'

    w.println("<table border='1' cellspacing='0' width='30%'>");
    w.println(" <tr>");
    w.println(" <td>编号</td>");
    w.println(" <td>姓名</td>");
    w.println(" <td>职位</td>");
    w.println(" <td>薪资</td>");
    w.println(" </tr>");
    w.println("</table>");

  • 那目前呢,table,就有了,标题行有了,那还差什么呢,数据,那数据有几行呢,我拼几行数据行呢,后面有几行数据啊,能写死成3行么,不能,你得看集合对吧,集合查到几条数据,就拼几条,所以在这,我们得遍历集合了,根据集合来拼这个数据,判断一下,如果说集合非空,那我就遍历,那每次循环拼一行,那就拼呗,一行,行里还得有4列,在行内,再写4个td,复制粘贴,很快4个就有了,那么每一个td之内,我们要拼员工的一点数据,这个数据是从哪来,是不是这参数e,我们把这个e中的值拼到td里,拼吧,我们把这4个td都拼上,对应的数据,那终于算是拼完了:

    if(list != null) {
    for(Emp e : list) {
    w.println("<tr>");
    w.println(" <td>"+e.getEmpno()+"</td>");
    w.println(" <td>"+e.getEname()+"</td>");
    w.println(" <td>"+e.getJob()+"</td>");
    w.println(" <td>"+e.getSal()+"</td>");
    w.println("</tr>");
    }
    }

  • 你看,我们就想拼一个table,仅仅是这么点东西,仅仅是4列,就这么麻烦,哎呀,可想而知啊,咱们要想做一个复杂的网页,这得累到什么程度,对吧,太麻烦了,但是十几年前,我们的前辈就这样干的,现在呢,我们可以有替代方案了,但是呢,这种笨的方案,笨拙的方案,你还必须要掌握,因为将来,我们会有变通用法,不是说没有用的,所以要了解,要掌握,那这个都写完以后,还是不能直接访问,我们还要怎么样呢,还差最后一步,写配置文件,那我们打开这个web.xml,快速的把这个Servlet配置好:

    <servlet>
    <servlet-name>findEmp</servlet-name>
    <servlet-class>web.FindEmpServlet</servlet-class>
    </servlet>
    <servlet-mapping>
    <servlet-name>findEmp</servlet-name>
    <url-pattern>/findEmp</url-pattern>
    </servlet-mapping>

  • 配置完以后,把这个项目部署一下,启动Tomcat,然后呢,访问一下试试,Add and Remove,从左边移到右边,完成,启动Tomcat,启动完以后呢,打开浏览器,按照路径访问一下这个Servlet,localhost:8080/EmpManager/findEmp,没问题,OK。
    在这里插入图片描述

8.员工查询案例完整代码

目录结构

在这里插入图片描述

Emp

package entity;

import java.io.Serializable;

public class Emp implements Serializable {
	
	private Integer empno;
	private String ename;
	private String job;
	private Double sal;
	
	public Integer getEmpno() {
		return empno;
	}
	public void setEmpno(Integer empno) {
		this.empno = empno;
	}
	public String getEname() {
		return ename;
	}
	public void setEname(String ename) {
		this.ename = ename;
	}
	public String getJob() {
		return job;
	}
	public void setJob(String job) {
		this.job = job;
	}
	public Double getSal() {
		return sal;
	}
	public void setSal(Double sal) {
		this.sal = sal;
	}
}

EmpDao

package dao;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import entity.Emp;

public class EmpDao implements Serializable {
	
	public List<Emp> findAll(){
		//模拟查询所有的员工
		List<Emp> list = new ArrayList<Emp>();
		
		Emp e1 = new Emp();
		e1.setEmpno(1);
		e1.setEname("唐僧");
		e1.setJob("领导");
		e1.setSal(9000.0);
		list.add(e1);
		
		Emp e2 = new Emp();
		e2.setEmpno(2);
		e2.setEname("悟空");
		e2.setJob("职员");
		e2.setSal(9000.0);
		list.add(e2);
		
		Emp e3 = new Emp();
		e3.setEmpno(3);
		e3.setEname("八戒");
		e3.setJob("职员");
		e3.setSal(6000.0);
		list.add(e3);
		
		return list;
	}
}

FindEmpServlet

package web;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.EmpDao;
import entity.Emp;

public class FindEmpServlet extends HttpServlet {

	@Override
	protected void service(
			HttpServletRequest req, 
			HttpServletResponse res) throws ServletException, IOException {
		//1.接收参数
		//2.处理业务
		EmpDao dao = new EmpDao();
		List<Emp> list = dao.findAll();
		//3.发送响应
		res.setContentType("text/html;charset=utf-8");
		PrintWriter w = res.getWriter();
		//当前:/EmpManager/findEmp
		//目标:/EmpManager/add_emp.html
		w.println("<a href='add_emp.html'>增加</a>");
		w.println("<table border='1'  cellspacing='0'  width='30%'>");
		w.println("	<tr>");
		w.println("		<td>编号</td>");
		w.println("		<td>姓名</td>");
		w.println("		<td>职位</td>");
		w.println("		<td>薪资</td>");
		w.println("	</tr>");
		if(list != null) {
			for(Emp e : list) {
				w.println("<tr>");
				w.println("	<td>"+e.getEmpno()+"</td>");
				w.println("	<td>"+e.getEname()+"</td>");
				w.println("	<td>"+e.getJob()+"</td>");
				w.println("	<td>"+e.getSal()+"</td>");
				w.println("</tr>");
			}
		}
		w.println("</table>");
		w.close();
	}
}

9.总结

  • 本篇首先介绍了http协议,那这个协议呢,它是整个web项目基础, 我们写的Servlet是在这个协议框架之内,所规定的。http协议,它指的是呢w3c呢 ,制定的一个规范 ,它规定了浏览器和服务器如何通信,以及通信的数据格式,那么有了这个规定以后,这个市场上,有任何的厂商,去开发浏览器,去开发服务器,都遵守这个规范,那么就方便 了,它有章可循了,只要你按照这个规范,所开发出来的浏览器和服务器,那么,是兼容市场上任何的这个其他的服务器或浏览器的,是互相兼容。不然的话呢,你按照自己的规则,开发出一个,这个浏览器或服务器,它不可能支持别人,所以,要想让你的这个 软件能够通用,你就必须满足这个协议,满足这个规范,那么很多互联网方面的这个规则,都是由w3c规定的,这个协议呢,也只是其中一个,然后呢,协议规定了两件事情,第一件事呢,怎么通信,第二件事呢,通信的格式,那具体来说,这个浏览器和服务器怎么通信呢,大致是4步,但其实呢,非常详细,大概是4步。大概这4步呢是,浏览器和服务器建立连接,发送请求,浏览器接收服务器的响应,然后关闭连接。浏览器向服务器发送的是请求,服务器给浏览器返回的是响应,我们说请求一定指的是浏览器访问服务器,我们说响应一定是服务器给浏览器返回内容。
  • 那么这种方式,它和jdbc的那个套路差不多对,那么 这两者不约而同,都采用这种方式去设计,主要的目的,是为了降低我们要访问的目标的压力。啊,那么http协议这种方式,是为了降低服务器压力,那么jdbc的那种方式,是为了降低数据库的压力,所以说这种方式,是经过检验的,这个能够降低目标压力的一种手段,然后呢,又介绍了,那请求和响应这两份数据,它们的结构是什么,那请求时,浏览器向服务器发送的数据,它不是一条数据,是好多条,一堆数据,响应时也一样,那么浏览器向服务器呢,发送这么多条数据,是要对数据加以封装的,是需要组织这个格式的,而这个数据的格式,是按照w3c的规定来组织的,所以,我们说请求数据的结构,其实指的是数据的格式,那么请求和响应数据,它本质上组织的时候,组织的是一个字符串,它会把请求数据组织成一个字符串,然后呢,把字符串转化为byte,传给这个服务器,服务器返回数据时也一样,它组织的是一个大字符串,然后呢,转成byte,发送给浏览器,都是这样的,所以我们说,请求的数据结构,指的是浏览器,组织的那个字符串,它有哪几部分,它分为哪几方面,是这个意思。
  • 当然了,那个控制台输出,或者是在浏览器network里,看到的那些数据,其实就是那个字符串的构成,只不过呢,浏览器上用插件看,它给我们把那个结构呢,就是稍微调整,不是那个原始结构,数据还是那些数据。可能是还理解不到那到底是哪些方面,如果你想知道服务器给浏览器发送的数据,它到底是什么结构,这个规范到底是怎么声明的,也可以看文档,响应的数据怎么声明的,可以看文档,有个叫RFC的文档,RFC这里面呢,有一篇文章就是介绍这个协议的,比如说这个,HTTP/1.1 RFC2616,这个RFC就是这个文章,就是w3c规范的那个文档,HTTP/1.1,它这个文档的名字叫,RFC2616,这个文章是英文的,有目录,每一部分介绍什么内容,是什么含义,这里很细详细的说明,或者说,有关HTTP的内容是来源于w3c所规定的这篇文章,那这篇文章讲的非常详尽,一般人也没有精力把它看一遍,所以如果想知道某一方面到底是什么,就看这个文章。那么,我们在浏览器的network里,或者是在那个控制台输出的什么请求相关的数据,它的格式就是这段文章规定的,它有什么是这篇文章所描述的,是这样的。
  • 那么,RFC2616这篇文章规定了请求的数据,浏览器向服务器发送的数据,要包含3部分,当然这3部分是揉到一起做成一个超大字符串而存在的,但是呢,它是3部分。第一部分呢,叫请求行,请求行包含请求的基本信息,它包括什么呢,包括这个协议,是什么版本,包括呢,访问路径是什么,包括呢,请求方式,这个3个内容,为啥包括这3个内容呢,w3c规定的,没有为什么,人家这样规定的;然后呢,消息头,包括了更多的内容,比如说,这个要求返回数据的格式啊,这个语言啊,这个浏览器的这个版本呢,等等,就是相关的内容。总体来讲,消息头中的数据,它是对实体内容,就是发送具体业务数据的一个描述,那么实体内容,就是浏览器向服务器发送的业务数据,比如说登录的信息,比如说注册的内容,比如说财务数据等等,是这样的。那么如果你详细看那篇文章的话,它就是这样规定的,请求数据包括3份,3部分,请求行应该包括什么什么,叫什么名字,都有很明确的规定,消息头包含什么什么什么,什么名字,数据应该是什么格式的值,都有明确规定,然后呢,响应数据结构,也是类似,响应时有状态行,状态行是响应的基本信息,包括了服务器的这个的协议,包括了这个响应时的状态码,状态码比如说404啊,405啊,500啊,200啊,后面还有个三零几啊,等等,每一个状态码,文档上也有说明,所以,我们这里的每一项内容,都是文档规定的,那么浏览器和服务器是按照文档的指示去开发的,所以我们看到的只是这样一个结果,然后呢,消息头存的是这个服务器是什么版本,这个内容有多长,内容是什么格式等等,这也是对实体内容的描述,那么实体内容呢,是服务器向浏览器发送的业务数据 ,就返回的你想要的业务数据,比如说,注册成功与否,登录成功与否,比如说,你想要的财务数据等等,是这样的。
  • 总体来讲吧,就是说,这个HTTP协议,它是一个规定,这个规定就是一篇文章,一个文档,就是指示这个行业内的这个开发者,应该如何去开发浏览器和服务器,然后呢它规定了通讯的方式,这里有说明,规定的数据结构,这里有说明,我们所看到的这些内容,都是根据文档,规定来的,那文档呢,是超级长,它所规定的 内容实在是太多了,但是呢,大部分内容不用我们处理,这个大部分内容,都是由浏览器和服务器呢,自行实现了,比如说,通信的步骤,它们已经实现了,比如说浏览器发请求数据时,它请求数据的什么消息头啊,这个请求行啊,它都自己呢,封装了,自己组织了,而实体内容,我们提供内容,数据也是由它来组装的,所以最终呢,这份数据,这一包数据的组织,是浏览器根据这个文章组织在一起的,我们仅仅是需要提供一个表单就可以;然后呢,响应时也是一样,响应时这个状态行,服务器自动填写,消息头,一般也是由服务器自动填写,那内容我们来提供,我们自己来写print,那我们写print的东西,由这个服务器,自动的发送给,也是自动打包,发送给这个浏览器,那么,我们只需要在整个环节内,做一点点的事情,做什么呢,请求的具体的内容我们提供,怎么提供呢,写个表单,响应的实体内容,我们提供,怎么提供呢,print,仅此而已。所以到我们这就,事就少了,但是呢,人家背后做了那么多的事情,浏览器和服务器你要知道,你要懂得感谢,人家做好了。
  • 那归根结底,我们需要学会什么,我们就需要学会呢,request和response,因为request能处理请求数据,response能处理响应数据,那么,request能处理一切请求数据,能够获得一切请求数据,response能够设置一切响应数据,但是通常,不用我们都设置,比如说请求行,什么协议类型啊,什么路径啊,都不用我们设置,响应时,比如说这个状态行啊,状态码啊,等等,这些不用我们设置,所以说,我们会用不假,但是通常呢,需要常用的就那么几个方法。以上就是我们对这个http协议的介绍,这个协议呢,很多人,对规范啊,对协议,其实就是不理解,当然,这个也正常,因为什么呢,你这个开发经验太少了,你只会写出一个比较简单的语法来,然后呢,这个语法背后的规定,它的意义你并不太懂也很正常,主要是你在这个行业内,就是你还没有真正的进入到这个行业内,你还不知道一个规范对于一个企业,对于一个行业的这个价值,等你在工作的时候,你不断的去接触每一个企业,它的一个规定,不断的接触这个行业的规定,慢慢慢慢你就懂了,当你和别人一起配合着开发一个项目时,你就懂了,一切规范都是,不是针对某个人,而规定的,都是针对一个群体,我们这个群体,都遵守这个规范,这件事才好办,如果说有人不遵守规定,有人遵守,这就乱了,所以,慢慢体会,现在我们就是各自写各自的代码,互相没有配合,可能你体会不到,慢慢再来吧。
  • 然后,又通过一个注册案例,阐明了浏览器向服务器怎么去发送数据,怎么传数据,那么浏览器要向服务器传数据呢,浏览器表单需要有相关的配置,第一点,表单的action上,你得声明提交的目标,提交给谁,那么我们在声明目标的时候呢,那个路径可以写完整的,可以写绝对的,可以写相对的,那么相对的最简单,但是,相对的话,你去分析,这个思路上比较麻烦,那我们一般工作时,还是用相对路径,所以呢,相对路径,我们需要不断的去体会。然后,控件中的数据要提交给服务器,我们必须给数据起个名字,所以我们需要在控件上通过name属性给它命名,然后,对于单选多选,它里面没有输入的文字,只是勾选,那么传送到服务器端的,是它的一种状态,on,那么多个选项都是on,区分不了,我们需要通过value声明具体的要发送的值,这3点,你要记住。那么服务器端呢,我们在Servlet里要想接收这个参数,用这样的方法,req.getParameter("code");,得到一个值,和,PgetParameterValues("interest");,得到多个值,那么,这个方法呢,返回的都是字符串,或字符串数组,都是字符串,如果你有需要呢,自己转型,然后呢,括号里面写的是那个名字控件属性name的值。
  • 那么通过这个案例呢,咱们又查明了请求方式这样一个话题,什么是请求方式呢,就是浏览器向服务器发送请求时,传递数据的方式,方法,方案,那么请求方式呢,常用的有两种,这两种是万能的,一切请求都能解决,分别是GET和POST,那么GET和POST两者的区别,很重要,这是一道非常常见的面试题,Servlet这个阶段,有很多面试题是很常见的,越往后你会越发现,很多题是经常会问的,那每一个常见的面试题,如果你不会回答的话,给人家的感觉都非常糟糕,就是说你这个web项目的基础,没有掌握,会让人家感觉呢,这个你能不能做web项目,会让人家产生怀疑,所以说,一定要把每一道题,都把它搞清楚。那第一个呢,我们讲的是GET,GET请求有什么区别呢,它采用路径传参,参数在传递过程中可见,隐私性差,那么地址栏能输入的地址是有限的,浏览器对它做了限制,一般是2k,所以呢,它的大小有限制,传递的参数不能是任意多,是受限的,那什么请求是GET呢,一切请求默认的都是GET啊,那么,其次呢,是POST,这种请求,它采用实体内容传参,那么不是用路径传参,所以呢,参数不可见,隐私性好,而实体内容,专门用来,设计出来的时候,就是为了传参的,它的大小没有限制,因为它不是用路径,大小不受限,那什么请求为POST呢,在表单上加上method="post",就是POST。
  • 那建议什么时候,选择GET,什么时候选择POST,那么,如果你有参数,是保密的,那就一定是POST,如果你传递参数较多,那就一定是POST,是这样的,那么了解到这以后,又以这个注册案例为基础,我们归纳总结了Servlet运行的原理,然后呢,通过这个原理,是为了阐述后面的一些内容,比如说这个请求的过程中,响应的过程中,出现乱码怎么办,说的这样一个话题,那么请求的,就是浏览器访问服务器,服务器返回响应,大致是4步,但详细来讲 ,有十几步,这也是一个大概,那么在底层在实现时,比这要更详细多了。大概是这样,尽量去体会。那么,浏览器要访问服务器,首先呢,要和服务器建立连接,连接好以后,服务器要打包数据,所谓的打包数据,就是浏览器,把要发送给服务器的数据,拼成一个大字符串,比如说,它拼一个字符串,比如说,这个servletPath:"...",然后呢,这个method:"...",比如说这个,protocal:"...",这是请求行,还有消息头,比如说还有这个,比anguage:"...",它就是这样拼一个大字符串拼好,那拼这个字符串的过程,就是对数据的打包,所以打包数据,就只是一个形象的描述,其实,实现打包数据就是拼那个字符串,因为服务器要的就是一个大字符串,w3c这样规定的,打包好数据以后呢,不能在网络上,直接传字符串,是需要转成byte,转换为byte再发送给服务器,服务器拆包,所谓的拆包,就是把byte再还原为字符串,从而得到每一部分数据,这个数据呢,数据是要给我们使用,怎么给我们呢,服务器呢,提前帮我们创建了两个对象,一个是request,一个是response,它把数据帮我们存到了request里,然后呢,它帮我们实例化Servlet,并且呢,调用了它Servlet,调用时把request和response传入,所以我们在这个Servlet对象里,通过request能得到数据,是这样的,得到数据以后,我们做一些业务处理,处理完以后,向浏览器输出内容,那我们print输出的内容,服务器也进行了打包,就是它也拼字符串,拼完之后,把这个转成byte,发送给浏览器,浏览器呢,拆包,就是再还原为String,得到这个,字符串,最后呢关闭,大概就是这么一个过程。这个过程呢极其的繁琐,那么整个的流程是http协议所规定的,是w3c的规定,那么整个过程中呢,大部分的环节,这个都由浏览器和服务器实现了。我们只需要写Servlet这个类就可以,那这个类的实例化和调用都不用我们去管,我们只是完成这个环节当中的一点点。
  • 了解完Sevlet的运行原理,又说了出了乱码时怎么办,这个浏览器访问服务器 ,服务器返回数据,在整个环节会乱码,有两地方会乱码,传数据时,浏览器向服务器传数据,传的是请求数据,我们得到以后可能会乱,服务器返回的响应数据,浏览器得到以后可能会乱。那先说的是请求数据,那乱码产生的根源,是在于编码解码不一致,因为你String转成byte得编码,byte还原String得解码,这编解码不一致就会乱码,那么请求时确实不一致,咱们这个网页上,我们一般声明的编码为utf-8,而服务器默认编码是iso-8859-1,不一致,怎么解决呢,第一种方式是修复,得到乱码以后,我再把它还原为byte,按照iso-8859-1还原为byte,那就是乱码以前的byte,我再把这个byte,用utf-8编为String,就ok了,那这种方式是万能的,对什么方式都有效,缺点是太麻烦,一般不用。那第二种方式呢,是修改配置文件,我们在Tomcat里,改这个配置文件,在65行,加上这句话URIEncoding="utf-8",这句话的意思是路径的编码为utf-8,只有GET用路径来传参,只对GET有效,优点是简单,缺点是局限。那第3种方式呢,是声明,我们在获取参数以前写上这句话,req.setCharacterEncoding("utf-8");,那这句话的意思呢是声明,实体内容编码为utf-8,优点呢也是比较简单的,缺点呢是只对POST有效,因为只有POST请求,用实体内容传参,那实际工作时, 我们两者结合在一起,那么配置文件也写上URIEncoding="utf-8",一切GET请求都解决了,那么在获取参数之前加req.setCharacterEncoding("utf-8");,这句话,一切POST请求也解决了,就都解决了,就这两个结合在一起用就好了。
  • 那响应时呢,也会有乱码,响应时,服务器也是按照iso-8859-1编码,并且呢服务器还告诉浏览器,说我是用这个方式编码的,你用这个方式解码,浏览器呢,也很听话,也会用这个方式解码,那其实呢两者编码方式是相同的,那为什么会乱码呢,主要是iso-8859-1,这个编码不支持中文,我们需要把它改了,那改的方式,改编码的方式,一个是写这句话,res.setCharacterEncoding("utf-8");,一个是在res.setContenType("text/html;charset=utf-8");里加上后半句,这两个地方,而这两个地方呢,二选一,你要么写res.setCharacterEncoding("utf-8");这句话,要么写这个res.setContenType("text/html;charset=utf-8");后半句,通常我们会写后半句,因为这个setContentType,这个方法是一定要写的,另一句就省略了,通常是这样。最后呢,咱们模拟了查询员工的案例,在这案例中去巩固Servlet相关的内容,那这个案例,巩固了什么呢,巩固了Servlet开发的步骤,这是第一点,配置文件怎么写,再一个呢,也巩固了就是,那服务器如何呢,给浏览器返回响应数据,如果拼一个网页,就这么一个环节。再一个呢,我们把就是说,业务串联起来了,如果说我们想开发一个查询的功能的话,如果写完整的话,需要具备哪些东西,我们还得写Dao,还得写实体类,它们的关系,大概是这样的。先体会一下将来,我们所做项目的这个大概的关系。

参考文献(References)

文中如有侵权行为,请联系me。。。。。。。。。。。。。
文中的错误,理解不到位的地方在所难免,也请指教!在成长过程中,也将继续不断完善,不作为专业文章。不喜勿喷。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值