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了,就把它成为你的一种习惯,这个很关键的。
  • 或者说啊,我就是想用我们所要讲的项目,去演示一下这个开发思路,我们这段课的主要的展示东西是这个,但是你看啊,简简单单是几句话,是几个基本的流程,但是我们在操作时呢,可能没有这么简单啊,那么遇到具体的问题时,具体的细节时候啊,我们再看,再说,再分析。那按照我的要求,我们第一步,讨论需求,那这个需求其实没什么可讨论的,就是资费管理是什么作用,也说过了是吧,用来维护资费的数据,然后呢,我们要做的是什么呢,查询功能,增加功能,修改功能,这些功能,而启用禁用,我们就不做了,就别管它了,因为我时间有限,做不出来那么多东西,那我们现在呢,先讲做这个查询功能,那查询的这个使用方式也很简单,就是说,我们一登录以后,进到了首页主页,我点资费管理图标,就打开了查询页面,默认就查到了所有数据,明白吧,就这么简单啊,就这样操作,这就是业务,这就是需求,这是简单到不能再简单的需求了。

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

  • 那这个需求你了解以后,我们需要做出设计,刚才说了,做出设计的第一步呢是,你要先将这个功能,拆分成若干请求,那我们需要根据操作推导出,它有多少个请求,那大家看下我的操作啊,你感觉这个功能有几个请求,你看啊,在主页里点击资费管理的图标,打开查询页面,一个请求,就点一次对吧,所以就一个请求,所以最简单了,那这一个请求,整个执行过程是什么样呢,我们再按照以前,我所画图的习惯,再画一下,然后呢,你也去怎么样呢,就想一下,这个图为什么是这么画,合不合理,然后呢,也可以练一练,这个画图,因为这是一种解决问题的能力,那画一下啊。
  • 其实我天天给你画这个图,你应该也,也了解了啊,我画图的套路是这样的,这是个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<Cost>,封装多条数据,这样。这还不够啊,我们调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.List<Cost>3.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过来看看,就是复制粘贴,但是你注意,你放的位置一定要放对了,因为这个部署的时候,有的代码会被丢弃,你要放错位置的话,不小心整没了,麻烦了啊,一定要严谨,这个程序员呢,最重要的一个品质就是要严谨,但我看很多人这个不够严谨,你还得练啊,还得注意啊。
  • 那copy过来以后,咱们最好测一下,你不要只看啊,不就是复制粘贴么,有什么问题呢,未必,有可能复制粘贴就会导致问题,最好测一下。那这样,我就在DBUtil里写个main方法测,可以吧,我在netctoss,这个拷贝的DBUtil.java内,这个类的内部,我在后面写个main方法,对这个类啊,DBUtil类,加以测试。那测试怎么写呢,就是,我们调用这个工具类,获取一个连接,输出一看,没有问题就可以,写一下。因为咱们这只是一个测试,不是正式代码,所以异常呢,我就简单处理,直接往外抛了,我就没有try-catch,那么写完以后呢,大家把这个main方法执行一下,看一下输出结果,我执行一下,没报错,没问题,就这样了。

开发思路的重要性

  • 当然有可能有的人,个别的会报错,有可能啊,是不是你之前连的那个数据库,那数据库连不上了呢,对吧,检查一下,遇到问题呢,想办法解决一下,实在是那库连不上了,你可以换个库,再调一调。那这个开发的思路介绍了。那么我说的开发思路,这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包下的接口。
  • 这个dao啊,CostDao,咱们去写啊,那刚才我们说这个思路是说了,我们当前呢,做的是查询功能,那么查询功能,它需要dao提供一个查询方法对吧,findAll,我们写这个findAll,没有参数,非常简单啊,那我声明这个方法啊,public List<Cost> findAll(){ return null; },这个方法呢,我们声明完了,声明完以后呢,加以实现。首先呢,第一步,创建连接,那么这个连接,最终是要关闭,对吧,无论是否报异常,我们都要尝试关闭它,然后呢,如果捕获到异常的时候,我们要对异常呢,进行处理,所以,创建连接啊,不是一句话,是一个套路,是一段话啊,写一下。

public List<Cost> 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<Cost> list = new ArrayList<Cost>();,循环之前呢,创建一个集合,整个循环完成以后,我们返回这个集合,return list;,那你看啊,咱们在上面创建集合,在下面返回集合,那我们最后那句话,return null;,还有用吗,没有了,把它删掉啊。

