NETCTOSS代码实现第一版

NETCTOSS代码实现第一版:资费查询功能

  • 前言
    • 资费查询功能需求分析与程序设计
      • 根据需求拆分请求
      • 根据请求推导程序内部执行过程
      • 根据执行过程进行代码设计与实现
        • 项目公共业务层之JDBC封装工具类:/util/DBUtil.java
        • 开发思路的重要性
        • 资费模块公共业务层之数据建模实体类:/entity/Cost.java
        • 业务层之业务数据处理与数据存储:/dao/CostDao.java
        • 控制层之业务逻辑处理与控制分发:/web/MainServlet.java
        • 视图层之业务基本处理与视图显示:/WEB-INF/cost/find.jsp
      • 资费查询页面find.jsp存在的问题
        • 动态显示表格数据
        • 表格中日期时间的数据问题
        • JSP页面中的编码问题
        • 浏览器加载网页的过程(JSP页面中的路径引用问题)
        • WEB-INF目录对于JSP与静态资源的作用和意义
      • 资费查询功能代码实现
        • 1.src/main/java/util/DBUtil.java
        • 2.src/main/resources/db.properties
        • 3.src/main/resources/jdbc.properties
        • 4.src/main/java/entity/Cost.java
        • 5.src/main/java/dao/CostDao.java
        • 6.src/main/java/web/MainServlet.java
        • 7.src/main/webapp/WEB-INF/web.xml
        • 8.servlet-doc/NETCTOSS_HTML/fee/fee_list.html
        • 9.src/main/webapp/WEB-INF/cost/find.jsp
  • 写在后面

前言

  • 返回 NETCTOSS

资费查询功能需求分析与程序设计

  • 根据 NETCTOSS 的开发思路,所以,第一步啊,你要搞清楚需求,我在笔记里写一下,就是我们开发的这个流程,开发的思路吧,开发的思路呢,第一步搞需求,需求没搞明白,千万别写代码,很容易写错,第二步呢,需求弄明白了,也不是立刻写代码,而是要怎么样呢,做设计,然后呢,你有了设计,根据设计去开发,必须是这3步,你任何一步缺失了,都会付出巨大的代价啊,不信,你去工作时试试看,那么我们搞需求的话呢,就是说,主要是和需求人员这个沟通需求,然后呢,明确需求,就总之啊,你工作时啊,有问题啊,多问问,有任何这个不明确的地方,多问问,别嫌这个,别嫌烦,或者需求嫌烦了,你也别嫌烦,多问问他。
  • 然后呢,弄明白需求以后,我们做设计,这个设计啊,每个人的方式不一样,有的人画图,有的人写字,有的人呢,在脑海里想,都可以,但是呢,我的建议是什么呢,你最好还是用图和文的方式去展现,不要只想啊,那么一个业务呢,过于复杂的时候,它可能有几十步,你没法把他们都想出来,那你的想法是零散的,是离散的。这一块,那一块,那一块,那一块,不是有机的一个整体,你把它写出来之后啊,这个有点乱,你会整理整理,整理,不断整理,理顺了,哎,这是我的一个设计,所以,一定是用图和文的方式,去呈现。
  • 当然了,咱们工作时这个,看这个企业,有的企业呢,它不用你做设计,有专门的设计人员,给你设计好,给你讲,说你要这么做,说你看,我这图是这么画的,这个流程是这样写的,按照流程去写,尤其是日企,那个设计文档是极其的详尽,你根据设计文档写出的代码,几乎是没有偏差的,但是呢,同时这样的企业,可能是对这个技术的要求也是,不是那么高的,因为设计文档太详尽了,然后呢,还有的企业,就没有专门的设计,你自己写,自己设计,还有的企业呢,也对设计有明确的要求啊,就是你程序员开发之前要做设计,领导告诉你,你的设计要出什么成果,你要出什么文档,比如说,数据推演的文档,比如说,你对这个用例图,比如说你的UML图,比如说你的数据库的这个关系图等等,各种图形,你都得画,那有人说图形怎么画,我不会啊,在工作中现学现卖,因为每个企业要求不一样,没法一概而论,所以说,到企业也是现学现卖的,画图的话,其实有了工具就很简单啊,你看人家怎么花的,照葫芦画瓢画呗,你知道每个图形表达的含义,照的画呗,有固定的规则啊,照着画就可以了。
  • 总之呢,一定是要有这一步,那我的看法是这样的啊,我们怎么去做设计呢,我的习惯是,就是我们先把一个功能,拆分成若干个请求,我的建议是什么呢,把一个功能,先拆分成若干个请求,因为什么呢,对我们来说啊,一个完整的功能,比如说,资费管理功能,它包含了增加,修改,删除,查询的功能,它包含了好几部分,而每一部分,比如说查询,可能还包含好几个请求,那我们在开发web项目时啊,其实,一个功能,它由若干个请求所构成的,我们先把它拆解成请求比较好,那我们之所以呢,开发不出来这个功能的原因是什么呢,因为它的内容太多,太大,我们这个思路太乱,我们想开发出来这个东西,就要把它拆解,把它拆解成这个请求,就是先将先将这个功能拆分成若干请求,那每一个请求,画出这个程序执行的过程图。
  • 那之前,我在讲课时,每个请求,这图我都画了,你比如说,员工查询咱们画过,对吧,员工增加也画过对吧,你可以按我这种方式画,或者说呢,你是不习惯画,习惯写字,你把每一步写上也可以,总之呢,最好是这样,我们先把一个功能,拆分成若干请求,然后呢,每个请求,画出程序执行的过程的那个图形,那这里呢,最关键的点是什么呢,不是画图,其实画图还好,还比较容易,你在我课上啊,老看着我话,然后课后的话,你照着我的图,做一个参考,这个其实你也能,大概也能画出来。

根据需求拆分请求

  • 那主要是这一步,我们怎么去把一个功能拆分成若干请求呢,这个很关键啊,那这个依据是什么呢,解释一下,就是说根据操作,推导出本功能所包含的请求,你去先去把需求搞明白,把操作搞明白,根据操作一般就能够推导出请求,比如说,我们在地址栏写了个地址一回车,这是不是一个请求,是的,比如说我点了一个超链接,是不是一个请求,也是,我点了个按钮呢,也是,所以啊,用户的操作,他就什么样的操作能包含什么样的请求,这个咱们应该有所能体会到,因为平时我们也上网,应该能知道,所以我们先根据操作呢,先想一想,那这个功能能包含多少个请求,然后呢画出图来,然后呢,再去根据图形去开发,开发的时候就方便了啊。就是逐个请求,逐个这个组件开发,就可以了。
  • 就总之啊,我们的开发,是可以很科学的,是有规律,规则可循的,而不是说很凌乱的,不是说随随便便,上来就写的,一定不是那样,当然了,这个开发的思路啊,未必说非得这样,这个是因人而异的,但是呢,现在大家基本上都没有自己的想法,或者说你没有一个成熟的,这个开发的思路,那我给你一个建议,这是我个人的习惯,你可以做一个借鉴,那么如果说呢,将来你工作了,一年两年三年,甚至更久以后,你在工作中呢,总结出了自己的经验,我喜欢按照这个方式开发也可以,那是后话,现在先按照我的思路去体会,那我们在开发这个项目的时候,其实,我们并不是说,重要的不是说写出那些代码来,重要的是什么呢,是我们在写代码的过程中,能够把这个,这种开发思路,这个思维方式,把它练的6了,就把它成为你的一种习惯,这个很关键的。
  • 或者说啊,我就是想用我们所要讲的项目,去演示一下这个开发思路,我们这段课的主要的展示东西是这个,但是你看啊,简简单单是几句话,是几个基本的流程,但是我们在操作时呢,可能没有这么简单啊,那么遇到具体的问题时,具体的细节时候啊,我们再看,再说,再分析。那按照我的要求,我们第一步,讨论需求,那这个需求其实没什么可讨论的,就是资费管理是什么作用,也说过了是吧,用来维护资费的数据,然后呢,我们要做的是什么呢,查询功能,增加功能,修改功能,这些功能,而启用禁用,我们就不做了,就别管它了,因为我时间有限,做不出来那么多东西,那我们现在呢,先讲做这个查询功能,那查询的这个使用方式也很简单,就是说,我们一登录以后,进到了首页主页,我点资费管理图标,就打开了查询页面,默认就查到了所有数据,明白吧,就这么简单啊,就这样操作,这就是业务,这就是需求,这是简单到不能再简单的需求了。

根据请求推导程序内部执行过程

  • 那这个需求你了解以后,我们需要做出设计,刚才说了,做出设计的第一步呢是,你要先将这个功能,拆分成若干请求,那我们需要根据操作推导出,它有多少个请求,那大家看下我的操作啊,你感觉这个功能有几个请求,你看啊,在主页里点击资费管理的图标,打开查询页面,一个请求,就点一次对吧,所以就一个请求,所以最简单了,那这一个请求,整个执行过程是什么样呢,我们再按照以前,我所画图的习惯,再画一下,然后呢,你也去怎么样呢,就想一下,这个图为什么是这么画,合不合理,然后呢,也可以练一练,这个画图,因为这是一种解决问题的能力,那画一下啊。
    NETCTOSS代码实现第一版_第1张图片
  • 其实我天天给你画这个图,你应该也,也了解了啊,我画图的套路是这样的,这是个web项目,它得有浏览器,对不对,不就这么干的么,还得有什么呢,服务器,不都这么干的么,那还得有数据库对吧,还得有数据库,然后我们资费查询,我们要访问数据库的哪张表啊,之前建那张表叫什么来着,就叫cost,是吧,对吧,我们要访问这个表啊,当然,我们都加了后缀啊,我这个没写后缀,就这样。我们要做的是一个web项目,web项目的结构,我先把它画出来,然后呢,我去想一下,这个web项目在应用的时候,在使用的时候,是怎么用的,它的这个操作的起点是在哪里。
  • 那起点是在浏览器上,我们点了一个图标对吧,那个图标呢,叫资费管理,我点这个图标,当然了,现在还没有做主页,还没有图标对吧,没有图标也没关系,咱们在地址栏敲个地址,是不是也可以访问呢,也可以,但将来会有的,我按照将来完整的来画啊,我们点这个资费管理图标,一点图标呢,向服务器发出请求,要求服务器返回一个网页给我对吧,查询页面,那你说这个查询页面是静态的还是动态的,静态的,为什么是静态的,谁点都一样,完了,你看我们之前,模拟做过查询员工,当时就说了,这个是动态的,因为今天你查这员工是6个,明天是8个,有可能会有变化,对吧,资费也一样,你今天查10条,明天可能12条对吧,怎么又把那事给忘了呢。
  • 其实,你看我们查询资费,和以前讲的查询员工,这个套路差不多吧,只不过以前没有讲全对吧,这不能忘的这么快啊,动态的啊。再有一个啊,你这个数据存到了表里,我们想从表里取数,肯定是java干的事对吧,一定是动态的组件,你就写一个静态的html,处理不了,所以呢,还得是访问一个Servlet,那访问Servlet,我们访问哪个Servlet,叫什么名字好呢, 有人说叫select,叫find等等,那或者我换个角度问你吧,咱们在这项目里啊,我们是一个请求写一个Servlet,我们还是多个请求共用一个Servlet,我们怎么办,共用一个,我们做项目时,一定不是一个请求,一个组件对吧,太啰嗦了,一定是共用的,那共用的话,可以这样,可以是,这个一个组件处理所有请求,或者说,一个组件处理若干请求,对吧,都可以。
  • 那这里咱们,因为做的模块不全,比较少,我们干脆呢,就一个组件处理所有请求好吧,简单处理,行么,就写一个组件,一个Servlet,它的名字呢,我叫MainServlet,行吧,是我们这个项目中的主Servlet,最核心的,最主要的。那么,它要想处理查询请求,我单独给它加个方法,叫findCost,行么,MainServlet.findCost(),我们在这个类当中加一个findCost方法,专门处理查询类的请求,可以吧,就这么干啊,那么它在处理请求时,数据来源于这个表,我们得访问这个表,对吧,得查询,那你要查询的话,你看我们是怎么查,写点什么,你是不是得写个dao,是不是啊,写个dao,一般什么表我们习惯就叫什么dao,是吧,我们就叫CostDao。
  • 然后呢,当前是查询功能,我们需要,提供一个什么方法呢,findAll(),查询全部的,可以吧,CostDao.findAll(),这个方法,那这个方法,它需要能够访问cost这个表,从表中能够得到数据,然后呢,Servlet可以调这个CostDao,dao可以给Servlet返回这个findAll()查询到的数据,是这意思吧。那么返回的数据叫什么名字比较好,这个返回的数据,咱们是不是得取个名字啥的,一般就是返回的是这个,返回的是我们用实体类封装一下,实体类可以叫Cost,是这样吧,那么它有几个,返回几条数据呢,一个,还是多个呢,多个,怎么体现多个呢,集合可以吧,返回一个集合啊,List,封装多条数据,这样。这还不够啊,我们调dao,dao这回咱们真的要访问数据库,不像以前模拟了,明白吧,这回真的是要动真格的了啊,要玩真的了,要玩真的话,你要访问数据库,还得写个啥呢,你是不是还得写个啥,之前咱还说了,写一个DBUtil,用来建立连接对吧,得有这工具,要不然不方便啊。
  • 那DBUtil,我们在开发的时候,它是不是还得依赖于一个配置文件啊,db.properties,这个我也把它画一下吧,我们还需要呢,调用一个文件,叫db.properties,这是参数,配置文件啊,连接参数,还有连接池参数。那dao可以调用DBUtil得到连接对吧,DBUtil要读取db.properties,是这样吧,就这样干。然后再看,那最终呢,我们在这里得到了数据以后,要给浏览器返回,怎么返回,怎么办呢,你是不是得写一个jsp,是这样吧,得写个jsp啊。那因为呢,我们是开发一个正式的项目,我们一定是遵守MVC模式是吧,那MVC模式呢,一定是Servlet处理请求,然后呢,它将结果给jsp展现对吧,相互配合,那么jsp啊,以前我们是把它直接放到了webapp之下,对吧,但其实呢,我们正式项目里,不是放到呢webapp之下,正式项目里通常是放到,这个神秘的目录之下/WEB-INF,放这里来啊,这个东西很神秘,我没解释呢,那为什么要放这里来,它有哪些神秘之处,我们后面再讲,等这个功能做完以后再讲,明白吧,别着急,反正先放进去啊。
  • 然后呢,我们把jsp放到/WEB-INF这里来,最好是一个模块建个目录是吧,分开,是这样吧,你看美工做的静态网页不是分开了么,对吧,分开,别放到一块去,太乱啊,那对于这个资费模块,我们建一个目录叫cost,查询页面,我叫find.jsp,行吧,即/WEB-INF/cost/find.jsp,可以吧,能看出这个文件的,属于某个模块,什么意思对吧,很直观了,就这样,然后呢,Servlet把得到的数据,转发给它find.jsp,由find.jsp向浏览器做出响应,那我们这个项目,这个功能啊,它的完整的,这个过程就是这样,它只有一个请求,过程就是这样。那就是一开始呢,就是我们可能是,没有讲这么全,我们的脑海里呢,出现的一些离散的,发散的,一些个东西,比如说有的人想到了实体类,有的人想到了dao,有的人想到了DBUtil,对吧,都想到了某一部分,没有想全,但是呢,我们通过了这个图形的串接,能够把它们呢,都串接起来,因为我们需要这些内容,串接好以后,这些内容摆这,我们还要最好呢,给出一个合理的开发的顺序,那你想,我们先开发谁后开发谁比较,合适啊。
  • 有人说先写实体类,有人说先写DBUtil,这俩谁先谁后,有影响吗,没有影响,那你想啊,咱们做一个项目,这个实体类和DBUtil,DBUtil需要在一个项目里写几遍,实体类需要写几遍,DBUtil只需要写一遍,一个项目只需要一个就够了,是这样吧,实体类是一个模块至少要写一个实体类,是这样吧,所以DBUtil说白了,是万年不变的,甚至来说,我们再写一个新的项目可不可以,也利用之前的那个DBUtil啊,也是可以的,所以我们干脆把这个万年不变的东西,先把它处理好,然后呢,再写实体类,是吧,因为我们写dao时,需要依赖于这两者,对吧,然后我们再写dao,有了dao以后,咱们就可以开发Servlet,因为Servlet依赖于它对吧 ,然后呢,就可以写jsp,因为呢,jsp依赖于Servlet,是这样吧,就是,我们按照程序的依赖关系去开发,先把被依赖者开发完,是吧,这样是比较顺利的。
  • 那么总之啊,我们去开发一个功能,我们现在这个功能还算简单啊,你工作时,你会发现,那功能很复杂,复杂到什么呢,连你理解业务都费劲,那那个时候,你说我怎么能把它开发出来呢,你一定是像我们这样啊,把这个功能拆解成请求,把请求拆解成步骤对吧,然后呢,你当你把这些步骤都罗列出来以后,你说你还哪一块不会写,你没有不会写的可能,为啥呢,DBUtil不会写,我们可以参考以前的,写过的,对吧,可以参考,实体类不可能不会写,dao不会写,以前写过,可以参考对吧,Servlet你说我那个,怎么处理多个请求,我忘了,可以参考,jsp忘了,可以参考,任何地方都可以参考,但思路通了,就好办了啊。

