Servlet3

前言(Preface)

  • Servlet2 通过模拟注册案例,阐明了一些有关Servlet的基础问题,然后又通过,模拟查询员工案例,进一步深入,但是很遗憾,这个案例它没有把内容都这个,就是涵盖全,比如说,浏览器向服务器发送的数据,这个案例中就没有,然后呢,没有发送的数据,我们也没有解码请求乱码的问题,就这个也没涵盖到,响应倒是有,响应乱码解决,是涵盖到了,所以,这个案例没有覆盖全我们的内容,那没关系。
  • 在查询员工案例的基础上,在此基础上,再做一个案例,把我们没有演示全的这个内容,再把它演示全。另外呢,我们要演示的这个案例,起到一个承上启下的作用,就是说,案例写完以后,还会利用这个案例,引出了我们的下一个话题,那这个案例是什么呢,我想做一下,增加员工的功能,在查询基础上,去做一个增加的功能。

1.增加员工功能案例

员工增加案例需求分析与设计

  • 增加功能是在查询功能基础上完成的功能,这个功能的要求,首先 在这个查询的功能页面,查询到员工列表,在查询基础上,去做增加功能,这个功能的思路,这个员工表格是查询的结果,那我们增加功能一般是这样,我们在查询的列表的上方,通常是这样,加一个按钮,比如说叫,增加按钮,当然超链接也可以,增加按钮和超链接都行在这,然后呢,一点增加按钮时,我们打开增加页面,去增加数据,一般都是这样。那我希望是,在查询页面上增加一个增加按钮,那么加完以后,我们在浏览器上,在这个网页上,能看到的内容就是这些内容,增加按钮和表格。
  • 然后,要求是当我们点增加按钮的时候,要给我打开增加员工的那个网页,那你要打开一个网页,我们要访问服务器,我们这是完整的web项目,所以一定要访问服务器,获取这个网页,那要访问服务器,获取一个网页,我们增加员工的这个网页,是动态的,还是静态的,动态的,为什么呢,我们每个人打开增加页面看到的结果都不一样么,我们每个人打开增加页面和打开注册页面一样,都是空的,但是每个人填的数据是不一样,这是后话,我们打开的那一刻是不是一样的,这是一样,所以是动态的还是静态的呢,静态的。
  • 这个我们无论是工作的时候,还是面试的时候,我们想说出一个答案,一个结论,最好要仔细想一下,不要张口就来,这个有的时候,你的直觉未必是准确的,你不能凭直觉在说答案,这个不太靠谱。所以是个静态的网页,这个静态的我写html,这个html呢,我起名叫add_emp.html,我访问它,就是我们在查询页面上,多一个增加按钮,当然超链接也可以,然后呢,一点,访问这个静态网页,那么服务器呢,会把这个静态网页,给我返回,返回以后,那增加页面和注册和登录差不多,都得有个表单,所以我们会看到一个表单,那表单里应该有什么呢,表单里应该包含几个数据呢4个,哪4个呢,编号,姓名,职位,薪资,提交,我说数据,提交是按钮,我说数据,有人说有3个,哪3个呢,姓名,职位,薪资,为啥没有编号呢,因为我们在保存数据的时候,这个编号是id,是不是自动生成的,id是自动生成的,有必要让用户填么,没有必要,所以呢,增加页面上,我们只要用户填3个数据就可以了。
  • 第一个数据呢,是姓名,我就这样简单画一下,姓名,然后第二个呢,是职位,然后下一个呢,是薪资,这些是需要用户填写的,那编号就不需要了,编号因为数据库端呢,会自动生成,当然了,这些数据写完以后,还得点按钮,它才提交的,这个按钮叫什么名比较合适呢,一般呢,我们增加修改的功能,这个按钮我们习惯于叫保存,一般习惯于叫保存。这个流程,就是点增加按钮得到一个网页,页面上有表单,这是增加功能中的第一个请求,这个请求,我们就得到一个表单,然后,用户看到表单以后,填数据,那每个用户填的数据就不一样了,然后呢,用户要提交数据,提交给服务器,是这样,那这个请求,是动态的,还是静态的呢,动态的,因为每个人填的数据不一样,所得到的结果也是有区别的,你填的数据不一样,有人对有人错,有人重名,有人如何如何,所以就是结果就不一样,这是动态的。
  • 那既然是动态的呢,我们需要在服务器上写一个Servlet,那增加我写一个Servlet叫什么呢,叫AddEmpServlet,取这么个名字,同时呢,我给出一个要求,我要求呢,我们在写这个类的时候,它的网名,网络访问路径叫,/addEmp,叫这么一个网名,这是我的一个要求。然后呢,这个Servlet,它需要给用户呢,它需要接收表单的数据,需要保存,但我们这个保存呢,还是模拟一下,那你要保存的话,咱们这块就是模拟,咱们最终还是得调Dao,它得调EmpDao,EmpDao写过了,那Dao里,我们得写一个方法,保存就叫save()吧,一般习惯于叫这个方法,那Servlet要调Dao,它调Dao的时候,是不是得把数据传给Dao,它接收到了3条,3份数据(姓名,职位,薪资),那传给Dao的话,这个数据怎么传,以什么方式来传呢,是不是得以那个Emp对象的方式来传呢,加以封装,你要不加以封装的话,咱们现在字段倒是少,你工作时的话,字段一多的话,几十个上百个,你要是直接零碎的传过去,那太恶心了,所以一定是要封装成对象,一下全传过去。
  • 然后呢,Dao把数据一保存,保存完了以后,如果没有问题,那么Servlet要给这个浏览器一个反馈,我希望它这个给浏览器返回什么呢,说保存成功,一句话,就这样。这是我们增加的功能的思路,在回顾一下。就是想增加的话,能够打开增加页面,增加按钮呢,我放到查询页面上,用户访问,查询就看到了,然后呢,当你点增加按钮的时候,我们给他返回一个增加的页面,主要是一个表单,然后呢,表单的内部,有这么多内容(姓名,职位,薪资),他填数据,点保存,保存的数据一定是由Servlet处理,因为这个数据每个人不一样,所以是个动态的逻辑,然后呢,我们写一个Servlet,接收表单的数据,然后呢,把数据封装好以后,传给Dao,由Dao来保存,然后呢,最后给浏览器 返回一点内容,保存成功,当然,这是一个简单的网页啊,保存成功就可以了。这是我们这个思路。
    在这里插入图片描述
  • 那下面我们按照这个步骤来开发,先开发第一个请求,那第一个请求比较简单,就是说,我们写一个静态网页,写一个增加按钮,增加按钮调这个网页,访问这个网页,仅此而已,那这样,我们先写这个网页,网页有了,我们再加按钮,再访问它,就比较合理,比较顺利,先写这个网页。