public List<Cost> 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<Cost> list = new ArrayList<Cost>();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<Cost> 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<Cost> list = new ArrayList<Cost>();//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<Cost> findAll(){Connection conn    = null;try {conn = DBUtil.getConnection();//String sql = "select * form cost_lhh "+"order by cost_id";//测试错误页面error.jspString sql = "select * from cost_lhh "+"order by cost_id";Statement smt = conn.createStatement();ResultSet rs = smt.executeQuery(sql);List<Cost> list = new ArrayList<Cost>();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<Cost> list = dao.findAll();for(Cost c : list){System.out.println(c.getCostId()+","+c.getName());}
}

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

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

  • 那把这个dao写完了,那这个内容是我们对以前的jdbc,那个知识点的回顾,然后呢,第3步完成以后啊,我们进行第4步,我想呢,写一个MainServlet,我希望呢,在这个类当中呢,处理所有请求,那我的要求是这样,我们这个项目中啊,所有的这个功能,所有的请求,都是点do,.do,这是一种习惯,点do。我有时这样讲,我们项目中所有的普通的请求,咱们都叫点do,以.do为后缀,那将来呢,如果有很特殊的请求,我们再加别的后缀,这样比较灵活;那么,我们工作时呢,很多项目都是这么干的。那下面我们来写这个Servlet啊。那么打开开发工具,然后啊,我们在这个web这个包下,创建这个类,就叫MainServlet,继承于HttpSerlvet:
  • 然后呢,我们在这个类当中呢,要想处理请求啊,也需要重写父类的方法,所以呢,我重写父类的service方法,那么对这个方法,咱们加以整理,那么我们在这个方法之内吧,如果说想处理所有请求,那这个代码得怎么写,这是Servlet的内容,得回顾一下,怎么办,先获取路径,然后呢,判断,那我获取路径了,我获取路径的话,之前说过,获取路径有好几种方式对吧,都有哪些方法来着,getContextPath,得到的是什么呢,项目名,还有呢,getServletPath,得到的是Servlet访问路径对吧,还有呢,getRequestURI,对吧,和RequestURL,得到的是URI和URL,那在我们这个java项目里,URI我们得到的是绝对路径对吧,URL是完整路径。
  • 那你说我现在要得到哪一个路径去判断,我要获取哪个路径去判断,ServletPath,然后我怎么去判断呢,它等于多少呢,是不是还差点事啊,差点什么事呢。就是我们在使用一个组件处理多个请求之前,必须得有规范对吧,一定要有规范,没有规范的话,就没法写了,我写不下去了,那么我想获取某一个路径,也得知道规范,那规范的路径,到底怎么规定的对吧,我看取哪一个合适,如果没有规范的话,我也不知道该怎么取好。所以呢,在此之前我们需要补充一下规范啊,那么这个,咱们这个没有具体的文档啊,所以,我说的就是规范,我告诉你就是规范啊,那这个规范为了直观一点,我直接把它写在图里算了,你可以看这个图,图上就把这个规范写清楚,可以吧,就在这体现啊,改一下这个图。
  • 那我的要求啊是,资费管理这个功能,它的访问路径是,是这个,是/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<Cost> 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


<servlet><servlet-name>main</servlet-name><servlet-class>web.MainServlet</servlet-class>
</servlet>
<servlet-mapping><servlet-name>main</servlet-name><url-pattern>*.do</url-pattern>
</servlet-mapping>

  • 那配置好这个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之下,不要放错地方啊,这有规定,你看我粘贴完以后,是这样一个结构:
  • 就这样,在这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了,试一下:
  • 那我们访问/findCost.do,它转发到了jsp,jsp呢,向浏览器输出了这样一个内容,内容是有,但是少点,差点东西对吧,这是差什么了呢,样式和图片不对,是吧,那样式和图片显示的不对,说明什么问题呢,应该是路径不对,对吧,所以,我们打开那个find.jsp,我们检查一下,那个网页里面的图片,样式的路径,如果有问题的话,我们改一下,因为之前,我们看的是静态网页,现在呢,我们做的是动态网页对吧,这个结构有所变化,这个项目的访问的方式有所变化,所以说呢,路径可能是需要调整。那我们再回到开发工具当中。那我打开呢这个find.jsp,然后呢,我们从第一行,往下看,然后你看第7行,第8行有link:

<link type="text/css" rel="stylesheet" media="all" href="../styles/global.css" />`
<link type="text/css" rel="stylesheet" media="all" href="../styles/global_color.css" />`

  • 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之下,哎,我看了一下,部署以后还是差两级啊,那为啥不行呢。
  • 那应该怎么写呢,我先说一下结论啊,应该是这样写,应该是一个点点杠都没有,就直接不写点点杠,才是ok的。那这个我们先留个悬念,就先把它去掉,先试一下,然后没有问题,我这个功能演示完,再统一讲,这个话题啊,不是说一两句话能说清楚的,那去掉以后,那这个网页上还有图片呢,对吧,还有其他的内容呢。那么我们搜一下,这个页面,比较长,我们搜一下图片,在当前的文件里,搜索一个单词,这会么,ctrl+f,find对吧,那么我要搜的单词啊,图片img,image,然后你点Find,你看哪有图片啊,find,32行有一个对吧,还有么,再搜一下,66,再搜又回去了,对吧,就32和66有这个图片,那图片和样式,它们这个目录是同级对吧,所以处理方案应该是相同的吧,所以说,如果没有点点杠,都没有对吧,把图片的前面的点点杠也去掉,32还有66。

<img src="../images/logo.png" alt="logo" class="left"/>
... ...
<img src="../images/close.png" onclick="this.parentNode.style.display='none';" />

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

<link type="text/css" rel="stylesheet" media="all" href="styles/global.css" />`
<link type="text/css" rel="stylesheet" media="all" href="styles/global_color.css" />`
... ...
<img src="data:images/logo.png" alt="logo" class="left"/>
... ...
<img src="data:images/close.png" onclick="this.parentNode.style.display='none';" />

  • 去掉以后,咱们再试下,看这回行不行呢,这回可以吧。

资费查询页面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之前,我再插入一行,插入一句话,那这句话就是循环对吧,因为你要循环输出行,循环,这么写啊,<c:forEach></c:forEach>,那么这个循环啊,它的结束应该是写到哪去呢,不应该在这对吧,应该是在它下面这个tr之后结束对吧,那我把这个结束符呢,剪切一下,把它放到tr的后面,那这样的话呢,我们每次循环,循环内部输出一行对吧,就这样一个结构,那么另外呢,forEach标签上呢,还有一些属性,有一个属性叫items,其次呢,还有一个属性叫var,<c:forEach var="" items=""> ,那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,即<td>${c.costId }</td>,那么这个c.costId,costId是什么呢,这叫什么啊,这叫bean属性,我们写的是costId ,但它底层在调用时,调的是getCostId方法对吧,就是这样。
  • 那继续啊,同理,这是第一列是id,叫costId,第2列是名称,<td><a href="fee_detail.html">${c.name }</a></td>,我这个程序啊,它一写完之后呢,就跳一下啊,这个可能跟开发工具有关系啊,c.name,同理,第3列,第3列是基本时长,<td>${c.baseDuration }</td>,然后呢,下一列是基本费用,<td>${c.baseCost }</td>,再往后呢,是单位费用,<td>${unitCost }</td>,然后再往后下一列,下一列是这个创建时间,<td>${c.creatime }</td>,然后呢,再下一列,这个是开通时间,<td>${c.startime }</td>,最后一列呢,是状态,那么状态呢,咱们表里存的数据,字段叫status对吧,存的是什么呢,是那个char,存的是0和1,对吧,0代表开通,1代表暂停,我们得到是0和1,但是呢,不能给用户看到0和1对吧,它不知道啥意思,我们需要翻译一下,那怎么翻译呢,你得用判断对吧,和我们翻译那个M,F,性别一样( 学生性别判断功能之JSTL标签if )。
  • 那我们要判断的话,用哪个标签啊,if,除了if,还有么,choose,区别是什么呢,一个有else,一个没有,那这里我用哪一个呢,其实用哪个都行,用if也可以,因为我写两个条件不一样,也没关系对吧,还是写if吧,if简单啊,我也喜欢写这个标签啊,<c:if test=""></c:if>,if标签之上有test属性,这里面写条件,当这个条件成立时候,我们输出某值,那我们要判断什么呢,那个status是否等于0或者1,对吧,判断取值,那就直接写了啊,<c:if test="${c.status==0 }"></c:if>,要写el表达式,就总之啊,我们在书写这个数据啊,条件的时候,特别容易呢,把这个el给忘了,所以你要注意啊。那你看,咱们el表达式,这里呢是,既取了值,又做了运算对吧,是吧,又做了运算,判断是否相等,那么如果等于0,我们显示的是什么呢,开通,否则,那再写个if,<c:if test="${c.status==1 }">暂停</c:if>,到这,咱们这个jsp,它里面的逻辑就完成了,这个数据呢,由原来的静态的,改为了动态的。