根据执行过程进行代码设计与实现

  • 所以呢,我们所画的这个图,所分析的这个思路,它的价值要比我们写出的代码来,要更重要啊,思路到位了,开发出代码来,只是一个时间的问题,我认为它不是什么问题,那下面我们就来写吧,开发吧,首先呢,我们写DBUtil,那你注意啊,这个DBUtil刚才说了,我们每个项目只需要写一遍对吧,以后就不用写了,那我们工作时,一个项目可能会干半年,干一年,甚至干三五年,这玩意,不用老写明白吧,所以说这东西呢,我们以后写的频率是极少极少的,因此呢,我就再写一遍了,因为之前不是写过么,我们干脆啊,把以前的项目中的这个类,这个文件直接拿过来用,明白吧,如果你忘了,自己看一看,自己练一下,然后呢,我们把这个留下来的时间主要留给2.List3.CostDao.findAll()4.MainServlet.findCost()5./WEB-INF/cost/find.jsp,这样的步骤明白么,因为这些内容,我们工作时要常写,我就需要把它们练熟啊。

项目公共业务层之JDBC封装工具类:/util/DBUtil.java

  • 那打开你的开发工具,找到以前的项目jdbc,那个项目下有配置文件db.properties,有这个工具类DBUtil.java,对吧,咱们一个一个copy啊,别着急,我们先把这个db.properties,copy到我们这个项目中来,放到也是这个目录下,src/main/resources/,一定不要放错位置,copy过来啊,看一下。然后呢,还要copy那个工具类,那copy工具类之前呢,我们最好把那个,没关系,我们把这个包先建好吧,要不然没有包对吧,我们在netctoss/src/main/java下,先建几个包,有util包,有实体类entity的包,有这个dao,还有web,先准备好,看一下建几个包。那包都建好了,建好以后,我们从jdbc的项目中,把那个DBUtil类,copy到这个util包下,copy过来看看,就是复制粘贴,但是你注意,你放的位置一定要放对了,因为这个部署的时候,有的代码会被丢弃,你要放错位置的话,不小心整没了,麻烦了啊,一定要严谨,这个程序员呢,最重要的一个品质就是要严谨,但我看很多人这个不够严谨,你还得练啊,还得注意啊。
    NETCTOSS代码实现第一版_第2张图片
  • 那copy过来以后,咱们最好测一下,你不要只看啊,不就是复制粘贴么,有什么问题呢,未必,有可能复制粘贴就会导致问题,最好测一下。那这样,我就在DBUtil里写个main方法测,可以吧,我在netctoss,这个拷贝的DBUtil.java内,这个类的内部,我在后面写个main方法,对这个类啊,DBUtil类,加以测试。那测试怎么写呢,就是,我们调用这个工具类,获取一个连接,输出一看,没有问题就可以,写一下。因为咱们这只是一个测试,不是正式代码,所以异常呢,我就简单处理,直接往外抛了,我就没有try-catch,那么写完以后呢,大家把这个main方法执行一下,看一下输出结果,我执行一下,没报错,没问题,就这样了。
    NETCTOSS代码实现第一版_第3张图片

开发思路的重要性

  • 当然有可能有的人,个别的会报错,有可能啊,是不是你之前连的那个数据库,那数据库连不上了呢,对吧,检查一下,遇到问题呢,想办法解决一下,实在是那库连不上了,你可以换个库,再调一调。那这个开发的思路介绍了。那么我说的开发思路,这3步,指的是任何的功能,你都基本上可以按照3步来做,只不过呢,细节可能有点,有点区别,或者说你使用的技术可能有点区别,但大体上,都可以这样做啊,那做的话呢,其实最关键呢,是第2步,如果你设计做好了,那开发的话,是自然而然的,如果你设计没做好,开发你想也别想了,你写出来的东西啊,也都是错的。
  • 那有的人呢,可能是一开始工作的时候,会有一种很奇葩的一种状态,什么状态呢,这东西啊,洗了糊涂的,根本就不知道该怎写,最后呢,就连做出来以后,都不知道该怎写,为啥呢,你像我以前工作就是这样,第一份工作,那时啥也不会,然后领导跟我说,你要这么做,反正我听的很糊涂,也不敢再深问,做吧,反正洗了糊涂做,写的我自己都不知道自己在写啥,反正就堆代码,就堆的乱七八糟的,然后让领导看,说你看我这样写行不行,领导一看,他快绝望了,一看这,不知道该从何讲起啊,然后怎么办呢,他一着急,一上火,帮我敲两行代码,急了咕嘟敲了一大堆,那你在这基础上再怎么怎么,再给我想下,再接着写,然后我就写吧,反正我还是不懂,接着写,然后呢,写完一段以后,再问他,你看是不是这样,他一看又不对,又给我改一改,最后,功能做出来了,做出来以后,这怎做出来的,我也不知道,领导帮我做出来的,往往是这样。
  • 所以说,经常会有这么尴尬的一种情况啊,可能你连功能做出来之后啊,你都不知道到底是怎么回事,因为什么呢,关键的那几部分代码是人家帮你敲的,你会发现你同事没有那么多耐心呢,给你讲一遍,给你画个图什么的,没有,他烦着呢,他着急着呢,有的事干,怎么办,就直接上手给你 ,劈了哐啷给你敲一段,接着写啊,然后你就反复如此,一开始呢,就这种状态,后来的话呢,你慢慢啊,真的做过一些功能以后,慢慢慢慢才ok的,但是我,我是什么意思呢,最好咱们别这样,别像我当初那样,咱们最好是一开始就能上手,一开始就能想出来大概的思路,能够和人有一个比较好的,友好的沟通,然后的话,有一个正确的方向,做的时候呢,不至于闹出这种笑话来啊,这不太好啊。
  • 这种情况呢,搞不好啊,就容易把我给开了,那个时候我压力挺大的,我老是愁的慌,我说哪一天领导能不能把我开了呢,我就老是跟我同学,说这事,我同学说,哎,你放心吧,他开不了你,为啥开不了你呢,你说你一天,这个给领导,擦桌子扫地,端茶倒水的是吧,你这活值你那,那个2000块钱了,我那时候工资就2000块钱,值2000块钱了,你还会写代码,不错了,想想也是,领导果然没开我啊。总之啊,这个思路是非常的关键,一定要多想一想啊,那我们这个功能讲完,还有别的功能,你看看别的功能你能不能,自己想出来,或者说呢,还是这个功能,然后呢,你别去看我画的图了,你也别去看代码了,你自己呢,这个放空一下自己的脑子,然后自己想一想,看能不能想出来,然后呢,你自己呢,能把它想出来,能做出来,到这你才是真的会了啊,然后呢,咱们分析呢,这个功能有一个请求,请求的过程是这样的,然后呢,按照依赖关系给出了一个步骤,刚才呢,咱们已经把第一步啊,这个DBUtil写完了,当然是复制的,如果呢,有人忘了这里面的逻辑啊,自己要复习一下。

资费模块公共业务层之数据建模实体类:/entity/Cost.java

  • 那下面呢,我们按部就班的去开发啊,第2步,写这个实体类,那实体类用来存数据对吧,然后呢,它是一个典型的javaBean,我们要满足javaBean规范,那表里呢,10字段,实体类呢,要与之对应,有10个属性。那下面呢,在这个项目之内,在entity包之下,创建一个实体类,这个类呢,和表同名,就叫Cost,要实现呢,序列化接口,那表里有10个字段,类中有10属性,我先把这10个属性写出来,然后呢,每个属性是什么意思,我再补充注释,再加以解释,咱们先把属性写出来,

    private Integer costId;
    private String name;
    private Integer baseDuration;
    private Double baseCost;
    private Double unitCost;
    private String status;
    private String descr;
    private Timestamp creatime;
    private Timestamp startime;
    private String costType;

  • 一共呢,10个属性,那现在我把每一个属性的意思解释一下啊,这个第一个costId,是这个表的组件,没什么好说的,组件,第二个呢,name是,这是资费表,name呢,是资费名,比如说60元套餐,是这个名字,80元套餐是名字,比如说计时是名字,总之啊,这是资费的名称,然后呢,下面这3个字段,baseDuration,baseCost和unitCost,属性,是这个资费相关的数据,那么,这个叫baseDuration,是基本时长,如果呢,用户选择资费是,这个套餐的话,那这个套餐,它默认有多少小时可用,比如说我们这手机号,你选择套餐,默认你能打多少分钟电话,就这个意思,或者说,默认你带多少流量,基本时长,写一下啊,基本时长。

  • 然后呢,baseCost,这个是基本费用,这个基本费用指的是什么呢,比如说,我选了一个套餐,这个套餐呢,默认多少钱,比如说我这个手机号,我选了个,比如说,168的套餐,那基本的费用就是168,就这个意思啊,那这个基本的费用,是金额,是小数,基本时长一般都是整数,多少分钟,多少小时明白吧,没有谁还卖半分钟的,没有那样的,是整数;然后呢,这个是unitCost,这是单位费用,那什么叫单位费用,那你选了个套餐的话,你这个套餐有可能会用超,对吧,是吧,套餐超了以后,那每一分钟多少钱,那是这个意思,单位费用,也是小数,因为是金额,那么我们呢,所用的这个数据的类型啊,你会发现,都是封装类型,之前我说过,我们为什么都用封装类型呢,为什么呢,因为啊,封装类型,它可以为none,是这样吧,是吧,如果是基本类型,它不允许为none,是吧,是这样的,那这个数据有没有可能为none呢,有可能,如果我选的是包月,有这个单位费用么,没有,包月没有超出的可能,没有单位费用,对吧,也没有基本时长,不限时长,是这意思吧,所以呢,这些数据是可能为空的,数据库里也允许为空,所以这块呢,我们用这个封装类型,是比较合理的啊 。

  • 再看啊,下一个呢,是status,这个单词是状态的意思,这个我们选的这个资费,它有状态,比如说呢,今年,我们新推出了一个套餐,非常划算,比如校园套餐,很优惠,那可能明年没有了,禁用了对吧,后年又开启,又可用了,所以这个资费,它有状态,可以切换,这个状态呢,只有两种,那么像着这种这种状态,这种字段,我们在存的时候,一般啊,不是存这个中文,不是说存这个启用,禁用,存汉字,不是,我们存的是什么呢,0,1,2,3,4,或者是呢,这个字母,比如说,M啊,F啊,这样的。那为什么这样呢,就是说,如果说我们数据库啊,存的是几个选项,如果存的是选项的话,这个叫枚举项,一般我们习惯叫枚举,就是可列举,这写一下啊,像这种呢,可以列举的字段,我们叫枚举,枚举类型通常存的是整数,或者是char,习惯于这样。那我们表里呢,这个字段存的是char,那存了什么呢,0和1啊,0代表启用,然后呢,1代表禁用,项目页面的状态叫开通暂停啊,其实和启用禁用差不多,我就稍微修一下啊,这个最终显示的是,0-开通1-暂停啊。

  • 然后呢,咱们数据库字段呢,是char,那我用的java类型呢,是String,当然我可以用char,那我为啥用String呢,我怕将来呢,万一有一天呢,这个状态太多了,这个char存不下了,明白吧,就用字符串了,那我这个String的话,你给我个char也可以对吧,它比较兼容啊,所以呢,咱们这个,写实体类的时候啊,一般都要考虑兼容性,你像之前,这个封装类型,能兼容none啊,字符串也可以兼容char,所以说,主要是从这个角度来考虑,最好是我们写过这个类以后啊,将来呢,一旦是字段发生变化,我这类不用动,因为类一动的话,咱们调它的代码,就也得动啊,最好别动。然后呢,下一个是descr,是description那个描述单词的缩写,就是对这个资费的说明,或者说描述吧,资费说明吧。

  • 然后呢,是creatime,创建时间啊,那我们要在jdbc当中使用时间的话,时间以前说过( JDBC对日期时间的处理 ),咱们用什么类型了,用的是哪个包下的类型啊,java.sql对吧,所以我用的是java.sql.Timestamp,时间戳,这个包名别引错了,引错会有问题,然后呢,字段名,它就叫creatime,它把create和time拼在一起,并缩写掉了一个t,只有一个t啊,这主意,creatime去掉了一个t,这是,这一个数据呢,创建的时间啊,然后呢,后面是,开通时间啊,就是说,咱们这个数据啊,有这个状态,状态呢,是开通暂停,那我们在这个页面上,可以通过启用,禁用这样的按钮,去调整这个状态,可以修改这个状态啊,那么当你修改状态时,你把数据开通了,你要给出开通时间,是资费这条数据被开通的那个时间,做一条记录,这个叫startime,也是把t拼掉了一个,这要注意,也是java.sql包下的这个日期啊。

  • 最后呢,还有一个字段叫costType,那你注意,这个建表这个人啊,他的规则不统一啊,你像这样的单词,creatime和startime,俩t缩掉一个对吧,costType,他没缩掉,它就直接两个单词拼到一起了,所以说这个有点不太统一,不过,他既然这么写,我们就跟着,就这样写吧,那这什么意思呢,就是cost是费用的意思,是资费的意思,type是类型,这是资费类型,也是一种枚举的数据。那么因为呢,它是枚举类型,咱们数据库里存的呢,还是char啊,那它存了什么呢,存了也是数字啊,1,2,3 啊,那代表什么意思呢,1代表包月,2代表套餐,3代表计时,3个选项。那么10个字段,咱们分别呢,进行了一个解释,大家了解一下,然后呢,写完这个解释以后吧,我们需要生产get/set啊,大家生成get/set方法。

  • 那这个实体类呢,咱们就写完了,就这样,这个没啥难度啊 ,反正你就注意,我们写实体类的几个原则啊,第一个,它要满足javaBean规范,这是第一个;第二个,实体类当中的类型,我们尽量采用封装类型,因为封装类型支持none,基本类型不支持none,而很多数据是允许为none的,那么我们不用去想,这个字段允不允许none,我是基本类型还是,封装类型呢,干脆都封装类型,咱们就无脑模式,这个意思啊。然后呢,我们咱实体类当中啊,所写的时间,要采用java.sql这个包下的时间,然后,如果你是想记录完整的时间,用这个类型Timestamp,如果只想要日期,就用Date,如果只想要时间,就用time,我们这里要记录完整的,所以用Timestamp这个。那么实体类写完了,再看下一步,下一步呢,我们是可以写dao了,对吧,那在dao当中呢,我们要写查询,我们通过DBUtil创建连接,通过连接执行sql,对吧,再将sql返回的结果封装为实体类以及集合,返回就可以,就这么一个过程,那么这个过程呢,我们在jdbc的时候呢,说过的,所以到目前为止,我们所讲的内容,依然是在复习,它并不是什么新的内容。