员工增加案例代码分析

  • 打开开发工具eclipse,项目EmpManager,然后,这个项目,我们要写网页,网页要放到webapp的下面,所以呢,我展开这个目录找到webapp,我在webapp的下面创建一个网页啊,叫add_emp.html。那么创建完这个网页以后,这个网页所具备的内容,就是一个表单,写一个表单,3个框,分别是姓名,职位,薪资,都是文本框就可以了,这里没有密码,也没有选择,把这个标题改一下,像那么回事,就增加员工。那么body里,写一个表单form,这个表单先不用做配置,只要写出表单和它的内容就可以,那在做第二个请求保存的时候,根据实际情况再加以配置,先写出内容元素来,这个表单写好以后,表单中呢,一共是有3个文本框,分别代表姓名,职位和薪资啊,还有一个提交按钮,名字叫保存,内容非常简单,那么写完了这个网页之后,我们需要把那个按钮加上去,并且呢,点那个按钮的时候,要访问add_emp.html这个网页。

  • 那我们要加那个按钮的话,在哪加,那个查询查询员工的页面在哪呢,查询页面是动态的,它是一个Servlet,它不是html,所以,查询的网页就是FindEmpServlet,是由FindEmpServlet拼的,拼的这些东西就是动态网页,所以呢,我们打开查询的Servlet,要在table之前,w.println("<table border='1' cellspacing='0' width='30%'>");,图中在table之上加个按钮,当然,做的话就是尽量简化,画的是按钮居右,那居右还得写样式,咱们就算了,咱们就居左也没关系,那我在table之前加个按钮,也得print,拼这块,咱们加一个按钮也可以,加一个超链接也行,这先加个超链接,因为超链接呢,简单一些,省点事。按钮的话,有点麻烦。但是我们点超链接,我能够链到一个网页上,我点按钮,能不能也打开这个网页呢,我点按钮打开那个网页,那按钮上得写什么呢,那得写点啥,我们要写location,点按钮触发它的单击事件,我们调某函数,函数内一句话,locatin.href等于网址,那我们按钮访问这个网页,和超链接访问网页,两者的区别,是在于哪,就点按钮,更为灵活,因为我在location.href,访问目标之前可以加判断,加任何的处理,而超链接的话,不能加判断,不能做任何处理,一点就过去了,有所区别,但超链接呢,更简单,按钮呢,略为复杂,因为你还得写函数,我们这写那玩意就不方便了,我们就写超链接吧。

  • 这写个超链接,w.println("<a href='add_emp.html'>增加</a>");,然后呢,href里面,我们需要写出路径来,访问路径,那么,在web项目里我们要访问路径,都是3种方式,第一种方式,完整路径,太长,第二种方式,绝对路径,以斜线开头,项目名,斜线,其他的内容,还有相对路径,那总体来说呢,相对路径,它呢,写起来比较简短,但我们分析相对路径怎么写,这个呢,稍微有点麻烦,那我们工作时,很多地方都会用到相对路径,所以,相对路径是非常关键的,绝对路径,只要把那个规则记住,肯定就会了。

  • 我们写代码,不要老去想,就只停留在想的层面,给你安排个活,一想想半天,一行代码都不写,结果你发现你想,能想明白么,也想不明白,为啥呢,一个功能一项业务,它的这个逻辑呢,太复杂了,它需要很多步骤,甚至几十步,上百步,你用你脑子想的话,想到10步以后,你就乱了,前面几乎就忘了,你往后想,前面就忘了。所以说,必须呢得动动手,要么画图,要么呢写字,你把你的思路,通过文字和图形呢,把它表现出来,串接起来,那把前后的这个内容,看能不能串通,一旦串不通的话,这个问题摆在这,你可以问问别人说哪不通,你帮我看一下,别人一看,你的思路是这样的,给你分析一下也可以 ,然后呢,你说我靠脑子想,然后去跟别人口述的话,这个很不靠谱,为什么呢,因为首先,你的脑子想的时候,可能就乱了,你再给它口述的时候,这个逻辑关系就乱套,人家听你说的话呢,还得费劲脑子的去想,你到底什么意思,再加上你的这个表述能力,比较薄弱,说一段话以后,别人一点都没清楚,反而晕了,你说这种情况怎么办,所以最好呢,你是动动手,把你的想法落实在纸面上,然后的话呢,不管你自己看,还是别人看,更方便更直观,这有利于促进你的这个思维分析能力,你不要只停留在想的层面。

  • 所以,我们写个注释分析相对路径,那我现在要访问的目标,就是增加页面,那增加页面的绝对路径,是什么呢,就是/EmpManager/add_emp.html,是这样吧,增加页面的绝对路径,是这样,我摆出来。那么当然了,实际上开发久了,一般就不用分析了,有时候有规律了,顺手就写了,但是呢,如果你开发一个新的项目,自己开发一个新的项目的话,如果没有以前的一些内容,作参考的话,有时候还会乱,因为现在我写一些项目,有的时候路径复杂了,我也会搞晕了,我得把它列出来,然后看一对比才行,有时候自己想,想来想去就想乱了,得试,那目标是/EmpManager/add_emp.html,那我们当前在哪,我们是从哪去访问这个目标的呢,就是谁访问谁,当前是起点,目标是终点,由哪到哪,当前就是我们起点,我们的起点是点增加按钮,而增加按钮是在查询页面上,所以我们当前所处的位置就是查询页面,那查询页面的访问路径是什么呢,当前:/EmpManager/findEmp,就是它,这个罗列出来。

    //当前:/EmpManager/findEmp
    //目标:/EmpManager/add_emp.html
    w.println(“增加”);

  • 罗列出来以后,两者平级,非常直观了,那我们要访问增加页面,就是写add_emp.html,不要写斜线,表示这是相对路径。关于路径,工作时,你就看别的模块,人家路径怎么写的,你就照着扒,如果实在也没扒明白也没关系,你大不了试一下,不对,我就加个点点杠什么,你自己调整,自己试,反正费点劲。我这个按钮也加上去了,也链到了那个网页上,静态网页也有了,那测试一下,把这个,项目重新部署一下,因为修改了代码,重新部署以后,启动Tomcat,启动以后,打开浏览器,我们访问一下这个查询,localhost:8080/EmpManager/findEmp,刷新一下,刷新以后,那么表格上方,确实出现了一个超链接:
    在这里插入图片描述

  • 那么,当你把鼠标悬停到超链接的上方的时候,那么在这个浏览器的左下方,会出现完整的路径,左下方已经提示了这个超链接的完整路径链到哪去了,一目了然,能看出来:
    在这里插入图片描述

  • 然后我点一下这个超链接,点增加,点了以后,它帮我打开了增加页面,就这样了:
    在这里插入图片描述

  • 那么到这,增加功能的第一个请求就完成了。这个增加功能的第一个请求,打开增加页面已经完成,那下面我们再做第二个请求,第二个请求,我们是要把表单中的数据,提交给这个服务器,这里数据很少,都是文本框,然后呢,提交给服务器的Servlet,然后呢Servlet,它又调Dao,然后呢,做对数据的保存,这个保存数据,咱们也是模拟,不真的存了。那么第二个请求呢,我们开发的时候,我个人的习惯和建议是倒着开发,请求是这样从前端分析,我们开发时是这样反过来,因为什么呢,Servlet依赖于Dao,你先写Dao比较合理,而作为一个表单,它要提交给Servlet,它依赖于Servlet,你得先开发这个比较合理,按照这个依赖关系的倒叙来开发,是比较合适的,那所以先呢,开发这个Dao,我们在Dao里加一个保存的方法。

  • 打开开发工具,然后找到那个EmpDao,在这个Dao里,再加一个方法,这个方法呢,因为也是要被别人调用,复用的,所以呢,它是公有的,那么保存,或者增加修改的方法,这种方法呢,一般不需要返回值,它只要不报错,就是成功了,报错就是失败了,不需要返回值,然后呢,这个方法名我们习惯于叫save,调用时,要求传入相关的数据,是一个对象Emp,即public void save(Emp e){...}。这个方法呢,就声明完了,那怎么实现呢,那如果真的用jdbc来实现的话,我们需要创建连接,要写个sql,我们要创建prepareStatement对象,因为有参数;然后呢,我们要执行这个sql,就可以了。但这里呢,我们还是模拟,因为当前的案例,这块不是重点,重点在于Servlet,怎么模拟呢,就一句话来代替,输出一句话,输出什么呢,说增加员工,某某某,System.out.println("增加员工:"+e.getEname());,就输出这样一句话就行了。

  • 那这个增加员工的方法,就模拟实现了,实现了以后,再看下一步,有了这个方法以后,下面就可以写Servlet,AddEmpServlet,那么任何的Servlet处理任何请求,它的规律大概都一样,基本上是3步,第一步呢,是接收页面传入的参数,接收参数,第二步根据参数处理这个业务,第3步,给客户端以响应,给浏览器以响应,那有些时候呢,可能有些步骤可以省略,没有,可以变成两步,甚至是一步,那这个呢,恰好都有,又要接收参数,又要处理业务保存数据,又要响应,完整了。

  • 那再回到开发工具,我们要创建一个全新的Servlet ,那想一想,如果说我不创建一个新的Servlet ,我就在原来的FindEmpServlet里写这个增加的逻辑,可不可以写,在哪里写,在哪个方法里写,在service方法里,我现在是有service的,但我这个service里,已经写了查询的逻辑,我再写增加的逻辑,写一起去,这行吗,你是不是想把这个删了,写个增加,这样合适么,不合适,那么一个Servlet,它只有这样的一个方法,这个方法已经被查询功能占用了,就不能再写其他的逻辑了,那怎么办呢,这里还必须得创建一个全新的Servlet,* 当然了,这样做的话,会比较麻烦,一个请求创建一个类,比较麻烦,我们工作时,一个大的项目得有上千个请求,甚至还不止,为啥会有那么多请求呢,因为每一个功能,就是怎么说呢,一个大项目能够包含几十个模块,每一个模块至少都包含增删改查的功能,一般呢,还会包含什么审批啊,弃审啊,上传啊,下载啊,等等这样的功能,所以,它的功能有十来个,有的复杂的模块,功能有几十个,那每一个功能还不是一个业务,像这个增加功能,是两个请求,现在做的简单的增加功能两个请求,所以很多功能,它不是一个请求,是多个请求,所以,一个大项目包含几十个模块,每一个模块包含十来个功能,每一个功能包含两三个请求,所以加起来的话,就是一个模块有十个功能,平均每个功能有两个请求,20个请求,所以一个模块20个请求,如果是100个模块,2000个请求,所以这个大项目的话,它的这个请求数是上千个,那我们要写上千类是挺麻烦,效率也不高,那后续会有简化的方式,当前呢,还只能用这种麻烦的方式。

  • 那在这个web这个包下,创建一个新的Servlet,这个类名,我叫AddEmpServlet,继承于javax.servlet.http.HttpServlet,创建完这个类,完成:
    在这里插入图片描述
    *那么在这个类当中,我们需要处理增加的业务,所以我们需要重写service方法来处理增加的业务,选择父类的service方法,对于这个方法,需要进行一些整理,把这个方法整理完以后,那下面来写这个方法内部的逻辑,那第一步是要接收网页表单传过来的数据,就是第一步,接收参数,那么接收参数之前,我们分析一下,我们接收的参数有没有可能有中文呢,有,像那个姓名,职位就中文,那中文的话,传过来默认会有乱码,因为浏览器的编码是utf-8,服务器的编码是iso-8859-1,不一致,怎么办,怎么解决这个问题,有修改那个tomcat配置文件,有写req.serCharacterEncoding,两种方式。

  • 那我用哪种,用哪种的前提是看一下我们当前的请求是POST还GET,那为啥是GET呢,第一点没有密码,不需要保密,第二点数据少,你看待问题有点太局限了,你眼光能不能放到长远一点,为啥呢,我们当前案例,我特意简化了,本来数据是8个,本来这个表里不是8个字段么,我简化了,那将来你工作的时候,我们正式做项目时,即便当前这个表里是8个字段,那有没有可能,我们下一个版本,把它加成16个字段,20几个字段,有可能吧,将来有可能会多,所以保存的数据,即便是当前少,以后没准会多,我们通常也用POST,是这样的,以备后患。所以呢,这种情况我们一般保存数据,还是建议用POST,那POST我们怎么解决这个乱码问题呢,加一句话,那req.setCharacterEncoding("utf-8");,那这句话写完,只要是POST请求传过来的参数,就不会有乱码了,那我们就大胆的接收吧,一共3个参数,那当然了,目前表单还没有配,咱们那个参数还没有名字,那没关系,我们这,request.getParameter(..);,我先把名字写在这,反过来再写那边,倒着写。那我就接收了,都是字符串,

    //1.接收参数
    req.setCharacterEncoding(“utf-8”);
    String ename = req.getParameter(“ename”);
    String job = req.getParameter(“job”);
    String sal = req.getParameter(“sal”);

  • 那么这3个数据都是单个数据,什么情况下是多个数据呢,就是多选,没有多选,都是单个值,所以,我用的方法都是getParameter,这个方法呢,我们得到的数据都是字符串,但我们很清楚,这个salary应该是什么,应该是小数,那我们需要的是小数怎么办,自己转型,自己转就可以了,没办法,就是我们在网络上传数据,这个类型会丢失的,你认为是小数,你认为是Date,我们传的时候,它都会以字符串的方式来传,这个类型会丢失的,都是字符串,字符串转为byte,byte再转为字符串,只能是这样,然后呢,你需求什么类型自己再转 。那么接收到这3个数据以后,那第二步,我们要利用它们,那主要是保存,就保存员工数据,那我保存员工数据,我们可以调Dao,那个Dao的方法,要求我们传的是对象,所以我们还得new个对象,把这数据封装一下,

    //2.保存员工数据
    Emp e = new Emp();
    e.setEname(ename);
    e.setJob(job);
    e.setSal(Double.valueOf(sal));
    new EmpDao().save(e);

  • 我们new了Emp,给Emp呢,设置了一些数据,最后一个数据,因为对象是要求传入是小数,我们业务上,它确实是小数,所以呢,我们把参数呢,转为小数,把一个字符串转为小数有多种方式,new Double,传入字符串可以,Double.valueOf可以,Double.parseDouble也可以,都行,那我们有了这个对象以后,可以调Dao了,那我实例化Dao,然后呢,调用它的方法,把参数传入,就可以了,即new EmpDao().save(e);。那数据传完以后,最终,我们要给客户端一个响应,告诉它成功与否,当然目前呢,我们保存的逻辑是模拟的,我们一律告诉它成功,将来我们做完整的业务时,这块呢会有动态的变化,那第3步,是发送响应,

    res.setContentType("text/html;charset=utf-8");
    PrintWriter w = res.getWriter();
    w.println("<p>保存成功</p>");
    w.close();

  • 那这个发送响应,咱们也是输出一个网页,当然我们是把这个简化了,我只输出了body中的最核心的一句话,一个段落,保存成功,那么其他的内容就省略了,然后呢因为我们输出的内容当中呢,有中文,所以响应时,要处理中文乱码,我们需要在text/html这句话后面res.setContentType("text/html;charset=utf-8");,再加上编码的声明。那么这个案例,它就把Servlet的相关内容,基本上都串接起来了,就是我们怎获取请求的参数,请求的时候有乱码怎么解决,响应时乱码怎么解决,都涵盖到了,那这个写完以后,那这个类我们要加以配置,那打开这个配置文件,之前查询员工案例时,我们配置这个FindEmpServlet查询,那现在,我们再追加一段配置,配置这个增加员工,再加一段,

    <servlet>
    <servlet-name>addEmp</servlet-name>
    <servlet-class>web.AddEmpServlet</servlet-class>
    </servlet>
    <servlet-mapping>
    <servlet-name>addEmp</servlet-name>
    <url-pattern>/addEmp</url-pattern>
    </servlet-mapping>

  • 那增加员工这个Servlet,也配置好了,那么它访问路径是/addEmp,路径的前面别忘了写下划线,那么完成以后,还有最后一步,最后一步呢,是我们需要对表单加以声明,我们要配置表单,要配置哪几个地方,有几个地方,有3个地方,第一个action,要声明访问的路径,第二个控件上得加name,声明数据的名字,第三个如果单选多选加value,声明这个值,就这件事一定要记得很清楚,我们得到一个表单以后该配置哪项内容,这样才不会忘,如果你没有记清楚,做电信计费项目时,肯定还会搞混,搞乱,很多人就把它搞混了,这个控件上要不要么就写name,要不要写value,完全就乱了,这个一定要记下来,那我们这个控件稍微简单点,它没有单选多选,所以不用写value,只需要配置前两步就可以。

  • 那回到增加的页面上,add_emp.html,那么第一项内容,我们在form上通过action,声明提交的目标,顺便同时要声明请求方式,请求方式设置为POST,我们用POST,那么action之内,我们要写的是访问路径,又要写路径,那还是要写相对路径,那么写相对路径时,还是列举,列举什么呢,我们访问目标到底是谁,我们当前正在访问的又是谁,我们是从哪到哪的访问,把这两者搞清楚,这个路径自然而然就推出来了,首先我们要想一下,我们要访问的目标是谁,就是刚才缩写的那个Servlet,是AddEmpServlet这个类,(网名/addEmp),所以,它的绝对路径是/EmpManager/addEmp,访问它。

  • 那我们是从哪去访问这个目标呢,我们当前所处的位置又是哪呢,如果实在搞不清楚,看图,我们要访问的是AddEmpServlet,我们当前所处的位置就是增加页面,而增加页面的访问路径是谁呢,不就是这个页面的名字么,就是它的名字add_emp.html,或者你看地址栏,你看我当前打开的这个增加页面,增加页面的路径,就是它,localhost:8080/EmpManager/add_emp.html,从地址栏也能看出来,当前是这,所以呢,当前的路径是/EmpManaget/add_emp.html,那么把这个两者路径列出来以后,咱们很容易就能得出结论,那相对路径就是这个单词addEmp,这是第一个,第一项配置,配置好以后,要把表单中的这些文本框,还得加name,那咱们现在给框加name,这个name能随便写么,不能,我们得和之前的AddEmpServlet当中的,那个字符串一致,两者要一致。

  • 这3个name都加好以后,它和getParameter,那个AddEmpServlet中接受参数时候的那个参数一致了,就行,写完以后到这,这案例终于算是完成了,那完成以后,加以测试。把这个项目再重新的再部署一下,然后呢,启动Tomcat,启动好以后,试一下,我还重新访问这个查询页面findEmp,查询页面是我们整个功能的起点,然后呢,在此页面上,我点增加,打开增加页面,在增加上呢,我填这个数据,比如说这个姓名,张三;职位,经理;薪资,8000;我填上相关的数据,然后点保存,保存以后,给我一个提示保存成功:
    在这里插入图片描述那我们看一下控制台,控制台输出一句话,增加员工:张三。这个模拟案例就ok了:
    在这里插入图片描述


<body>
	<!-- 
		当前:/EmpManager/add_emp.html
		目标:/EmpManager/addEmp
		//EmpServlet改造后:
		当前:/EmpManager/add_emp.html
		目标:/EmpManager/add.emp
	 -->
	<form  action="add.emp"  method="post">
		<p>
			姓名:<input type="text"  name="ename"/>
		</p>
		<p>
			职位:<input type="text"  name="job"/>
		</p>
		<p>
			薪资:<input type="text"  name="sal"/>
		</p>
		<p>
			<input type="submit"  value="保存"/>
		</p>
	</form>
</body>