<table id="datalist"><tr><th>资费ID</th><th class="width100">资费名称</th><th>基本时长</th><th>基本费用</th><th>单位费用</th><th>创建时间</th><th>开通时间</th><th class="width50">状态</th><th class="width200"></th></tr><c:forEach var="c" items="${costs }">                <tr><td>${c.costId }</td><td><a href="fee_detail.html">${c.name }</a></td><td>${c.baseDuration }</td><td>${c.baseCost }</td><td>${c.unitCost }</td><td>${c.creatime }</td><td>${c.startime }</td><td><c:if test="${c.status==0 }">开通</c:if><c:if test="${c.status==1 }">暂停</c:if></td><td>                                <input type="button" value="启用" class="btn_start" onclick="startFee();" /><input type="button" value="修改" class="btn_modify" onclick="location.href='fee_modi.html';" /><input type="button" value="删除" class="btn_delete" onclick="deleteFee();" /></td></tr></c:forEach>
</table>

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

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

  • 那我们这个功能完成以后,可能还会有一些问题,心中有一些疑问,那关于这些问题呢,我做一些解答,那你看啊,这个功能开发完以后,我们去看这个表格,发现表格里有几列数据,有几个数据,你看,一个两个,三个,四个,5个,6个,7 个,8个,后面是按钮,不是了,对吧,表格里显示了8个数据,但是我们的表里是10个字段对吧,那为什么俩没显示呢,这能想明白么,就是我工作的时候啊,表里的字段可能会很多的,有的长,多达一两百个,那我们在给用户看这个,显示这个数据的时候,只是显示一个大概的,主要的数据,明白吧,并不是都要显示,你都要显示完,也没有必要,用户也看不过来,明白吧,有些不想看,主要看哪几列,给他显示完就可以 了,其他的就默认不显示,就这样,那如果你想看到呢,其实你可以点,点这个,点修改进去看,或者点资费套餐这个地方,这个表格列有超链接,点进去,你去看一个详细的记录也可以,那是后话。
  • 总之呢,我们做查询功能,查询页面上,并不是一定要显示所有的列,显示主要几列就可以了,这是一个疑问,那么第二个疑问,有人可能会看到了,说这个创建时间,这个时间啊,是年月日,时分秒,还带个点0,点0,是什么呢,点0显然是毫秒对不对,那你想啊,我们给用户看这个数据,需要让他看到毫秒么,不需要那么精准对吧,到秒就够了,那么如果呢,我想把这个时间,把这个毫秒去掉,怎么办,这是一个问题啊。就是我们这个,做完这个功能以后啊,我们主要呢,是要解释一些问题,就是说,第3个话题是问题,然后呢,刚才第一个问题说,为什么显示一部分字段,那个我就不写在这了,那没必要,然后呢,后来又说了,这个时间怎么办。
  • 那你看,这个时间多了个毫秒,我想把它去掉,你能想到应该怎么办呢,有人可能会想到,那我能不能截取,subString对吧,可能会这样想,倒是可以,但是呢,这个截取呢,不是一个万全的办法,为啥呢,比如说,用户看到这个时间以后,说,我不不要毫秒,我还不想要这个格式,我想换一个格式,有可能吧,是吧,如果他想任意换任何的格式,怎么办,你要想万全的解决问题,就得对这个时间能够格式化,对吧,那我们如何去格式化jsp上所显示的时间呢,这是一个话题,就是说如何格式化jsp上显示的时间。
  • 那我们说格式化这时间的话,那我们在 JSP4:JSTL自定义标签的实现与应用 中,写过那个自定义标签,是不是我们当时这样用的,<s:sysdate format=""/>,然后呢,可以写format等于格式,是这样吧,只不过呢,这个标签呢,它格式化的是一个当前时间对吧,如果说,我们给这个标签,再进一步,再加一个属性,比如加个value,我们把这个时间传给它进去,比如说,我能通过value,把时间呢,传给这个标签,他能不能把这个时间给我格式化了呢,也可以,是吧,<s:sysdate value="${c.creatime}" format=""/>,可以这么去想,但其实呢,没有必要,这个标签啊,不用我们写,这个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,对吧,但这句话,跟以前我们写的不一样,以前我们写的是,<meta charset="utf-8">,但在这个find.jsp里,它整的这么复杂,<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />,你看,这能想出来么,它为什么这么麻烦呢,为啥这个写法,跟我们以前不一样呢,能想到么,就这个meta,怎么跟我们以前写的不一样,是这样的,你看find.jsp中的这个地方,<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">,这个地方跟我们以前写的一样么,不一样,以前我们直接写<!DOCTYPE html>,到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,然后是什么呢,图片,它是有一定的规律的,就是不用去细看,就是说大概,大概了解一下。
  • 当然了,我们看的话,你会发现呢,它这有一些解释啊,你看它是什么呢,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这个路径,没有以斜线开头,写的是相对路径对吧:

<link type="text/css" rel="stylesheet" media="all" href="styles/global.css" />
<link type="text/css" rel="stylesheet" media="all" href="styles/global_color.css" />

  • 所以,谁和谁的关系呢,是这个网页和这个图片的关系,那么,对于浏览器而言,这个网页的访问路径是什么,是什么呢,有人说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是代码,在这代码里,你能看到这个静态网页,当然它这个是在,部署到云上面去了啊,你能够看到什么呢,这个网页所依赖的所有的图片,这都能一目了然,都能看到,这不是什么秘密。
  • 所以说,静态资源,没有保护的必要。那么通过这个图形啊,通过这个,这个过程啊,咱们就解释了一下,3个问题,那么关于后两个问题,WEB-INF这个问题,就这件事吧,大家尽量去理解,还是那句话,你实在不理解,它并不会影响你的编程,但你能把它理解透了的话,你对web项目的理解,就比较到位了,对整个的过程,就理解比较到位了,那么我们将来呢,在做很多事情的时候,遇到很多问题的时候呢,都好解决,都好解释。我把这图存一下。
  • 所以啊,我们感觉呢,当前阶段,有一些内容,有一些这个抽象的地方,主要就是什么呢,它这个流程是比较繁琐的,我们在互联网上能够看到这些个网页,能够看到丰富的内容,这个不容易,所以我们想开发一个web项目呢,在底层的一些的逻辑,还是挺复杂的,好在呢,这么复杂的事啊,我们平时,不用我们去处理,只不过要了解一下,遇到了问题也好解决啊,仅此而已。
  • 最后呢,还有一个问题啊,这个问题就是看图啊,刚才那个图叫什么来着,写上就行。然后呢,还有一个编码问题,就是说,我们jsp上page那块写了个编码,然后呢,meta里又写了个编码对吧,这两个编码都是utf-8,有什么关系么,有什么这个联系么,解释一下。其实这件事,也容易理解啊,就是在 jsp的运行原理 时,其实就说过类似的这个意思啊,只不过没说完善,咱们再完善一下啊,jsp原理:
  • 之前我就说过,咱们在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那个东西么,看一下啊,我们可以这样,右键/查看网页源代码:
  • 你看第一行,空白对吧,没了,那服务器给我们的是什么呢,就是纯静态的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");/** <bean id="ds" class="dbcp...">*        <property name="driverClassName" value="#{db.driver}"/>*        <property name="url" ..../>* </bean>*        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<Cost> findAll(){Connection conn  = null;try {conn = DBUtil.getConnection();//String sql = "select * form cost_lhh "+"order by cost_id";//测试错误页面error.jspString sql = "select * from cost_lhh "+"order by cost_id";Statement smt = conn.createStatement();ResultSet rs = smt.executeQuery(sql);List<Cost> list = new ArrayList<Cost>();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<Cost> 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 {@Overrideprotected 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<Cost> list = dao.findAll();//将请求转发到jspreq.setAttribute("costs", list);//当前:/netctoss/findCost.do//目标:/netctoss/WEB-INF/cost/find.jspreq.getRequestDispatcher("WEB-INF/cost/find.jsp").forward(req, res);}
}