业务层之业务数据处理与数据存储:/dao/CostDao.java

  • 那下面呢,我们在这个dao这个包下,创建一个实体类啊,一般呢,这个实体类,什么表就叫什么什么dao,所以这里呢,我叫CostDao,然后呢,作为初学者我们是不太清楚,到底哪些类需要满足javaBean规范,哪些不需要满足,那于是我们就都满足啊,养成一个习惯啊,所以呢,为了满足javaBean规范,我要实现序列化接口对吧,直接点Add,选那个Serializable那个java.io包下的接口。
    NETCTOSS代码实现第一版_第4张图片
  • 这个dao啊,CostDao,咱们去写啊,那刚才我们说这个思路是说了,我们当前呢,做的是查询功能,那么查询功能,它需要dao提供一个查询方法对吧,findAll,我们写这个findAll,没有参数,非常简单啊,那我声明这个方法啊,public List findAll(){ return null; },这个方法呢,我们声明完了,声明完以后呢,加以实现。首先呢,第一步,创建连接,那么这个连接,最终是要关闭,对吧,无论是否报异常,我们都要尝试关闭它,然后呢,如果捕获到异常的时候,我们要对异常呢,进行处理,所以,创建连接啊,不是一句话,是一个套路,是一段话啊,写一下。

public List findAll(){
	Connection conn	= null;
	try {
		conn = DBUtil.getConnection();
	} catch (SQLException e) {
		e.printStackTrace();
		throw new RuntimeException("查询资费失败",e);
	}finally {
		DBUtil.close(conn);
	}
	return null;
}

  • 那么我就声明了连接啊,然后呢,调用工具,创建了连接,那么,conn = DBUtil.getConnection();,这句话,我们需要catch,如果捕获到异常,对于异常,我们处理不了,对吧,一般都处理不了,处理不了怎么办呢,抛出去,交给调用者处理,那调用者也处理不了怎么办呢,抛出去,交给上层调用者处理,如果谁都处理不了怎么办呢,最终,交给tomcat处理,那么tomcat有能力啊,处理所有异常,到时候呢,我们会演示,后面会演示,先别着急啊。然后呢,finally啊,最终无论如何,无论是否发生异常,一定要尝试关闭连接啊。那这个结构啊,无论是增删改查都一样,我们之前jdbc的时候呢,这段话写过无数遍对吧,现在又回顾了一下。
  • 那创建好连接以后,我们需要呢,执行sql,我们需要有sql对吧,先得写个sql,那么查询全部的资费啊,这个sql非常简单啊,String sql = "select * from cost "+"order by cost_id";,我从表里呢,查出所有数据,然后呢,为了保证,这个数据呢,有序,我按照id呢,排下序,大家注意啊,你写sql时,用点心,别照我抄,因为表名,咱们不一样对吧,你写你自己的表名啊,如果抄错的话,一执行报错了,别赖我啊,我现在不怕你了啊,为啥呢,我那个数据库,连的是我本地的数据,你随便写啊,但是你访问不到你就别赖我了。那sql写完以后啊,要执行这个sql,那你看,我执行这个sql, 我是用Statement还是用preparedStatement,为什么呢,为啥用Statement呢,因为没有条件。
  • 那Statement和preparedStatement,两者的主要区别是什么啊,你是说一个执行静态的,一个执行动态的,这句话说的有点歧义,你这么讲我还以为,那Statement只能执行静态的,它执行不了动态的,是这意思么,也不是,都能执行有条件的,是吧,但是Statement适合执行静态的,对吧,PreparedStatement适合执行动态的,那背后的原因是什么呢,就背后的原因和这个对象处理sql的方式有关系,Statement呢,是一次性执行,就是发送整个sql对吧,连sql带条件,都发送过去对吧,是一次性的,而preparedStatement是分两次,先发送sql,再发送条件,对吧,然后用问号占位,是这意思吧,所以这个有区别,所以说你在复习的时候呢,不是说我只把那个代码看一下,把这个我们当时讲的知识点也要回顾一下,为啥呢,因为我们复习是为了,在学这段课的时候呢,更容易,再一个呢,也是为了将来我们面试的时候呢,也好能回答上来这个问题啊。
  • 那么写完sql以后啊,我们创建Statement对象,执行sql,我们使用这个对象啊,执行这个sql,这个Statement和PreparedStatement主要有几个方法,3个,一个是executeQuery执行查询,是吧,一个是executeUpdate,执行DML语句对吧,其实还有一个是,execute,能执行一切对吧,通常呢,除了DML,DQL之外的语句,用它来执行,比如说,建表的语句,DDL可以,但一般情况下啊,咱们不会用它,为啥呢,咱们是在写这个项目之前,表都建好了对吧,不是说我们用程序去建,一般很少这样做啊,所以说那个用的少啊,这里呢,是查询啊,我们调的是executeQuery方法,传入sql,返回结果集啊,ResultSet rs = smt.executeQuery(sql);,那我们得到了结果集,结果集当中呢,封装了所有的数据,那对于结果集我们需要遍历对吧。
  • 那么结果集呢,采用了迭代器模式进行了设计,那通常的遍历,我们采用while啊,while遍历它,那每次遍历咱们能得到一条数据,这条数据,我需要封装到Cost对象里,对吧,但是呢,每一个对象,我需要把它加到集合里,因为最终要的是集合对吧,所以这样,我们在循环之前啊,先建立一个集合啊,List list = new ArrayList();,循环之前呢,创建一个集合,整个循环完成以后,我们返回这个集合,return list;,那你看啊,咱们在上面创建集合,在下面返回集合,那我们最后那句话,return null;,还有用吗,没有了,把它删掉啊。

public List findAll(){
	Connection conn	= null;
	try {
		conn = DBUtil.getConnection();
		String sql = "select * from cost "+"order by cost_id";
		Statement smt = conn.createStatement();
		ResultSet rs = smt.executeQuery(sql);
		List list = new ArrayList();
		while(rs.next()) {
		
		}
		return list;
	} catch (SQLException e) {
		e.printStackTrace();
		throw new RuntimeException("查询资费失败",e);
	}finally {
		DBUtil.close(conn);
	}
	return null;//删掉
}

  • 然后呢,继续啊,那每次遍历的时候,我们需要呢,从结果集中,取出一行数据,封装到对象里,加到集合里返回啊,所以呢,每次遍历,我要实例化一个Cost,Cost c = new Cost();,名字简单点,就叫c吧,然后呢,每次循环结束的那一刻,我要把这个c呢,加到集合里,我提前写好,省的一会忘了,这个容易忘啊,list.add(c);,写到这呢,有人可能会想啊,说你看啊,我们循环,每次循环都创建一个对象,那这样的话,我们创建这么多对象,这个是不是效率会偏低呢,我能不能把这个对象,挪到前面去,我每次都加这个c,这不也没有问题么,它也不报错么,这样行不行呢,这样可不可以呢,行不行。

public List findAll(){
	Connection conn	= null;
	try {
		conn = DBUtil.getConnection();
		String sql = "select * from cost "+"order by cost_id";
		Statement smt = conn.createStatement();
		ResultSet rs = smt.executeQuery(sql);
		List list = new ArrayList();
		//Cost c = new Cost();//放到此处会覆盖集合中的所有元素
		while(rs.next()) {
			Cost c = new Cost();
			list.add(c);
		}
		return list;
	} catch (SQLException e) {
		e.printStackTrace();
		throw new RuntimeException("查询资费失败",e);
	}finally {
		DBUtil.close(conn);
	}
}

  • 那我也是list.add(c),也是add多次啊,你循环100遍,也add100个对吧,有问题么,行不行,不行在哪,哎,有人说到点子上了,你不用说那么多,就说一句话,一个词,覆盖,就到点子上了,为啥呢,咱们一共就new了一个对像,第一次循环,往里存了个数据,唐僧对吧,第二次存了个猪八戒,最后,存了个悟空,那你说最后集合里装的是啥呢,全都是悟空,是这样吧,是吧,因为你即便是add 100次,add100个同一个对象对吧,是这意思吧,所以这是不行的啊,不能这么搞啊。所以呢,一个对像封装一条数据,你就得new这么多对象,没有办法啊。
  • 那么我们创建完对象以后,我们要从结果集中呢,取出数据,存入对象,那这块就c.set一个什么什么东西对吧,值呢,是rs.get,10个字段,写10句话,慢慢写,写对了,别着急啊。

public List findAll(){
	Connection conn	= null;
	try {
		conn = DBUtil.getConnection();
		//String sql = "select * form cost_lhh "+"order by cost_id";//测试错误页面error.jsp
		String sql = "select * from cost_lhh "+"order by cost_id";
		Statement smt = conn.createStatement();
		ResultSet rs = smt.executeQuery(sql);
		List list = new ArrayList();
		while(rs.next()) {
			Cost c = new Cost();
			c.setCostId(rs.getInt("cost_id"));
			c.setName(rs.getString("name"));
			c.setBaseDuration(rs.getInt("base_duration"));
			c.setBaseCost(rs.getDouble("base_cost"));
			c.setUnitCost(rs.getDouble("unit_cost"));
			c.setStatus(rs.getString("status"));
			c.setDescr(rs.getString("descr"));
			c.setCreatime(rs.getTimestamp("creatime"));
			c.setStartime(rs.getTimestamp("startime"));
			c.setCostType(rs.getString("cost_type"));
			list.add(c);
		}
		return list;
	} catch (SQLException e) {
		e.printStackTrace();
		throw new RuntimeException("查询资费失败",e);
	}finally {
		DBUtil.close(conn);
	}
}

  • 这个慢慢写,别着急,尤其是要注意啊,这个字符串,这个字段名,不要写错,仔细点,认真点啊。那反正呢,一共是10个字段,你要呢,挨个set ,然后呢,别嫌烦,没办法啊,我看有同学写代码的时候啊,这唉声叹气的,好像这个代码是给人写的似的,非常的不情愿,10个字段呢,并不多,你可能感觉很多,其实一点都不多,我们工作时,随便找个表,几十个字段,大一点的表一两百个字段,你就set吧,是吧, 反正呢,你是一个程序员,这活你得干,别嫌麻烦,很正常。不过呢,将来啊,咱们会学到框架,学到框架以后就好了,学到框架以后呢,这段代码不用我们写了,这段代码就new一个对象,往对象里设置数据,封装为集合,这段话:

	Cost c = new Cost();
	c.setCostId(rs.getInt("cost_id"));
	c.setName(rs.getString("name"));
	c.setBaseDuration(rs.getInt("base_duration"));
	c.setBaseCost(rs.getDouble("base_cost"));
	c.setUnitCost(rs.getDouble("unit_cost"));
	c.setStatus(rs.getString("status"));
	c.setDescr(rs.getString("descr"));
	c.setCreatime(rs.getTimestamp("creatime"));
	c.setStartime(rs.getTimestamp("startime"));
	c.setCostType(rs.getString("cost_type"));
	list.add(c);

  • 框架一句话就搞定了,就方便了啊。但是呢,现在没学到框架,我们还只能费点劲啊,所以现在呢,我们在这多麻烦,多费劲,将来呢,你才能体会到框架呢,它有多好,没有对比,就没有伤害,是吧。那写完以后啊,咱们可以测试了啊,我们就直接在这个类当中呢,写个main方法测一下,就好了。那么我在这个类的后面啊,在这个findAll()方法的后面,写个main方法,对这个方法加以测试。