利用重定向完善保存成功页面跳转功能

  • 那目前这个查询,增加的案例就完成了,那么通过这两个案例吧,就把相关Servlet大概的内容涵盖了,那么现在还有一个问题,需要再探讨一下,就是我们增加完以后,给用户返回了一个提示,保存成功:
    在这里插入图片描述

  • 那这样的方式,并不太好,用户呢,保存完成功以后,你给他这样一个提示,它就看到四个字,不太合适,一般网站也不会这么干,一般会怎么样呢,比如说,我们注册一个账号,注册成功以后,它立刻跳转到登录比较合适,你跳转到我想要去的那个地方比较合适,如果 是保存数据的话,跳到哪合适呢,回到查询比较合适,如果你直接保存完以后回到查询,我直接能看到这个数据,那不言而喻就是成功了,那就更好,所以,给一句话提示,这个只是暂时的,工作时不会这么干,所以说我们把这个案例呢,再完善一下。

  • 我们处理一下保存成功以后的这个提示,那我希望这样,保存完以后,不是给用户这样一个提示,不要提示,而是要怎么样呢,跳转到查询,跳到查询,那这个查询页面之前是开发过了,不就是之前写过的那个FindEmpServlet么,就是它,我跳到这,就可以了,当然这个对象,它调了谁呢,它也调了Dao,调了EmpDao里面的.findAll方法,这都是写过的东西,那这个findAll方法呢,给它返回了相关的内容,然后呢,我们想从增加跳转到,从AddEmpServlet跳转到FindEmpServlet这来,那我们自然而然会想到,那我是不是可以直接调一下这个对象,调一下FindEmpServlet,它调了EmpDaofindAll方法得到数据,它帮我们输出了,它帮我们把结果输出给浏览器,输出的是查询结果,这样,这是可以的:
    在这里插入图片描述

  • 但是呢,我们工作时,通常不会这么写,这样写有一个非常大的缺陷,什么缺陷呢,你这样写,用AddEmpServlet去调FindEmpServlet,这两者之间的耦合度就增加了,那其实增加功能和查询功能,是两个彼此独立的功能,可以这么讲,是彼此独立的,是不互相依赖的,但我AddEmpServlet调了FindEmpServlet,它就依赖了,这两个组件的耦合度就产生了,我们编程时,一开始这个耦合度就要控制住,你不要想着有点耦合度,就有点耦合度吧,将来我再解决,你会发现,你永远都没有解决这个问题的时间,中国的程序员太忙了,你时刻都在做新的项目,时刻都在加班,所以说我们没有经历去解决这个问题,一旦耦合度产生以后,然后呢,它会越来越大,越来越糟糕的,直到项目失控为止,所以这个不太好。

  • 那么,为什么这个耦合度会越来越高,一个是我们这个业务会越来越复杂,咱们今天做了第一版,明天可能做第二版,不断的维护,不断地升级,在升级的过程中,这个代码会增多的,那你是基于上一版做的升级,上一版的代码,你又不敢轻易改,没有时间改,你就往里加,越来越,耦合度就越高,再有呢,那还有可能啊,就咱们这个中国的IT行业,人员流动率是极高的,有的人呢,工作3年能换好几份工作,所以说人员流动率大,你经常是什么呢,你这个开发完了,你走了,别人接收你的代码,他一看你的代码已经写成这样了,他不敢去改,还是在这基础上呢,去接着写,所以是一个恶性的循环。

  • 那有人说也不关我的事,这个我写完了,反正我走了,爱咋咋地,也不能那么讲。那说个小例子,以前有个同事啊 ,他就是,写代码的时候 呢,不喜欢写注释,从来不写注释,也没人管他,挺美的,那要离职了,项目经理说哎呀,你这代码没有注释啊,没人好接收你的工作,你把注释都写完再走吧,哈哈,天天写注释,写的都恶心死了,写了好长时间注释,如果你不写注释的话,你说我硬走也可以,那得等一个月,你必须得等够一个月才能离职,那等一个月的话,这时间太久了是吧,所以说,不要给自己造成这么多的麻烦,再一个,就即便你走了,万一呢,你将来要办什么这个,公积金的提取啊,什么居住证的续签啊,或者是这个背景调查啊,可能还会和以前的企业有牵扯,你得罪以前的企业,也没有太多的好处,所以,你还是要把,怎么说呢,说句不好听的,你要把屁股擦干净,你让别人接收你的工作,这个德行,口碑也不好,将来也会对你有影响,总之呢,是要注重质量的。

  • 所以呢,你本来是这样两个对象,互相之间呢,没什么关系,它俩没有耦合度,你现在用AddEmpServlet调了FindEmpServlet,有了联系,有了联系以后,那你的后面的维护者,或者是你自己,指不定 就在哪,增加一些方法,会增强这个耦合度,两者耦合度,越来越强,直到有一天呢,这两者拆不开了,一定是要互相依赖了,那如果将来业务发生变化,只要改查询,你连增加也得改,要改增加,查询得改,你的工作量也大了,这是小到两个功能,那大到整个软件都是这样的,如果你不注重耦合度的话,你的软件当中呢,各个组件都有联系, 将来业务一发生变化,你会发现,你是改不动的,你要想改一遍的话,得花很长的时间,而且,我们改代码啊,不是说改完就拉倒,你改完你要测,你测完以后,测试要测,需求要测,而且呢,它不但测你的功能,还会测和你功能相关的业务功能,有的核心功能呢,这个功能会和整个软件当中的任何功能都会发生联系。

  • 像以前我写的那个成本统计,以前我做过一个成本统计的公功能,是项目中的一个环节,那么它和项目当中的各个功能都有关系,如果说这个组件的耦合度高了,那得,其他的组件都要这个有联系,都要改,那相当于把整个项目都一遍,代价太高了,所以一定要降低耦合度,一定不能那么干。就是说,我们最终啊,还要调到它FindEmpServlet,最终还要跳到这来,FindEmpServlet,还不能直接调它,那怎办呢,我们用个特殊的方式,这个方式呢,我们称之为重定向,重定向大概的意思,就是说,服务器AddEmpServlet,不去主动调用FindEmpServlet,而是怎么办呢,服务器给浏览器一个建议,建议浏览器去访问FindEmpServlet,然后呢,让浏览器去访问FindEmpServlet,浏览器可以访问FindEmpServlet么,可以,浏览器是不是谁都能访问呢,是的,通过这种方式就降低了耦合度,因为这个FindEmpServlet,不是我调的,是人家访问的,是通过浏览器衔接起来的,那AddEmpServletFindEmpServlet之间呢,没有直接关系。

  • 这种感觉,举个例子啊。举个生活中的场景,就像我们去这个体检,体检的话,大家都做过,有一间间小屋子,这间屋子,比如说,抽血,这间屋子,做B超,抽完血做B超,那我抽完血以后, 那护士它一般不会去叫那个做B超的护士说,哎,你给他做个B超,不会这样的,因为啥呢,这两者是平级关系,你凭什么命令我呢,是这意思吧,你凭啥命令我啊,咱俩是平级关系,一般他会这样,他会建议我,先生,你出门往左走,去做个B超,我就去,这个耦合度是截然不同的,虽然说事都是一个事,结果是一个结果,但是感觉不一样。总之啊,这件事叫重定向,重定向就是服务器给浏览器一个建议,当然这个建议,是一个特殊的响应,然后呢,告诉浏览器,你去访问谁,浏览器就去了,就可以了,他俩之间不直接发生关系,这块我标一下,这叫重定向。
    在这里插入图片描述

  • 那么重定向呢,是下一个要说的话题,在这标注一下重定向,重定向是服务器给浏览器一个建议,建议它访问别人,这里先通过员工增加的案例体验一下重定向的应用,体验以后,再去理解它背后的逻辑。打开eclipse,在AddEmpServlet中,增加完以后,第三步发送响应,print了一个保存成功,w.println("<p>保存成功</p>");,现在我们发现这一步不合理,把它注释掉了,不要了,取而代之的是什么呢,是重定向到查询页面,即建议浏览器自己去访问查询页面,是这样的一个意思,虽然说的比较啰嗦,说重定向怎么怎么样,耦合度如何如何,但其实呢,重定向在实现的时候,就一句话,非常简单。

  • 重定向我们要实现的话,肯定也是依赖于这个方法的参数,request和response,那是用request重定向,还是response呢,用谁来实现重定向呢,response,因为这是响应的过程,这是响应不是请求,所以是response,那怎么写呢,res.sendRedirect("");,然后呢,res.sendRedirect("");里面有个参数,这个参数写什么呢,写我们重定向的目标的访问路径,又是路径,那路径还是可以写相对的,相对路径,那这个相对路径,是从哪到哪呢,当前是谁,目标是谁呢,我们是从AddEmpServlet,到FindEmpServlet,当前是AddEmpServlet,目标是FindEmpServlet ,你把图画出来,这一目了然,非常清楚。

    //当前:/EmpManager/addEmp
    //目标:/EmpManager/findEmp
    res.sendRedirect(“findEmp”);

  • 把当前和目标的路径罗列一下,很自然就能够知道相对路径是什么,是这个,findEmp,需要强调的是,我们每个地方都写绝对路径,都用固定的格式来写也可以,但在我们工作当中,一般相对路径写的很多。那么重定向就这句话,res.sendRedirect("findEmp");,写完以后,再试一下,把这个项目重新部署一下 ,然后呢,启动Tomcat,启动以后呢,打开浏览器试试,刷新一下查询页面,点增加,填数据,填完数据,点保存,看保存以后的反应,保存,回到了查询页面,我们的目的达到了,但有人想啊,说哎,你回到查询,那增加的数据怎么没有呢,因为咱们现在是假的,是模拟的,没有把数据真存上,查询的数据也是死的,所以就这样,这就是我们一个模块的雏形,后续的电信计费项目也是这么干的,只不过要比这个更细,比这个更对而已,那还有很多细节需要处理,但大概的套路就这样了。

员工增加案例完整代码实现

AddEmpServlet
package web;

import java.io.IOException;
import java.io.PrintWriter;

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 AddEmpServlet extends HttpServlet {

	@Override
	protected void service(
		HttpServletRequest req, 
		HttpServletResponse res) throws ServletException, IOException {
		//1.接收参数
		req.setCharacterEncoding("utf-8");
		String ename = req.getParameter("ename");
		String job = req.getParameter("job");
		String sal = req.getParameter("sal");
		//2.保存员工数据
		Emp e = new Emp();
		e.setEname(ename);
		e.setJob(job);
		e.setSal(Double.valueOf(sal));
		new EmpDao().save(e);
		//3.发送响应
//		res.setContentType("text/html;charset=utf-8");
//		PrintWriter w = res.getWriter();
//		w.println("<p>保存成功</p>");
//		w.close();
		//重定向到查询页面,即
		//建议浏览器自己去访问查询页面。
		//当前:/EmpManager/addEmp
		//目标:/EmpManager/findEmp
		res.sendRedirect("findEmp");
	}
}
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();
	}
}
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>EmpManager</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>findEmp</servlet-name>
  	<servlet-class>web.FindEmpServlet</servlet-class>
  </servlet>
  <servlet-mapping>
  	<servlet-name>findEmp</servlet-name>
  	<url-pattern>/findEmp</url-pattern>
  </servlet-mapping>
  
  <servlet>
  	<servlet-name>addEmp</servlet-name>
  	<servlet-class>web.AddEmpServlet</servlet-class>
  </servlet>
  <servlet-mapping>
  	<servlet-name>addEmp</servlet-name>
  	<url-pattern>/addEmp</url-pattern>
  </servlet-mapping> 
</web-app>