7.src/main/webapp/WEB-INF/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>netctoss</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>main</servlet-name><servlet-class>web.MainServlet</servlet-class></servlet><servlet-mapping><servlet-name>main</servlet-name><url-pattern>*.do</url-pattern></servlet-mapping>
</web-app>

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

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>达内-NetCTOSS</title><link type="text/css" rel="stylesheet" media="all" href="../styles/global.css" /><link type="text/css" rel="stylesheet" media="all" href="../styles/global_color.css" /><script language="javascript" type="text/javascript">//排序按钮的点击事件function sort(btnObj) {if (btnObj.className == "sort_desc")btnObj.className = "sort_asc";elsebtnObj.className = "sort_desc";}//启用function startFee() {var r = window.confirm("确定要启用此资费吗?资费启用后将不能修改和删除。");document.getElementById("operate_result_info").style.display = "block";}//删除function deleteFee() {var r = window.confirm("确定要删除此资费吗?");document.getElementById("operate_result_info").style.display = "block";}</script>        </head><body><!--Logo区域开始--><div id="header"><img src="../images/logo.png" alt="logo" class="left"/><a href="#">[退出]</a>            </div><!--Logo区域结束--><!--导航区域开始--><div id="navi">                        <ul id="menu"><li><a href="../index.html" class="index_off"></a></li><li><a href="../role/role_list.html" class="role_off"></a></li><li><a href="../admin/admin_list.html" class="admin_off"></a></li><li><a href="../fee/fee_list.html" class="fee_on"></a></li><li><a href="../account/account_list.html" class="account_off"></a></li><li><a href="../service/service_list.html" class="service_off"></a></li><li><a href="../bill/bill_list.html" class="bill_off"></a></li><li><a href="../report/report_list.html" class="report_off"></a></li><li><a href="../user/user_info.html" class="information_off"></a></li><li><a href="../user/user_modi_pwd.html" class="password_off"></a></li></ul>            </div><!--导航区域结束--><!--主要区域开始--><div id="main"><form action="" method=""><!--排序--><div class="search_add"><div><!--<input type="button" value="月租" class="sort_asc" onclick="sort(this);" />--><input type="button" value="基费" class="sort_asc" onclick="sort(this);" /><input type="button" value="时长" class="sort_asc" onclick="sort(this);" /></div><input type="button" value="增加" class="btn_add" onclick="location.href='fee_add.html';" /></div> <!--启用操作的操作提示--><div id="operate_result_info" class="operate_success"><img src="../images/close.png" onclick="this.parentNode.style.display='none';" />删除成功!</div>    <!--数据区域:用表格展示数据-->     <div id="data">            <table id="datalist"><tr><th>资费ID</th><th class="width100">资费名称</th><th>基本时长</th><th>基本费用</th><th>单位费用</th><th>创建时间</th><th>开通时间</th><th class="width50">状态</th><th class="width200"></th></tr>                      <tr><td>1</td><td><a href="fee_detail.html">包 20 小时</a></td><td>20 小时</td><td>24.50 元</td><td>3.00 元/小时</td><td>2013/01/01 00:00:00</td><td></td><td>暂停</td><td>                                <input type="button" value="启用" class="btn_start" onclick="startFee();" /><input type="button" value="修改" class="btn_modify" onclick="location.href='fee_modi.html';" /><input type="button" value="删除" class="btn_delete" onclick="deleteFee();" /></td></tr><tr><td>2</td><td><a href="fee_detail.html">包 40 小时</a></td><td>40 小时</td><td>40.50 元</td><td>3.00 元/小时</td><td>2013/01/21 00:00:00</td><td>2013/01/23 00:00:00</td><td>开通</td><td>                                </td></tr></table><p>业务说明:<br />1、创建资费时,状态为暂停,记载创建时间;<br />2、暂停状态下,可修改,可删除;<br />3、开通后,记载开通时间,且开通后不能修改、不能再停用、也不能删除;<br />4、业务账号修改资费时,在下月底统一触发,修改其关联的资费ID(此触发动作由程序处理)</p></div><!--分页--><div id="pages"><a href="#">上一页</a><a href="#" class="current_page">1</a><a href="#">2</a><a href="#">3</a><a href="#">4</a><a href="#">5</a><a href="#">下一页</a></div></form></div><!--主要区域结束--><div id="footer"><p>[源自北美的技术,最优秀的师资,最真实的企业环境,最适用的实战项目]</p><p>版权所有(C)加拿大达内IT培训集团公司 </p></div></body>
</html>

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