public static void main(String[] args) {
	CostDao dao = new CostDao();
	List list = dao.findAll();
	for(Cost c : list){
		System.out.println(c.getCostId()+","+c.getName());
	}
}

  • 那么我实例化了dao,调用它的方法,得到了集合,我遍历集合啊,每次呢,输出呢,对象中的两个属性,没有全输出啊,两个属性没问题,其他的应该也没问题,那我执行它这个方法看一看啊,一执行啊,我这输出了6条数据啊,123456什么什么什么,没问题,但估计有同学可能会把错对吧,比如说,什么表名找不到啊,是吧,这个Column怎么怎么样,往往就是你这个sql写错了,或者是呢,字段名写错了,检查一下。
    NETCTOSS代码实现第一版_第5张图片

控制层之业务逻辑处理与控制分发:/web/MainServlet.java

  • 那把这个dao写完了,那这个内容是我们对以前的jdbc,那个知识点的回顾,然后呢,第3步完成以后啊,我们进行第4步,我想呢,写一个MainServlet,我希望呢,在这个类当中呢,处理所有请求,那我的要求是这样,我们这个项目中啊,所有的这个功能,所有的请求,都是点do,.do,这是一种习惯,点do。我有时这样讲,我们项目中所有的普通的请求,咱们都叫点do,以.do为后缀,那将来呢,如果有很特殊的请求,我们再加别的后缀,这样比较灵活;那么,我们工作时呢,很多项目都是这么干的。那下面我们来写这个Servlet啊。那么打开开发工具,然后啊,我们在这个web这个包下,创建这个类,就叫MainServlet,继承于HttpSerlvet:
    NETCTOSS代码实现第一版_第6张图片
  • 然后呢,我们在这个类当中呢,要想处理请求啊,也需要重写父类的方法,所以呢,我重写父类的service方法,那么对这个方法,咱们加以整理,那么我们在这个方法之内吧,如果说想处理所有请求,那这个代码得怎么写,这是Servlet的内容,得回顾一下,怎么办,先获取路径,然后呢,判断,那我获取路径了,我获取路径的话,之前说过,获取路径有好几种方式对吧,都有哪些方法来着,getContextPath,得到的是什么呢,项目名,还有呢,getServletPath,得到的是Servlet访问路径对吧,还有呢,getRequestURI,对吧,和RequestURL,得到的是URI和URL,那在我们这个java项目里,URI我们得到的是绝对路径对吧,URL是完整路径。
  • 那你说我现在要得到哪一个路径去判断,我要获取哪个路径去判断,ServletPath,然后我怎么去判断呢,它等于多少呢,是不是还差点事啊,差点什么事呢。就是我们在使用一个组件处理多个请求之前,必须得有规范对吧,一定要有规范,没有规范的话,就没法写了,我写不下去了,那么我想获取某一个路径,也得知道规范,那规范的路径,到底怎么规定的对吧,我看取哪一个合适,如果没有规范的话,我也不知道该怎么取好。所以呢,在此之前我们需要补充一下规范啊,那么这个,咱们这个没有具体的文档啊,所以,我说的就是规范,我告诉你就是规范啊,那这个规范为了直观一点,我直接把它写在图里算了,你可以看这个图,图上就把这个规范写清楚,可以吧,就在这体现啊,改一下这个图。
    NETCTOSS代码实现第一版_第7张图片
  • 那我的要求啊是,资费管理这个功能,它的访问路径是,是这个,是/findCost.do,行吧,这样,我们一会配的时候,是按照这种后缀的方式进行处理,进行配置,那下面我们就要处理这个路径,那你看,这个/findCost.do是Servlet的访问路径,是吧,所以要想去判断它的话,我们得取到谁呢,ServletPath,那下面呢,我们就获取访问路径,String path = req.getServletPath();,那得到了访问路径以后,要判断啊,根据规范对路径加以处理,那写一下,就是根据规范处理路径,规范在哪里,我把它标在了图上,那就判断吧,咱们的规范里只有一个图,只有一个路径对吧,就判断它自己,如果说呢,这个路径就是,/findCost.do的话,if("/findCost.do".equals(path));
  • 那如果路径是/findCost.do,我们要执行呢,查询资费,这件事啊,再写一下,要查询资费,那么你要查询资费的话,我们要再单独调个方法,别在这直接写,在这直接写的话,将来判断多了,代码太罗嗦,对吧,调个方法,这个方法,我就叫findCost(req,res);,然后呢,给它传入request和response,那么这个方法呢,咱们一会再写啊,先不着急,然后啊,否则,如果不是这个路径,那就是错的对吧,就这一个是对的,错的怎么办呢,抛异常,异常抛出去以后,最终由tomcat统一处理, 将来会处理好,抛出去,throw new RuntimeException("没有这个页面");
  • 那最后呢,咱就来写这个方法,findCost,那这个方法的结构啊,其实和当前的service方法一样,它也是俩参数,request和response,对吧,它也是处理请求,所以,这个方法的声明啊,咱们可以直接呢,copy当前的service方法,改个名就可以了,这样效率高。那么你copy的时候呢,不要带上这个东西@Override,这东西呢,表示说我们是继承对吧,咱们在copy以后要改名,不是继承,所以你把这个方法的声明复制一下,复制完以后呢,我们在service之后粘贴一下,那粘贴以后啊,我就有了一个service方法,重名报错了,我把它改个名,改成就叫做findCost,然后呢,写上注释啊,说明白是什么作用,查询资费。
  • 那下面呢,我们在这个方法里啊,处理查询资费的请求,那它处理任何请求,基本思路,步骤都差不多,大概是3步,第一步呢,咱们接收浏览器传过来的数据,第二步呢,我们利用这个数据对业务进行处理,增删改查等等,第3步,不是立刻输出,而是,或转发,或重定向,对吧,因为我们现在MVC模式,要么转发,要么重定向。那你看啊,咱这个查询,应是查询全部的数据,所以呢,浏览器直接一点就访问,这里面没有什么条件,对吧,不需要条件,没有参数,因此呢,第一步参数就省略了,那我们直接呢,进行第2步,就是查询,那调dao就可以查,那直接写了啊,那第一步啊,就是查询所有的资费啊。

//查询资费
protected void findCost(
	HttpServletRequest req, 
	HttpServletResponse res) throws ServletException, IOException {
	//查询所有的资费
	CostDao dao = new CostDao();
	List list = dao.findAll();
}

  • 那这一步比较容易,就是实例化dao,调一下,得到数据就可以了,那么,得到的数据,我们需要呢,把它转发给jsp,所以进行下一步,就是将请求转发到jsp。那么如果呢,你要把请求转发到jsp,我们首先呢,得把数据绑定到request之上对吧,然后呢,转发时,把request给jsp,jsp呢,通过request,才能得到这个数据,所以呢,咱们下一步,是绑定数据,这也算是转发的一部吧,request点setAttribute,数据的值呢,就是集合,名字,取个名字,我叫costs,即req.setAttribute("costs", list);

  • 数据绑定以后,可以实现转发了,那转发呢,就一句话,我们先获取到转发器对吧,并且呢,获取转发器的时候啊,要声明转发的路径,然后呢,调forward方法,传入request和response就行了,那写一下,req.getRequestDispatcher("").forward(req, res);,那么在调用getRequestDispatcher转发器的时候,我们需要呢,传入转发的路径,相对路径,那么要想写相对路径,我们还得分析一下,这个相对路径怎写,是谁和谁的相对,是从哪到哪,你把这个起点和终点,这个两个路径呢,列举一下,然后呢,经过观察,很容易就知道了,所以呢,写相对路径的关键之处呢,是你要知道,我们是从哪跳到哪,那看图啊,有了图啊,很多问题都很直观的能够解释清楚。

  • 那咱们是从Servlet到jsp对吧,从MainServlet.findCost()这,到/WEB-INF/cost/find.jsp这,那么Servlet,它的访问路径不就是/findCost.do么,是吧,那么jsp,它的访问路径和静态资源一样,是它存放的目录对吧,是吧,以前是这么干的啊,那么之前的 Servlet4/访问路径 中,有说过,这个项目部署完以后,我们所谓的路径指的是部署后的,项目访问路径,而对于部署后的内容,那么访问时,有两种情况,一种是静态的内容对吧,像html,图片啊,等等,静态的,是它存放的位置,另外呢,是动态的,是Servlet对吧,是网名,那么jsp也是位置,它存放的位置。那么,我们把这者路径呢,列举一下。我们是当前,正在访问的是Servlet,那么它的路径是/netctoss/findCost.do;目标jsp的是,/netctoss/WEB-INF/cost/find.jsp

    //当前:/netctoss/findCost.do
    //目标:/netctoss/WEB-INF/cost/find.jsp

  • 那从Servlet到jsp啊,那么它们的路径,分别是上面的两个,所以你一看,相对路径咱们怎写,写哪一部分呢,那从哪开始写,WEB-INF,因为findCost.doWEB-INF平级对吧,就我们从WEB-INF到最后find.jsp是吧,/WEB-INF/cost/find.jsp,就行了。即req.getRequestDispatcher("WEB-INF/cost/find.jsp").forward(req, res);,总而言之啊,咱们在开发的时候,遇到问题,不要呢,永远都不要只停留在想的层面,很多事啊,光想,越想越乱,最好呢,是动动手,列举一下,写一写,甚至呢,试一试,很多时候,你一试,什么都知道,什么都清楚了。那这个Servlet,我们写完了,写完以后呢,对于这个类是不是得加以配置,所以下面呢,我们来配置这个类,配置的时候你要注意,它能够处理的请求,是以点do为后缀是吧,所以呢,访问路径你要写点do,那我们打开这个配置文件啊,对这个类加以配置,打开web.xml



	main
	web.MainServlet


	main
	*.do


  • 那配置好这个servlet,那么它的访问路径是*.do,当我们以后缀的方式加以配置时,那么这个不能写斜线,是吧,不要写斜线,这要注意,那多说一句啊,这个有的时候,有同学面试,说面试官问这样一个话题,说,那你做这个项目为啥,所有请求都是点do呢,这样有什么好处呢,这样好么,就是他的问题是,一个组件处理所有请求,有啥好处呢,你可能连这句话的意思都没听明白,有人说你在说啥呢,是吧,这不挺好的么。其实,我们站的角度不一样,我们的角度是我们去学习,对吧,我们是,之前是一个请求一个servlet,那个不好是吧,我们改进了,一个类处理所有请求,这就好了。
  • 但其实呢,实际工作时,那个请求太多了,咱们一个组件处理所有请求,也不合适,明白么,正式项目里的话,可能是一个模块一个后缀,可能会这样,或者是呢,几个模块一个后缀,是这样,所以呢,面试官,他可能是以他的实际的工作经验为背景,来问你这样一个话题,他是这样问的,他因为什么呢,他的项目中,他不是只有一个后缀,明白吧,他问,他习惯什么呢,那为啥你只有一个后缀,你这样写这么多请求好么,他不会站在我们的,学习的角度来讲,因为我们做这个项目,就做那么一两个模块,就做那么几个请求,明白吧,我们做的少,所以的话,我们再折腾一下,一个模块搞一个后缀,你做完以后,感觉这个东西,好像没必要,明白这意思吧。
  • 因为我们的项目小,听懂了吧,这个意思,所以啊,这个面试官呢,他面试的时候,他未必会从你的角度,去考虑你的情况,他只会考虑他工作时,他的现状,他的情况,所以呢,你回答问题的时候呢,可能也需要从他的角度,去思考一下问题,或者说我们在学习的过程中吧,也要多思考,那我们真正做项目时,这个项目变大的时候,我们这样好不好,我们应该怎么样更好,明白这意思吧,但毕竟啊,现在这个项目还是比较小的,我们是,说白了,我们是什么样规模的项目,就选用什么方案,这是比较合理的,不能一刀切啊,说我们就一刀切,不管大项目小项目,都一个处理方式,那是不对的,得看情况,我们的请况就适合这样,因为没有几个请求,比较小。