2.重定向

  • 现在已经把这个增加功能里面的,最后的响应做个处理,不再是呢,简单的一句话,而是重定向,当这个数据保存完以后,我们把这个请求,重定向到了查询,那么关于重定向呢,它是我们在开发时,经常用的一种手段,这种手段极为重要,那么这种手段,它的背后,是什么意思,什么逻辑,或者说它的使用场景,都需要了解清楚,然后,还有另外一个知识点叫转发,再把转发和重定向做一个对比,因为两者是有相似之处的。重定向在一些书上会对重定向做这样的解释,什么是重定向,是服务器向浏览器发送一个302状态码及一个Location消息头(该消息头的值是一个地址,称之为重定向地址),浏览器收到后会立即向重定向地址发出请求。这种解释,还是过于这个学术,不够通俗,不好理解,那我们需要呢,理解它背后的本质,说为什么要有重定向,重定向,它最典型的用途是解决什么问题,这个要搞清楚。
  • 那还是画图来说明,第一点,重定向经典的使用场景,它经典的场景是什么呢,是解决互联网上,两个网站之间的跳转问题, 重定向最先是用来解决互联网上,两个网站之间的跳转问题,那什么叫两个网站之间的跳转问题呢,举个例子,听这个例子,一听就懂了,比如说,现在,我打开了浏览器,我在浏览器上呢,打开了百度,我从百度中呢,搜索出一些内容,比如说,我搜XXX,假设我已经看到了搜索结果,比如我搜到了这个,比如说,XXX官网什么什么什么,是有一段介绍,然后,这有一段超链接可以点,比如说还有,比如说还有这个,北京XXX如何如何,等等等吧,就总之,我们百度返回了很多匹配的结果,我就不一一列举了,反正很多。
  • 然后呢,我去点击某一个链接的话呢,比如说,我点XXX官网,应该最终打开XXX的界面,应该链到XXX去,应该访问XXX的服务器,但是其实不是这样的,我们点XXX官网,它并不是直接链接到了XXX的服务器,它访问的是谁呢,它其实呢,是悄悄的访问了百度的服务器,访问了百度的服务器中的东西,比如说,它访问的是,我假设,假设访问的是,百度提供的一个Servlet ,就BaiduServlet ,就总之,百度它给我们返回的结果当中,这个它的超链接,链向的还是百度,那如果说,大家平时多注意的话,应该有所体会,有时候你网速慢的话,我在百度搜个东西,我一点,你看地址栏,第一个是百度什么什么,一大长串,过一会又变了,有这体会么,你们也太没有生活了,回去,啊,你们网速现在太快了,没有网速慢的时候啊,好吧,这个网速慢的时候,比较明显,网速快的话,一瞬间马上就过去,明白吧,看不到啊。
  • 如果呢,这个,这个怎么说呢,如果你体会不到,你回去可以看一下,我百度搜到任何东西,我把鼠标悬停到那个链接上的时候,在咱们浏览器左下方,不是会出现网址么,你看出现的网址,是百度还是目标的,你是百度的,还是XXX的,保证是百度的,你可以自己看,那为什么,它为什么要这么干,因为百度呢有一个,怎么说呢,不可告人的秘密,什么秘密呢,你可以想一下,我只要是在百度上,搜过,比如说搜过冰箱,那么我上任何网址,很多网站,都向我推荐冰箱广告,有这样情况么,我上个什么论坛啊,看个什么新闻啊,对吧,看个什么手册啊,好像全都是冰箱,这个,这些网址凭什么知道我的喜好,因为我在百度上搜过,那当我搜到一些结果以后,我点了这个结果的那一刻,百度立刻把我这个行为记录下来,好向别的网站卖广告,那些网站的广告是向百度买的,因为百度知道它的需求,就精准投放广告明白吧,成功率比较高一点,是这样的。
  • 所以呢,百度呢,悄悄的,把返回的结果,因为这个结果是百度返回的对吧,就我们搜到的这个结果,这个超链接,是百度给我们的吧,百度在这个超链接上,想访问谁就访问谁,对吧,它给我们的网页么,它想访问百度也可以,所以链接到了百度去,百度呢,在服务端啊,迅速的采集了一下我们的行为,记录下来了啊,它把这个信息呢,采集到以后,它得赶紧让我们去访问目标,因为我们要的是,要访问的是XXX对吧,并不是百度,所以赶紧得去,去向XXX,那你想啊,XXX这个官网,它是不是另外的一个服务器,和百度一毛钱关系都没有,是这样吧,我们要访问,比如说啊,是达内的服务器中的XxxxxServlet,访问的是它,我要访问的是它,那百度呢,在采集完这个数据以后,信息以后,它要跳到这来,XxxxxServlet,你看它能直接调用么,肯定是不能,因为这是两个服务器,是两家公司的对吧,是两个不同的对象,它没有权利调XXX的东西,那这个时候怎么办呢,就得用重定向。
  • 重定向可以,为啥呢,你看BaiduServlet,给浏览器返回一个建议,建议浏览器呢,去访问XXX,浏览器有权利访问达内么,有的,因为浏览器是客户端,访问谁都行对吧,它有权利可以访问XXX,所以,我们用重定向,可以解决这个问题,重定向最早,最经典的使用场景是,解决互联网上两个毫不相关的网站之间的跳转问题,是这个意思,现在也在用,百度就这么干的,google就这么干的,不过我们国内访问不了谷歌对吧,就这么干的。
    在这里插入图片描述
  • 然后,那我们也经常呢,在我们这个项目内部使用,这是第二个,就是也经常在项目内部使用重定向,那怎么使用呢,就是解决一个项目内,两个独立的组件之间的跳转问题,就是也经常这样用,也可以用这项技术啊,解决一个项目内部的两个独立组件之间的跳转问题,就好比我们所写的增加员工的功能,结合这个增加功能再说一下,我们在浏览器上,访问到了增加的页面,然后,我点这个增加保存按钮,浏览器上增加页面的保存按钮,它会访问服务器,服务器上的谁呢,AddEmpServlet,由它来保存数据处理这项业务,处理完以后,应该去到哪呢, FindEmpServlet,查询,那么我们是一个项目之内,我们其实可以这样调,但我们这样调,会增加两个对象的耦合度,这样不好,再三强调了,我们一定要从一开始就降低耦合度,怎么降低呢,重定向,那AddEmpServlet可以给浏览器一个建议,建议浏览器它访问查询,由查询返回结果,这样也行,所以也经常呢,解决一个项目内两个独立的组件之间的跳转问题,这样的话呢,会降低耦合度:
    在这里插入图片描述
  • 那么重定向这项技术,两个不同的这个软件,都能够跳转过去,更何况是一个项目呢,它的能力是很强的,然后呢,重定向的时候,它的这个细节是这样的,重定向,其实就是一个特殊的响应,在响应的时候呢,这个服务器,会给浏览器返回一个状态码,状态码呢是302。浏览器一得到200的状态成功了;得到404找不到资源了;得到500报错了;得到302,这是要找别人,这是让浏览器访问别人,访问谁呢,后面还跟着我们要访问的目标,所以,重定向其实是一种特殊的响应,它会在响应时,给浏览器返回状态码以及呢,要重定向的目标,那么这件事啊,咱们在浏览器上能看到,看一下。
  • 打开浏览器,点增加打开增加页面,打开增加页面以后,我们要看一下那个network,我们需要在network里面观察这个行为,F12打开插件,然后打开network准备好,准备好以后呢,表单中填入数据 ,填完数据以后,就点保存,保存以后,我们去看右侧的这个请求,你看network里是不是两个请求,一个是增加,一个是查询,有先有后:
    在这里插入图片描述
  • 我们主要呢,是看这个addEmp增加,看增加时有什么行为,点一下addEmp,然后仔细看,你会发现Status Code: 302 Found,是不是302,所以,重定向是服务器给浏览器一个特殊的状态,302,表示让它访问别人,那访问谁呢,在这,看Response Headers,响应的消息头里有一个内容叫Location,Location是地址,地址是谁呢,findEmp,能看到:
    在这里插入图片描述
  • 所以呢,在响应时,我们这个浏览器,就得到这样一个信息,它根据这个指令,这个指示去访问findEmp。最后,再概括一下,就是第三点吧,就是重定向的本质,它本质是什么呢,就是服务器向浏览器发出特殊的响应,那么,本次响应包含一个特殊的状态码,状态码是302,另外呢,还包含要访问的目标,那个目标,我们通过Location能看到,这是重定向的本质。
    在这里插入图片描述
  • 那第一个结论,重定向经典的场景,解决互联网上两个网站之间的跳转,那它有能力解决两个网站的跳转,也自然有能力解决一个项目内两个对象之间的跳转,也可以,我们平时也这么干,然后呢,它的本质是浏览器向服务器发出一个特殊的响应,包含302状态码,和访问的目标,就完了。单从字面书籍上的定义,去理解重定向,上来这样一看,会感觉很懵,没有那么通俗,但通过看图形,就能够理解重定向的意思了,所以要从通俗角度,去理解了它的本质。

3.访问路径