<%@page pageEncoding="utf-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>达内-NetCTOSS</title><link type="text/css" rel="stylesheet" media="all" href="styles/global.css" /><link type="text/css" rel="stylesheet" media="all" href="styles/global_color.css" /><script language="javascript" type="text/javascript">//排序按钮的点击事件function sort(btnObj) {if (btnObj.className == "sort_desc")btnObj.className = "sort_asc";elsebtnObj.className = "sort_desc";}//启用function startFee() {var r = window.confirm("确定要启用此资费吗?资费启用后将不能修改和删除。");document.getElementById("operate_result_info").style.display = "block";}//删除function deleteFee() {var r = window.confirm("确定要删除此资费吗?");document.getElementById("operate_result_info").style.display = "block";}</script>        </head><body><!--Logo区域开始--><div id="header"><img src="data:images/logo.png" alt="logo" class="left"/><a href="#">[退出]</a>            </div><!--Logo区域结束--><!--导航区域开始--><div id="navi">                        <ul id="menu"><li><a href="../index.html" class="index_off"></a></li><li><a href="../role/role_list.html" class="role_off"></a></li><li><a href="../admin/admin_list.html" class="admin_off"></a></li><li><a href="../fee/fee_list.html" class="fee_on"></a></li><li><a href="../account/account_list.html" class="account_off"></a></li><li><a href="../service/service_list.html" class="service_off"></a></li><li><a href="../bill/bill_list.html" class="bill_off"></a></li><li><a href="../report/report_list.html" class="report_off"></a></li><li><a href="../user/user_info.html" class="information_off"></a></li><li><a href="../user/user_modi_pwd.html" class="password_off"></a></li></ul>            </div><!--导航区域结束--><!--主要区域开始--><div id="main"><form action="" method=""><!--排序--><div class="search_add"><div><!--<input type="button" value="月租" class="sort_asc" onclick="sort(this);" />--><input type="button" value="基费" class="sort_asc" onclick="sort(this);" /><input type="button" value="时长" class="sort_asc" onclick="sort(this);" /></div><input type="button" value="增加" class="btn_add" onclick="location.href='fee_add.html';" /></div> <!--启用操作的操作提示--><div id="operate_result_info" class="operate_success"><img src="data:images/close.png" onclick="this.parentNode.style.display='none';" />删除成功!</div>    <!--数据区域:用表格展示数据-->     <div id="data">            <table id="datalist"><tr><th>资费ID</th><th class="width100">资费名称</th><th>基本时长</th><th>基本费用</th><th>单位费用</th><th>创建时间</th><th>开通时间</th><th class="width50">状态</th><th class="width200"></th></tr><c:forEach var="c" items="${costs }">                <tr><td>${c.costId }</td><td><a href="fee_detail.html">${c.name }</a></td><td>${c.baseDuration }</td><td>${c.baseCost }</td><td>${c.unitCost }</td><td>${c.creatime }</td><td>${c.startime }</td><td><c:if test="${c.status==0 }">开通</c:if><c:if test="${c.status==1 }">暂停</c:if></td><td>                                <input type="button" value="启用" class="btn_start" onclick="startFee();" /><input type="button" value="修改" class="btn_modify" onclick="location.href='fee_modi.html';" /><input type="button" value="删除" class="btn_delete" onclick="deleteFee();" /></td></tr></c:forEach></table><p>业务说明:<br />1、创建资费时,状态为暂停,记载创建时间;<br />2、暂停状态下,可修改,可删除;<br />3、开通后,记载开通时间,且开通后不能修改、不能再停用、也不能删除;<br />4、业务账号修改资费时,在下月底统一触发,修改其关联的资费ID(此触发动作由程序处理)</p></div><!--分页--><div id="pages"><a href="#">上一页</a><a href="#" class="current_page">1</a><a href="#">2</a><a href="#">3</a><a href="#">4</a><a href="#">5</a><a href="#">下一页</a></div></form></div><!--主要区域结束--><div id="footer"><p>[源自北美的技术,最优秀的师资,最真实的企业环境,最适用的实战项目]</p><p>版权所有(C)加拿大达内IT培训集团公司 </p></div></body>
</html>

写在后面

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