视图层之业务基本处理与视图显示:/WEB-INF/cost/find.jsp

  • 那配完以后,还差点上什么呢,最后就差一个jsp了对吧,那我们需要呢,在WEB-INF下,创建一级目录叫cost,目录下创建一个页面叫find.jsp,那我们创建这个内容,那么在WEB-INF下,创建一个目录叫cost,cost之下创建一个jsp,叫find.jsp,创建好以后,那这个jsp,首先要写的是指令对吧,我们先写一个page指令,<%@page pageEncoding="utf-8" %>,那么在这个page指令之上,我们需不需要写出其他的属性呢,比如说contentType,比如说import,用不用写啊, 不用,因为contentType有默认值对吧,import我们用el表达式取值,不用了,对吧,不需要写,那我需不需要写出别的指令呢,比如说,include呀,taglib呀,需不需要写别指令呢,用不用,那想不到,先不写也没关系,这个page指令,<%@page pageEncoding="utf-8" %>,必须要写对吧,那个include呀,taglib,我们需要时,再加也没关系对吧,先不写了,遇到时再说吧。
  • 然后呢,jsp上,它的标签,我们不用手写了,对吧,咱不是有静态页面么,我们从静态页面上,一复制粘贴就可以了,那这个静态页面叫什么名字呢,我们看一下啊,你看NETCTOSS_V02之下,这个资费查询页面,它在fee的下面,对吧,叫fee_list.html,有人说,哎,这怎么叫fee呢,这个fee,这个单词也是费用的意思,它和那个cost类似,同义词对吧,那为啥叫俩不同的名字,不统一呢,咱们这个设计数据库啊,是张三,写网页呢,是李四,是俩人明白吧,这俩人对费用的理解产生了分歧,所以就不一致,那我们用哪一个呢,我们一般还是跟数据库这哥们站一边啊,因为我们差不多是一个意思啊,所以我们一般还是习惯于叫cost,我们就叫cost,因为我们那个实体类就叫Cost是吧,咱们就叫cost算了,就是我们取名叫cost,你看刚才我取名叫cost,它这个文件,它原来叫fee,我们把这个文件的代码copy过来。
  • 我们找到呢,这个静态页面,在servlet-doc之下,然后呢,在NETCTOSS_V02的里面,然后呢,在fee目录的下面,找到以后,用这个文本编辑器,把这个fee_list.html打开,打开它,打开以后呢,得全选吧,全选会吧,ctrl+A ,复制会吧,ctrl+C,然后呢,打开你的eclipse,粘贴ctrl+V啊,粘贴到find.jsp的page指令下面,就行了。 那查询的功能到这,咱们把这个实体类dao,还有Servlet都完成了,然后呢,jsp已经创建了,jsp当中呢,这个静态的代码已经粘贴过去了,这还没完,这个jsp,我们copy过来的静态网页,它是依赖于这个样式文件和图片的,所以呢,我们还得把这个它所依赖的样式文件和图片呢,也copy到项目中来。
  • 所以呢,再打开那个文件夹,就是servlet-doc,然后呢NETCTOSS_V02,再打开那个静态网页存放的目录,我们这个项目当中,所有静态网页所要依赖的图片和样式文件在哪里呢,在这,image是我们这个项目所有的图片,然后呢,styles是我们这个项目所有的样式文件,所以呢,我们需要把这个images和styles,copy一下,粘贴到我们项目中来。那选中这两个目录,images和styles复制一下,复制完以后,我们打开这个开发工具,那这个两个目录放到哪去呢,我们直接把它粘贴到webapp的下面,粘贴到webapp之下,不要放错地方啊,这有规定,你看我粘贴完以后,是这样一个结构:
    NETCTOSS代码实现第一版_第8张图片
  • 就这样,在这webapp呢,就是说,我们在开发项目时啊,我们所写的jsp,我们是放到了WEB-INF里,那么,我们所引用的图片和样式,如果有js,js也是,都要放到webapp里,这是一般项目的一些规则,那为什么要这样放呢,后面会讲,我们先把它写完会讲,这个先别着急。那放进来以后,咱们这个jsp在这里也有了,它所依赖的样式和图片,也有了,当然了,目前的话,这个jsp中,只有静态代码对吧,还没有动态的逻辑,那我们先不着急,我们先把这个项目呢,部署一下,我们尝试呢,去访问一下这个页面,看一看,能不能得到结果。把这个项目呢部署一下,部署以后,启动tomcat,那启动完以后,我们要访问这个资费查询的功能,怎么访问呢,应该访问谁呢,应该访问的是/findCost.do,我们现在呢,是采用MVC模式去开发项目,那么在MVC模式当中呢,我们永远都不要试图,直接访问jsp,我们通常呢,访问的都是Servlet,由它处理请求转发给jsp,所以我们访问的是/findCost.do,那么,如果项目做完整,我们应该点这个图标,但目前还没有图标,我们直接敲路径访问,测试。
  • 那我打开浏览器,试一下,http://localhost:8080/netctoss/findCost.do,然后回车,回车以后你看,你看啊,有点问题,但是呢,先别管这个问题,我们先呢,显示出这样的内容,到目前为止就算是ok了,试一下:
    NETCTOSS代码实现第一版_第9张图片
  • 那我们访问/findCost.do,它转发到了jsp,jsp呢,向浏览器输出了这样一个内容,内容是有,但是少点,差点东西对吧,这是差什么了呢,样式和图片不对,是吧,那样式和图片显示的不对,说明什么问题呢,应该是路径不对,对吧,所以,我们打开那个find.jsp,我们检查一下,那个网页里面的图片,样式的路径,如果有问题的话,我们改一下,因为之前,我们看的是静态网页,现在呢,我们做的是动态网页对吧,这个结构有所变化,这个项目的访问的方式有所变化,所以说呢,路径可能是需要调整。那我们再回到开发工具当中。那我打开呢这个find.jsp,然后呢,我们从第一行,往下看,然后你看第7行,第8行有link:

`
`

  • link是引入样式文件,对吧,就得有路径,我们看一下,发现呢,它在写路径的时候,写了一个点点杠,没管用,那你觉的应该怎么写,怎么办,你感觉应该是,这块得怎么写,点点杠,点点杠,得写两个点点杠,是这意思吧,为什么呢,为啥写两个点点杠呢,因为有人看了说,啊,这个find.jsp在这,webapp/WEB-INF/cost/find.jsp,我们要访问的,比如说images,或者是styles,对吧,在webapp目录下,中间差几级呢,一级,两级,是吧,差两级,所以两个点点杠,那你写上试试看,行不行,行么,不行,为什么不行呢,那有人提到了说,我们不应该看webapp这个地方,我们看的这个eclipse,这是源代码,我们访问的是源代码么,不是,你看这个就是错误的对吧,不应该看这。
  • 所以呢,就强化一下,我们所谓的路径指的是,部署后的项目的访问路径,是这样吧,不是源代码,看eclipse这是错误的,那我们看下部署代码,部署以后是什么结构呢,看我这个D盘,D/tts9/apache-tomcat-7.0.67/wtpwebapps/netctoss,netctoss之下有styles和images,对吧,那那个find.jsp在哪呢,netctossWEB-INF/cost之下,哎,我看了一下,部署以后还是差两级啊,那为啥不行呢。
    NETCTOSS代码实现第一版_第10张图片
  • 那应该怎么写呢,我先说一下结论啊,应该是这样写,应该是一个点点杠都没有,就直接不写点点杠,才是ok的。那这个我们先留个悬念,就先把它去掉,先试一下,然后没有问题,我这个功能演示完,再统一讲,这个话题啊,不是说一两句话能说清楚的,那去掉以后,那这个网页上还有图片呢,对吧,还有其他的内容呢。那么我们搜一下,这个页面,比较长,我们搜一下图片,在当前的文件里,搜索一个单词,这会么,ctrl+f,find对吧,那么我要搜的单词啊,图片img,image,然后你点Find,你看哪有图片啊,find,32行有一个对吧,还有么,再搜一下,66,再搜又回去了,对吧,就32和66有这个图片,那图片和样式,它们这个目录是同级对吧,所以处理方案应该是相同的吧,所以说,如果没有点点杠,都没有对吧,把图片的前面的点点杠也去掉,32还有66。

logo
... ...


  • 那总而言之啊,就是说,这个jsp,它所引用的样式,和图片的路径不对,我们需要调整,那我们把这个,大概是七八行吧,以及呢32和66,这4行做了调整,把这4行的路径呢,点点杠都去掉了:

`
`
... ...
logo
... ...


  • 去掉以后,咱们再试下,看这回行不行呢,这回可以吧。
    NETCTOSS代码实现第一版_第11张图片

资费查询页面find.jsp存在的问题

动态显示表格数据

  • 这回就好了,那为什么会这样,别着急,后面会讲,反正目前呢,是可以了,咱们已经能够访问到这个网页了,只不过呢,页面上的内容,数据啊,是固定的对吧,是美工呢,写死的,是给我们的一个例子,那下面呢,我们需要呢,把这个数据啊,变成动态的,不是例子。那么我们查到多少条数据,在table里面,要加以显示,那么我要在table之内啊,显示这个数据,我得用什么来显示,我得怎么显示,就是,你看咱们当前是jsp对吧,我们看到的是jsp给我们的结果,jsp,它也有数据来源,它的数据来源于servlet是吧,有这个数据,现在呢,我想把数据呢,显示到table里,我们得怎么办,用el表达式取数对吧,取数以后,那是多条数据啊,怎么办,多条数据是个集合么,对不对,那怎么办,你是不是还得遍历啊,遍历得用什么呢,jstl,用哪一个,具体,forEach,所以我们在jsp上呢,想要处理这个数据,我们得得到这个数据用el,处理数据用jstl对吧,我们得引入jstl标签吧,对不对。
  • 那下面我们把这个jstl标签呢,引入进来,那么,forEach循环标签啊,它在核心的标签库之内,那我们再重新的找到那个c.tld文件,重新呢,copy一下那个名字,熟悉一下,省的你忘了,那咱们当前的电信计费项目,这里面有Libaries库,展开以后,有Maven Dependencies,是吧,然后展开以后,里面有我们所导入的jar包,我们要看的是jstl对吧,展开, 展开以后呢,看的是META-INF,对不对,META-INF之下,我们要看的是c.tld,那么打开c.tld,打开以后,那么这个文件的名字是在多少行呢,12行uri,那我们呢,把这个uri,这个名字复制一下,复制时呢,你这个严谨一点,仔细一点。那复制以后,我们可以呢,回到刚才的find.jsp,然后呢,用taglib指令,把它引入进去,那么在find.jsp,这个,就是第2行,这个位置,我们在这插入一句话 ,一个指令,在这把这个指令写好,<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>,那指令的名字呢,叫taglib,属性uri当中呢,写的是我们,就copy的那句话,后面的prefix,写的是前缀,前缀是c。
  • 那这个标签呢,导入完以后啊,咱们再找到这个网页上的那个 table,我们要把那个table中的数据呢,变成动态的数据,我们找到那个table,那找啊,在多少行呢,72行,然后呢,这一行,这个table啊,它的第一行是标题,对吧,73到83是标题,哎,你会发现什么呢,这个tr里面不是td,是th,th是什么呢,是特殊的td,那它是特殊的列,它里面的文字呢,会自动的居中,自动的加粗,明白吧,就说白了,你要不写th,写td,我们写样式,居中加粗也可以,如果你想省点事,写个th,会自动的加粗和居中也可以,所以就是特殊的td啊,没什么这个神奇的地方。那第一行标题呢,不动,然后这个84开始到98,这是第一行数据对吧,然后呢,99开始到110是第2行数据,那这两行数据都是例子对吧,我们不用,但是呢,我们删一个留一个,我把第二行删掉,保留第一行数据,我以第一行为模板,在此基础上循环输出可以吧,要删掉的话,没有参考了,我就把,就是110行,一直到之前的99行,这些行,这个tr,把它删了。
  • 删掉以后呢,保留了一行,这一行呢是,98往上一直到84。那我想以这一行为基础进行循环,那我在84之前,我再插入一行,插入一句话,那这句话就是循环对吧,因为你要循环输出行,循环,这么写啊,,那么这个循环啊,它的结束应该是写到哪去呢,不应该在这对吧,应该是在它下面这个tr之后结束对吧,那我把这个结束符呢,剪切一下,把它放到tr的后面,那这样的话呢,我们每次循环,循环内部输出一行对吧,就这样一个结构,那么另外呢,forEach标签上呢,还有一些属性,有一个属性叫items,其次呢,还有一个属性叫var, ,那items当中啊,我们应该写上呢,我们要遍历的那个数据对吧,这个数据是由Servlet转发过来,转发的时候,那个数据的名字叫什么,当时我叫的是costs,对吧,复数,那我就写了,我要获取它,在这写了,写items="costs",对吧,行不行啊,行什么啊,合适么,不行,你要取数,得写什么啊,el表达式,但是我们很容易,在这个地方容易忘,所以说,演示一下,这样是不行的,你要取数,必须得写el,别忘了,items="${costs }"
  • 就总而言之啊,基本原则就是,我们这个jstl标签,它所依赖的数据,都是用el取的,对吧,是这样的。然后呢,我们遍历的,这是一个集合,那每次遍历能从这个集合中得到一条数据,这一条数据是一个cost对象,是这样吧,那为了引用这个对象方便,我们给它取一个别名,var得取个别名,别名叫啥都行,那我们就叫c吧,因为这个costs是以c开头对吧,叫c比较合理,那c啊,是一个对象,那我想把对象中的属性,输出到td里,是这样吧,是吧,我就想把cost属性,什么id啊,name啊,输出到td里么,那在这啊,我把这个默认值,这个写死的值,替换成变量,那我要输出var="c",这个c的内容,用什么来输出呢,还是el,因为el可以取数,也可以输出对吧,它有两个功能,那我输出呢,是c.costId,即${c.costId },那么这个c.costId,costId是什么呢,这叫什么啊,这叫bean属性,我们写的是costId ,但它底层在调用时,调的是getCostId方法对吧,就是这样。
  • 那继续啊,同理,这是第一列是id,叫costId,第2列是名称,${c.name },我这个程序啊,它一写完之后呢,就跳一下啊,这个可能跟开发工具有关系啊,c.name,同理,第3列,第3列是基本时长,${c.baseDuration },然后呢,下一列是基本费用,${c.baseCost },再往后呢,是单位费用,${unitCost },然后再往后下一列,下一列是这个创建时间,${c.creatime },然后呢,再下一列,这个是开通时间,${c.startime },最后一列呢,是状态,那么状态呢,咱们表里存的数据,字段叫status对吧,存的是什么呢,是那个char,存的是0和1,对吧,0代表开通,1代表暂停,我们得到是0和1,但是呢,不能给用户看到0和1对吧,它不知道啥意思,我们需要翻译一下,那怎么翻译呢,你得用判断对吧,和我们翻译那个M,F,性别一样( 学生性别判断功能之JSTL标签if )。
  • 那我们要判断的话,用哪个标签啊,if,除了if,还有么,choose,区别是什么呢,一个有else,一个没有,那这里我用哪一个呢,其实用哪个都行,用if也可以,因为我写两个条件不一样,也没关系对吧,还是写if吧,if简单啊,我也喜欢写这个标签啊,,if标签之上有test属性,这里面写条件,当这个条件成立时候,我们输出某值,那我们要判断什么呢,那个status是否等于0或者1,对吧,判断取值,那就直接写了啊,,要写el表达式,就总之啊,我们在书写这个数据啊,条件的时候,特别容易呢,把这个el给忘了,所以你要注意啊。那你看,咱们el表达式,这里呢是,既取了值,又做了运算对吧,是吧,又做了运算,判断是否相等,那么如果等于0,我们显示的是什么呢,开通,否则,那再写个if,暂停,到这,咱们这个jsp,它里面的逻辑就完成了,这个数据呢,由原来的静态的,改为了动态的。

                
    