项目结构及部署项目时的相关问题

  • 那么重定向这个话题说完以后,下一个话题,就是这个路径,以及呢,Tomcat如何处理这个路径,服务器怎么处理路径,或者说Servlet容器如何处理请求资源路径,和路径相关的话题,这个话题比较大,也比较长,慢慢来,还是那句话,很多书上呢,对路径的描述不是那么的通俗易懂,非常的罗嗦,所以通过把路径相关的内容进行一个梳理,逐渐的递进,逐渐的理解路径的本质。首先,我们要理解路径的本质,要想理解路径的本质,就我们就先得了解,这个Eclipse部署项目是怎么部署的,那我们所谓的路径是到底指的是哪块的路径,到底指的是谁,把它搞清楚,所以第一个话题,部署项目,这个项目部署的过程,通过这个过程,再去理解路径,这是一个前提。
  • 那这个部署的过程,我们需要画图来说明,但我们首先先明确一点,我们写好的项目,这个项目它一共,这个代码有几份,咱们写的项目,这应该有几份代码,其实是有两份,一份是我们在Eclipse里编写的代码,这个代码存放在哪呢,它存放在我们的workspace里,tts9/workspace之下,这个代码我们称之为源代码,是我们直接编辑的代码,那我们运行时,是直接运行workspace里这段代码么,不是 我们是要把这份代码部署到Tomcat下,我们运行的是Tomcat里的代码,那个代码我们叫部署代码,有两套代码,源代码和部署代码,源代码在workspace里,部署代码在tomcat里,那这两者之间有什么关系,是怎么被部署过去的。
    在这里插入图片描述
  • 那首先呢,先看一下,以这个EmpManager为例,先看一下我们的源代码的基本结构,当然了,这个基本结构之内,我们捞干的,稀的就不要了,你比如说,Deployment Description...这个东西,什么这个JAX-WS Web Services,这都用不上,这不是代码,这只是配置,这都不用,我们只是看我们代码的基本结构,那我们的代码呢,分为两部分,一部分是java,我们写在Java Resources之下,一部分是网页,我们写到了webapp之下,但是你看,我们展开这个src/main/java,也有java,也有src/main/resources,展开java以后,也有这个dao什么的,所以,其实呢,这个src文件夹之下,是包含了所有的代码,它包含了类.class,也包含了html,都包含了,但是呢,它这个模式,它不是包模式,它是文件夹模式,它这个层次结构是文件夹结构,而我们写java代码,在这里写不方便,我们一般还是在Java Resources这里写,这是包结构,那其实呢,Java Resources这套代码,包模式里的和文件夹模式里的这套代码是一份,是同一份,是一模一样的,是一份数据,只不过呢,它的表现形式有两种形式,所以,咱们上面的可以不看了,我们只看下面这个。
  • 总之,在src下,文件夹结构里就有我们所写的所有代码,包括java,包括html,都有。那我们所写的是java代码,我们所写的是原文件,那这个java代码,是需要编译的,还要编译,那编译的代码放哪去了呢,代码被编译的地方,我们从项目中可以看到,这个,我们选择项目,右键Properties,然后呢,弹出框里,看Java Build PathJava Build Path右侧,有几个选项,第一个选项叫Source,资源,会发现上面有一些内容,上面这些内容呢,是.java代码存放的路径,是java的源文件路径,后面还有一些内容,叫Default output folder,是我们编译后的路径,是.class文件存放的路径,它存到哪去了呢,EmpManager/target/classes之下,这有明确的说明。
    在这里插入图片描述
  • 就是你想看的话,可以这样看,选择项目,右键Properties,左侧选择Java Build Path,右侧选择Source,一目了然能看到。当然你看我这个屏幕,你懂就行了,但是呢,我们在这个项目下,去看target下,看不到这个classes,因为什么呢,这个Eclipse认为,你没有必要看classes,没有必要看里面的东西,没有必要看,所以呢,它把它隐藏了,其实你有的,看一下,咱们这个程序呢,存放到了workspace里,在我tts9之下,workspace里,然后我们项目名叫EmpManager,EmpManager我打开target,有classes么,不但有classes,还有test-classes,那这个classes下,放正式的代码的.class文件,test-classes,这里放的是测试代码的class文件,是这样的,所以总之,编译后的代码放到target这里来,为什么要说这么细呢,一会部署的时候,我们说部署的逻辑的时候,和这个还有关系。
  • 那现在呢,我们就了解了这个源代码的结构,我把这个结构呢,画到图上,然后呢,加以解释,再串一下思路,就是我们所写的程序有两部分,或者说我们的项目有两个,一个是源代码,源代码存放在哪里呢,存放在workspace里,那这个代码,我们能编写,但不能直接运行,能够运行的是在服务器里的代码,那个代码叫什么呢,部署代码,部署代码在哪呢, 在服务器Tomcat之内,是这样的。那么源代码的结构,先标一下,我们以这个EmpManager为例,EmpManager它下面有src,还有target,src下有main和test,target下的结构比较简单,就有classes,其他的目录我们不管,咱们用不上,还有呢,test-classes,src之下有什么呢,有main,还有什么,test,然后呢再看,main下又有什么呢,有java,有resources,有webapp。main下有java,resource,webapp,还不算完,那webapp下有什么呢,它是不是有WEB-INF,WEB-INF下是不是有web.xml,那webapp之下和WEB-INF平级,是不是还有一些网页相关的内容html,所以,源代码的结构,基本上是这样的,我做了一个罗列,我们要说清楚的话,就必须罗列这些内容,这没办法。
  • 然后呢,这个源代码我们编写的时候,我们随手编写,随手会编译,那什么时候会编译呢,我们一保存代码就自动编译了。这里有个开关,这个Eclipse顶部,有一排图标,有一排这个选项,有一个选项叫Project,点开Project,有一个选项,叫Build Automatically,这个就自动编译,因为默认勾选了,所以我们一保存,它就编译,自动的,这个勾你要勾上,你别不勾上,你不勾上的话,不编译会有问题,运行不了了。
    在这里插入图片描述
  • 总之呢,就是保存时,代码将会被编译,那么被编译在哪呢,咱们这个正式代码java,会被编译到classes里,然后呢,测试代码test,会被编译到test-classes里,那在标识一下,就是保存时自动编译,这是一个规则,当然你也可以改为手动编译,但是我想你还是别改了,那个太麻烦了,有时候,你忘了,忘了就会有问题,总之保存时自动编译,这是源代码的这个逻辑,然后呢,源代码是要被部署到Tomcat里,才能运行的, 由Tomcat运行,那部署的过程是这样的,首先呢,部署就是拷贝,那么Eclipse会把源代码拷贝到Tomcat之下,它拷贝谁呢,它并不是拷贝整个项目,因为我们看最终的项目,它里面少了很多东西,很多东西是没有的。那它拷贝谁呢,它是拷贝webapp过去,它只拷贝webapp过去,那么如果说,Eclipse把webapp拷贝过去,那这里有什么呢,它就只有webapp,然后webapp下有什么呢,WEB-INF,还有什么呢,web.xml,和WEB-INF平级的还有html,它有这些内容,所以拷贝过去以后,它这个里面呢,就有这样的一些内容,这是第一步啊,拷贝。
  • 但是有问题啊,它拷贝过来以后,叫webapp是不是会重名啊,你要每个项目都这么干,不就重名了么,所以呢,它立刻要对项目改名,对webapp改名,那第二步呢,它是把这个webapp改名,改成什么呢,改成和项目同名,所以第二步是改名,这是第二步,改名。它会立刻呢,把webapp的名字,改为和项目同名,改成叫什么呢,EmpManager,可以打开看一下tomcat之内,就可以看到tomcat的wtpwebapps下面,比如说EmpManager这个项目,我一打开EmpManager,它里面有的是WEB-INF和网页,和我说的这个结构是一致的:
    在这里插入图片描述
  • 所以,我们这个EmpManager,Tomcat里面的EmpManager对应谁呢,对应的是我们所写的项目的webapp,它对应的不是整个项目,是经过改名以后的结果,它是这么干的。然后呢还有,然后呢,Tmcat这里面现在的话,有了这个配置文件了,咱们源代码中还缺少什么呢,还缺少java,还要拷贝java过来,肯定是这样的,那拷贝java时,它是要拷贝源文件,还是要拷贝class文件呢,class,因为它运行的是class,所以下一步呢,拷贝的是class,拷贝class的时候这个测试class需要么,不需要,因为这是测试,正式环境中不用了,所以它只拷贝class,因此,下一个环节,Eclipse只是把编译后的文件,拷贝到Tomcat这里面,它会把这个class放到哪去呢,看一下,这是我tomcat/EmpManager这个项目,打开以后,它有WEB-INF,WEB-INF之下,有classes,它把classes拷贝到了WEB-INF之下:
    在这里插入图片描述
  • 它把这个classes啊,拷贝到WEB-INF之下来了,这是第3步,那第3步是什么呢,还是拷贝,拷贝的仅仅是这个class,测试类和java源文件就不要了,那你可以想一下,如果说你这个程序,我们这个代码,没有编译的话,那我们在正式环境里,这个程序能运行成功么,不能,因为你没编译的话,你有java文件,没错,但class没有,一拷贝过去啥都没有,一执行,能执行么,就不能执行,所以必须得编译,必须要自动编译,这个要注意,所以这个是拷贝。拷贝完以后,还有一步,可以看,但是现在看不到,这个我靠嘴说吧,这个现象看不到,因为目前没有,那如果我们这个项目中,有使用maven导的包的话,那么这个包也会被拷贝到tomcat项目下的WEB-INF里面来,为啥呢,我们导的包,那tomcat执行,需不需要用呢,它也需要用,所以它会把我们用maven导的包拷贝过来,拷贝到哪去呢,也是放到WEB-INF之下,它会把这个包呢,放到WEB-INF之下,它会建一个文件夹叫lib,然后呢,把这个包都放到lib之下,那如果是有maven的包的话,会导过来,会拷贝过来,就是在这块就标注一下吧。
  • 若项目中,就是项目中,就源代码中,使用maven导入的包,这块别这么说啊,就是说,这第一点吧,就是创建lib目录,第二点,将maven导入的包,拷贝到此处,那么如果我们的源代码中有maven导入的包,那么Eclipse呢,会在tomcat里,在WEB-INF这个地方,创建一个名为lib的目录,它会把那个包呢,拷贝到这lib之下,目前没有,因为目前我们没有用maven导包,所以目前没有,这是最后一步,第4步;所以呢,这个Eclipse部署一个项目,它需要这4步,4个环节,经过这4步以后,部署的代码,就是tomcat里这样一个结构,EmpManager之下,有WEB-INF,那还有个META-INF,这个是工具自己要用的,我们不用,有WEB-INF,有网页,WEB-INF之下,有classes,有web.xml,如果有maven导入的包,这里还会有lib,此刻没有,做项目时会有,到时候再看。
  • 那么所以,我们要强调,你要用maven导包,因为用maven导的包,部署是会拿过来,tomcat可以调,如果你不是用maven导的包,比如说你是手动导的包,那么它在部署时会丢弃,那丢弃的话,一运行就会找不到那个包,就有问题,那怎么办呢,如果你说有各种原因吧,你说网络不通,我就是没法用maven,我就手动导包,我手动导包以后,这块没有这个lib,那怎么办呢,你自己建个lib把它拷贝进来,这样也可以,反正费点劲,每次部署都得拷贝一遍,或者还有第二种办法,就是你可以呢,把你手动导的包呢,直接丢到tomcat那个lib目录下,我们看一下,咱们这个tomcat不是有一个,有一个lib文件夹么,tomcat里有个lib文件夹,这里本来就有很多包,我们不是让项目已经依赖了这些包么,你把你手动导的包,直接扔到这里也可以,因为我们已经依赖了tomcat,依赖于它们,这也可以,但是呢,我们平时练习时,你实在没招了,说我这个maven确实用不了了,我确实网络有问题了,我有各种问题,我就得手动导,你就暂时这么做还行,但你工作时,一定别这么干。
  • 你工作时要这么干的话,容易让人打死,为啥呢,因为你想啊,你手动导的包,你把你的包,放到了你的tomcat的lib之下,你的同事,张三李四王五,他有他的tomcat,他那里面有这个包么,没有,它一执行,肯定报错,那为什么报错呢,找找原因,最后找到,你把你的包没上传,你放到这来了,所以这个就遭了,就容易被人打死,所以你就注意吧,平时练习时,倒是凑合啊,以后可别这么干,总之啊,我们所写的代码包括源代码,包括部署代码,两者的关系,你要搞清楚,源代码怎么被部署的,要搞清楚,因为它和我们后面,将来要讲的路径,有直接的关系,对某些事情的解释,有这个指导的意义。那我们平时所说的访问路径,访问路径,指的是源代码的访问路径,指的还是部署代码的访问路径,指的一定是部署的,因为我们访问的一定是tomcat下的东西,我们访问的是tomcat,8080的,我们访问的是tomcat,不是源代码的,所以,以后我们再说路径,路径的时候,不要条件反射,去看源代码,这是错误的行为,你要去想,tomcat这边的东西,看的是tomcat这,不是源代码,两边不一样。
  • 再强调一下,就是所谓的访问路径都是指部署代码的访问路径,并不是这个源代码的访问路径, 所谓的路径,指的是部署代码的路径,你要把这个前提搞清楚,然后呢,部署代码的访问路径,有什么规则呢,规则很明显,就是部署代码的路径规则,第一种是静态资源,它跟别的不一样,第二种呢是动态资源,那么,静态资源和动态资源的路径的规则是不一样的,那么静态资源指的是什么呢,指的是HTML/CSS/IMG(图片)/JS,等等,就这些我们前端的,能够直接书写,直接运行的内容,我们都称为静态资源,那么静态资源的访问路径是,就是文件存放的位置,静态资源的访问路径就是文件存放的位置,比如咱们访问这个,访问这个EmpManager里边的add_emp.html,是不是直接访问的,它就存在EmpManager之下,这是直接访问的,当然了,目前呢,我们的案例比较简单,还没有这个样式图片js,以后会有,到时候再说,这和以后也有关系,而动态资源呢,它的访问路径是什么呢,它不是它存放的位置,它是web.xml中配置的路径,那动态资源我们指的是谁呢,Servlet,就这两种情况。那么静态资源,是文件存放的位置,动态资源是配置的路径,听没有别的了,就是这样了。
    在这里插入图片描述
  • 那么,这个关于路径,咱们这么去理解呢,你看有的文件,它的路径是位置,有的文件,它的路径是配置的路径,那笼统的来说,我们应该怎去理解路径呢,我们更应该把路径呢,理解为,这个文件的一个资源的,这个资源的网上的一个名字, 我们不应该把路径理解为位置,应该把路径理解为一个名字,别管它到底是不是位置,我们都把它理解为名字,举个例子,你比如说兰州拉面,兰州拉面,一定在兰州拉,北京拉不出来么,去楼下拉,那你还吃得进去么,一会儿,是吧,兰州拉面,这个兰州呢,它不代表位置,它只是一个名字,那重庆小面,还有什么,扬州炒饭对吧,这个不是代表它的位置,不是代表地址,只代表名称而已,同样道理,我们通常所说的路径,都是指的是网络上,互联网上某一个资源,它的名字,我们更应该是这样理解。当然,你尽量去体会吧,可能一时半会体会不上来,慢慢体会。

获取访问路径及代码演示

  • 我们所说的访问路径指的是,部署代码的访问路径,因为我们是Tomcat访问的这个程序,访问的这个项目,我们访问的是Tomcat之下部署的代码的这个路径,那么部署的过程有四步,这里面的一些细节要了解,然后呢,有些时候,我们遇到一些问题,其实呢,可以用这个来解释,比如说有的时候,我这个程序开发完了,我一部署没有任何变化,没有任何效果,那你考虑一下,是不是它没有自动编译呢,是不是呢,你不小心把那个自动编译的勾去掉了呢,那么如果你没有自动编译的话,部署的时候,这个java文件其实没部署过去,少东西了,就有问题。那再有一个,就是说,你没有使用maven导包,你要是手动导的包,导完包以后,你手动导的包,它是不会拷贝过去的,所以tomcat里没有,所以一运行这个项目也会有问题,所以你也要处理。总之呢,这个部署的细节了解了,那么遇到一些问题呢,也有章可循,好解决。

  • 然后呢,所说的路径,都是指部署代码的访问路径,那这个部署代码的访问路径有两种情况,一种情况是静态的资源,静态资源指的是静态网页,CSS,图片,JS,一切我们可以直接打开,直接看的文件,这都是;这个静态资源,包括什么word啊,excel啊,都算。那么静态资源的访问路径呢,就是它存放的位置,其次呢是动态资源,动态资源就是Servlet。那动态资源,因为它是class,我们是不能直接访问的,我们需要通过它配置的路径加以访问,就两种情况,你把它记住。这是关于这个部署的过程,以及路径指的到底是什么。

  • 那下一个话题,还是访问路径,我们在程序当中,如果说我想获得当前的访问路径,那怎么获得呢,下一个话题,如何获取访问路径,我们想获取访问路径,怎么获取来着,有一个方法,哪个方法,在案例中所有演示了,在 Servlet2/HTTP协议/Servlet如何处理HTTP协议 中,我们用request读取了请求的一些数据 ,其中请求行,包括了什么协议,包括请求方式,还有一个就是访问路径,那当时,我们获取那个访问路径,调用的方法是,req.getServletPath();,但其实呢,不止是这一个方法能够取到访问路径,还有其他的方法,那当然了,每一个方法所获取的访问路径有点区别,有的取的是完整的,有的取的是局部的,范围有大有小,那我们在开发时,要用哪一部分,那我们就用哪一个方法,所以下面呢,我们再去说一下 ,我们到底用哪些方式能够取到访问路径。

  • 获取访问路径一共呢,有这样几种方式,我就直接写了吧,一个是req.getContextPath();,这是一个,还有呢,req.getServletPath();,还有呢,req.getRequestURI();,这是一个,以及req.getRequestURL();,那么,这4个方法,我们都能够得到访问路径,

    req.getContextPath();
    req.getServletPath();
    req.getRequestURI();
    req.getRequestURL();

  • 那么,它们的区别是得到的路径的部位是不一样的,或完整或局部。那么,它们之间有什么区别,咱们写一个小例子一看就知道了,那这些方法呢,它都是Request方法,因为这个是浏览器访问服务器发出请求,我们可以获得它请求的访问路径,所以通过request获取。那下面,写一个小例子,通过这4个方法,在控制台输出一下,看看它们的区别。打开Eclipse开发工具,打开以后,新建一个项目,这个项目就演示和路径相关的内容,那么这个项目名呢,我们叫servlet3,把项目建好,点Generate…,把叉去掉,依赖tomcat的包,建项目,去掉叉,导包。那么下面我们在这个项目中,写一个Servlet,然后在这个类当中,我们去获取访问路径,看一下能取到什么东西,那首先呢,我再这个src/main/java的下面,创建个包,然后包内建那个类,包名呢,还叫web,这都是习惯,然后呢,web之下,我们创建一个新的Servlet,那么它的名字叫什么都行吧,我看叫啥好呢,随便写一个,我们叫HelloServlet,随便写一个,继承于HttpServlet,在web这个包下,把HelloServlet这个类建好。

  • 那么创建好这个类以后,需要重写父类的service方法,在这个方法之内处理请求,咱们重写父类的方法,重写方法然后将方法名加以整理,那么在这个方法之内,我们要处理请求,这个请求,我没有具体要求,就随便写,咱们也没有什么业务逻辑,那我们访问这个方法,在这里呢,没有什么业务,我主要是怎么去获取路径,那我就直接写了,获取访问路径,那获取访问路径的方法有4个,我们把这4个路径得到,然后呢,输出到控制台看一下,

    System.out.println(req.getContextPath());
    System.out.println(req.getServletPath());
    System.out.println(req.getRequestURI());
    System.out.println(req.getRequestURI());

  • 那么我们通过request的这4个方法,得到了一些内容,我们把这些内容输出,准备看一眼。那么这个案例,主要的目的就是为了输出这4个方法的值看一下,了解一下访问路径的获取方式,因为没有具体的业务,我就不向浏览器返回具体的这个响应信息了。那么如果在这呢,我没有向浏览器输出响应信息的话,浏览器会怎么样呢,注意它并不会报错,就是我们没有写响应信息,仅仅是说这个浏览器得不到具体的内容,但是服务器依然会向浏览器做出响应,这是自动的,就是服务器会自动的向浏览器做出响应,只不过内容为空,那浏览器端,我们会看到一片空白,那没关系,我们这案例不在乎要得到什么,只是为了看到这些方法的返回值。那在这我写清楚,就是本案例主要演示获取访问路径的方式,所以呢,就不向浏览器输出内容,但是服务器会自动的向浏览器做出响应,只是内容为空而已,浏览器看到一片空白。

  • 总之呢,在演示某个小知识点,小案例的时候,就没有必要输出什么东西,所以就是,只是演示输出一些内容(到控制台),这个不用响应。那这个HelloServlet方法呢,就写完了,主要就是那4句话,输出4个内容,那这个类写完以后呢,我们需要加以配置,那我们再打开这个项目下的配置文件,那在配置文件里,我们把这个HelloServlet加以配置,别名为hello,网名为/hello,

    <servlet>
    --<servlet-name>hello</servlet-name>
    --<servlet-class>web.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
    --<servlet-name>hello</servlet-name>
    --<url-pattern>/hello</url-pattern>
    </servlet-mapping>

  • 那么,我们把这个Servlet配置好以后,把这个项目部署一下,然后呢,我们去访问这个Servlet,访问以后,看一下这个控制台输出的那4个值到底是什么,试一下,部署一下这个项目,部署以后,启动Tomcat,启动以后,打开浏览器,访问这个网名,那项目名叫servlet3,然后呢,访问路径是/hello,浏览器地址栏输入,http://localhost:8080/servlet3/hello,一回车以后,我们访问了服务器,然后我们在Servlet当中,没有输出响应信息,但是呢,服务器最终也会给浏览器一个响应,只不过呢,任何内容都没有,是一片空白。我们不关注浏览器给网页返回的内容,我们只看控制台,看它输出的这4个值。第一个值,第一个值我们调的方法是什么呢,我们第一个调的方法是getContextPath()ContextPath是项目名/servlet3,然后呢,我们第二个调用的方法是getServletPath(),它返回的是Servlet的访问路径,/hello,那我们第3个调的方法是getRequestURI(),我们得到的是绝对路径,/servlet3/hello,第4个调的方法是getRequestURL(),差一个字母,那么,这个方法返回的是完整路径,http://localhost:8080/servlet3/hello

    /servlet3
    /hello
    /servlet3/hello
    http://localhost:8080/servlet3/hello

  • 那么经过观察以后,我们得出了一定的结论,归纳一下。第一个方法getContextPath(),我们得到的是什么呢,项目名,第2个方法getServletPath(),得到的是Servlet访问路径,Servlet访问路径,或者说Servelt网名,第3个方法getRequestURI(),我们得到的是什么呢,我们通常是URI,就叫URI,不用翻译,因为翻译过来的话,会比较别扭,第4个getRequestURL(),就是什么呢,URL。

    项目名:getContextPath()
    Servlet访问路径: getServletPath()
    URI:getRequestURI()
    URL: getRequestURL()

  • 代码演示:


package web;

import java.io.IOException;

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

public class HelloServlet extends HttpServlet {

	@Override
	protected void service(
		HttpServletRequest req, 
		HttpServletResponse res) throws ServletException, IOException {
		//获取访问路径
		System.out.println(req.getContextPath());
		System.out.println(req.getServletPath());
		System.out.println(req.getRequestURI());
		System.out.println(req.getRequestURL());
		//本案例主要演示获取访问路径的方式,就不向浏览器输出内容了。
		//但服务器会自动的向浏览器作出响应,只是内容为空而已,浏览器看到了一片空白。	
	}
}

URI和URL的区别

  • 那这里面呢,这个URI和URL,这两个词呢,太接近了,那么它们之间呢,是什么关系,有什么区别,我们需要呢,加以介绍,再一个,URI和URL的区别,也是一道面试题,所以,我们有必要了解一下,说一下URI和URL的区别。那么,这个URI和URL,它是一组单词的缩写,这组单词是什么呢,我也没记住,你可以去百度一下(URI=Universal Resource Identifier;URL=Universal Resource Location),不过呢,你会发现,它那个单词翻译过来,直译过来很别扭,是非常别扭的,所以呢,你就别去看那个翻译了,我只要记住通俗的理解就可以了,而不用记住它的学名,太抽象了。那我们理解这两个内容的话呢,从两个角度去理解,第一个角度是,狭义的web项目,就狭义的理解;第二种呢是,广义的理解。那我们面试时,我们回答的是狭义的,还是广义的呢,那肯定要回答广义的,就是,那还是要回答广义的,这个广义的是重要的。狭义的理解只是我们表面现象,但它背后的原始的逻辑,或者说,发明这个词汇的那个人的真实的想法,是广义的理解。
  • 狭义的理解就是什么呢,就是我们单纯的在java项目中去看,就只是在java项目中去看,Java WEB项目,那么在Java WEB项目里,刚才所演示的就是java的web项目,我们看到了URI是什么呢,它是一个绝对路径,因为它是一个斜线,项目名,斜线,网名,这是绝对路径的格式,然后呢,URI是什么呢,是完整路径,但是呢,广义的理解,或者说,这个URI和URL的设计者,他的本意不是这样,它的本意不是想这样表达,说URI是绝对路径,URL呢,是完整路径,不是这意思,那广义的理解是什么意思呢,或者说,什么叫广义的理解呢,就是,任意的web项目,在任意的web项目下去理解它,那任意的web项目,就包括java项目,.net项目,php项目,python项目等等,只要是web项目,别管是什么语言的,其实都有这个概念,就互联网上的项目,都有URI和URL的概念,不管是什么项目当中,这个URI和URL,我们怎么去理解,总体的理解。
  • 那么,在广义的理解上呢,URI,这个设计认为,它是这个意思,就是资源的名字,URI是资源的名字,比如说,我们访问的是网页,就是网页的名字,那URL呢,是资源的真名,有人说你这,有区别么,这有区别,名字和真名是不一样的,举个例子,这个,像苍老师,有很多名字,但真名是不是只有一个呢,刘苍松,是吧,真名只有一个,刘苍松,但他的名字有很多,刘苍松是一个,苍老师是一个,什么二蛋是一个,等等,那这很多了。所以说,你看,真名和名字,这两者之间是什么关系,谁包含谁呢,是名字包含真名,真名是众多名字中的一个,是这意思吧,所以,得出一个结论,就是URI包含URL。但是我们在上面,在狭义的理解上,我们会发现,表面上看是,URL包含URI, 但是,它的本意,是URI是资源的名字,URL是资源的真名,那名字是包括真名的,是这样的,面试时问到了,你就把这个就是你的理解,你就这样理解的,一说就可以了,所以这个要了解,就是任意的web项目中,这个URI和URL我们怎么理解,是这样的去理解,狭义的理解,URI:绝对路径,URL:完整路径,就是表面现象,不是本质。