NETCTOSS代码实现第一版相关推荐

  1. NETCTOSS代码实现第五版

    NETCTOSS代码实现第五版:Cookie的使用场景与使用方式 前言 Cookie的使用场景与使用方式 模拟Cookie的使用场景 Cookie的创建,存储,获取与修改 Cookie的生存时间 小总 ...

  2. NETCTOSS代码实现第八版

    NETCTOSS代码实现第八版:过滤器之登录检查功能与监听器 前言 模拟案例jsp6:演示过滤器的使用 过滤器模拟案例需求分析与设计以及代码分析 模拟资费增加和资费查询功能组件:AddCostServ ...

  3. NETCTOSS代码实现第六版

    NETCTOSS代码实现第六版:Session的原理与应用 前言 Session的原理与应用 模拟Session应用案例需求分析:Session的运行原理 模拟Session应用案例代码分析:Sess ...

  4. 【读书笔记】原型模式代码(C++) 第一版

    代码如下:这个版本的代码明显带着C#的影子,下个版本会进行修改,会用到const,加油加油. 代码下载地址如下:/Files/cappuccino/PrototypeModelFirstForC.ra ...

  5. javascript 代码_如何使您JavaScript代码保持简单并提高其可读性

    javascript 代码 by Leonardo Lima 莱昂纳多·利马(Leonardo Lima) 如何使您JavaScript代码保持简单并提高其可读性 (How to keep your ...

  6. 200行代码,一行行教你自制微信机器人

    参加 2018 AI开发者大会,请点击 ↑↑↑ 作者|上海小胖,四大咨询的TechLead,mongoDB Professional 获得者.「Python专栏」专注Python领域的各种技术:爬虫. ...

  7. All-In-One Code Framework [一站式示例代码库] 【转】

    All-In-One Code Framework [一站式示例代码库] 2010 对一站式示例代码库,对奋战在一站式示例代码库上的每一位工程师来说都是不同寻常的一年. 在我们共同努力和开发社区的支持 ...

  8. 200 行代码,一行行教你自制微信机器人

    作者 | 上海小胖 责编 | 胡巍巍 写机器人的原因 笔者当初决定自己写这么个机器人,有几个原因: 1) 用一个Windows客户端工具运营公众号,真的很局限.虽然工具的功能很强大,能自动添加好友,自 ...

  9. 什么是低代码平台 low-code?

    简介:什么是低代码?我们为什么需要低代码?低代码会让程序员失业吗?本文总结了低代码领域的基本概念.核心价值与行业现状,带你全面了解低代码. 一 前言 如果选择用一个关键词来代表即将过去的2020年,我 ...

最新文章

  1. 英雄联盟如何指挥团战?AI帮你做决策(附资源)
  2. python3.6for循环_使用parser_args值输入for循环(python3.6)
  3. mysql java datetime_Java向mysql数据库插入datetime类型数据实例(精)
  4. linux 本地做yum源,linux——制作本地yum源
  5. MySQl的一些基本知识(1)
  6. Compiler__visual_studio_2010_pro 激活码
  7. 5G 来袭,数据暴增,新一代云存储平台如何承载?
  8. OpenCV-寻找轮廓cv::findContours绘制轮廓cv::drawContours
  9. Excel中index和match函数的应用实例
  10. IR2104驱动原理--恩智浦智能车电机驱动
  11. ib网卡命令_IB交换机配置命令总结
  12. OBS 电脑推流直播指南
  13. HTML+css网站设计布局模板
  14. 眼光独到便能发现刷脸支付带来的商机
  15. matplotlib pyqt4
  16. mac 树莓派 kali linux系统,MacOS制作树莓派kali系统
  17. python爬虫——正则表达式
  18. 输入字体之间的间隔突然变大了
  19. 云计算助推金融行业信息化发展
  20. GTY‘s gay friends 线段树+前缀和

热门文章

  1. Unity3D初级案例-经典贪吃蛇二
  2. jq加css制作图片切换,jQuery+css实现的切换图片功能代码
  3. windows mysql 创建数据库_MySQL创建数据库
  4. 猎头职场:职场也有忌讳
  5. 学习工作和生活总结(十三)
  6. A Perfect Indian (完美的印第安人)
  7. construct2,游戏制作终结者
  8. kcf 跟随_KCF目标跟踪方法分析与总结
  9. VeeValidate 中文文档-Guide
  10. 2016全球大数据战略版图剖析(2)