资费ID 资费名称 基本时长 基本费用 单位费用 创建时间 开通时间 状态
${c.costId } ${c.name } ${c.baseDuration } ${c.baseCost } ${c.unitCost } ${c.creatime } ${c.startime } 开通 暂停

  • 那么写完以后啊,咱们试一下,看行不行啊,行吧,可以吧,1,2,3,4,5,6,数据都有了,这个开通时间没有,没有原因是什么呢,就是我预置的数据,它没有写这个时间,明白吧,它就是空的,这个是数据的问题,不是程序的问题,就这样。
    NETCTOSS代码实现第一版_第12张图片

表格中日期时间的数据问题

  • 那我们这个功能完成以后,可能还会有一些问题,心中有一些疑问,那关于这些问题呢,我做一些解答,那你看啊,这个功能开发完以后,我们去看这个表格,发现表格里有几列数据,有几个数据,你看,一个两个,三个,四个,5个,6个,7 个,8个,后面是按钮,不是了,对吧,表格里显示了8个数据,但是我们的表里是10个字段对吧,那为什么俩没显示呢,这能想明白么,就是我工作的时候啊,表里的字段可能会很多的,有的长,多达一两百个,那我们在给用户看这个,显示这个数据的时候,只是显示一个大概的,主要的数据,明白吧,并不是都要显示,你都要显示完,也没有必要,用户也看不过来,明白吧,有些不想看,主要看哪几列,给他显示完就可以 了,其他的就默认不显示,就这样,那如果你想看到呢,其实你可以点,点这个,点修改进去看,或者点资费套餐这个地方,这个表格列有超链接,点进去,你去看一个详细的记录也可以,那是后话。
  • 总之呢,我们做查询功能,查询页面上,并不是一定要显示所有的列,显示主要几列就可以了,这是一个疑问,那么第二个疑问,有人可能会看到了,说这个创建时间,这个时间啊,是年月日,时分秒,还带个点0,点0,是什么呢,点0显然是毫秒对不对,那你想啊,我们给用户看这个数据,需要让他看到毫秒么,不需要那么精准对吧,到秒就够了,那么如果呢,我想把这个时间,把这个毫秒去掉,怎么办,这是一个问题啊。就是我们这个,做完这个功能以后啊,我们主要呢,是要解释一些问题,就是说,第3个话题是问题,然后呢,刚才第一个问题说,为什么显示一部分字段,那个我就不写在这了,那没必要,然后呢,后来又说了,这个时间怎么办。
  • 那你看,这个时间多了个毫秒,我想把它去掉,你能想到应该怎么办呢,有人可能会想到,那我能不能截取,subString对吧,可能会这样想,倒是可以,但是呢,这个截取呢,不是一个万全的办法,为啥呢,比如说,用户看到这个时间以后,说,我不不要毫秒,我还不想要这个格式,我想换一个格式,有可能吧,是吧,如果他想任意换任何的格式,怎么办,你要想万全的解决问题,就得对这个时间能够格式化,对吧,那我们如何去格式化jsp上所显示的时间呢,这是一个话题,就是说如何格式化jsp上显示的时间。
  • 那我们说格式化这时间的话,那我们在 JSP4:JSTL自定义标签的实现与应用 中,写过那个自定义标签,是不是我们当时这样用的,,然后呢,可以写format等于格式,是这样吧,只不过呢,这个标签呢,它格式化的是一个当前时间对吧,如果说,我们给这个标签,再进一步,再加一个属性,比如加个value,我们把这个时间传给它进去,比如说,我能通过value,把时间呢,传给这个标签,他能不能把这个时间给我格式化了呢,也可以,是吧,,可以这么去想,但其实呢,没有必要,这个标签啊,不用我们写,这个sysdate标签呢,本来就有啊,这个告诉你啊,就是说,我们可以 看那个jstl当中呢,有一个文件,有一个tld文件,叫fmt.tld,先不用急着看,这个fmt呢,是format缩写,是格式化的意思,那这个文件之内所包含的标签,都是用来格式化的,其中有一个标签呢,专门格式化时间的,它叫做formatDate,那我们可以利用这个已有的标签,只要你把这个tld文件导入到jsp上,就用这个已有的标签就可以格式化这个时间啊。

JSP页面中的编码问题

  • 那这个内容,就不演示了,自己根据一些那个标签的说明,一些解释,你自己用一下,就是自学一下,这是第一个问题。然后呢,还有第2个问题,第2个问题就是和路径相关的问题,这不是一个问题,是好几个,第一个你看,刚才呢,我们在写那个jsp的时候,引入的样式文件或图片,没写点点杠,对吧,这个是一个疑问,就是说,在jsp上引用css,引用这个样式啊,样式文件和图片,为什么没有写点点杠,这是第一个问题;还有,那么你像刚才,我们写代码的时候,我们jsp要放到WEB-INF里,对吧,为什么要放这里来,这也是个问题,就是说,为什么要将jsp放在WEB-INF之下,那和它相对的,为什么要将这个样式文件和图片,就是为什么要将静态资源吧,放在webapp下,静态资源包括什么呢,静态网页,图片,样式,js,等等,一切静态的资源,都要放到这里来,这是为什么。那这3个话题啊,咱们可以一起说,但是呢,可以先思考一下。
  • 然后还有一个问题,第3个问题,我们在写这个jsp的时候,我在顶部写了一个编码utf-8,<%@page pageEncoding="utf-8" %>,然后呢,我在写这个网页head里,它也有这句话utf-8,对吧,但这句话,跟以前我们写的不一样,以前我们写的是,,但在这个find.jsp里,它整的这么复杂,,你看,这能想出来么,它为什么这么麻烦呢,为啥这个写法,跟我们以前不一样呢,能想到么,就这个meta,怎么跟我们以前写的不一样,是这样的,你看find.jsp中的这个地方,,这个地方跟我们以前写的一样么,不一样,以前我们直接写,到html结束了是吧,表示这是用的是html5,那它还没结束,它写了很长是吧,这用的是什么呢,用的时X1,即XHTML 1.0,用的是另外的一个旧的版本,明白吧,那这个旧的版本,想声明meta,就必须这样写明白吧,所以,这是旧的版本,所以是这样写,是这样的,如果是H5,就不用了。
  • 但不管怎么样吧,它这里不是有这个,有编码吧,charset="utf-8",那你看,上面也有编码,这也有编码,那这两个编码,有冲突么,可以省略其中一个么,对吧,有什么关系呢,或者说,哪个编码,对什么地方产生影响呢,这个也需要探讨一下。总之啊,我们平时呢,开发小案例,模拟案例,这没什么大问题,但是呢,一开发一个项目以后,我们发现代码多了对吧,内容一多了,这个问题就多了,所以,你的项目呢,越复杂,你在这个项目中呢,所遇到的问题就会越多,困难也会越多,那你能够学到的东西也会越多,所以,我们呢,做这个项目的目的,也是希望让大家见到一下,一些问题,我们去解决问题,能成长。这是第3个话题啊,第3个问题,就是说,jsp上,怎么说呢,编码问题,具体来说呢是,为什么jsp上,就是有两处声明了编码,这两处有什么联系么,这是第3个问题。
  • 那就着这个案例,我们主要是想解决这3个问题,那第一个问题,你看这个标签怎么写的,怎么解决的,然后呢,这个第3个问题,第3个问题其实是容易能想到的,但第2个问题呢,稍微复杂一点。其实这个资费查询模块功能并不复杂,它和以前我们模拟做的这个员工查询也差不多( JSP1:利用JSP重写员工查询功能(Model1) ),只不过呢,咱们这次做的时候啊,多了一些规则,比如说jsp要放到WEB-INF里,这个静态资源要放到webapp下,然后呢,之前做的话,没有这么啰嗦,再者呢,我们这次做啊,咱们是动真格的了,真是访问数据库,真的是有静态的页面加进来,这个内容呢,都涵盖到,以后呢,可能有的环节可能会出些问题,但是呢,出问题是好事啊,因为你毕竟呢,自己尝试写了一下这个功能,遇到问题的话,解决了,你就成长了。