配置Servlet访问路径的3种方式

  • 那现在我们了解了项目部署的过程,我们知道了怎获取访问路径,以及呢,访问路径当中,URI和URL,这两者的区别,那么下一个话题,我们探讨什么呢,探讨一下Servlet的访问路径,它如何进行声明,如何配置Servlet访问路径,其实Servlet的访问路径,一共有3种配置方式,我们在此之前,只是演示了第一种,还有两种,那么,这个不同的配置方式,会使得Servlet这个组件,它的处理能力就不一样,那么,我们之前所使用的第一种方式,斜线一个名字,这种方式,它的处理能力是极为有限的,而另外的两种方式,会增强它的处理能力,那我们把这个事呢,说一下。如何配置Servlet访问路径,有3种方式,第一种方式,我们称之为精确匹配。
  • 举个例子,比如说,上面HelloServlet这个案例,我将那个Servlet访问路径,设置为/hello,是这样写的,那这种方式,我一定得通过hello才能够访问到这个Servlet,我写个hi,写个其他的单词都不行,就是必须通过/hello才能访问此Servlet,那反过来讲,我们也可以这样理解,那这个Servlet,它只能处理/hello这一个请求,只有/hello能访问它,它只能处理/hello这一个请求,第二个请求都处理不了,所以,这种方式,它的处理能力是极为有限的,只能处理一个请求,这是它的特点,叫精确匹配。那之前所演示的都是这种方式,我们用这种方式处理请求,那就必须一个请求写一个Servlet,一个请求写一个组件,有点麻烦,有点多了。
  • 那再看第二种方式,第二种方式叫通配符,举个例子。我可以不写/hello,这样的单词,我写斜线什么呢,斜线星,/*,星是通配符,星能代表一切,如果我配置Servlet访问路径时,我访问了斜线星,啥意思呢,是通过任何路径都能访问此Servlet,那星代表一切,我写个hi,写个hello,写个abc,都可以,就是说这种情况下,你通过任何路径,都能访问这个组件,那反过来讲,这个组件Servlet,它能处理一切请求,它的处理能力范围极大,是这样的。那当然了,那它怎么处理一切请求呢,这还有一定的这个,就是一定的经验,这种配置方式,是我们运用这个经验,处理多个请求的前提,先把这个前提搞定,再说经验是什么,这是第二种方式。
  • 然后呢最后,还有第3种方式叫后缀。举个例子比如说,*.abc,注意,当我们采用后缀的方式,配置访问路径时,前面不要写斜线,我没有写斜线,不能写斜线,那是啥意思呢,就是说,我们必须通过,以.abc为后缀的请求都能访问此Servlet,反过来讲,此Servlet能够处理,它不是一切,因为我换个后缀,换个比如说,.hello,它能处理么,处理不了,它只能处理.abc,后缀为abc的,它能处理,不是一切,但也不是一个,是多个,它能处理多个请求,那abc之前是什么都行,你hello.abchi.abc你好.abc,都行,前面无所谓,但只要后缀是abc就能处理,所以这种情况,这个Servlet类,能够处理多个请求。
  • 总而言之,就是我们配置Servelt访问路径3种方式,后两种方式,是我们现在重点要说的,那这两种方式呢,都能够增强Servlet处理请求的能力,第二种方式,这个组件能处理一切请求,第3种方式,能处理多个请求,那到底行不行,咱们试试看,我们把它试明白了,了解了,再去介绍它到底处理所有请求,到底怎么处理,详细的使用方式。
  • 那么回到servlet3的HelloServlet案例,打开Eclipse,还是这个项目servlet3,然后,直接打开这个项目之下的配置文件,web.xml,之前我在配置HelloServlet时,我的路径是斜线hello,/hello,这是第一种方式,叫精确匹配,只有/hello才能访问它,给它写个注释,第一种方式,这是精确匹配,那现在演示其他的方式,我把这种方式注释掉,把<url-pattern>/hello</url-pattern>这句话,注释掉。注释掉以后,下面演示第二种方式,第二种方式是通配符,<url-pattern>/*</url-pattern>,那么,这回没有写明确的这个名字,我写了个斜线星,/*,代表一切,一切单词都能访问这个组件。写完之后,我们试一下,把这项目,再重新部署一下,部署完以后,启动Tomcat,启动完以后,打开浏览器,我们去访问这个Servlet,这回你写什么路径都可以,比如说我还是/hello,看控制台可以,可以访问。
  • 但是呢,有个小小的问题,那当我们配置/*的时候,我们访问这个服务器时,第二个ServletPath,Servlet访问路径: getServletPath(),咱们没得到,那这种情况,就有这样的问题,这不知道是bug,还是什么原因,反正就是这种情况,得不到,不过也没关系,你要非得想要这个Servlet访问路径,你是你是不是可以用这个URI,/servlet3/hello,去掉ContextPath这部分,/servlet3,是可以的,你可以用它URI减去它ContextPath,怎么减呢,这字符串replace,你把URI里面的ContextPath替换掉就可以了。这是hello,你换个单词也可以,再试一下,比如说,我把它改为不是hello,改为hi,localhost:8080/servlet3/hi再看,依然可以访问,我再把它改一个,比如说改成nihao,localhost:8080/servlet3/nihao,再试一下,然后呢再看控制台,也可以。
  • 那经过测试,我们发现,我们只要这样去配置访问路径,写任何的路径,都能访问到,万能的,所以这种情况下,这个Servlet,它的处理请求的能力,就会加强。那下面呢,再演示第3种方式。把第二种方式注释掉,这个先不用了,注释掉,注释掉以后,写第三种方式,后缀,<url-pattern>*.abc</url-pattern>,那么,我所规定的后缀是abc,写成什么都行,然后呢,一定要注意,这种方式前面是不能写斜线的,这是要求,前面不要写斜线,那写完以后,再试一下,把这项目再重新部署一下,然后呢,重启Tomcat,启动以后,那试一下,打开这浏览器,随便输入一个路径,比如说,ok.abc,localhost:8080/servlet3/ok.abc,回车看控制台,可以得到没问题,再换一个,比如说弄一个,还是hello吧,hello.abclocalhost:8080/servlet3/hello.abc,回车也可以,那么如果后缀不是abc就不行了,试试别的,比如说,hello.duanglocalhost:8080/servlet3/hello.duang,这就找不到了,服务器处理不了,没有这个能力,它不管啊,duang不行,那试个别的,比如说再来一个hello.duilocalhost:8080/servlet3/hello.dui也不行。经过测试呢,发现确实是这样,我们必须写这样的后缀,才能够访问到这个组件,否则就不行。

  <servlet>
  	<servlet-name>hello</servlet-name>
  	<servlet-class>web.HelloServlet</servlet-class>
  </servlet>
  <servlet-mapping>
  	<servlet-name>hello</servlet-name>
  	<!-- 精确匹配 -->
  	<!-- <url-pattern>/hello</url-pattern> -->
  	<!-- 2.通配符 -->
  	<!-- <url-pattern>/*</url-pattern> -->
  	<!-- 3.后缀 -->
  	<url-pattern>*.abc</url-pattern>
  </servlet-mapping>

  • 那访问路径的配置方式,先演示到这个,非常容易,很简单,很容易理解,那重点是什么呢,就是说,后两种方式,后缀或通配符,怎么去用,那下面说一说,使用后两种方式,处理请求的这个思路,所以我们第5个话题,说的是什么呢,就是,后两种方式讲的是什么呢,就是我们怎么去用一个组件,处理很多个请求,通配符也好,后缀也好,咱们用一个组件,是不是都能处理多个请求,甚至是所有请求,所以后两种情况想说的就是这样一个话题,我们如何用这种配置方式,用一个组件处理多个请求,所以解释一下怎么处理,就是如何使用一个Servlet处理多个请求,那我们想用一个组件处理多个请求的前提,你得要采用第2种方式,或第3种方式去配,你用第一种方式肯定是不行,要么用通配符,要么用后缀,是这样的。
  • 那我们可以用第二种方式,可以用第3种方式处理多个请求,那这两者有区别么,很明显是有区别的,那哪种情况用第2种呢,如果你想用一个组件处理所有请求,我们这个项目中只有一个Servlet ,那你就用第2种,不过这种方式也不太好,因为一个类处理太多的请求,它也乱,最好还是什么呢,就是说一个类处理多个请求,处理一部分,咱们一共写个十几,二十个Servlet处理所有的就可以了,所以最好是那样,所以一般呢,更流行的,还是第3种,那第3种也是很灵活的,就是说,我用一个组件比如说处理后缀为abc的请求,我可不可以把项目中的所有请求都定义为abc呢,可以,那这样我就这个类,就能处理所有请求,也可以实现处理所有请求,那我可不可以这样,比如说,一个模块就一种后缀,员工模块后缀为emp,部门模块后缀为dpt,那这样的话,一个类可以处理一个模块的请求,也行,所以第3种方式呢,非常灵活,是我们所推荐的方式,那么我们通过定义后缀就可以呢,让这个类处理多少个请求都行,所有的也可以,一部分也行,非常灵活。

4.配置多请求的Servlet访问路径

如何保证一个Servlet处理多个请求

  • 那如何使用一个组件处理多个请求,那下面我们探讨的是什么呢,就是这个Servlet组件,它里面的代码怎么写才能够保障处理多个请求。画图说明,假如说这个方块是Servlet里面的方法,那这个代码我怎么去书写, 才能够让它去处理多个请求呢,就探讨这样一个话题 。
    在这里插入图片描述
  • 那我们处理请求的话,调用的service方法,我简单写,它有参数,参数分别是request,response,可以简单写req和res,那在这个方法之内,我们怎么去处理多个请求呢,是这样的,我们首先呢,通过request获得当前的访问路径,我们先获取访问路径,获取访问路径有4种方式,getContextPath()getServletPath()getRequestURI()getRequestURL(),虽然我们一般最常用的还是这个,getServletPath(),但就写笼统点,getXxxPath()吧,反正就是获取某一个访问路径。首先我获得了访问路径,获得访问路径以后,我对路径做个判断,如果说,这个path p,这个路径,它假设等于/find,假设当前用户的访问路径是find,那我就执行查询,那具体查询逻辑,就不写了,再判断,否则,else if,也有可能呢,刚才的path是/add,增加,那么如果是增加的话,我就写增加的逻辑,以此类推,大概是这个流程,是这个意思。

一个Servlet处理多个请求的前提

  • 就总之,我们想让一个类处理多个请求,第一个是你是要把它的路径配置成通配符,或者后缀的方式,那这个类的代码,应该是这样写,就是先获取访问路径,当前的路径取到以后,对路径加以判断,看它是什么,是find就查询,是add就增加,以此类推,大概是这个意思 。那有人可能会有想法说,那你凭什么判断,它等于find,等于add呢,那路径是随便输出的,还有可能输出hello,nihao,任意的,你凭啥就写,它等于find,就写它等于add,这个依据是什么,就这个find,这个add,从哪来,你咋知道会有find,你咋知道会有add呢,用户是可能会输入任何路径,那么多路径,你怎么就能判断是find和add呢,用户怎么就能猜对是要写这个find和add呢,那到底是怎么回事呢,是这样的。
  • 我们在写出这段代码之前,应该先有什么呢,先有规范,必须先有开发规范,规范中规定了我们这个软件,一定有多少个请求,那每一个请求,它的访问路径是什么,后缀是怎么怎么样,规定好了,比如说,有查询功能,路径是/find,增加功能路径是/add,我是按照规范去做这个增加和查询,find就是查询,add就是增加,如果说,规范上没有这个路径,是用户瞎敲的,是错的,我就不管了我们能够写出判断,是有前提和依据的,前提是开发规范,那很多企业呢,它并不正规,尤其是小企业,它就没有规范,口头说,说完之后出了问题,然后不买账了,有的小企业就是这样,但是也没有办法。那即便是口头说,也算是规范,项目经理告诉你,你要这样这样,那也是个规范,只是不太正规而已,那正规的公司呢,它是要有这个详细规范文档的,包括路径,包括这个异常怎么处理,包括命名,都有规范,包括sql怎么写都有规定,sql的格式都有规定,非常详细。所以呢,在此之前,应该是有规范。
  • 这个规范谁来写呢,一般情况下,是你到这个项目组以后早就有了,是之前的前辈们写的,是项目经理写的,但未必都是他写的,有可能是,项目经理从它以前的项目中直接带过来的,稍微改了改,根据公司情况改了改,是这样的,也有可能呢,是项目经理现写的,那都不一定,还有可能是什么呢,我们这项目组刚成立,大家都是新人,刚启动,这一行代码都没有呢,然后这个项目甚至还没谈妥,还在谈的过程中,我们现在还有一两个礼拜时间比较闲,怎么办呢,咱们去写规范吧,一起写,一人分点,也可以这样,反正不同公司,不同情况,总而言之,这个规范是有人写,那在此之前,就得有规范,开发规范,那开发规范中明确规定,它规定了这个查询功能路径是/find,比如说规定了这个增加功能路径是/add,其他的没有写,假设是这样,那这个规范一定是在我们写正式代码之前就有。
  • 所以它们的步骤是这样的,第一步是先有规范,第二步,才是去写这个Servlet类,写这个Servlet类的时候,根据规范去做出一些判断,然后,那这个类写完以后,我们得对它加以配置,那配置时呢,我们会在 web.xml里,写那句话,叫<url-pattern>/* 或 *.abc</url-pattern>
    在这里插入图片描述
  • url-pattern里呢,我用第二种方式和第3种方式都可以,这是笼统的说,第二种方式通配符,/*可以输出一切,第3种方式星点后缀,后缀我就随写一个,假设就abc吧,*.abc,就举个例子,这里没有明确,这是第3步,我们对它做配置。那这个规范也有了,我们根据规范呢,写出了这样的代码,然后呢,我们针对这段代码,做出了相关的配置,这些都写完以后,用户呢,可以发起访问了,那么用户呢,可能会输入路径,去访问我们这个方法,那比如说它的路径,假设就是/find,它路径就是/find,是可以访问到的,那假如它的路径不是/find,是这个/add,也可以访问到,只要你满足我的规范,就能访问到。那反过来,如果它随便瞎写一个,它写了一个hello,还能访问到么,访问不到,因为规范中没有,我们也没处理,那这种访问不到它,怎么办呢,咱们统统报异常,报错就可以了。所以最终啊,第3种情况,不满足规范,报叉了,这是访问时,第4步,用户的访问时,这个的结果。
    在这里插入图片描述
  • 那有人可能会想,那用户也不知道你的规范,它输入路径时,它怎么知道输入啥呢,那不分分钟都得错么,那还能对么,那我们使用互联网上的某个软件,比如说我使用淘宝,我是随时随地都要敲个路径么,我是老需要路径么,我不用,我只要敲它的首页,甚至首页,我还不是敲的,我甚至可能是通过百度搜到的,通过我的收藏得到的,通过hao123推荐的,我甚至都不用敲,总之,我们访问一个软件,我首页要敲一个地址,进入首页以后,我们点什么注册啊,登录啊,保存啊,都是点按钮,点超链接,所以这个路径,只是首页需要敲,其他的都在那个按钮上,超链接上写死了,这个谁写的呢,程序员写的,程序员一定会按照规范写,把它写对了,所以不用担心这个事,用户在使用这个软件,不可能说随手去敲路径,敲路径去访问这个东西,那怎么玩啊,不是那样的,路径是程序员,在按钮里,在超链接里,根据规范写上去的,他一定是写的是对的,但如果说路径它非得不敲,不点这个按钮,不点超链接,他非要瞎敲路径报错了,赖谁呢,赖用户呗,比如说,我访问淘宝,我瞎敲,我就www.taobao.com/abc/def,下级路径,我就瞎写一个/abc/def,这玩意能有么,没有,那我写错了,报错了,这赖我,这不赖淘宝,因为没有这个东西,我瞎敲的。
  • 那以上就是使用一个处理多个请求的,大概的一个思路,那么重中之重,不在于这个代码怎么写,这个代码不就是个判断么,这没有什么高难的,那一定要重视什么呢,这一步之前的这个规范,很重要,不要忽视规范的作用,没有这个规范,这个代码无从写起。所以Servlet访问路径的后两种方式,确实能够增强这个组件的处理能力,那么我们想用这种方式,用一个组件处理多个请求的前提是,一定得有规范,没有规范,这代码没法写,有了规范就好办了,我们按照规范去判断路径,处理请求就行了,然后呢,配置时,或者选择第2种方式通配符,或者选择第3种方式后缀,都可以,但我个人呢,更建议用第3种方式,因为它更为灵活,然后呢,用户在访问时,其实用户往往不会手动去敲这个路径,他往往都是点按钮,点超链接,而按钮和超链接上的路径,是我们程序员呢,开发时写上去的,那我们写的时候呢,肯定也是根据规范写的,进而去根据规范处理的,就是这样。

员工管理案例的功能重构与设计

  • 那下面我们根据上述的思路,在这个项目EmpManager当中演示它具体怎么应用,回到这个EmpManager这个项目,这个项目已经实现了员工查询和员工增加两个功能,因为当时咱们没有掌握这种一个Servlet组件处理多个请求的方式,我们就是 一个请求写了一个Servlet组件,我们做了两个请求写了两个组件,一个是查询,一个是增加,那如果这个项目做大,做完整的话,会有更多请求,每个请求一个组件,组件太多了,然后,每个组件都要实例化,这内存中存的对象太多了,再一个呢,看起来也乱。现在呢,我想对这个项目加以改造,我希望呢,这回我们用一个Servlet处理所有请求,把原来的这两个Servlet替换掉,是这样的,就总而言之,就是在这个项目内运用这个内容,用一个类处理多个请求。

  • 那怎么处理呢,我的要求是这样,我希望啊,咱们一个Servlet处理一个模块的请求,我们刚才都是做的员工什么什么,要是做完整的话,可能还有部门增删改查,可能还有公司的增删改查,可能还有员工的绩效的查询,员工的考勤的查看,等等,这个很多功能了,但我们只做这一点点,那么我希望呢,就是说,每一个模块我们写一个Servlet,或者说一个组件处理一个模块的请求,那么这个员工模块我希望呢,我们用后缀的方式,它的后缀是emp,员工模块,后缀为emp;如果有其他模块,后缀再定义,那就可以了。那大概是这样一个思路,那我们写的话,我是再新建一个类,还是说我再利用FindEmpServlet,或者是AddEmpServlet这两个类去改造,怎么办好呢,最好还是新写一个。

  • 我们在重构代码时,该重写要重写,不要老想着去改,有些时候是这样,我们做一个比较大的项目,我们改的话,有时候会改一半之后,改也改不下去了,然后呢,退又退不回去 了,就很尴尬,我就遇到过这种事,那我们改之前呢,还得把这个项目备份好,那万一说没改明白,改死了,再把它弄回来,所以还得备份一下,然后的话呢,不要直接上来就改,你万一说哪块改完之后,都改乱套了,改忘了,改串了,不好解决,所以这个地方我们重写一个类,但重写一个类的前提,是将原来这两个类得作废掉,那我直接删掉不合适,怎么把它作废掉,有人说,我能不能把这个类,挪到那个test包下呢,那倒是也可以,但这样的动作有点大,其实有一种方案。

  • 我们可以把这个配置文件当中的,关于这个类的配置注释掉,类也不改,我把配置注释掉以后,这个类是不是就是失效了呢,因为我们在网络上访问这两个组件是通过网名访问,如果网名已经不存在了,这个两个类有和没有,有什么区别么,没有区别,就无效了,所以把它注释掉,把这个web.xml中的,这两个类的配置注释掉就可以了。把这个/findEmp和/addEmp,这两段代码注释掉,那么注释掉以后,再创建一个新的Servlet,然后呢,在这个Servlet之内处理多个请求,我希望用这个类处理这个项目下所有和员工相关的请求,那我们选择web这个包,右键new一个类,这个类的名字,起名叫EmpServlet,那这个名字是比较合理的啊,因为这是员工的Servlet,它里面包含了员工这个模块相关请求的处理,之前创建建的类FindEmpServlet和AddEmpServlet,前面带有动作,什么find,add,就限制死了,这个没有限制死,什么动作都支持,就这个意思。然后呢,完成。

  • 完成以后,我们在这个类当中要想处理请求,那还是要重写父类的方法,重写父类的service方法,对这个方法加以整理。那这个类创建好了,方法也整理好了,那它里面的代码怎么去写呢,按照之前我们所分析的,我们在写的时候,需要先获取访问路径,然后对路径加以判断,那现在我获取路径获取谁,我判断是判断谁,那4个方法都能取到路径,我取哪个方法呢,然后我判断,我和谁相比,有依据么目前,还没有,还没有写这个规范呢,那之前没有规范,现写一个,当然,我这个规范呢,写的不那么正式,在注释里写,没有那个专门的文档,就意思一下。在这个类的注释里写上这个规则规范。那么我的要求,是查询员工的路径是/find.emp,然后,增加员工的路径是/add.emp,这是我的要求,其他的就没有了。所以,如果是这两个路径之一,我们要处理,如果不是,就不处理。

  • 那咱们就处理吧,回到这个service方法,方法之内,我要获取到请求路径,然后判断是不是这两个规范的路径之一,那我取路径,我调哪个方法来取,调ServletPath,因为什么呢,ContextPath肯定不对,那是项目名,不是这个,然后呢,URI是绝对路径,带有项目名,也不是这个,那URL是完整路径,也不是,只有ServletPath是Servlet访问路径,就是它。那我就获取,就是通过判断路径处理请求,调的是这个方法,String path = req.getServeltPath(),那么取到路径以后,咱们根据这个规范,对路径加以判断:

    String path = req.getServeltPath();
    if("/find.emp".equals(path)) {

    } else if("/add.emp".equals(path)) {

    } else {

    }

  • 如果路径是/find.emp,那我们就执行查询的逻辑,如果路径是/add.emp,就执行增加的逻辑,否则,既不是这个,也不是那个,不满足规范就不处理,不处理怎么办呢,我们抛异常,给提示查无此页,那么如果路径是对的,我们要分别处理,那这回处理的话,没有必要把之前的查询,增加的代码再重新写一遍,之前写过,我们可以 把之前的写过的代码,把它拷贝过来,打开FindEmpServlet,然后找到这个service方法,把整个方法拷贝过去,拷贝过去以后,改个名字调用就可以了,当然copy的时候,不要带这个@Override注解, 因为这表示重写的意思,那我现在拷贝过去要改名,不重写,把整个service方法复制一下,复制好以后,再打开EmpServlet,然后把这一段代码,粘贴到service方法之后,粘贴到后面,那粘贴过来以后报错,为啥呢,同名了,重名了,我们把这方法改个名字,这个方法的逻辑是查询员工,方法改名叫findEmp,那然后呢,再回到上面写判断的地方,那如果路径是这个/find.emp,我们就得调这个方法findEmp,在第,一个if执行体里调这个findEmp方法,然后把参数传给它:findEmp(req,res);

  • 那么同样的方式,如果是/add.emp,把那个增加的方法调一下,而增加的方法,需要在另外的一个类copy过来,把这个AddEmpServlet打开,然后把它里面这service方法整个copy一下,copy完以后,再回到EmpServlet,把这个方法copy到后面,粘贴到后面去,那同样粘贴完以后重名了,需要改名,那这个方法改名为addEmp,那么copy完以后,再回到上面的service方法那个判断的地方,如果路径是/add.emp,调这个addEmp方法。总而言之,就是当前这个类,它只有一个service,那么service方法的内部,我们对请求部分加以判断,对判断的结果,我们调用了不同的方法,或者是findEmp,或者是addEmp,那么这两个方法,是我们从另外的两个类copy过来的,copy过来改了名字,直接调用就可以了。

  • 那肯定有人会想,说这不多此一举么,你为何要拷过来,直接调用那俩类,不也可以么,是不是也可以,也行,那我为什么没有调用那两个类呢,就这样讲吧,咱们将来在工作的时候,你认为AddEmpServlet和FindEmpServlet,它会存在么,会有么,根本就没有,我们直接就写出这个EmpServlet,直接就写它,就不会有Add和Find这两个东西,因为我是按照将来工作时的实际情况,那个开发的方式来写的,尽量贴近于最终的那个现实,最终的那个状态,而不是说,简单的那么一调,将来Add和Find这东西都没有,做项目时就会看到,这都没有。所以呢,就当这两个类不存在就好了,那这样写完以后,就行了么,还差点什么别的事么,配置文件是不是得写,还得写配置文件。你打开配置文件web.xml,再配置一个Servlet,注意路径的配置方式:


  <servlet>
  	<servlet-name>emp</servlet-name>
  	<servlet-class>web.EmpServlet</servlet-class>
  </servlet>
  <servlet-mapping>
  	<servlet-name>emp</servlet-name>
  	<url-pattern>*.emp</url-pattern>
  </servlet-mapping>

  • 配置完以后,注意最终的路径的配置,写的是*.emp,是以emp为后缀,表示说这个类,它能够处理emp员工模块的请求,然后呢,在配置路径的时候,这种方式不要写斜线啊,后缀的方式没有斜线,这样就可以了么,其实还不行,哪不行呢,咱们的程序当中,有些地方,代码里有路径,那那个路径是旧的方式,现在得改成新的,是不是要改一遍,我们还得把代码,重头到尾过一遍,哪个地方有路径,都要加以修改,所以你会发现,代码一旦写完了,我们想去改造的话,挺麻烦的,你得从头到尾看一遍,而且呢,还得测试测一遍,那我们这还好,只是做了两个请求,如果工作时,这个项目比较大,我们写了一千个请求了,都写完了,然后你想改造,难度可想而知了,你改的时间也很长,测的时间也很长,所以代码呢,一旦写完以后,想去从头到尾改一遍,这几乎就是不可能的事情,所以说,我们要一开始就把这个代码设计好,写好,否则的话呢,将来,容易失控。

  • 那回到这个EmpServlet,从头到尾检查一遍,看哪些地方有路径,看看有没有不对的地方,那service方法不用看,这是按照最新的路径判断的,这个没有问题,我们是看后面,这个findEmp这个方法,有没有路径呢,真有,这个超链有路径,不过路径指向的是静态页面,它的路径没变,静态网页没改,这个不用动,再往后看,后面就没了,然后呢,增加的时候,有么,有,最后我们做的是重定向,那以前呢,我们的路径是这样的,/EmpManager/addEmp/EmpManager/findEmp,现在是什么样的呢,变了,现在是

    当前:/EmpManager/add.emp
    目标:/EmpManager/find.emp

  • 所以得写成什么呢,find.emp,直接改一下,res.sendRedirect("find.emp");,那这个类呢,改完还不够,咱这个项目里呢,还有一个静态页面,就是add_emp.html,那个静态网页当中也有路径,也要看一下,我们再打开那个静态页面,add_emp.html,好之前呢,我们访问的路径是/addEmp对吧,现在变了,变成了/add.emp,所以也需要要加以修改,这回改完了,应该是没有了。那下面呢,我们可以就是测试了,把这个项目重新部署一下,然后启动Tomcat,打开浏览器去访问一下这个查询,访问的时候,注意路径变了,现在最新的路径是什么呢,是/EmpManager/find.emplocalhost:8080/EmpManager/find.emp,这是查询,再点增加的超链接看一下,打开增加网页,然后呢,填数据,填完数据呢,点保存,又回到了这个查询页面,表示说呢,这是没问题,对了。

员工管理案例重构代码实现

EmpServlet
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;

/**
 * 开发规范:
 * 查询员工:/find.emp
 * 增加员工:/add.emp
 * @author fhy
 *
 */