浏览器加载网页的过程(JSP页面中的路径引用问题)

  • 那下面呢,我说一下,刚才我们所提到的3个问题,第一个问题呢,其实呢,给了你答案,就是说,你用这个tld文件当中的这个标签,就可以解决问题,那怎么解决,自己解决,我就不给你演示了,因为工作时也是这样的,你去问项目经理,问你的这个同事啊,说有一些问题,它也是给你一个思路,给你一个方向,然后呢,你有了一个正确 的方向以后,你呢,沿着这个方向,你是翻文档啊,是百度啊,反正你用各种办法解决就ok了。所以,已经给你方向了,也非常明确,你只要看明白这个标签,就会用。然后呢,我们讲第二个话题啊,路径相关的问题,那么这个问题呢,其实,包含了3个小问题,那我们可以一起讲,因为,这些问题呢,是有一个相通之处的。
  • 那下面我来讲这个话题啊,那当然了,我们的目的不是单纯的讲清楚这个问题,透过这个问题,我们能看到web项目的某些本质,你能够加深呢,对这个项目的一些理解,而这个理解呢,是我在以前呢,做模拟项目中,所不具备的啊。那么说一下这个问题吧,那在说这个问题之前,我们先说这样一个前提,就是说浏览器,我们说浏览器访问服务器,其实呢,我们在访问服务器的查询功能的时候,其实这不是一个请求,其实呢,这里面包含了多个请求,但主要请求是一个,还有很多这个,怎么说呢,不能说次要的请求,很多自动的请求,这件事可能,大家都没有意识到,那我先提一下,那怎么确认它到底有多少个请求,到底是不是有多个请求呢,很容易看啊,我们向服务器发的请求,服务器返回了响应,我们一律在那个network里能看到。
  • 所以呢,我们在这个查询页面上localhost:8080/netctoss/findCost.do,点F12,打开这个控制台,打开这个network,打开以后啊,我们刷新一下这个查询功能,刷新一下, 刷新以后,你看network里有多个请求么,有,很多个,对吧,然后,大概看一下啊,最先是什么呢,findCost.do,然后是什么呢,css,然后是什么呢,图片,它是有一定的规律的,就是不用去细看,就是说大概,大概了解一下。
    NETCTOSS代码实现第一版_第13张图片
  • 当然了,我们看的话,你会发现呢,它这有一些解释啊,你看它是什么呢,Initiator,这个单词我也不认识啊,但是它的意思是什么呢,就是说,比如说这个请求,它是由findCost.do引起的,明白吧,你看后面的请求,都是由findCost.do引起的,就是我们主动访问的是findCost.do,但其他的,关于样式,关于图片的请求,是在这个请求之内,和它相关的请求,自动的。那大概了解了这一点以后啊,下面呢,我们就解释一下,它为什么会这样,它这个工作原理到底是什么,我们更进一步去理解web项目,那这边呢,我画的是浏览器啊,然后呢,右边再画一个服务器,那当前呢,我们访问的是服务器端的Servlet,那具体来说,我们访问的是这个MainServlet当中的findCost()方法,MainServlet.findCost(),我们访问的是它,然后呢,它处理请求的时候,它会呢,进行转发,它是把请求呢,转发给了,jsp,那这个jsp呢,它叫find.jsp啊,当然,这个jsp呢,放到了WEB-INF之下,这样的,然后呢,最终由jsp,向浏览器做出响应,关于dao,我就不写了,我就写个大概啊,这样。
  • 然后呢,我们在访问时,我们输入的路径是findCost.do,就是我们当前,把我们所知道的信息呢,把它串起来,从中呢,寻找一些答案。那么最终啊,这个find.jsp,它给浏览器返回的,返回的是什么呢,这东西给浏览器返回的是什么呢,浏览器从它这里得到了什么呢,返回的是啥啊,返回的仅仅是一个静态网页,HTML,那么Servlet也好,jsp也罢,这两者,都是服务器端的组件,那么他们的使命是用来处理HTTP协议,那么往通俗来讲,它是用来拼动态资源的,对吧,那么,我们利用的jsp拼的是什么呢,拼的是HTML,为什么这么讲呢,因为我们在写jsp的时候,我那个page指令上,没有写 contentType,是吧,那没有写,它有默认值,之前说过,默认值是什么呢,就是text/html,是这样么,对吧,所以呢,这个jsp向浏览器输出的是html,它向浏览器输出了图片么,它向浏览器输出了样式么,输出了么,有么,并没有啊,它只是向浏览器输出了html而已,所以呢,在这个请求,就在我们访问findCost.do,这个请求结束时,响应时,浏览器得到了,仅仅是html。那么,在这一刻,浏览器并没有得到样式和图片。
  • 那浏览器什么时候得到样式和图片呢,是在它加载这个网页的过程当中,是这样啊,浏览器访问MainServlet.findCost,得到了一个静态网页,静态网页是被jsp组件生成的,仅仅是网页而已,然后呢,浏览器会加载网页,加载过程中,才显示出具体的内容,那么浏览器加载网页呢,会由上向下,按顺序加载,由上往下,按顺序加载,那么首先呢,它会加载网页中的head,这一部分,加载head,然后呢,再加载body,它会这个按顺序加载啊,先后。那总之啊,这个过程啊,这个环节,我们称之为加载。然后呢,它是先加载head,然后再加载body,有先后顺序。那你注意,那浏览器啊,在加载head的时候,它会看到,这个head里有一个子标签,这个子标签叫什么呢,叫link,有吧,有的,叫link,那浏览器一看啊,link标签,link标签表示说要引入一个样式文件。
  • 那此时它并没有得到样式文件,但是网页上声明了,这里应该有,就它得到了这个网页,网页html指示浏览器它,说这里应该有个样式文件,那浏览器怎么办呢,根据指示去获取,明白吧,浏览器一看啊,这有link样式文件,它就去获取,于是呢,它会根据这个link标签的指示路径,访问服务器,然后呢,得到一个样式文件过来,那服务器呢,服务器我们这里有样式文件么,有没有啊,有,咱们之前不是存了么,对吧,在项目中有,部署过去了啊,所以,这个浏览器加载样式文件,是一个独立的请求,是和它加载网页,是一个截然不同的请求,明白吧,所以为甚么刚才你看那个network一看,第一个findCost.do,对吧,第2个,样式文件对吧,它是有先有后的,好了,所以,这是第二个请求。
  • 不过呢,平时我们开发时,我们不用关注第二个请求,因为第二个请求是自动的,对吧,只要你给它拼好了这个网页,网页中声明了这个样式,它自动取对吧,你就不用管了,但主要是第一个请求,第二个请求是自动的,但是你要知道这么回事,知道这件事,当然了,head里不止是一个link,还有另外一个对吧,又加载了一个css,所以,每当它发现head里有link,它就多加载一个css,那比如说,head当中还有一个script,应用了一个js, 它也会加载一个js,明白吧,是同样道理的,只不过我们这里没有而已。那么它在加载到样式文件以后,会读取样式文件,读取的时候发现,哎,样式文件声明了,这个地方应该有个图片,那它会去加载图片明白吧,所以紧接着这图片又来了,但我们就不说这个样式了,我们说这个body也可以。
  • 那么浏览器呢,在加载body的时候,它会发现,哎,body里又有一个标签,那个标签叫什么呢,img,那只是说这个网页中声明了,此处有图片img,但是此时此刻,图片得到了么,还没有,只是声明而已,那浏览器根据这个声明,访问服务器,服务器事先存了图片么,存了,咱们项目中有,对吧,部署后的也有,所以就得到了一个图片,得到了一个图片,因此呢,这是第3个请求,当然了,这未必就是3个请求,有可能还有很多图片对吧,大概这个意思啊,就3类请求,是这样的。所以呢,我们在想解决这个问题(第二个路径相关的问题)之前,想讲清楚这样一个前提,就是浏览器访问服务器,获得网页,获得网页之后,还要对网页加载,在加载的过程中,还要加载样式和图片,那么,总之浏览器加载网页,加载图片,加载样式,加载js,是分开的,明白吧,这里面包含了多次请求,那我们的问题的解决,是基于这个前提才能解决的,如果你不知道这个前提,没法解释,这是一个前提。
  • 这个前提呢,我在哪写一下,我在这之前写一下吧,在这顶上写一下得了吧,就是我想说的第一个前提是什么呢,就是浏览器访问服务器获得网页,及加载网页的过程中包含多次请求,这是我想说明的第一个前提,这个前提必须理解,才能理解后面的这个解释。那这是一个客观存在的事实啊,浏览器服务器的通信就是这样设计的,明白吧,这是w3c的要求,只不过以前没讲的那么细,现在告诉你,细一点,你要去接受。就总之啊,我们无论是在浏览器的network里去看,还是我给你的解释,都是这个意思。那么,浏览器加载网页的过程是比较复杂的,它里面包含了多次请求,平时我们开发时,主要关注的是第一个请求,findCost.do这个,而后者呢,是自动的,这你不用关注了,因为那么多请求,它自动会访问,但这个事实你要知道。
  • 那么在这个前提条件之下,我们要解释这样一个话题,解释这个问题啊,什么问题呢,就是我们为什么在jsp上引入样式文件和图片,不用写点点杠,../,那你看啊,这个我们在网页上引入样式文件和图片,那么是网页,咱们就以图片为例吧,是网页和图片的关系,是这样吧,是吧,网页上引入图片,是网页和图片的关系,我们从网页到图片,是这样吧,网页时当前,图片是目标,这样的。那你得知道,那网页的路径是什么啊,图片的路径是什么啊,你得知道,从这个图中看啊,这个web项目浏览器加载网页的关系图中看,就是从网页上加载图片,这件事是谁干的,肯定是浏览器干的,对吧,所以我们应该站在浏览器的角度,去分析这个问题啊,很多问题,我们需要找一个角度,因为这里有浏览器,还有服务器对吧,你站的角度得对,这件事浏览器干的,我们是以浏览器为角度。
  • 那浏览器呢,是在加载网页时访问图片,我们的目标是图片,我把它标下,换个颜色吧,换个比如说绿色,img这是我们的目标,那我们是从哪访问这个目标的,是谁去访问,从哪访问它img,是浏览器,浏览器是不是,浏览器加载这个html时访问img,访问它的啊,当前,浏览器加载的是html对吧,是这个html,所以你看啊,是谁和谁之间的相对关系,我们写的是相对路径对吧,因为没有以斜线头么,你看,咱们find.jsp这个路径,没有以斜线开头,写的是相对路径对吧:




  • 所以,谁和谁的关系呢,是这个网页和这个图片的关系,那么,对于浏览器而言,这个网页的访问路径是什么,是什么呢,有人说find.jsp,你再想一下,JSP1:转发和重定向,转发这件事,浏览器知道么,不知道,它知道有jsp存在么,不知道,它就是写了findCost.do,访问的服务器对吧,在它看来,这个网页,我是通过findCost.do得到的,而这个网页访问路径就是.do,是这意思吧,是这样的,听明白了么,这里面就没有,就是对于浏览器而言,jsp不存在,明白吧,就当它没有。所以呢,你看啊,那么是浏览器在加载这个网页时访问图片,是浏览器端得到的HTML和img的相对关系,而网页的访问路径就是findCost.do,对吧,对于浏览器而言。那图片img的路径,就是图片存在的位置,对吧,是这样吧,就是图片存放的位置啊。
  • 那我们列举一下,列举一下,这就容易推出答案了啊。这个一句话啊,还说不清楚,咱们多分几句话来说吧,第一句话,就是说,浏览器在加载网页时获取图片,是这样吧,这是第一句话;第2句话,那我们要写的图片的路径是网页,是这个被加载的网页和图片的关系对吧,所以, 所写的这个,就是怎说呢,获取图片的相对路径是加载网页,是加载的网页和图片的相对关系,这个获取图片的相对路径,它是加载的网页和图片的相对关系,这是第2句话;第3句话,那么,加载的网页的访问路径是什么呢,是完整写是 /netctoss/findCost.do(浏览器不知道转发这件事),是这样的吧,括号解释一下,浏览器不知道转发这件事,它不知道转发这件事,它不知道有jsp,明白吧,它就认为是findCost.do给我的网页啊;然后呢,第4点,那么图片的访问路径,就是图片存放的位置啊,就是什么呢,就是/netctoss/images/logo.png,咱们直接在webapp下放的图片目录对吧,所以就在这底下啊,然后呢,比如说logo.png,随便写个图片啊,就是它。然后你再看,那既然是这两者的相对路径,那是不是,是不是只写image/logo.png这一段啊,是吧。

1.浏览器在加载网页时获取图片
2.获取图片的相对路径,是加载的网页和图片的相对关系
3.加载的网页的访问路径 /netctoss/findCost.do(浏览器不知道转发这件事)
4.图片的访问路径 /netctoss/images/logo.png
5.相对路径:images/logo.png				

  • 所以,得出结论啊,这个相对路径它就是images/logo.png,所以就没有点点杠,是这样得出的一个结论。那么这个结论呢,我把它摆到这来了,这块有点绕,再多想想。所以你看啊,咱们之前啊,做案例啊,讲路径啊,有人很烦,感觉这路径好像没什么可讲的,但其实呢,为啥我老强调路径呢,路径呢,没你想的那么简单啊,如果你真的能把每一个地方的路径都搞清楚的话,那一定是我们对整个网页加载的这个顺序和结构,有了一个非常清楚的认识才能做到,所以,通过路径的背后,我们是理解这个程序执行的完整流程,它背后是这样一个话题,所以呢,我们探讨路径这件事啊,是很有,很有意思啊,挺有意思的,它真的是把这个,怎么说呢,把这个浏览器和服务器之间的交互,就是演绎的比较,怎么说呢,比较生动吧,那你到底能不能理解这个web项目,其实通过路径,能看到很大的一部分,那尽量去理解吧。

WEB-INF目录对于JSP与静态资源的作用和意义

  • 如果说,还是那句话,如果说你实在不能理解,它也不会太影响你的开发,为啥呢,工作时啊,你看人家路径怎么写的,照葫芦画瓢,明白吧,一个项目的风格都一样,它有点点杠就都有,没有就都没有,明白吧,一般问题不大,实在没招了,你全都写绝对路径,也都绝对不会有问题,明白吧。但是我们理解这个路径,能够理解更多的内容,这是本质,还有,就你面试的时候呢,面试官肯定也不会,揪着路径,问起来没完,应该也不会,不会问这么细,但是呢,我们讲它,主要是让你理解这个图所表现的这个意思。那么关于这个路径,为什么没有点点杠,这就说明白了,然后呢,我们再去看,还有,还有两个小问题,这个什么呢,为什么要将jsp放到WEB-INF下,对吧,说这个话题。
  • 为什么要把jsp放到WEB-INF里呢,又有一个前提,这个前提是什么,这个WEB-INF,它有这个特殊的能力,这个,我这个圈一下啊,这个find.jsp,它放到了WEB-INF里,假设我画的这个黄色的范围,就是WEB-INF啊,假设啊,好,这个也是一个前提吧,咱们也解释一下,WEB-INF目录,它有,天生有保护能力,它有保护能力,它可以保护内部的资源避免其,避免该资源被直接访问。这是一个,这是前提条件,咱们需要先想一下。假设这个黄色的范围就是WEB-INF啊,WEB-INF具有保护能力,它可以保护内部的资源,避免资源被直接访问,那怎么才能访问呢,需通过转发才能访问,这样的。
  • 当然,这个资源呢,咱们不算Servlet,有人说,那Servlet也是放到WEB-INF之下,因为它是class对吧,是吧,Servlet是class ,class是在WEB-INF之下吧,那有人说,那Servlet为啥能直接访问呢,因为Servlet有独立注册的网名,明白吧,它和别人不一样,我们说的这个资源,是不包括Servlet的,它比较特别,它有网名,所以可以直接访问,那么其他的资源,没有网名的,就不能,WEB-INF具有保护能力,它能保护内部资源,避免该资源直接访问,那怎么访问呢,必须通过转发,才能访问。有人说,那为什么要这样呢,这个没有为什么,Sun是这样设计的,那有人说这有用么,肯定有用,你想啊,咱们上网时,很多软件会提供一个上传的功能,对吧,上传,如果把这个资源传到了这个服务器的硬盘上,如果对资源不加以保护它,有没有可能被别人直接访问到呢,有没有可能啊,很有可能对吧,如果你不保护的话,是不是别人可以直接访问这个资源啊,是这样吧,对吧,是的。
  • 如果说你看,如果说我们去这个智联招聘,填个简历,你传了一个文档上去,如果是智联招聘,直接把这个简历放到它的服务器上,没有加以保护,别人要知道路径的话,就能访问,对吧,就危险了,所以呢,WEB-INF很好,你像其他的项目,想有这个东西,还没有呢,你像PHP和.net没有,如果说其他的语言,你想保护一个东西的话,得自己写代码,那java呢,就考虑到这一点了,它给我们一个目录WEB-INF,能直接保护,如果你上传的话,直接把那个上传文件啊,丢到WEB-INF下,谁也访问不了,明白吧,就是说保护了,必须转发才能访问,而转发的前提是,你得处理它的请求啊,这是前提啊。
  • 那我们说一下,那为啥jsp要放到这里来呢,jsp放到这里来,就是想保护它,对吧,就是要保护它,那为啥要保护它呢,因为你想啊,咱们当前呢,这个项目是MVC模式,如果我不保护jsp,jsp能不能被,别人任意的访问,可不可以访问,可以访问,那么你访问它的话,会怎么样呢,它的数据来源会丢掉对吧,如果你程序严谨的话,会看到一片空白,没有报错对吧,没有数据,如果不严谨的话,可能你没判空的话,直接抛异常了,对吧,空指针异常,都有可能,是这样吧,那你看啊,一个软件,它有这个东西,人家也能访问到,但是呢,报错了,你说是不是这个软件有漏洞,是吧,有漏洞,不管怎么样,你有漏洞,不好,反过来呢,我们把它保护起来,保护起来,你直接访问,还能访问到么,访问不到,你会得到什么结论呢,404,那404,这个问题,怨我们么,不怨我们,怨用户对吧,你说我,随便访问淘宝,后面加个什么什么路径,访问不到,你怨淘宝么,不怨,对吧,怨自己瞎写,这样的。
  • 所以啊,我们把jsp放到WEB-INF里就是想保护它,这写一下啊,就是将jsp放到WEB-INF下,就是要保护它,避免它被直接访问,从而丢掉数据来源导致报错,那一报错就是bug,我们就避免了这个bug,这是主要的目的。那为什么这个静态资源不能放到WEB-INF里呢,jsp放在那里可以,因为访问jsp一定是转发对吧,这可以,正好是转发,这没问题,为什么静态资源不能放到WEB-INF里呢,因为这些资源是直接访问的对吧,它就没有转发,你要放进去的话,还能访问到么,访问不到了,所以这样的,就是静态资源,是浏览器直接要访问的,不存在要转发,因此不能放到WEB-INF里,那有人说,那你静态资源不用保护么,那个图片,随便别人访问么,是随便访问的,静态资源不需要保护的啊,谁都可以访问,那个没关系的。
  • 你像我们平时这个网站啊,还要做这个,还要配置那个CDN加速呢,还会被这个第3方的公司缓存的,所以说,这东西它不需要保护,它是一个开放的东西,再一个只要我能访问到你这个网页,比如说我这里,比如说我这能访问这个淘宝,https://www.taobao.com,访问淘宝的话,我右键,右键查看网页源代码,那淘宝的代码一目了然对吧,他想保护,保护不了,因为你最终啊,想让浏览器看到这个内容,就必须把代码给它,明白吧,这东西没有什么保护的这个必要,就谁也,没人保护它,就这样。再一个呢,比如说你看这个,看这个网站啊,F12,你看这个Sources,Sources是代码,在这代码里,你能看到这个静态网页,当然它这个是在,部署到云上面去了啊,你能够看到什么呢,这个网页所依赖的所有的图片,这都能一目了然,都能看到,这不是什么秘密。
    NETCTOSS代码实现第一版_第14张图片
  • 所以说,静态资源,没有保护的必要。那么通过这个图形啊,通过这个,这个过程啊,咱们就解释了一下,3个问题,那么关于后两个问题,WEB-INF这个问题,就这件事吧,大家尽量去理解,还是那句话,你实在不理解,它并不会影响你的编程,但你能把它理解透了的话,你对web项目的理解,就比较到位了,对整个的过程,就理解比较到位了,那么我们将来呢,在做很多事情的时候,遇到很多问题的时候呢,都好解决,都好解释。我把这图存一下。
    NETCTOSS代码实现第一版_第15张图片
  • 所以啊,我们感觉呢,当前阶段,有一些内容,有一些这个抽象的地方,主要就是什么呢,它这个流程是比较繁琐的,我们在互联网上能够看到这些个网页,能够看到丰富的内容,这个不容易,所以我们想开发一个web项目呢,在底层的一些的逻辑,还是挺复杂的,好在呢,这么复杂的事啊,我们平时,不用我们去处理,只不过要了解一下,遇到了问题也好解决啊,仅此而已。
  • 最后呢,还有一个问题啊,这个问题就是看图啊,刚才那个图叫什么来着,写上就行。然后呢,还有一个编码问题,就是说,我们jsp上page那块写了个编码,然后呢,meta里又写了个编码对吧,这两个编码都是utf-8,有什么关系么,有什么这个联系么,解释一下。其实这件事,也容易理解啊,就是在 jsp的运行原理 时,其实就说过类似的这个意思啊,只不过没说完善,咱们再完善一下啊,jsp原理:
    NETCTOSS代码实现第一版_第16张图片
  • 之前我就说过,咱们在jsp上所写的pageEncoding,在什么时候会用到,翻译的时候,是吧,那翻译的时候啊,是tomcat去翻译,tomcat会读这个文件对吧,它得知道这个文件是什么编码,我怎去读,所以看第1行pageEncoding,那pageEncoding在翻译的时候会用到啊,然后呢,你注意jsp的使命,是向浏览器输出东西,当然不是它输出的,是它的替身对吧,实际是这个根据JSP自动生成的Servlet,当然了,我们就认为这个Servlet,就是这个JSP,可以吧,这俩就是一个东西啊,别去分开看,我们就认为生产的Servlet它就是jsp,它就来源于jsp,那你看,它输出的时候,它输出的是什么呢,通常,都是一个静态网页html,是这样么,那刚才画的浏览器加载网页流程图也是这样,那最终给我们的是什么呢,不就是个静态网页么,是吧,静态网页。
  • 那得到静态网页的那一刻,这个网页上,还有pageEncoding那句话么,早就没了,不信你看啊,现在我们有这个项目好说了,你看,我访问的服务器,localhost:8080/netctoss/findCost.do,访问的是Servlet转发到jsp了吧,我肯定访问到了jsp对吧,那jsp输出了这个内容,我们这个网页里有,还有这个pageEncoding那个东西么,看一下啊,我们可以这样,右键/查看网页源代码:
    NETCTOSS代码实现第一版_第17张图片
  • 你看第一行,空白对吧,没了,那服务器给我们的是什么呢,就是纯静态的html对吧,就是这个,没有那些个指令,那些个tld标签什么的,没有了。那浏览器要加载这个网页,它需不需要知道一个编码,需要,那它看谁呢,看meta,是这意思吧,那meta给谁用呢,浏览器用,pageEncoding给谁用呢,服务器用,两个东西,两个软件,各取一个明白吧,这个意思。所以呢,解释一下,那么为什么jsp上声明了两个编码 ,其中呢,pageEncoding是给服务器翻译jsp时用的;然后呢,meta中的编码,是给浏览器加载静态,加载html时用的,是这样的。
  • 所以你看啊,很多事情呢,咱们都是,还是在这个话题,这个框架之内解释的。就是说,我们在理解这个web项目时啊,你一定要有这样的意识,Servlet和jsp都是一个动态的组件,它最终返回的是一个静态网页明白吧,你不要以为这个jsp,直接把jsp给浏览器了,不是,浏览器没有能力运行jsp,明白吧,jsp是给浏览器拼的一个html,我们看到的得到的是一个html,只不过呢,这个html啊,我们看到的是结果,我们没看到那个文件对吧,但它是客观存在的,是在浏览器的内存里存在的,你的心里要想到它,是这样的,是个虚的东西。
  • 那这3个话题就说完了。我这个图,我的解释有好几段对吧,可能看的时候,最后哪段是那段,什么关系有点乱,我把它标个顺序,你这先看哪个后看哪个,这样好一点,标个顺序。我换一种图标啊,换个方块,大家看的话呢,先看这个,这是一个基本的前提,浏览器访问服务器加载网页的过程中包含多个请求,先把它看明白;然后第2个呢,看这个,浏览器在加载网页时的访问路径,第一个浏览器加载网页的过程,这个前提能解释访问路径这件事啊;然后第3个呢,看这个,WEB-INF有保护能力,这个是解释JSP和静态资源存放位置的前提,然后第4个呢,看这个JSP需要放到WEB-INF下,第5,看这个,静态资源不能放到WEB-INF下,按照顺序这么看,这样比较好。