public class EmpServlet extends HttpServlet {

	@Override
	protected void service(
		HttpServletRequest req, 
		HttpServletResponse res) throws ServletException, IOException {
		//判断访问路径并处理请求
		String path = req.getServletPath();
		if("/find.emp".equals(path)) {
			findEmp(req,res);
		} else if("/add.emp".equals(path)) {
			addEmp(req,res);
		} else {
			throw new RuntimeException("查无此页");
		}
	}

	protected void findEmp(
			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
		//EmpServlet改造后:
		//当前:/EmpManager/find.emp
		//目标:/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();
	}
	
	
	protected void addEmp(
			HttpServletRequest req, 
			HttpServletResponse res) throws ServletException, IOException {
			//1.接收参数
			req.setCharacterEncoding("utf-8");
			String ename = req.getParameter("ename");
			String job = req.getParameter("job");
			String sal = req.getParameter("sal");
			//2.保存员工数据
			Emp e = new Emp();
			e.setEname(ename);
			e.setJob(job);
			e.setSal(Double.valueOf(sal));
			new EmpDao().save(e);
			//3.发送响应
//			res.setContentType("text/html;charset=utf-8");
//			PrintWriter w = res.getWriter();
//			w.println("<p>保存成功</p>");
//			w.close();
			//重定向到查询页面,即
			//建议浏览器自己去访问查询页面。
			//当前:/EmpManager/addEmp
			//目标:/EmpManager/findEmp
			//res.sendRedirect("findEmp");
			//EmpServlet改造后:
			//当前:/EmpManager/add.emp
			//目标:/EmpManager/find.emp
			res.sendRedirect("find.emp");
		}
}
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>EmpManager</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>findEmp</servlet-name>
  	<servlet-class>web.FindEmpServlet</servlet-class>
  </servlet>
  <servlet-mapping>
  	<servlet-name>findEmp</servlet-name>
  	<url-pattern>/findEmp</url-pattern>
  </servlet-mapping>
  
  <servlet>
  	<servlet-name>addEmp</servlet-name>
  	<servlet-class>web.AddEmpServlet</servlet-class>
  </servlet>
  <servlet-mapping>
  	<servlet-name>addEmp</servlet-name>
  	<url-pattern>/addEmp</url-pattern>
  </servlet-mapping> -->
  
  <servlet>
  	<servlet-name>emp</servlet-name>
  	<servlet-class>web.EmpServlet</servlet-class>
  </servlet>
  <servlet-mapping>
  	<servlet-name>emp</servlet-name>
  	<url-pattern>*.emp</url-pattern>
  </servlet-mapping>
</web-app>
add_emp.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>增加员工</title>
</head>
<body>
	<!-- 
		当前:/EmpManager/add_emp.html
		目标:/EmpManager/addEmp
		//EmpServlet改造后:
		当前:/EmpManager/add_emp.html
		目标:/EmpManager/add.emp
	 -->
	<form  action="add.emp"  method="post">
		<p>
			姓名:<input type="text"  name="ename"/>
		</p>
		<p>
			职位:<input type="text"  name="job"/>
		</p>
		<p>
			薪资:<input type="text"  name="sal"/>
		</p>
		<p>
			<input type="submit"  value="保存"/>
		</p>
	</form>
</body>
</html>

5.总结

  • 那么,从Servlet1到Servlet3,都是相关Servlet最基础的内容。第一个主要话题Servlet,以及为了引出此话题,而介绍的web项目的发展。发展过程很重要,但所有的过程都是为了结果服务,理解Servlet是什么,就是最终的结果。因为你要不理解什么是Servlet,我们工作时,自然而然想不到使用它,即便是有的同事写出了,你也会说,哎,为什么这个项目要用这个东西解决问题呢,为啥呢,你总有疑问,所以,这个根本的问题一定要解决。那么,第二个重要的内容是Servlet开发步骤,在此之前,也介绍了Tomcat怎么用,这个不是重点,因为这个内容更多的是操作和习惯,熟能生巧。Servlet的开发步骤,或者说怎么去开发一个Servlet,就是写一个类,继承一个父类然后写个service方法,然后呢,怎么做出响应,用response,怎么处理请求,用request,然后它怎么配。操作不难,但操作背后的原理需要加深理解,这样才能更好的理解Servlet开发过程中所遇到问题的原因,并应该怎么去处理和解决,比如404,500等常见的错误。
  • 那第3个重要内容是浏览器和服务器怎么传参,或者说,浏览器如何向服务器传数据,那浏览器表单上得做一些配置,有3项,action,name,value,服务器接收数据很容易,getParameter,getParameterValues,通过request;然后是这个请求方式,GET和POST有什么区别,那GET就是路径传参,参数可见,传输数据小,那POST不用路径传参,参数不可见,那传输数据大。再下一个,第4个内容也是相对重要的,Servlet的运行原理,那什么原理,怎么运行的,总结成了一个图,那个图挺重要的,脑海中得有那个图,或者你自己能把那个图画出来。后面很多这个事情,很多问题,都是在这个原理的范畴之内去解释的,因为整个项目都是按照这个流程去运行,它有利于我们解释其他的问题。
  • 然后第5个话题,在请求时和响应时出现乱码怎么解决,或者说乱码解决方案。那么请求和响应乱码的根源在于编解码不一致,那么一般呢,都是做一些配置,把它一致就好了,配置方式就那么几种,如果是请求的乱码的话,要么改配置文件,要么写这句话,req.setCharacterEncoding("utf-8");,响应时,在setContentType("text/html;charset=utf-8");,加后半句,就搞定了。最后,还做了查询员工的案例。那个案例的话,就是对这些内容的巩固。然后,又通过增加员工的案例,引出了重定向,重定向就是一个特殊的响应,告诉浏览器你要访问别人,而不是给你直接的结果,仅此而已,就一句话,res.sendRedirect("find.emp");,然后呢,重定向以后的又一重要内容是访问路径,访问路径从这几方面来说,这个怎么部署的啊,路径怎么获取的啊,路径怎么去配置啊,怎么利用这个配置解决问题啊,就这些内容。这些内容都是Servlet最基本的内容,它的基本的用法。

参考文献(References)

文中如有侵权行为,请联系me。。。。。。。。。。。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值