资费查询功能代码实现

1.src/main/java/util/DBUtil.java

package util;

import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

import org.apache.tomcat.dbcp.dbcp.BasicDataSource;

public class DBUtil {

	//连接池对象- 由DBCP提供
	private static BasicDataSource ds;
	
	static {
		//加载参数
		Properties p = new Properties();
		try {
			p.load(DBUtil.class.getClassLoader()
				.getResourceAsStream("db.properties"));
			//读取这些参数
			String driver = p.getProperty("driver");
			String url = p.getProperty("url");
			String user = p.getProperty("user");
			String pwd = p.getProperty("pwd");
			String initSize = p.getProperty("init_size");
			String maxSize = p.getProperty("max_size");
			
			/*
			 * 
			 * 		
			 * 		
			 * 
			 * 		c3p0
			 */
			
			//创建连接池
			ds = new BasicDataSource();
			//设置参数
			//使用这个参数注册驱动
			ds.setDriverClassName(driver);
			//使用这3个参数创建连接
			ds.setUrl(url);
			ds.setUsername(user);
			ds.setPassword(pwd);
			//使用其他参数管理连接
			ds.setInitialSize(Integer.parseInt(initSize));
			ds.setMaxActive(Integer.parseInt(maxSize));
			/*ds.setInitialSize(new Integer(initSize));
			ds.setMaxActive(new Integer(maxSize));*/
			
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException(
				"加载db.properties失败", e);
		}
	}
	
	/**
	 * 由连接池创建的连接,其实现类由连接池提供.
	 */
	public static Connection getConnection() 
		throws SQLException {
		return ds.getConnection();
	}
	
	/**
	 * 连接池提供的实现类,其close方法内部逻辑是,
	 * 将连接归还给连接池,即它会清空连接对象中的数据,
	 * 并且将连接标记为空闲态.
	 * 或者说:
	 * 由连接池创建的连接,连接的close方法被连接池重写了,
	 * 变为了归还连接的逻辑,即:连接池会将连接的状态设置为空闲,
	 * 并清空连接中所包含的任何数据。
	 */
	public static void close(Connection conn) {
		if(conn != null) {
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
				throw new RuntimeException(
					"归还连接失败", e);
			}
		}
	}
	
	public static void rollback(Connection conn) {
		if(conn != null) {
			try {
				conn.rollback();
			} catch (SQLException e) {
				e.printStackTrace();
				throw new RuntimeException(
					"回滚事务失败", e);
			}
		}
	}
	
	public static void main(String[] args) throws SQLException {
		Connection conn = DBUtil.getConnection();
		System.out.println(conn);
		DBUtil.close(conn);
	}
}

2.src/main/resources/db.properties

# database connection parameters
driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@localhost:1521:orcl
user=SYSTEM
pwd=Oracle123
# datasource parameters
init_size=1
max_size=3

3.src/main/resources/jdbc.properties

url=jdbc:oracle:thin:@localhost:1521:xe
driver=oracle.jdbc.OracleDriver
user=lhh
password=123456

4.src/main/java/entity/Cost.java

package entity;

import java.io.Serializable;
import java.sql.Timestamp;

public class Cost implements Serializable {

	private Integer costId;
	private String name;
	//基本时长
	private Integer baseDuration;
	//基本费用
	private Double baseCost;
	//单位费用
	private Double unitCost;
	//状态(枚举):0-开通;1-暂停;	
	private String status;
	//资费说明
	private String descr;
	//创建时间
	private Timestamp creatime;
	//开通时间
	private Timestamp startime;
	//资费类型(枚举):1-包月;2-套餐;3-计时;
	private String costType;
	
	public Integer getCostId() {
		return costId;
	}
	public void setCostId(Integer costId) {
		this.costId = costId;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Integer getBaseDuration() {
		return baseDuration;
	}
	public void setBaseDuration(Integer baseDuration) {
		this.baseDuration = baseDuration;
	}
	public Double getBaseCost() {
		return baseCost;
	}
	public void setBaseCost(Double baseCost) {
		this.baseCost = baseCost;
	}
	public Double getUnitCost() {
		return unitCost;
	}
	public void setUnitCost(Double unitCost) {
		this.unitCost = unitCost;
	}
	public String getStatus() {
		return status;
	}
	public void setStatus(String status) {
		this.status = status;
	}
	public String getDescr() {
		return descr;
	}
	public void setDescr(String descr) {
		this.descr = descr;
	}
	public Timestamp getCreatime() {
		return creatime;
	}
	public void setCreatime(Timestamp creatime) {
		this.creatime = creatime;
	}
	public Timestamp getStartime() {
		return startime;
	}
	public void setStartime(Timestamp startime) {
		this.startime = startime;
	}
	public String getCostType() {
		return costType;
	}
	public void setCostType(String costType) {
		this.costType = costType;
	}
}

5.src/main/java/dao/CostDao.java

package dao;

import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import com.sun.corba.se.spi.orbutil.fsm.Guard.Result;

import entity.Cost;
import util.DBUtil;

public class CostDao implements Serializable {
	public List findAll(){
		Connection conn	= null;
		try {
			conn = DBUtil.getConnection();
			//String sql = "select * form cost_lhh "+"order by cost_id";//测试错误页面error.jsp
			String sql = "select * from cost_lhh "+"order by cost_id";
			Statement smt = conn.createStatement();
			ResultSet rs = smt.executeQuery(sql);
			List list = new ArrayList();
			while(rs.next()) {
				Cost c = new Cost();
				c.setCostId(rs.getInt("cost_id"));
				c.setName(rs.getString("name"));
				c.setBaseDuration(rs.getInt("base_duration"));
				c.setBaseCost(rs.getDouble("base_cost"));
				c.setUnitCost(rs.getDouble("unit_cost"));
				c.setStatus(rs.getString("status"));
				c.setDescr(rs.getString("descr"));
				c.setCreatime(rs.getTimestamp("creatime"));
				c.setStartime(rs.getTimestamp("startime"));
				c.setCostType(rs.getString("cost_type"));
				list.add(c);
			}
			return list;
		} catch (SQLException e) {
			e.printStackTrace();
			throw new RuntimeException("查询资费失败",e);
		}finally {
			DBUtil.close(conn);
		}
	}
	
	public static void main(String[] args) {
		CostDao dao = new CostDao();
		List list = dao.findAll();
		for(Cost c : list) {
			System.out.println(c.getCostId()+","+c.getName());
		}
	}
}

6.src/main/java/web/MainServlet.java

package web;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import dao.AdminDao;
import dao.CostDao;
import entity.Admin;
import entity.Cost;
import util.ImageUtil;

public class MainServlet extends HttpServlet {

	@Override
	protected void service(
		HttpServletRequest req, 
		HttpServletResponse res) throws ServletException, IOException {
		//获取访问路径
		String path = req.getServletPath();
		//根据规范(图)处理路径
		if("/findCost.do".equals(path)) {
			findCost(req,res);
		} else {
			throw new RuntimeException("没有这个页面");
		}
	}
	
	//查询资费
	protected void findCost(
		HttpServletRequest req, 
		HttpServletResponse res) throws ServletException, IOException {
		//查询所有的资费
		CostDao dao = new CostDao();
		List list = dao.findAll();
		//将请求转发到jsp
		req.setAttribute("costs", list);
		//当前:/netctoss/findCost.do
		//目标:/netctoss/WEB-INF/cost/find.jsp
		req.getRequestDispatcher("WEB-INF/cost/find.jsp").forward(req, res);
	}	
}

7.src/main/webapp/WEB-INF/web.xml



  netctoss
  
    index.html
    index.htm
    index.jsp
    default.html
    default.htm
    default.jsp
  
  
  
  	main
  	web.MainServlet
  
  
  	main
  	*.do
    

8.servlet-doc/NETCTOSS_HTML/fee/fee_list.html



    
        
        达内-NetCTOSS
        
        
                
    
    
        
        
        
        
        
        
        
        
删除成功!
资费ID 资费名称 基本时长 基本费用 单位费用 创建时间 开通时间 状态
1 包 20 小时 20 小时 24.50 元 3.00 元/小时 2013/01/01 00:00:00 暂停
2 包 40 小时 40 小时 40.50 元 3.00 元/小时 2013/01/21 00:00:00 2013/01/23 00:00:00 开通

业务说明:
1、创建资费时,状态为暂停,记载创建时间;
2、暂停状态下,可修改,可删除;
3、开通后,记载开通时间,且开通后不能修改、不能再停用、也不能删除;
4、业务账号修改资费时,在下月底统一触发,修改其关联的资费ID(此触发动作由程序处理)

9.src/main/webapp/WEB-INF/cost/find.jsp

<%@page pageEncoding="utf-8" %>


    
        
        达内-NetCTOSS
        
        
                
    
    
        
        
        
        
        
        
        
        
删除成功!
资费ID 资费名称 基本时长 基本费用 单位费用 创建时间 开通时间 状态
${c.costId } ${c.name } ${c.baseDuration } ${c.baseCost } ${c.unitCost } ${c.creatime } ${c.startime } 开通 暂停

业务说明:
1、创建资费时,状态为暂停,记载创建时间;
2、暂停状态下,可修改,可删除;
3、开通后,记载开通时间,且开通后不能修改、不能再停用、也不能删除;
4、业务账号修改资费时,在下月底统一触发,修改其关联的资费ID(此触发动作由程序处理)

写在后面

  • NETCTOSS代码实现第二版:资费增加功能

你可能感兴趣的:(Project)