老杜JavaWeb学习笔记:上

原视频:Javaweb老杜

JavaSE

javaSE包括:

  • java环境搭建
  • java基础语法
  • 数组
  • 常用类
  • 异常
  • 集合
  • 多线程
  • IO流
  • 反射机制
  • 注解Ananotation
  • ....

MySQL数据库

JDBC(java语言链接数据库)

WEB后端

  • Servlet
  • JSP
  • AJAX
  • jQuery
  • MyBatis
  • Spring
  • SpringMVC
  • SpringBoot
  • SpringCloud
  • ...

JavaEE

一、关于系统架构

  1. 系统架构有哪几种?

    • C/S架构
    • B/S架构
  2. C/S架构?

    • Client / Server(客户端/服务端)

    • C/S架构的软件或系统有哪些?

      • QQ(此类需要安装到本机的软件)
    • 优点:

      • 速度快
        • 软件中的数据大部分集成在客户端软件中,仅少量需要服务端传送
      • 体验好
      • 界面炫酷
        • 使用专门的语言去实现界面,更灵活,不想网页端仅仅是html css js
      • 服务器压力小
        • 因为大量数据存于客户端软件,少量才需要从服务器传输而来
      • 安全
        • 大部分数据在客户端上都有缓存,即便服务端出故障,数据也不易丢失
    • 缺点:

      • 升级维护麻烦,成本高

        每一个客户端都需要升级,同时部分软件安装较为麻烦

  3. B/S架构

    • B/S(Brower / Server,浏览器/服务器)

    • B/S架构的有哪些?

      • 百度网页版、淘宝网页版都是

        (此类无需安装,利用网址访问即可的)

    • B/S架构系统是不是一个特殊的C/S架构?

      • B/S本质还是一个C/S架构,不过C变成了一个固定不变的浏览器软件
    • 优点:

      • 升级维护方便,成本较低

        (只需升级服务器端即可)

      • 用户访问方便

        (只需打开网址访问即可)

    • 缺点

      • 速度慢
        • 因为大量数据都存于服务器上,用户的请求都需要由服务器响应数据,在网络中传输的数据量也很大
      • 体验差
        • 界面相对不酷炫,只支持html/css/js三种语言
      • 不安全
        • 数据都在服务器上,若服务器出故障,大量数据都会丢失
  4. 两者,哪个好?

    • 在不同的使用场景下,每个架构都有各自的优点
      • 如:娱乐性的软件-->C/S架构
        • 界面酷炫,体验感好
      • 公司内部企业级业务软件-->B/S架构
        • 维护成本低
  5. ❗:开发B/S架构,其实就是开发网站,开发一个WEB

    • 开发WEB系统需要哪些技术?
      • WEB前端
        • HTML
        • CSS
        • JavaScript
      • WEB后端
        • Java
          • JavaWEB核心规范:Servlet【Servlet Applet服务器端的Java小程序】
        • C也可
        • C++也可
        • Python也行
        • PHP也可
        • .....
1.1 B/S结构系统通信原理
  • WEB系统的访问过程

    • 第一步:打开浏览器
    • 第二步:找到地址栏
    • 第三步:输入合法的url地址
    • 第四步:回车
    • 第五步:在浏览器上展示相应的结果
  • 域名

  • ip地址是?==服务器位置

    • 类似于每个计算机的身份证,同一网段中,IP地址唯一
    • 两个计算机要通信,需要知道IP地址,才可以通信
  • 端口号是?==软件位置

    • 一个端口代表一个应用、一个服务
    • 每个软件启动后都有一个端口号
    • 同一计算机中,端口号唯一
  • 资源路径是?==访问页面

    • 通过资源路径确定,从服务器对应的软件中访问了哪个资源
    • 如index.html、login.html
  • WEB系统通信原理

    • 第一步:用户输入网址(URL:http://www.baidu.com)
      • URL统一资源定位符
    • 第二步:域名解析器进行域名解析
    • 第三步:浏览器软件在网络中搜索该IP地址(110.242.63.3)的主机,直至找到
    • 第四步:根据端口号,定位该IP地址主机80端口上的软件
    • 第五步:80端口对应的服务器软件由资源路径得知想要的资源名:(index.html)
    • 第六步:服务器软件找到该资源(index.html),并将其内容输出相应到浏览器上
    • 第七步:浏览器接收服务器的HTML、CSS、JS代码
    • 第八步:浏览器执行HTML、CSS、JS,渲染显示

通过ip找到对应的服务器位置,端口号确定服务器软件,最后资源路径获取要访问的资源,将其从服务器获取到页面中,页面再对html/css/js进行解析,最后渲染出页面效果

1680833839654

  • 什么是请求,什么是响应?
    • 请求和响应实际上是数据的流向不同
    • 从Browser端发送数据到Servlet端,叫请求 (request)
    • 从Server端发送数据到Browser端,叫响应 (repsonse)
    • B-->S (请求)
    • S -->B (响应)

二、JavaEE是什么?

  1. Java包括三大块:
    • JavaSE
      • Java标准版,是一套他人编写好的类库,但该类库为标准类库,无论学习EE、ME,都是以此为基础。
    • JavaEE(WEB方向、WEB系统)
      • Java企业级,也是一套他人写好的类库,专门用于企业内部提供一套/多套解决方案的类库。
      • 如:淘宝、京东此类网站...
    • JavaME
      • Java微型版,同样是一套类库,专门用于解决电子微型设备内核程序的开发
      • 如:机顶盒内核、吸尘器内核程序、电冰箱内核程序等....
  2. JavaEE有13种规范,其中Servlet是规范之一。

三、WEB服务器软件

  • WEB服务器软件有?(他人开发好的)

    • Tomcat (WEB服务器)
    • jetty (WEB服务器)
    • Jboss (应用服务器)
    • WebLogic (应用服务器)
  • 应用服务器和WEB服务器的关系?

    • 应用服务器实现了javaEE的所有规范
      • jvaEE有13种不同规范
    • WEB服务器只实现了JAVAEE中Servlet + JSP 两个核心规范
    • 即应用服务器包含WEB服务器
3.1 Tomacat
  • apache官网下载地址: https://www.apache.org/
    • apache是一个软件基金会,其中有许多开源项目,Tomcat就是其一
  • Tomcat官网下载地址:http://www.tomcat.org/
  • Tomcat是开源免费的,轻量级WEB服务器。
  • log为公猫(寓意Tomcat服务器体积小,运行速度快,同时也只实现Servlet+JSP规范)
  • Tomcat为java语言编写,因而要运行必须先有jre,因而要先安装jdk,配置java运行环境
  • Tomcat又被成为catalina
3.3.1 JDK下载

【详见 定制班-->环境部署文档】

3.3.2 Tomcat下载

①下拉至最后,点击Tomcat下载

1680834437794

②选择Dowload下需要的版本

1680834557068

③选择Window版的,zip进行下载

1680836164537

④将压缩包自行放在D盘的一个文件夹下

  • 在此我习惯放D盘,因为C盘太容易满
  • 同时与java开发相关的工具放在同一文件夹下,方便管理

1680836539295

⑤Tomcat绿色版解压缩即安装

1680836716937

3.3.3 Tomct目录
  • bin目录:Tomcat服务器存放命令文件的目录
    • 如:关闭Tomcat/开启Tomcat
  • conf目录:Tomcat服务器存放配置文件的目录
    • 如:server.xml文件中可以配置端口号,Tomcat默认端口号为8080
  • lib目录:Tomcat服务器的核心程序目录
    • 因为Tomcat服务器是Java语言编写的,该jre包都是class文件
  • logs目录:Tomcat的日志目录
    • Tomcat服务器启动等日志信息都会在该目录下生成日志
  • temp目录:Tomcat服务器的临时目录
    • 存储临时文件
  • webapps目录:该目录当中用于存放大量webapp
    • wepapp(web application:web应用)
  • work目录:该目录用于存放JSP文件翻译后的java文件,以及编译后的class文件
3.3.4 启动Tomcat
  • 1️⃣通过cmd,切换至tomcat的bin目录下,启动startup.bat可以启动Tomcat服务器

    • xxx.bat文件是什么文件?

      bat文件是windows操作系统专用的批处理文件,该文件中编写了大量window的dos命令

      • 执行该文件相当于批量执行dos命令。
    • startup.sh 该文件在windows中无法执行,在Linux环境中可以使用。

      • Linux执行的是shell命令,大量shell命令编写在shell文件中,执行shell命令就可以批量执行shell命令。
    • Tomcat提供了bat和sh文件,说明Tomcat既可以在Windwos环境使用,也可以在Linux环境中使用。

    • 分析 startup.bat文件得出,执行这个命令,实际上执行:catalina.bat文件。

    • 同时catalina.bat的一行配置:MAINCLASS=org.apache.catalina.Bootstrap

      ​ (该类就是main方法所在的类)

    • tomcat服务器就是java语言编写的,因而启动Tomcat就是执行main方法

    1680840033393

2️⃣但startup.bat启动等同于执行catalina.bat,需先配置CATALINA_HOME环境变量

  • 使其能找到catalina.bat

  • 否则运行startup.bat会出现如下报错:

1680844423774

配置CATALINA_HOME环境变量

①编辑环境变量->新建系统变量

  • 变量名:CATALINA_HOME
  • 变量值:Tomcat的根(tomcat在你本机上解压缩的位置)

1680841171214

3️⃣同时catalina.bat中还需要获取 JAVA_HOME

  • 即告知Tomcat的jdk在何位置
  • 否则运行startup.bat会出现如下报错:

1680844524717

配置JAVA_HOME环境变量

②再次编辑环境变量->新建系统变量

  • 变量名:JAVA_HOME
  • 变量值:JDK的根(jdk解压缩后在本机中存放的位置)

1680844371507

配置PATH环境变量

PATH=%JAVA_HOME%\bin;

​ %CATALINA_HOME%\bin;

4️⃣完成两者配置后,切换至tomcat中startup.bat存放的bin目录下,在此调起cmd

1680845266077

同时当JAVA_HOME和CATALINE_HOME都在系统变量中配置完成后

在系统环境变量的Path中,可以将原先本机路径的根--->环境变量

将如下:

1680845673314

变为如下形式:

1680845811910

执行startup.bat

1680844821411

出现Tomcat的执行窗口,Tomcat被启动

  • 乱码暂时不用管

1680844830208

关闭startup.bat

最好不要直接关闭弹框,通过cmd,执行目录下的shutdown.bat文件关闭

  • 运行shutdown.bat文件:shutdown.bat
    • 注意shutdown是关机命令,如果怕误敲,可以直接将shutdown文件更名为stop文件
    • 之后直接输入stop就可以关闭Tomcat

1680846051775

测试Tomcat是否启动成功

打开浏览器,在浏览器的地址栏上输入URL:

出现该页面则证明,Tomcat真正启动成功!!

1680850204233

四、编写第一个webapp

  • 第一步:找到CATALINA_HOME\webapps目录
    • CATALINA_HOME为环境变量中配置的tomcat所在目录位置
    • 因为webapp要放在webapp目录下【规定:Tomcat服务器是在该目录下寻找应用的】
  • 第二步:在CATALINA_HOME\webapps目录下新建一个子目录,命名:oa
    • 该目录名oa,即webapp的名字
  • 第三步:在oa目录下新建资源文件,例如:index.html
    • 编写index.html文件内容(向其中编写html代码)
  • 第四步:启动Tomcat服务器
    • 调起cmd,执行startup.bat
  • 第五步:打开浏览器,输入url地址

1680850918982

4.1项目路径的问题

①在编写超链接的路径时,从项目名开始

②一般都使用绝对路径的形式,以“/”开头

【扩展】:

用户使用超链接,相当于在浏览器输入url

<a href="http://127.0.0.1:8080/oa/login.html">user login</a>
  • 在使用中,超链接的ip地址+端口号常省略,变为绝对路径的形式
  • 绝对路径 :“/”开头
<a href="/oa/login.html">user login</a>

五、B/S架构的角色和协议

要将一个静态资源变为动态资源,需要JDBC程序,编写java程序去连接数据库。

让页面中的数据变为动态,根据数据库的记录而变化,该技术被称为动态网页技术

  • 使得数据有几条记录,页面上就显示几条数据

要获取数据库动态资源,就要涉及到利用java程序去数据库获取资源,而要将java程序获取资源一一对应页面,访问的不再是静态资源的路径,而是java程序所在的路径,再从java程序中,将获取到的数据返回给页面。

5.1动态web应用的参与角色,协议

1680854900960

  • 有哪些角色(整个BS架构参与的人)
    • 浏览器软件的开发团队
      • 谷歌浏览器、火狐浏览器....
    • WEB Server的开发团队
      • Tomcat、Jetty....
    • DB Server的开发团队
      • Oracle、MySQL.....
    • webapp的开发团队
      • javaWEB程序员开发应用
  • 角色和角色之间需要遵守哪些规范、哪些协议
    • webapp的团队 和 WEB Server的开发团队有一套规范:Servlet规范
      • 其中之一是Servlet
        • Servlet规范作用是什么?
        • WEB Server 和 webapp解耦合
    • Brower 和 WebServer的传输协议:HTTP协议
      • HTTP协议:超文本传输协议
    • webapp的开发团队 和 DB Server的开发团队有一条规范:JDBC规范

1680855692836

六、JavaWEB开发要做什么?

6.1模拟Servlet本质
  • Servlet先获取请求地址
  • 再获取.properties配置文件,得到地址对应的类名
    • 配置文件是自行程序员编写的
  • 根据类名创建实例化对象,统一强转为Servlet类型
    • 因为所有XxxServlet都继承于Servlet父类
  • 调用Servlet的公共方法,编译虽是Servlet类但具体方法的调用,则是继承实现它的一个个对象。

而实际开发过程中,Servlet已经由SUN公司规定好,服务器则由Tomcat完成。

6.2浏览器发送请求的全过程

浏览器发送请求,最终服务器调用Servlet中的方法的过程?

  • 用户输入URL,直接点击超链接
  • Tomcat接受URL请求,截取路径: /项目名/Servlet映射地址
  • Tomcat找到项目
  • Tomcat在wewb.xml文件,找到路径对应的XxxServlet全限定类名 存放位置
  • 通过反射机制,创建该XxxServlet的对象
  • Tomcat调用XxxServlet的service方法
6.2 开发人员的具体工作

则Javaweb开发只需要:

  1. 编写类实现XxxServlet接口
  2. 编写.properties配置类,指定 请求路径 和 类名 的对应关系

❗注意:

  • Tomcat是已经被写好的,所以配置类的名称+位置都有规定

    • 名称:web.properties
  • 位置:由Servlet规范而定

Tomcat会负责调用main方法,javaweb程序员只需要编写Servlet接口类,在将其注册到web.xml文件中

七、关于JavaEE的版本

  • JavaEE目前最高版本是JavaEE8
  • JavaEE被Oracle捐献了,将其捐给了Apache
  • Apache将JavaEE换名为jakarta EE

因而JavaEE8版本升级之后不再是“JavaEE9“,更名为“jakartaEE9”

  • Serlvet类名:
    • JavaEE8时,是 javax.servlet.Servlet
    • JakatarEE9时,是 jakatar.servlet.Servlet(包名更改)
  • 如果你之前的项目使用为javax.servlet.Servlet,那么你的项目无法部署到Tomcat 10+版本上,只能部署到Tomcat9-的版本上
  • Tomcat9和Tomcat9之前的版本还是能识别javax.servlet这个包的。

【同时在Tomcat官网的Tomcat 10上有所说明,同时还提供了一个迁移工具】

扩展:怎么找说明文档

打开apache官网->Full documentation

解压后找到html文件,打开

1681389091741

Servlet

一、Servlet是什么?

  • Servlet并非一个简单的接口
    • Servlet规范中规定了:
      • 一个合格的webapp应该是怎样的目录结构
      • 一个合格的webapp应该有一个怎样的配置文件
      • 一个合格的webapp配置文件路径放在哪里
      • 一个合格的webapp配置文件有哪些内容
      • 一个合格的webapp中java程序放在哪里
      • 以上都是Servlet规范中规定的
  • Tomcat服务器要遵循Serlvet规范。JavaWEB程序员也需要遵循这个Servlet规范。这样服务器和webapp才可以解耦合

Servlet规定的一个合法的webapp目录

webapp根
  |----WEB-INF
       |----classes(存放字节码)
       |----lib(第三方jar包)
       |----web.xml(注册servlet)	
  |----HTML
  |----CSS
  |----JavaScript
1.1 Servlet开发步骤
  • 开发步骤是怎样的?

    • **第一步:**在webapps目录下新建一个目录,起名xx

      • xx就是webapp的名字
      • xx是webapp的根
    • **第二步:**在webapp的根新建一个目录:WEB-INF

      • WEB-INF是Servlet规定写法
    • **第三步:**在WEB-INF目录下新建一个目录:classes

      • classes用于存放java程序编译后的class文件(字节码文件)
      • classes目录是Servlet规定写法
    • **第四步:**在WEB-INF目录下新建一个目录:lib

      • lib目录存放webapp所需的jar包
      • lib是Servlet规定写法
      • lib目录不是必须的
    • **第五步:**在WEB-INF目录下新建一个文件 : web.xml

      • web.xml配置文件,用于指定请求路径和Servlet类之间的关系
      • web.xml是Servlet的规定写法

      【web.xml文件建议从其他webapp中拷贝,再进行修改】

      <?xml version="1.0" encoding="UTF-8"?>
      <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                            https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
        version="6.0"
        metadata-complete="true">
      
      
      </web-app>
      
    • 基础目录如下:

    1680867244639

    • **第六步:**编写一个实现Servlet接口的Java程序

      • 实现Servlet : public class XxxSevlet implements Servlet{...}

      • Servlet接口(Servlet.class)是Oracle提供的(最原始是SUN公司),JavaEE规范中的一员

      • Tomcat服务器实现了Servlet规范,也需要Servlet接口

        • 所以Servlet可以在Tomcat的lib目录下找到servlet-api.jar

        • 将其解压缩得到Servlet.class文件

          1680868005564

          1680868370405

        • 同时注意java9以后Servlet不再是javax.Servlet,而变成了jakarta.Servlet

      • 重点:JavaEE9更名为JakartaEE9,同时其Servlet接口全名变了:jakarta.servlet.Servlet

      • 注意:编写的java代码,位置随意,但其编译后的文件必须放入WEB-INF/classes目录下

1.2 编写Servlet
  • 需要使用五个方法
public void service(ServletRequest request,ServletResponse response) throws ServletException,IOException{}

public void init(ServletConfig config) throws ServletException{}

public void destroy(){}

public String getServletInfo(){}

public ServletConfig getServletConfig(){}
  • **第七步:**编译编写的HelloServlet

    • 重点:将环境变量CLASSPATH = .; (Tomcat中lib包下servlet-api.jar包存放的位置)
  • **第八步:**将以上编译后的文件HelloServlet.class文件拷贝到WEB-INF\classes目录下

  • **第九步:**在Web.xml文件编写配置信息

    • 让"请求路径"和"Servlet"类名相关联
    • 专业术语:注册Servlet类
    <!--Servlet描述信息-->
    <servlet>
       <servlet-name>描述信息名称</servlet-name>
       <servlet-class>带有包名的全限定类名:com.xxx.servlet.HelloServlet</servlet-class>
    </servlet>
    <!--Servlet映射信息-->
    <servlet-mapping>
       <servlet-name>映射信息和描述信息的名称必须一样</servlet-name>
       <url-pattern>以斜杠开始的路径:Servelt请求的路径</url-pattern>
    </servlet-mapping>
  • **第十步:**启动Tomcat服务器 :startup.bat

  • **第十一步:**访问编写的HelloServlet,在浏览器输入请求路径

  • 请求路径:http://127.0.0.1:8080/项目名/web.xml下设置的Servlet请求路径

    • 请求路径必须和web.xml文件的url-pattern一致,但请求路径带 http://127.0.0.1:8080/项目名

    • 从浏览器发起Servlet请求后,Tomcat会根据web.xml文件中的映射信息去找到servlet-class中对应的类名,然后调用类其中的方法

    • index.html放在WEB-INF目录外面

    • 1680958246058

1.3从服务器响应数据到浏览器
(1)将XxxServlet类中信息输出到浏览器
  • 利用ServletResponse接口的:response
  • 从服务器向浏览器发送数据叫响应(response)
  • PrintWriter out = response.getWriter();
  • out.print("内容")===>即可输出到浏览器中
    • out为输出流,负责输出字符串到浏览器
    • 该输出流不需他人刷新和关闭,TomCat已经做了
(2)向浏览器输出一段html代码
  • 若要识别html标签,需设置response.serContentType("text/html");

    • 设置响应的内容类型为普通文本或html代码
  • ❗要在获取流之前设置有效

public void service(ServletRequest request, ServletResponse response){
       //设置响应的输出内容为文本或html代码
       reponse.setContentType("text/html");
       //调用reponse的getWriter方法,获取输出流对象
       PrintWriter out = response.getWriter();
       out.print("<h1></h1>")
}
1.4 设置web站点的欢迎页面

(1)什么是web站点的欢迎页面?

  • 设置欢迎页面后,访问webapp,或该web站点后,未指定任何“资源路径”

    eg: http://localhost:8080/项目名 ,默认访问欢迎页

  • 默认访问欢迎页面

(2)如何设置?

  • 第一步:写一个资源:动态/静态

  • 第二步:在web.xml中进行配置

    <welcome-file-list>
      <welcome-file>html文件路径</welcome-file>
    </welcome-file-list>
    • 该欢迎页面路径,该路径无需以“/”开始。并且默认从webapp的根下开始查找。
    • 注意:servlet的路径要以“/”开头,但是不带项目名
    • 再次访问,webapp即可生效
  • 如:

    路径为:index.html

    路径为: page/page.html

  • 欢迎页可设置多个,越往上的优先级越高,找不到再继续向下找

  • web根下的index.html会被自动设为欢迎页

    • 因为Tomcat中已经设置了

(3)欢迎页面的配置方式

  • 有两种:
    • 一个是webapp内部的web.xml文件中(局部配置)
    • 一个是CATALINA_HOME/conf/web.xml文件中(全局配置)
      • CATALINA即Tomcat文件夹
    • 注意原则:局部优先原则(就近原则)

(4)欢迎页的类型

欢迎页只是一个资源,可是静态资源:html、jsp,也可以是动态资源:java程序,servlet类

1.5 关于WEB-INF目录
  • 注意:放在WEB-INF目录下到资源是受保护到,浏览器无法通过路径访问

    • 因而HTML、CSS、JS、image等静态资源,应该放在WEB-INF目录之外。

二、Servlet连接数据库

mysql驱动为8.0.27,Tomcat10,jdk9.0

  • 在Servlet中直接编写java代码即可(JDBC)

  • 同时在WEB-INF的lib目录下放入mysql的驱动jar包

    • mysql驱动的jar包:在官网下载jdbc连接驱动,将其解压就可以得到 mysql-conncetor-java-8.0.27.jar,其中有com.mysql.cj.jdbc.Driver类
  1. 编写的JDBC连接代码如下:

    ①注意其中的mysql驱动在新版本中有更改

    将com.mysql.jdbc.Driver--->com.mysql.cj.jdbc.Driver

import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletConfig;
import java.ip.IOException;
import java.io.PrintWriter;
import java.sql.*;

public class StudentServlet implements Servlet{
    public void inti(ServletConfig config) throws ServletException{
        
    }
    public void service(ServletRequest request,ServletResponse){
        throw ServiceException ,IOException{
            reponse.setContentType("text/html");
            PrintWriter out = reponse.getWriter();
            
            //编写JDBC代码,连接数据库,查询所有学生信息
            Connection conn = null;
            PreparedStatement ps = null;
            ResultSet rs = null;
            try{
  //注册驱动              
                Class.forName("com.mysql.cj.jdbc.Driver");
 //获取连接
 String url = "jdbc:mysql://localhost:3306/数据库名";
                String user = "root";
                conn = DriverManager.getConnection(url,user,password);
                //获取预编译的数据库操作对象
                String sql = "selcet no,name from t_student";
                ps = conn.prepareStatement(sql);
                //执行SQL
                rs = ps.executeQuery();
                //处理查询结果集
                while(rs.next()){
                    //获取的数据库中的字段
                    String no = rs.getString("no");
                    String name = rs.getString("name");
                    out.print(no+","+name+"<br>");
                }
                
            }catch(Exception e){
                e.printStackTrace();
            }finally{
                //释放资源
                if(rs != null){
                    try{
                        rs.close();
                    }catch(Exception e){
                        e.printStackTrace();
                    }
                }
                if(ps != null){
                    try{
                        ps.close();
                    }catch(Exception e){
                        e.printStackTrace();
                    }
                }
                if(conn !=null){
                    try{
                        conn.close();
                    }catch(Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }
        public void destory(){
            
        }
        public String getServletInfo(){
            return "";
        }
        public ServiceConfig getServletConfig(){
            
        }
}

三、Servlet对象的生命周期

  • 什么是Servlet对象生命周期?

    • 表示一个Servlet对象从出生到最后死亡的整个过程是怎样。

      包括:

      • Servlet对象什么时候被创建
      • Servlet对象什么时候被销毁
      • Servlet对象创建了几个?
  • Servlet对象由web服务器(Tomcat服务器)全权负责的

    • 程序员无权干预
    • Tomcat服务器又称为 WEB容器
  • 【思考】程序员自行new的Servlet对象受WEB容器管理嘛?

    • 并不,我们自行创建的并不受WEB容器管理

    • 为什么?

      • WEB容器创建的Servlet对象,会被放到一个集合中(HashMap),只有放到这个HashMap集合中的Servlet才能被WEB容器管理。
    • WEB容器底层应该有一个HashMap集合,在其中存储了Servlet对象和请求路径间的关系

  • 【研究】默认情况下,服务器启动Servlet对象是否会被创建出来?

    • 验证方法:在Servlet中编写一个无参构造方法,查看启动服务器,构造方法是否被执行
    • 经过测试,默认情况下Servlet对象并不会被实例化
    • Tomcat启动后,会解析web.xml文件,将url-pattern请求路径和servlet-class Servlet类名放到一个Map集合中
    • 在请求Servlet时,才会创建,因为创建后并无用户访问,会占内存
  • 【设置】让服务器启动时,就创建Servlet对象

    • 在web.xml的< servlet></ servlet>标签中中添加子标签< load-on-startup>顺序</ load-on-startup>

    • 创建的顺序:越小越大 eg:0>1

      <servlet>
         <servlet-name>aServlet</servlet-name>
         <servlet-class>com.bookstore.servlet.AServlet</servlet-class>
         <load-on-startup>0</load-on-startup>
      </servlet>
      <servlet-mapping>
           <servlet-name>aservlet</servlet-name>
          <url-pattern>/a</url-pattern>
      </servlet-mapping>
Servlet创建
  • 默认情况下,服务器启动时,Servlet对象并不会被实例化

  • 用户发送第一次请求(访问路径)时,控制台输出以下内容:

    首先在XxxServlet类中的init() 和 service() 方法中添加System输出语句,才会显示👇

    AServlet无参构造方法被调用
    Aservlet init方法被调用
    Aservlet service方法被调用
  • 根据以上得出结论

    • 1️⃣用户第一次请求时Servlet对象会被实例化(无参构造方法会执行

    • 2️⃣创建对象后**,init()方法只被Tomcat调用一次**

      • 第一次才会调用 无参构造方法 + init()方法,说明Servlet对象是单实例的,但Servlet类并不符合单例模式,称之为假单例
      • 因为Servlet对象只能由Tomcat来创建,所以程序员管不着,就造成只创建一个,属于假单例
      • 真单例的构造方法是私有化的
    • 3️⃣之后,只有service()方法会被重复调用

      • 用户发送一次请求,就调用一次

Servlet消亡
  • 服务器关闭时,Servlet的destory()方法只被服务器调用一次

    • 因为服务器关闭,要销毁Servlet对象的内存
    • destory()方法执行结束后,对象才被销毁!!!
  • 关闭服务器时,控制台输出以下内容:

    • 说明服务器在关闭前,会调用Servlet的destory()方法
    AServlet destory方法被调用
  • 【探讨】:调用destory方法时,对象是否被销毁

    • destory()方法没有static,为实例方法,被调用时需要对象的存在,因而调用时,Servlet对象还未被销毁,

      当该方法执行结束后,Servlet对象才会被销毁。

  • 【思考】当Servlet类中编写了有参构造方法会怎样?

    • 有参构造方法会覆盖掉原先的无参构造方法
    • 导致程序报错:500错误
      • [HTTP协议的错误状态码]
    • 服务器错误都是500错误
  • 【注意】在javaweb程序员编写Servlet类,不建议手动编写构造方法。

    • 因为编写构造方法容易让无参数构造方法消失,可能导致Servlet对象无法实例化,因而init方法存在是很有必要的。
各方法何时使用?
  • 什么时候使用init()方法?
    • init方法很少用
    • 通常在其中做初始化操作,并且每个初始化操作只需要执行一次。如:初始化数据连接池、初始化线程池...
  • 什么时候使用destory()方法?
    • destory方法很少用
    • 通常在其中进行资源的关闭,对象即将销毁,还有什么没关闭抓紧时间关闭资源,还有什么没保存抓紧保存。

四、集成环境开发Servlet开发【IDEA】

4.1 开发步骤
  • 集成工具,目前使用最多的是:

    • IntelliJ IDEA (在提示方面强于Eclipse,更智能,但收费)
    • Eclipse(IBM公司开发,寓意为“日食”,表示将太阳(SUN公司)吃掉,但2009年SUN公司已经为Oracle公司并购了)
  • 使用IDEA集成开发工具开发Servlet

    • 第一步:New Project 新建空工程
      • Empty Project->next->修改项目名和默认位置->finish

    1681009409259

    修改工程名和路径,一般保持一致

    • 此处的javaweb是随便取的

    1681009484673

    提示是否创建一个新目录,create即可

    1681009618898

    • 第二步:新建模块(File->new-->Module)

      • 创建一个普通的javaSE模块

      1681012819872

      • 为模块命名,建议Module name的名称与下方都一致

      1681012897215

      • 创建后的Module会自动放在新建的空工程中

      1681012988590

    • 第三步:让该模块变为JavaEE的模块

      • 右键模块->Add Framework Support 添加框架支持

        • 让Module变为符合webapp规范的模块

        1681011899886

      • 选择JavaEE下的Web Application->Ok

        • 添加完框架支持后,IDEA会自动生成一个符合Servlet规范的webapp目录结构

          1681012135430

          1681013142357

          【重点】:IDEA中根据Web Application模版生成的目录有一个web目录,代表webapp的根

    • 第四步:引入Servlet所需的jar包

      • 因为此时创建的是一个普通的javaSE工程

      • 只有jdk,而Servlet并不在jdk中,而在Tomcat中

        • 找到Tomcat/lib目录中servlet-api.jar和jsp-api.jar ,添加到类路径( IDEA的classpath )中

          1681013462546

        • 点击File->Project Structure查看工程结构

          1681013658685

        • 点击Modules->选择工程servlet01->+号->JARs or Directories添加jar包

          • 添加library库也可以,但会将Tomcat中所有都添加
          • 添加jar更精准

          1681013805352

        • 找到本地Tomcat解压位置的lib目录下

          选中jsp-api.jar和servlet-api.jar,点击ok应用

          1681014063152

        • 选中两个添加的jar,apply即可

          1681014194634

        • 最后看到工程中External Libraies下显示jsp-api.jar和servlet-api.jar即可

          1681014280392

    • 第五步:编写XxxServlet类

      • 该XxxServlet类需要实现Servlet接口

        public class XxxServlet implements Servlet{
        
        } 
      • 如何快速继承Servlet的方法

        光标放在Servlet上,alt+enter后,点击Implement methods

        1681014615985

        全选后,点击ok即可

        1681014663471

        1681014701886

    • 第六步:在XxxServlet类中,实现Servlet接口的五个方法

      • 仅在service()方法中编写业务代码
        • 日常使用,多把servletRequest和servletResponse两个参数改为request和response
      @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    
        }
    • 第七步:连接数据库

      • 在工程WEB-INF目录下新建lib目录,将数据库驱动的jar包放入

        1681016471608

      • 连接数据库后显示的数据是动态的

      package com.bookstore.servlet;
      import jakarta.servlet.*;
      
      
      import java.io.IOException;
      import java.io.PrintWriter;
      import java.sql.*;
      
      public class UserServlet  implements Servlet {
          @Override
          public void init(ServletConfig servletConfig) throws ServletException {
      
          }
      
          @Override
          public ServletConfig getServletConfig() {
              return null;
          }
      
          @Override
          public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
              //设置响应的数据类型
              response.setContentType("text/html;charset=UTF-8");
              PrintWriter out = response.getWriter();
              out.println("hhhh");
              //获取数据库连接
              Connection conn = null;
              //预编译语句
              PreparedStatement ps = null;
              //结果集
              ResultSet rs =null;
              try{
      
                  //注册驱动
                  Class.forName("com.mysql.cj.jdbc.Driver");
                  //获取连接
                  String url = "jdbc:mysql://localhost:3306/bookstore?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC";
                  String user = "root";
                  String password = "d17379703772";
                  conn = DriverManager.getConnection(url,user,password);
                  //获取预编译语句对象
                  String sql = "select * from booktype";
                  ps = conn.prepareStatement(sql);
                  //执行sql语句,返回结果集
                  rs = ps.executeQuery();
                  //处理结果集
                  while (rs.next()){
                      String no = rs.getString("id");
                      String typename = rs.getString("typename");
                      out.println(no + ", " + typename);
                  }
              } catch (ClassNotFoundException e) {
                  e.printStackTrace();
              } catch (SQLException e) {
                  e.printStackTrace();
              } finally {
                  //释放资源
                    if (rs!=null){
                        try{
                            rs.close();
                        } catch (SQLException e) {
                            e.printStackTrace();
                        }
                    }
                    if(ps!=null){
                        try{
                            ps.close();;
                        } catch (SQLException e) {
                            e.printStackTrace();
                        }
                    }
                    if(conn!=null){
                        try {
                            conn.close();
                        } catch (SQLException e) {
                            e.printStackTrace();
                        }
                    }
              }
          }
      
          @Override
          public String getServletInfo() {
              return null;
          }
      
          @Override
          public void destroy() {
      
          }
      }
      
    • 第八步:在web.xml注册XxxServlet类

      • 让请求路径和Servlet对应起来
      <?xml version="1.0" encoding="UTF-8"?>
      <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
               version="4.0">
          <servlet>
              <servlet-name>bookServlet</servlet-name>
              <servlet-class>com.bookstore.servlet.UserServlet</servlet-class>
          </servlet>
          <servlet-mapping>
              <servlet-name>bookServlet</servlet-name>
              <url-pattern>/booklist</url-pattern>
          </servlet-mapping>
      </web-app>
    • 第九步:编写一个有超链接的html页面,让用户点击超链接,发送请求,Tomcat执行后台的XxxServlet。

      • html页面文件要创建在WEB-INF目录外面
      • 其中超链接的href 需要有项目名(自定义即可,此时无法动态获取)
    • 第十步:让IDEA关联Tomcat服务器

      • 关联过程让webapp部署到Tomcat服务器中

      • IDEA工具右上角,绿色小锤子->Add Configuration

        1681018211253

      • 点击+,找到Tomcat Server->local

      1681018240545

      • 点击Server ,进入配置页面 【基本不用修改,默认即可】

        • 1️⃣Name:服务器名称

          • 配置页面设置的name 和 左侧的服务名对应,可任取

          1681018276393

        • 2️⃣Application server ->Configure 选择本机Tomcat所在位置

        1681018341894

        • 3️⃣Open brower:设置浏览器打开

          • After launch 勾选即Tomcat运行自行打开xxx浏览器
          • URL:为访问地址,localhost=172.0.0.1:表示本机

          1681018372691

        • 4️⃣VM opeions:指定虚拟机参数

        1681018407418

        • 5️⃣On 'Update' action :代码改动时,默认为重启服务器

        1681018419910

        • 6️⃣JRE:使用的jdk版本

        1681018449418

        • 7️⃣Tomcat Server Settings :服务器配置(端口号....)

        1681018458905

      • 点击Deployment,部署web应用

        • +号,选择Aritifact..

        1681018482837

        • 修改Application context: 项目名

          • ❗项目名要和html页面超链接href处的项目名一致

          1681018566724

        • 最后ok,完成所有部署

    • 第十一步:启动Tomcat服务器

      • 点击右上角的绿色箭头或绿色小虫子

        • 绿色箭头,直接启动
        • 绿色小虫子,采用debug模式启动服务器

        1681018580718

    若出现以下报错,说明IDEA的jdk版本过低,点击File->Project Structure->SDKs->选择更高版本的jdk即可

    【在此我是Tomcat10,所以用了最新的jdk17,报错就完美解决了!!】

    类文件具有错误的版本 55.0, 应为 52.0
    请删除该文件或确保该文件位于正确的类路径子目录中。

    1681020057697

    IDEA出现Tomcat状态栏,即Tomcat启动成功

    1681020250882

    • 第十二步:打开浏览器,在地址栏输入url:http://localhost:8080/项目名/index.html

      • 在此我是http://localhost:8080/bookstore/index.html

      • localhost与172.0.0.1一样,是本机地址

        点击图书信息超链接

        1681020603023

        Tomcat会根据超链接的路径发起请求,先去web.xml文件中,找到对应的Servlet类名和全限定包名,在工程中找到该类,运行后,向数据库读取数据,显示在页面

        1681020645545

4.2 Servlet类的编写

(1)第一步:编写一个XxxServlet类,直接继承HttpServlet类

(2)第二步:重写doGet()/doPost()方法,程序员自行决定

(3)第三步:将Servlet类配置到web.xml文件中

第一、二步:实际开发中,程序员编写的XxxServlet类都是继承于(extends)HttpServlet类

//getServlet示例
public class getServlet extends HttpServlet {
  @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException{
      response.setContentType("text/html");
      PrintWriter out =response.getWriter();
      out.println("<h1>登录成功</h1>");
  }
}

第三步:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">  
<servlet>
        <servlet-name>getServlet</servlet-name>
        <servlet-class>com.servlet02.servlet.getServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>getServlet</servlet-name>
        <url-pattern>/getMethod</url-pattern>
    </servlet-mapping>
</web-app>    

五、Servlet、GenericServlet和HttpServlet三者关系

1️⃣Servlet(老祖宗,接口,包含5个Servlet的方法)

①但常用的只有service,或偶尔init

---->为了简化,编写的Xxx不必每次都实现Servlet所有的方法,出现GenericServlet

2️⃣GenericServlet(爸爸,抽象类,实现 [ implements ] 了Servlet接口)

①将常用方法变为抽象方法,使子类只需要重写抽象方法即可

  • 不必重写大量冗余的重复代码

②为了方便子类重写init()方法

  • 原先的init(ServletConfig) 最后,调用无参的init()方法
    • 有ServletConfig参数
  • 利用方法重载,定义了一个无参的init(),供子类重写
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }

    public void init() throws ServletException {
    }

③利用适配器模式,将常用方法定义为抽象方法,该类变为抽象类,而子类继承该类后,只需重写抽象方法即可

3️⃣HttpServlet(儿子,继承 [extends] GenericServlet抽象类)

①程序员自行编写的XxxServlet都将继承HttpServlet

  • why?
  • HttpServlet是基于Http协实现的Servlet接口
  • 利用重载,编写了service,其中包含了各种请求类型405报错
    • 405报错,当发送到请求为Xxx,就调用对应到doXxx()方法,该方法就是用于抛出405报错
    • 因而约束,继承HttpServlet的XxxServlet,如果后端为post请求时,应该重写父类HttpServlet的doPost()方法,以此将父类HttpServlet的doPost()方法中的405报错给重写覆盖掉。

②将原先继承的service(ServletRequest req, ServletResponse res)进行重写

  • 将ServletRequest-->HttpServletRequest
  • 将ServletResponse-->HttpServletReponse
  • 最后传入参数,调用HttpServlet中重载的service(HttpServletRequest req, HttpServletResponse resp)方法

(1)service(ServletRequest req, ServletResponse res)方法代码:

    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        HttpServletRequest request;
        HttpServletResponse response;
        try {
            request = (HttpServletRequest)req;
            response = (HttpServletResponse)res;
        } catch (ClassCastException var6) {
            throw new ServletException(lStrings.getString("http.non_http"));
        }

        this.service(request, response);
    }

(2) service(HttpServletRequest req, HttpServletResponse resp)方法

  • 对请求作处理,调用对应到doXxx方法,以此抛出405错误
  • 约束继承的子类,重写对应到doXxx方法,而约束前段发来请求类型,不匹配则报405
 protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader("If-Modified-Since");
                } catch (IllegalArgumentException var9) {
                    ifModifiedSince = -1L;
                }

                if (ifModifiedSince < lastModified / 1000L * 1000L) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }

    }

六、GenericServlet类详解

【源码解析】适配器模式改造Servlet-GenericServlet

  • 【改进一】:使用适配器模式

    (问题)原先一个所有类都继承同一个接口,但其中常用的方法只有一个,其他方法都不使用,其他类要重写方法,会将所有方法都重写过来

    1681038532009

    (解决)定义适配器,将其中常用的方法定义为abstract抽象方法,同时适配器类为抽象类,继承接口

    • 【适配器】

      抽象类,实现有许多方法浪费的接口

      常用的方法,设为抽象方法

    • 其他类 extends 适配器抽象类,即可使用

    1681038367075

    再让类去继承适配器类即可,不必再都继承于同一个接口,而是各自重写使用频率最多的方法

    1681038428060

6.1 利用适配器模式改造Servlet
  • 第一步:编写一个标准通用的Servlet,起名:GenericServlet

    • 让之后所有的类都继承GenericServlet,而不直接实现Servlet接口
  • 第二步:将GenericSerlvet中的Service变为抽象方法,让类改为abstract抽象类

    1681039832163

  • 第三步:其他的XxxServlet直接继承GenericServlet

    • 而不再实现Servlet

    1681040169299

6.2 解决获取局部变量ServletConfig
  • 解决init方法中的ServletConfig是局部变量,不能被子类轻松获得的问题

    • 因其,可能要在Service方法中使用

    • 第一步:在GenericServlet中定义成员变量 config

      //成员变量
      private ServletConfig config;
    • 第二步:在初始方法中为成员变量赋值

         @Override
          public void init(ServletConfig servletConfig) throws ServletException {
               this.config =config;
          }
    • 第三步:通过getServletConfig的return得到成员变量config

         @Override
          public ServletConfig getServletConfig() {
              return config;
          }
    • 第四步:当继承的子类要得到ServletConfig,只需调用this.getServletConfig方法即可

       ServletConfig config = this.getServletConfig();
              System.out.println(config);

    同时因为子类可能出现重写init()方法的情况,父类的init()就不再会执行,导致config没法被获取到,return时就会为空,所以我们要设置子类无法重写父类的init()方法,保证父类init()方法执行,从而获得config成员变量。

6.3解决init()方法重写问题

子类重写了适配器父类初始的init方法,会导致调用子类的init(),而父类中编写获取ServletConfig config成员变量的init()方法无法执行。同时还要考虑到子类如要一定要重写init()方法该怎么办。

主要考虑以下两点:

①子类不能重写父类适配器的初始的init()方法

②子类能重写父类中自行定义的init()方法

  • 【解决一】:在父类适配器的init()方法中加上final属性

    • 使子类无法重写适配器初始生成的init方法,不耽误设置ServletConfig

    1681044122713

    • 【解决二】:与此同时,利用方法重写再定义一个init()方法,同时在初始的init()方法中调用它

      • 由此让子类可重写该无参的init()方法

      1681044415878

6.4自行编写GenericServlet完整代码
  • 我们自行编写的GenericServlet代码如下
    • 1️⃣采用适配器模式-->解决子类重复重写无用的方法-->将常用方法变为 abstact 抽象方法/类--->抽象类-->其他子类无需实现,只需继承适配器类
    • 2️⃣成员变量-->解决局部变量获取问题->定义成员变量 config->在方法中利用this设置成员变量值
    • 3️⃣final+方法重写 ->解决子类禁止重写初始init()方法,而重写自定义的init()方法->将初始init()设为final+调用自定义init()方法->定义一个无参的init()方法
package com.bookstore.servlet;

import jakarta.servlet.*;

import java.io.IOException;

/***
 * 编写一个标准通用的Servlet
 * 让其他的XxxServlet类都去继承它(extends)
 * 而无需再去实现Servlet(implements)
 * GenericServlet就是一个适配器
 */
public abstract class GenericServlet implements Servlet {
    //成员变量
    private ServletConfig config;

    /**
     * 其中this.config=config用于设置成员变量config
     * init()调用的是供子类重写的init()方法
     * @param servletConfig
     * @throws ServletException
     */
    @Override
    public final void init(ServletConfig servletConfig) throws ServletException {
         this.config =config;
         init();
    }

    /**
     * 该方法供子类重写
     */
    public void init(){

    }
    @Override
    public ServletConfig getServletConfig() {
        return config;
    }

    @Override
    public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse)
            throws ServletException, IOException;


    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}

由此引入SUN公司的Servlet规范中,即Tomcat的Servlet类中其实提供了GenericServlet类,我们只需要将编写的Xxx类给继承它即可。

  • 原先写法:实现Servlet

1681045648305

如今写法:继承GenericServlet

1681045801457

七、ServletConfig接口详解

7.1 ServletConfig是什么?

jakarta.servlet.ServletConfig是一个接口,也是Servlet规范中的一员

  • ServletConfig对象被翻译为:Servlet 对象的配置信息对象。

    • 一个Servlet对象就有一个配置信息对象。
  • ServletConfig对象包装了web.xml文件中< servlet></ servlet>标签中的配置信息

即Tomcat解析Web.xml文件后,会将其中的< servlet></ servlet>标签中的配置信息自动包装到ServletConfig对象中。

7.2 谁实现了这个接口?
  • 可利用子类继承GenericServlet,然后调用this.getSetvletConfig()得到config对象,然后打印输出

1681056755919

  • 【结论】Tomcat服务器实现了ServletConfig接口
7.3 服务器变化会怎样

若服务器变为jetty服务器,输出ServletConfig对象时,会有变化么?

  • 不一样,包名类名可能有区别,但不同的服务器都实现了ServeltConfig这个规范
7.4 Servlet和ServletConfig的关系

每创建一个Servlet对象会生成一个ServletConfig对象(关系:一对一)

  • 即web.xml文件中每个< servlet></ servlet>标签对应一个ServletConfig对象
7.5 ServletConfig对象由谁创建,什么时候?
  • 在用户第一次发送请求时,Tomcat调用init()方法需要传入ServletConfig对象参数
  • 同时创建Servlet对象的同时创建ServletConfig
7.6ServletConfig中的方法

以下四个方法都可直接在继承GenericServlet的XxxServlet中this.xxxx即可,不必利用getServletConifg()获取后,再去config.xxx。

public String getInitParameter(String name);
public Enumeration<String> getInitParameterNames();
public ServletContext getServletContext();
public String getServletNames();

public String getInitParameter()方法

---通过初始化参数名获取初始化参数的值,获取< param-value> </ param-value>标签的值

1681129370639

public Enumeration< String> getInitParameterNames()方法

---获取初始化参数的名字 ,获取< param-name></ param-name>标签的值

1681129208514

以上两方法可获取web.xml中的初始化参数配置信息

  • 在web.xml配置init-param的配置信息如下👇:

1681128925516

public ServletContext getServletContext()方法

public String getServletName()方法

--获取web.xml文件中< servlet-name> </ servlet-name>标签的内容

1681128751916

7.7 总结
  • ServletConfig是Servlet配置信息对象

  • 相当于web.xml中每个对象

  • 一个Servlet对应一个Servlet对象

    • ServletConfig对象由Tomcat创建,并实现,是在调用init()方法时传入的
  • ServletConfig对象有以下四种常用方法:

    • 同時GenericServlet中都有这些方法,在Servelt中可直接this.调用即可
    public String getInitParameter(String name);//通过初始化参数name获取value值
    public Enumeteraction<String> getInitPatameterNames();//获取所有初始化参数的name
    public ServletContext getSerlvetContext();//获取ServletContext对象
    public String getSerlvetName();//获取Servlet的name	

八、ServletContext接口详解

8.1如何获取ServltContext对象
  • 第一种:ServletConfig中有getServletContext()方法,由此可获得该对象
  • 第二种:GenericServlet中重写了getServletContext方法,由this.getServletContext()也可获得该对象
    • 注意this.所在的类必须继承了GenericServlet类
8.2ServletContext是什么?
  • ServletContext是一个接口,是Servlet规范中的一员

  • 被称为Servlet的上下文对象(或Servlet的环境对象)

    • 相当于整个web.xml文件
    • 即,放在ServletContext中的资源,所有Servlet对象都可以获取
8.3ServletContext是谁实现的?
  • Tomcat服务器实现了ServletContext接口
  • 1681131759434

8.4 ServletContext是谁创建,什么时候?
  • 服务器启动时创建,由Tomcat服务器创建

  • 对一个webapp而言,ServletContext只有一个

  • 服务器启动时创建,服务器关闭时销毁

【拓】Tomcat是一个容器,其中可放多个webapp,而一个webapp对应一个ServletContext对象

8.5 ServletContext的方法

①获取初始化参数名+值

public String getInitParameter(String name);//通过初始化参数name的值获得value
public Enumeration<String> getInitParameterNames();//获得name值
//ServletContext中两个方法获取的是<context-param></context-param>中的value和name【全局配置】
//ServletConfig中的两个方法获取的是<servlet></servlet>中的value和name值【局部配置】
  • 如下:该配置一般用于放需要共享的配置信息

1681139148720

②获取应用上下文的根

  • 根路径,即请求时的项目名
public String getContextPaht();
//在真实项目中,用于动态获取项目的根路径

③获取真实路径

  • 真实路径:即绝对路径
  • 传入路径加“/”表示web文件夹(即webapp的根),不加默认也是从此开始找
public String getRealPath(String path);
//path是想要获取的文件在当前项目中的位置
eg:
public class XxxServlet extends GenericServlet{
    
  ServletContext application = this.getContextServlet();
  String realpath = application.getRealPath("./index.html");//获取index文件的真实路径
}

④记录日志

  • 生成的日志自动生成在tomcat中的logs目录下【不使用开发工具时】

  • 生成的日志会在IDEA生成Tomcat服务器的副本,存放日志的位置变为CATALINA_BASE所对的目录

    1681140711397

public void log(String message);
public void log(String message,Throwable t);
//日志信息有哪些?
//catalina.2021-11-05.log 服务器端的java程序运行的控制台信息
//localhost.2021-5-11.log ServletContext对象log方法记录的日志信息存储到该文件中
//localhost_access_log.2021-11-05.txt 访问日志
8.6什么时候使用ServletContext?

ServletContext对象又称:应用域

  • 当所有用户共享一份数据,且数据很少并且少被修改,可以都放入到应用域中

    • 共享:ServletContext只有一个,共享资源放入才有意义
    • 数据小:因为大太占用堆,且ServletContext生命周期要Tomcat关闭才销毁,大数据量会影响服务器性能。
    • 少被修改:用户共享的资源,经常修改会导致线程并发带来的安全问题
    • 合理利用应用域,可以大大提高执行效率,因为无需到数据库中取
    //存(怎么向作用域中存数据)
    public void serAttribute(String name,Object value);
    //取(怎么从作用域中取数据)
    public void getAttribute(String name);
    //删(怎么删除作用域的数据)
    public void removeAttribute(String name);
    //存数据后,webapp下所有Servlet都可以使用
8.7总结
  • ServletContext是Servlet的上下文对象(环境对象),是应用级对象

    • 相当于一个web.xml文件
  • 生命周期,Tomcat启动时创建,Tomcat关闭时销毁

  • 同一个webapp中,共用一个ServletContext对象

【白学时刻到!】

今后编写代码时,实际上并不会直接继承GenericServlet类,因为我们web端开发采用的是b/s架构,基于HTTP协议,而Servlet规范中提供了HttpServlet,它专门为HTTP协议准备的一个Servlet类,今后我们编写的类都需要继承它!

但并不白学,因为HttpServlet是继承GenericServlet的!!

  • 继承结构如下:

    jakarta.servlet.Servlet(接口)【爷爷】
    jakarta.servlet.GenericServlet implements Servlet(抽象类)【儿子】
    jakarta.servlet.HttpServlet extends GenericServlet(抽象类)【孙子】
    
    //我们今后编写的Servlet类都要继承HttpServlet类

【拓展】:目前解除过度缓存机制有哪些?

  • 堆内存当中的字符串常量池

    • “asv” 先在字符串常量池中查找,如果有,直接用,如果没有则新建,再放入字符串常量池
  • 堆内存当中的整数型常量池

    • [-128~127]一共256个Integer类型的引用,放在整型常量池中,没有超出该范围的,就从常量池选取
  • 连接池

    • 此处是连接池,表示java语言连接数据库的连接对象:java.sql.Connection对象

    • JVM是一个进程,MySQL数据库也是进程,进程之间建立连接,较为耗费资源,就可以采用提前创建好N个Connection连接对象,让用户连接无需在新建连接对象,省去了新建这个动作,直接从连接池中获取对象,大大提升访问效率,而这个存放Connection对象的集合称为连接池。

    • 连接池有什么用?

      • 会限制最小连接数、最大连接数,在提高用户访问效率的情况下,也保证数据库的安全性
        • 不会被一次性创建太多的连接,导致服务器瘫痪
  • 线程池

    • Tomcat本身支持多线程
    • Tomcat服务器会在启动时,创建多个Thread线程对象,然后线程对象放到集合当中,称线程池。
    • 当用户发来请求后,会从线程池中拿取线程对象,用于处理请求。
      • 所有的WEB服务器,或应用服务器都是支持多线程的,都有线程池机制。
  • redis

    • NoSQL数据库,非关系型数据库,缓存数据库
  • 向ServletContext应用域中存储数据≈将数据存放到缓存cache中

九、HTTPServlet接口详解

9.1HTTP协议
  • 什么是协议?

    • 协议是一套规范,一套标准,是由第三方或组织制定的。
  • 什么是HTTP协议?

    • HTTP协议:是W3C制定的一种超文本传输协议

      • W3C : 万维网联盟组织

        • 负责制定标准的:HTTP 、HTML4.0、HTML5.0、XML等规范都是W3C制定的
        • 创立人:蒂姆 伯纳斯 李
      • 超文本:超越普通文本,比如:流媒体、文字、声音、视频、图片等

    • 该协议游走在B和S间,B->S发数据需遵循HTTP协议。同样S->B发数据同样也需要HTTP协议。

      • 由此B和S才可解耦合

        • 解耦合:两者互不依赖
      • B Brower浏览器

      • S Server服务器

  • HTTP协议包括:

    • 请求协议:
      • 浏览器 向 WEB服务器发送数据,这个发送的数据需要遵循一套标准,该标准规定发送数据的具体格式。
    • 响应协议:
      • WEB服务器 向 浏览器发送数据需遵循一套标准,规定发送数据的具体格式。
9.2 HTTP协议的组成
9.2.1请求协议

①HTTP的请求协议:包括4部分(B-->S)

  • 请求行
  • 请求头
  • 空白行
  • 请求体

请求协议,分为GET请求和POST请求

(1)HTTP请求协议的具体报文: GET请求

GET /servlet02/getMethod?username= HTTP/1.1             //请求行
Accept:                                                 //请求头 text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,ja;q=0.5
Cache-Control: max-age=0
Connection: keep-alive
Cookie: Idea-e0246bca=457d656a-65b3-4111-a3e4-ec18cac99483
Host: localhost:8080
Referer: http://localhost:8080/servlet02/
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 Edg/112.0.1722.39
sec-ch-ua: "Chromium";v="112", "Microsoft Edge";v="112", "Not:A-Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
                                                       //空白行
                                                       //请求体

(2)HTTP请求协议的具体报文: POST请求

POST /servlet02/getMethod HTTP/1.1             //请求行
Accept:                                        //请求头 text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,ja;q=0.5
Cache-Control: max-age=0
Connection: keep-alive
Content-Length: 9
Content-Type: application/x-www-form-urlencoded
Cookie: Idea-e0246bca=457d656a-65b3-4111-a3e4-ec18cac99483
Host: localhost:8080
Origin: http://localhost:8080
Referer: http://localhost:8080/servlet02/
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 Edg/112.0.1722.39
sec-ch-ua: "Chromium";v="112", "Microsoft Edge";v="112", "Not:A-Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
                                     //空白行
username=lis&1234123                 //请求体

1️⃣请求行

  • 包括三部分:
    • 第一部分:请求方式(7种)
      • get【常用】
      • post【常用】
      • delete
      • put
      • head
      • options
      • trace
    • 第二部分URI
      • URI:统一资源标识符,代表网络中某个资源的名字,但无法定位资源
      • URL:统一资源定位符,代表网络中某个资源,同时可以定位资源
      • URL包括URI
    • 第三部分:HTTP协议版本号

2️⃣请求头

  • 请求的主机
  • 主机的端口
  • 浏览器信息
  • 平台信息
  • cookie等信息

3️⃣空白行

  • 区分请求头和请求体的

4️⃣请求体

  • 向服务器发送的具体数据
9.2.2响应协议

②HTTP响应协议:包括4部分(S-->B)

  • 状态行

  • 响应头

  • 空白行

  • 响应体

  • HTTP响应协议的具体报文:【要点击Network->Response Headers->View parsed查看源才会显示】

    HTTP/1.1 200       //状态行
    Content-Type: text/html;charset=UTF-8  //响应头
    Content-Length: 29                   
    Date: Wed, 12 Apr 2023 10:42:25 GMT
    Keep-Alive: timeout=20
    Connection: keep-alive
                                   //空白体
    <h2>这是post的请求</h2>  //响应体:在控制台中Response中找到    

1️⃣状态行

  • 三部分组成:

  • 第一部分: 协议版本号(HTTP/1.1)

  • 第二部分:状态码 (HTTP中规定的响应状态号,不同响应结果对应不同号码)

    • 200 表示请求成功,正常结束

    • 400 表示访问资源不存在,通常是路径写错,如果路径写对,那就是服务器资源并未启动成功(属于前端错误)

    • 405 表示前端请求方式与后端处理方式不一致

      • 如:前端是POST请求,后端处理方式为get方式,就会报405
    • 500 表示服务器端程序出现异常

    • 以4开始,多为浏览器端

    • 以5开始,多为服务器端

  • 第三部分:状态的描述信息

    • ok表示正常成功结束
    • no found表示资源找不到

2️⃣响应头

  • 响应的内容类型
  • 响应的内容长度
  • 响应的时间
  • .......

3️⃣空白行

  • 用于分隔 响应头 和 响应体

4️⃣响应体

  • 是响应的正文,该内容是一个长的字符串,该字符串会被浏览器渲染,解释并执行,展示最终效果
9.2.3如何查看协议内容?
  • 使用chrome浏览器:F12。找到network,通过该面板可查看协议具体内容

    1681286357147

  • 其中标头含有 请求报文 和 响应报文 的信息。

    1681296493699

9.2.4 GET和POST的区别
  • 怎么向服务器发送get请求,怎么发送post请求?
    • 只有一种情况可以发送post请求: 使用form表单,同时method=“post”
    • 其他都是get
(1)发送数据位置的区别

①GET请求发送数据时,URI后添加“ ?”,同时数据挂在“ ?”后。 ---发送数据回显在地址栏

  • get请求在“请求行”上发送数据

②POST请求发送数据时。 ---发送数据不回显在地址栏

  • post请求在"请求体"上发送数据

③两者发送数据的位置不同,但格式统一:name=value&name=value

  • name是什么?
    • 以form表单为例: form表单中input标签的name
  • value是什么?
    • 以form表单为例:form表单中input标签的value
(2)数据格式的区别

①get请求只能发送普通字符串,同时长度有限制,因而get请求无法发送大数据量

  • W3C这样说:get请求比较适合从服务器端获取数据

②post请求可发送普通字符串、流媒体、视频、图片等信息,无限制。可以发送大数据量

  • W3C这样说:post请求比较适合向服务器端传送数据
(3)安全性的差异

【在此讨论的是get和post正常使用的前提,如:用户密码根本不会用get请求,直接暴露在地址栏,这不能说明get不安全!!】

①get请求是安全的,因为它只为了从服务器上获取数据

②post请求是不安全的,因为它会向服务器提交数据,若有害数据通过post请求进入服务器,服务器就会很危险。

  • 因此拦截(监听)请求,大多都监听提交数据的post请求
(4)是否支持缓存

①get请求支持缓存

  • 如:页面的图片在第一次访问较慢,因为要从服务器中读取;但是当第二次再访问,就会直接从缓存中获取
    • 任何一个get请求最终“响应结果”都会被浏览器缓存
    • 即当发送相同路径请求时,浏览器会先从缓存中找,再去服务器获取
      • 一个请求路径 对应 一个资源
    • 缓存机制目的:提高用户的体验
  • 怎样设置每次请求都访问服务器,而不去找缓存?
    • 保证每次请求路径不一样即可 [ 路径后加上时间戳 ]

②post请求不支持缓存

  • 因为post请求后的缓存没有意义
9.2.5 GET和POST的使用场景

①获取服务端的数据,建议GET请求

除以下,都可用POST

②向服务端提交数据,建议POST请求

  • 大部分表单都是POST请求,因为form表单中需要填写大量数据,用于收集用户信息,传给服务器
    • 同时表单中有敏感信息时,应用POST,防止信息回显到地址栏(例如:密码)
  • 文件上传,若不是普通文本,必须用POST

十、HttpServlet源码分析

10.1 HttpServlet是什么?

HttpServlet类是专门为HTTP协议准备的,比GenericServlet更适合HTTP协议下的开发

  • 它是一个模版类

1681389673340

  • HttpServlet在哪个包下?
    • jakarta.servlet.http.HttpServlet
  • 目前我们接触了servlet规范中哪些接口?
    • jakarta.servlet.Servlet 【Servlet核心接口】
    • jakarta.servlet.ServletConfig 【Servlet配置信息接口】
    • jakarta.servlet.ServletContext 【Servlet上下文接口 】
    • jakarta.servlet.ServletRequest 【Servlet请求接口】
    • jakarata.servlet.ServletResponse 【Servlet响应接口】
    • jakarata.servlet.ServletException 【Servlet异常】
    • jakarata.servlet.GenericServlet 【标准通用的Servlet类(抽象类)】
  • http包下有哪些类和接口呢? jakarta.servlet.http.*
    • jakarata.servlet.http.HttpServlet(HTTP协议专用的Servlet类,抽象类)
    • jakarata.servlet.http.HttpServletRequest(HTPP协议专用的请求对象)
    • jakarata.servlet.http.HttpServletResponse(HTTP协议专用的响应对象)
10.2 HttpServletRequest对象
  • 其中封装了什么信息?
    • HttpSerlvetRequest,简称request对象
    • HttpServletRequest中封装了请求协议的全部内容
    • 即Tomcat服务器会将“请求协议”中的数据全部解析出来,然后将这些数据全部封装到request对象中
  • 作用?
    • 通过HttpServletRequest中封装的对象,就可以获取请求协议中的数据
  • HttpServletRequest对象是专门用来获取客户端请求信息的。
  • HttpServletResponse对象是专门用来响应HTTP协议到浏览器的。
10.3源码解析

【回忆】Servlet声明周期

  • 用户第一次请求
    • Tomat服务器通过反射机制,调用无参数构造方法创建Servlet对象。
    • Tomcat服务器调用init()方法完成初始化。
    • Tomcat服务器调用Servlet对象的service()方法处理请求。
  • 用户第二次请求
    • Tomcat服务器调用Servlet对象的service()方法处理请求
  • 用户第三次请求
    • Tomcat服务器调用Servlet对象的service()方法处理请求。
  • ...
  • 服务器关闭
    • Tomcat服务器调用Servlet对象的destory方法,做销毁前的准备工作。
    • Tomcat服务器销毁Servlet对象。
【引入】HttpServlet干了什么?

相比于GenericServlet,HttpServlet新增了

  • 利用方法重载, service(HttpServletRequest,HttpServletResponse )
    • 专门针对http协议,方法中主要是判断发送请求的类型,然后调用对应doXxx()方法【该方法用于报405错误】
    • 因而当自行编写XxxServlet后,若后端为post请求就应该重写doPost方法,覆盖掉HttpServlet自带的doPost方法,去消除请求类型对应的405报错
      • 建议:不要为了防止405,在子类中又重写doPost,又重写doGet(还不如直接重写service!)

因而利用XxxServlet继承HttpServlet后

  • 用户第一次发起请求

    • ①会调用无参构造方法,创建对象

    • ②再调用intit()方法,完成初始化

      • Xxxx中未编写init()方法,会去HttpServlet中找,而HttpServlet中无init()方法,则又会去执行GenericServlet中的init(ServletConfig )方法

      • 注意调用的是GenericServlet中有参数的那个init()方法

        • 其中调用的init()是可让子类重写的init()

        1681388060966

    • ③执行有参的init(),同时执行无参的init

      • 便于子类重写
    • ④执行service()方法

      • XxxSerlvet中没有service()方法,会调用HttpServlet中最原始的Service()方法

        1681388460813

        • 不可能调用GenericServlet,因其Service()方法是抽象方法
        • 因GenericServlet继承的老祖宗Servlet中就是不带Htpp的Service()方法,因而调用HttpServlet中最原始的Service()方法

(原始的service()方法)

    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        HttpServletRequest request;
        HttpServletResponse response;
        try {
            //将ServletRequest和ServletResponse向下转型为带有http的...
            request = (HttpServletRequest)req;
            response = (HttpServletResponse)res;
        } catch (ClassCastException var6) {
            throw new ServletException(lStrings.getString("http.non_http"));
        }
         //重载service方法--》调用参数带有HTTP的service()方法
        this.service(request, response);
    }

(转型后的Servive()方法,参数为HttpServletRequest)

  • 该Service是模版方法
    • 方法中定义了核心算法骨架,具体实现步骤延迟到子类中完成
//模版方法没有加final,说明继承HttpServlet的子类可重写该service方法  
//但重写时要注意,在执行时就不会执行父类的service的方法了
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //调用request中的getMethod()方法,获取请求方式,七种之一(POST/GET...)
        String method = req.getMethod();
        long lastModified;
        //如果请求方式为GET,执行doget方法
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                //执行doget方法
                this.doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader("If-Modified-Since");
                } catch (IllegalArgumentException var9) {
                    ifModifiedSince = -1L;
                }

                if (ifModifiedSince < lastModified / 1000L * 1000L) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
           //如果请求方式为HEAD,执行doHead方法 
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
           //如果请求方式为POST,执行doPost方法 
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
            //如果请求方式为PUT,执行doPut方法 
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
            //如果请求方式为DELETE,执行doDelete方法 
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
            //如果请求方式为OPTIONS,执行doOptions方法 
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
            //如果请求方式为TRACE,执行doTrace方法 
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }

    }

/*
根据以上HttpServlet到源码分析:
    假设前端发送的请求是get请求,后端程序员重写的方法是doPost
    假设前段发送的请求是post请求,后端程序员重写到方法是doGet
    就会报405错误
    源码中,当发送的请求是get/post时,HttpServlet会执行其中的doPost和doGet方法
    而这两个方法是用来报405错误的,只有将其重写,才不会报405错误
 */
/*
此处:源码采用识别到哪种请求,就调用对应的doXXX()方法,报405错误
因而子类必须重写它的doXXX()方法,否则就会报错
*/
/*   要令继承HttpSerlvet的类要避免405,必须做到?
                                    ①后端重写doGET()方法,前端就发get请求
                                    ②后端重写doPost()方法,前端就发post请求
  • 用户发送N次请求
    • 依然是HttpServlet中的最原始的Service()方法在执行

【思考】

HttpServlet中重写了父类GenericServlet的service方法,同时添加了一个转型为Http类型参数的service方法

  • 可以将编写的XxxServlet直接继承HttpServlet,直接重写HttpServlet中带Http类型参数的service()方法么?
    • 可以,但无法享受405错误,也无法使用Http协议中专属的方法
    • HttpServlet中的service(不带有http)的方法,会调用HttpServlet中重载的service(带有Http)的方法
    • 而父类HttpServlet的service(带有http)到方法中编写了关于请求发送的一系列操作,而子类重写父类的service方法后,这一系列操作,都会被子类的service()覆盖
      • 编写了什么?使发来xx请求,就对应调用doXxx方法,报405错误
      • 因而子类后端需要post请求,必须重写doPost方法,覆盖掉父类的doPost方法
      • 否则会执行父类的doPost方法,即报405错!
    • 当前端发送post请求,而后端却重写doGET()方法时,最终提示405错误!
【原因】405错误的情况

一般是,后端重写的doXXX方法,并非是前端发来到XXX请求

十一、HttpServletRequest接口详解

  • HttpServletReques是一个接口,全限定名:jakarta.servlet.http.HttpServletRequest

  • HttpServlet接口是Servlet规范中的一员

  • HttpServletRequest接口继承于:ServletRequest

    public interface HttpServletRequest extends ServletRequest {
    
    }
11.1 HttpServletRequest接口的实现类由谁编写?

通过打印输出,service方法中的HttpServletRequest类型的request对象,得到:

  • org.apache.catalina.connector.RequestFacade实现了HttpServletRequest接口

    public class RequestFacade implements HttpServletRequest{}
    • 说明:Tomcat服务器(WEB服务器、WEB容器)实现了HttpServletRequest接口
11.2 由谁创建?
  • Tomcat将其创建
11.3 HttpServletReuqest是什么?
  • 其中封装了HTTP的请求协议
    • 当用于发送请求时,遵循了HTTP协议,发送的是HTTP的请求协议,Tomcat服务器会将HTTP协议中信息以及数据全部解析出来,然后将Tomcat服务器信息封装到HttpServletRequest对象中,传给java程序员
11.4 request和response对象的生命周期

两者仅在当前请求中有效,下一次请求会变换

  • 一次请求对应一个request
  • 两次请求对应两个request
11.5 request对象

实际上又称为“请求域”对象,

  • 应用域对象是ServletContext(Servlet上下文对象)

    • 以下几种情况会向ServletContext中绑定数据

      • 第一,所有用户共享的数据
      • 第二,该数据的数据量很小
      • 第三,共享的数据很少修改操作
        • 为什么要很少修改,修改次数太多,会涉及到线程安全问题,不必可少会使用锁,而锁又会让系统性能降低
      • 满足以上三个条件时,使用应用域对象,可大大提高程序执行效率
      • 向应用域当中绑定数据,相当于把数据放入缓存(Cache),用户访问时直接从缓存中取即可,减少IO操作,提升系统性能。
      • 缓存机制:在内存中准备好一块空间,将需要的数据放到内存中,当需要时直接访问内存控件,而不减少程序与数据库建立连接,减少IO操作,提高程序性能,所以缓存技术是提高系统性能的重要手段。
    • ServletContext操作域的方法有?

      void setAttribute(String name,Object obj);//向域当中绑定数据
      
      Object getAttribute(String name);//从域当中根据name获取数据
      
      void removeAttribute(String name);//将域当中绑定的数据移除
      
      //以上操作类似于Map集合操作
      Map<String,Object>map;
      map.put("name",obj);//向map集合中放key和value
      Object obj = map.get("name");//通过map集合的key获取value
      map.remove("name");//通过Map集合到key删除key和value这个键值对
11.5.1**“请求域“和“应用域”的区别**

(1)范围

  • “请求域”对象要比“应用域”对象范围小很多,生命周期短很多

(2)概念区分

  • 一个上下文对象ServletContext对应一个应用域对象

    • 当Tomcat停止时, 应用域被销毁
  • 一个请求对象request对应一个请求域对象

    • 一次请求结束后,该请求域就被销毁了

(3)使用场景

  • 使用原则
    • 尽量使用小的域对象,因为占用的资源更少
  • 请求域也有这三个方法:
void setAttribute(String name,Object obj);//向域当中绑定数据

Object getAttribute(String name);//从域当中根据name获取数据

void removeAttribute(String name);//将域当中绑定的数据移除
  • 见过到缓存技术?
    • 字符串连接池
    • 整数型常量池[-128~127]
      • 在范围内到Integer对象,不再创建新对象,直接从常量池获取,大大提高系统性能
    • 数据库连接池(提前创建好N个连接对象,将连接对象放入集合中,使用连接对象时,直接从缓存中拿,省去创建的过程)
    • 线程池(Tomcat服务器就是支持多线程的)
      • 线程池就是提前创建好N个线程对象,将线程对象存储到集合中,然后等用户请求过来后,会直接从线程池中获取线程对象,可直接使用
11.5.2 常用方法
(1)获取用户提交数据*
  • 以下四个方法,即可获取url路径中传来的数据,也可获得表单中传来的数据
Map<String,String[]>getParameterMap();//获取Map

Enumeration<String> getParameterNames();//获取Map集合中所有的key

String[] getParameterValues(String name);//通过key获取Map集合对应的value(一维数组)

String getParameter(String name);//获取key对应的value数组的第一个元素,该方法最常用,因为多为一个值

//以上4个方法,与获取用户提交数据有关系
  • 思考:前端的form表单提交数据后,该如何存储?

    • 若前端提交数据格式:username=abc&userpwd=111&aihao=s&aihao=d

      • 表单提交的120数字,其实是以字符串到形式“120”
    • 若采用Map集合进行存储:

      Map<String,String>
      
      key存储String
      value存储String
      
      该想法对吗?不对
      采用以上数据结构存储,会发现key重复时value会被覆盖
      
      Map<String,String[]>
       key存储String
       value存储String[]
       此时value的值就不会被覆盖

若前端传入的数据:username=abc&userpwd=111&aihao=s&aihao=d


变为Map集合形式:

"username" {"abc"}

"userpwd" {"111"}

"aihao" {"s","d"}

①request.getParameterMap()

获取Map集合,包含key和value

返回值类型:Map<String,String[]>

得到:
"username"    {"abc"}

"userpwd"    {"111"}

"aihao"    {"s","d"}
②request.getParameterNames()

获取Map集合中所有的key

返回值类型:Enumeration< String >

得到:
["username","userpwd","aihao"]
③request.getParameterValues(String name)

获取Map集合中key所对的value数组

返回值类型:String[]

request.getParameterValues(aihao)
得到:["a","d"]
④request.getParameter(String name)

获取Map集合中key所对value的第一个值

返回值类型:String

request.getParameter(aihao);
得到:"a"
(2)获取请求域数据*
①request.setAttribute()
  • 向域当中绑定数据
void setAttribute(String name,Object obj);//向域当中绑定数据
②request.getAttribute()
  • 向域当中获取数据
Object getAttribute(String name);//从域当中根据name获取数据
③request.removeAttribute()
  • 移除域中的数据
void removeAttribute(String name);//将域当中绑定的数据移除
(3)获取路径地址
①getRealPath()
  • 获取项目真实路径
request.getRealPath();//已过时,被ServletContext.getRealPath()取代
②getRemoteAddr()
  • 获取客户端IP地址
request.getRemoteAddr();
③getContextPath() *
  • 获取应用根路径(项目名)
request.getContextPath();
⑤getServletPath()
  • 获取Servlet的路径
  • 路径仅Servlet请求转发的路径
    • 注意是完全匹配的部分,*不包括
  • 如:/testServlet
request.getServletPath();
(4)获取请求信息
①getMethod()
  • 获取请求的方式
request.getMethod();//获取是get请求还是post请求
②getURI()
  • 获取请求的URI
  • URI即URL中,从项目名开始之后的路径
  • URI:/项目名/资源名
    • 资源可为动态(Servlet)/静态(html、css)
    • 如:/a/testServlet
request.getURI();//获取URI
(5)获取ServletContext对象
  • 利用this.getServletContext()即可
  • 因为自行编写的XxxServlet继承HttpServlet,而HttpServlet继承GenericServlet,其中有getServletContext()这个方法可以获取。
ServletContext servletContext = this.getServletContext();
(6)获取请求转发器*
①getRequestDispatcher()
  • 获取请求转发器
    • 请求:利用请求转发器的forward()方法
request.getRequestDispatcher("跳转路径")
(7)指定请求体(POST请求)字符集*

1️⃣利用request对象的setCharacterEncoding()

  • 指定请求体的字符集

  • (该方法是POST请求的乱码问题,GET请求不可以哦!)

    • GET请求在请求行上提交数据

      POST请求在请求体上提交数据

2️⃣同时注意:

  • Tomcat10版本之后的服务器,request请求体当中字符集默认为UTF-8,不必在乎POST请求中的乱码问题
  • Tomcat9以及之前版本,若前端请求体提交的是中文,后端获取之后出现乱码,要使用该方法:
request.setCharacterEncoding("UTF-8");
//解决POST请求体乱码问题
/*而GET请求发送时,数据在请求行上提交,乱码问题如何解决?*/
//GET请求未出现乱码问题,即从浏览器页面的路径中输入中文,并未出现乱码问题

3️⃣get请求的乱码问题

get请求发送的数据存储在请求行上,无需设置字符集

  • 从浏览器路径中传入中文参数,响应到后台并不会现乱码问题

  • 【注意】

    • Tomcat7以及之前,< Connector>标签中的URIEncoding属性默认为ISO-8859-Ⅰ,会出现乱码问题

    • 解决方案:

      //修改CATALINA_HOME/conf/server.xml配置文件下:
      <Connector URIEncoding="UTF-8">
      
      //注意:TomCat8以后,Connector的URIEncoding默认为UTF-8
11.6 response对象
11.6.1响应乱码问题

解决响应对象中的乱码问题,只需要利用setContentType,添加charset=UTF-8。

  • 【注意】

    Tomcat10以及10在内的版本,都不会再出现乱码问题,因为默认为UTF-8

reponse.setContentType("text/html;charset=UTF-8");
//Tomcat9以及之前,响应中文也会出现乱码,可利用以上代码消除
//而Tomcat10以及之后,响应中文不再出现乱码,因为响应体默认为UTF-8

十二、Web应用中的资源跳转

  • 一个Web应用中可通过两种方式,完成资源的跳转:

    • 第一种:请求转发
    • 第二种:重定向
  • 转发和重定向的区别?

    • 代码上的区别:

      • 转发:利用request对象

        1️⃣转发的请求路径不用加项目名

        //获取请求转发器对象
        RequestDispatcher dispatcher = request.getRequestDispatcher("另一个servlet的请求路径");
        //调用请求转发器对象的forward方法完成转发
        dispatcher.forward(request,response);
        
        //合并为一行
        request.getRequestDispatcher("转发资源路径").forward(request,response);
        
        ----------------------
        调用forward()方法时,再次传入request,response是为了将其传递给下一个Servlet,使其共享同一个请求域
      • 重定向:利用response对象

        1️⃣重定向的请求路径,要加项目名

        why?response具有响应能力,可将路径重新响应给浏览器,浏览器又自发的向服务器发送一次全新的请求

        • 利用request.getContextPath()获取
        response.sendRedirect("/项目名/重定向资源路径");
        
        ---------------------
        为什么要带项目名?
        response对象会将项目名/b,响应给浏览器,而浏览器会自发的向服务器发送一次全新的请求,请求的路径就是重定向时设置的:项目名/b
    • 形式上的区别:

    • 本质上的区别:

      • 转发:是由WEB服务器来控制的。A资源跳转到B资源,这个跳转动作是Tomcat服务器完成的。
      • 重定向:是由浏览器完成的。具体跳转到哪个资源,由浏览器说了算。
    • 【举个🌰】

      • (转发)我没钱了,找张三借钱,张三没有钱但是讲义气,偷偷向李四借了钱,再给我,而我并不知道钱是李四的,我只求了一个人。
      • (重定向)我没钱了,找张三借钱,张三没有钱并告诉我李四有钱以及他住在哪,我便去找李四借钱,我求了两个人。
  • 转发和重定向如何选择?

    • 如果上一个Servlet中向request域中绑定了数据,希望从下一个Serlvet中将request域中数据取出,使用转发机制。
    • 其余皆使用重定向
      • 当跳转另一个Servlet,但请求方式进行了改变,利用重定向,可以解决因转发导致跳转到另一个Servlet出现请求方式错误的问题。
  • 服务器中合法资源,都可以进行重定向/转发

    • 静态资源(html/css/js)
    • 动态资源(Servlet)
12.1 转发

浏览器发起请求oa项目的ASerlvet,然后转发到BServelt,其中由/a->/b由Tomcat服务器负责调用,然后将BServelt的结果响应到浏览器上。

  • 转发是一次请求,转发后的地址不变,为/a
  • 转发前后资源,共享同一个请求域
    • AServlet和BServlet共享一个request对象、response对象

1682092168208

12.2.1如何转发
  • 利用request对象提供了**getRequestDispatcher()**方法,用于用户获取请求转发器对象

    • 第一步:获取请求转发器对象-->传入参数
      • 该路径为动态资源时,路径为在web.xml中绑定的Servlet路径
      • 该路径也可以是静态资源,路径从web根下开始,不加项目名
    request.getRequestDispatcher("路径");//该路径为要跳转的路径,不带项目名
    • 第二步:调用请求转发器RequestDispatcher的forward()方法
    dispatcher.forward(request,response);
    • 第一步+第二步
    request.getRequestDispatcher("路径").forward(request,response);
12.2.2利用转发共享资源

定义AServlet和BServlet,如果A中想要获取B中请求域绑定的数据,该怎么办?

  • 可以在AServlet中new一个BServlet,然后调用么?
    • 不行,该方法可达到效果,但Servlet对象不能由程序员自己来new,Servlet对象只能由Tomcat服务器启动时来创建,Tomcat服务器关闭时销毁,程序员自行创建的Servlet对象,生命周期不受管理,导致无法回收(销毁),变成非法对象。

①第一步:将资源存储到请求域中

  • 利用request.setAttribute("设置的资源名",资源)
User user = new User();
request.setAttribute("user",user);

②第二步:转发到另一个Servlet中

  • 利用request.getRequestDispatcher().forwar(request,response);
  • 让两者共享一个请求域
request.getRequestDispatcher().forward(request,response);

③第三步:从请求域中取出资源

  • 利用request.getAttribute(“资源名“)
  • 在转发的Serlvet中,就可以取出请求域中存放的资源
User user = request.getAttribute("user");
12.2.3提示请求方式报错
【存在的问题】

从一个Servlet类跳转到另一个Servlet类时,要注意跳转时发出的请求类型是Post,还是Get

若发出的请求类型为Post、而跳转后的Servlet类只重写了doGet()方法,就会报错!

如:InsertServelt类插入数据【post请求】(通过表单提交信息,插入到数据库中)后需要跳转到ListServlet列表页面【get请求】(点击超链接,查询数据库表信息)

  • InsertServelt仅重写doPost()方法

  • ListServelt仅重写doGet()方法

  • 但利用转发器跳转时仍然为post请求,而ListServelt只重写了doGet()请求,就会报错:提示不支持POST

【解决方法】

①第一种:在跳转后的Servlet类中,添加doPost方法,再在doPost()中调用doGet()方法

 @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
     @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //列表信息执行的代码
    }

②第二种:利用重定向

12.2.4刷新页面导致多次执行sql
【存在问题】

当要向数据库插入数据,同时转发跳转到成功页面时,若多次刷新成功页面,会导致发送多次插入的请求,因为转发是一次请求,刷新成功页面,相当于重新发送一次插入数据请求。

1682093784254

【解决方法】

此时就应该要使用重定向!!!

1682093728855

12.2重定向

浏览器发送请求oa项目的AServlet,然后重定向到BServlet,此时服务器会将BServlet的请求路径响应到浏览器中,而浏览器自发以BServlet请求路径再次请求oa项目中的BServlet,最后将BServlet的结果响应到浏览器中。

  • 重定向是两次请求,重定向后地址发生改变
  • 重定向前后资源请求域不同
    • AServlet中使用的request、response对象和BServlet中使用的request、response对象并非同一个,是两份

1682092801727

12.2.1如何重定向
  • 利用response对象的sendRedirect()方法,将重定向的路径传入,路径需带项目名。

    • 该路径为动态资源时,路径为在web.xml中绑定的Servlet路径,加项目名
    • 该路径也可以是静态资源,路径从web根下开始,加项目名
    • 利用request.getContextPath()获取项目名
  • 第一步:调用response对象的sendRedirect()方法进行重定向

    response.sendRedirect(request.getContextPath()+"/b");
  • 为什么路径需要带项目名?

  • 同时重定向后地址栏的地址,为新发送的请求地址

12.2.2利用重定向共享资源
12.3总结

要让两个Servlet共享数据,有以下两种方法

①将数据放到ServletContext应用域中

②将数据放到request请求域中,当A绑定数据后,request.getRequestDispatcher("路径").forward(request,response)请求转发到BServlet中,使两个Servlet的请求一致,以此获取其中的数据。

12.4注意

request中容易混淆以下两个方法:

//1.前端发送来了数据,才执行
String username = request.getParameter("username");//根据key获取value中的第一个值
//2.请求域中已经setAttribute("name",xx)后,才执行
Object obj = request.getAttribute("name");//获取request请求域中绑定的数据

//以上方法的区别是什么?
//第一个:获取用户提交的数据
//第二个:获取请求域中绑定的数据

十三、利用纯Servlet做一个单表的CRUD操作

即使用Servelt完成单表【部门表】的增删改查操作

视频地址

一、前期准备

(1)准备一张数据库表

#部门表
CREATE TABLE dept(
    deptno int PRIMARY KEY,
		dname VARCHAR(255),
		loc VARCHAR(255)
);
#插入数据
insert into dept(deptno,dname,loc)value(10,'销售部','北京');
insert into dept(deptno,dname,loc)value(10,'研发部','上海');
insert into dept(deptno,dname,loc)value(10,'技术部','广州');
insert into dept(deptno,dname,loc)value(10,'媒体部','深圳');

(2)准备一套HTML页面(项目原型)

  • 包含以下页面:

    • 欢迎页面: index.html

    • 新增页面:add.html

    • 修改页面: edit.html

    • 详情页面: detail.html

    • 部门列表页面:list.html(以其为核心,画出其他)

(3)系统功能

  • 该系统包含以下几个功能:
    • 查看部门列表
    • 新增用户
    • 修改用户
    • 删除用户
    • 查看用户详情

(4)搭建开发环境

一个完整webapp项目需要1️⃣配置项目的jdk2️⃣添加Web框架支持3️⃣配置Tomcat服务器4️⃣添加jdbc驱动包5️⃣添加servlet和jsp的jar包

  • 第一步:创建一个webapp

    ①创建Module->java->创建一个普通的java应用

    1681907891205

    1681907790370

    1681907813118

    ②添加JavaEE框架支持

    1681907921941

    1681907965481

  • 第二步:向weapp中添加连接数据库的jar包

    ③在WEB-INF下创建lib目录,存放jar包

    1681908089407

  • 第三步:JDBC的工具类

    在com.oa.utils包下放入DBUtils工具类

    1681908207567

  • 第四步:添加jdbc的配置文件

    ①引入JDBC连接的DBUtils,其中mysql驱动不能写死,因而采用配置文件的方式

    1681908918857

    #jdbc连接驱动
    driver=com.mysql.jdbc.Driver
    #数据库路径
    url=jdbc:mysql://localhost:3306/test
    #用户名和密码dd
    user=root
    password=d17379703772

    ②将配置文件加载到DBUtils工具类中,进行资源属性绑定

     private static ResourceBundle bundle = ResourceBundle.getBundle("resource.jdbc");
        //资源文件绑定对应的属性名
        private static String driver = bundle.getString("driver");
        private static String url = bundle.getString("url");
        private static String user = bundle.getString("user");
        private static String password = bundle.getString("password");

    ③DBUtils工具类完整代码如下:

    /**
     * JDBC的工具类
     */
    public class DBUtils {
        private static ResourceBundle bundle = ResourceBundle.getBundle("resource.jdbc");
        //资源文件绑定对应的属性名
        private static String driver = bundle.getString("driver");
        private static String url = bundle.getString("url");
        private static String user = bundle.getString("user");
        private static String password = bundle.getString("password");
        //注册驱动只需要注册一次,所以放在静态代码块中,DBUtil类加载时执行
       static{
           try {
               //com.mysql.jdbc.Driver时数据库驱动,不能写死
               //因为当连接其他数据库时,还需要修改代码,违反了DCP开闭原则
               //OCP开闭原则:对扩展开放,对修改关闭
               //即功能拓展时,不需要修改java源代码
               Class.forName(driver);
           } catch (ClassNotFoundException e) {
               e.printStackTrace();
           }
       }
    
       public static Connection getConnection() throws SQLException {
           //获取连接
           Connection conn = DriverManager.getConnection(url,user,password);
           return conn;
       }
    }
  • 第五步:为项目添加servlet-api.jar和jsp-api.jar

1682042994764

二、正式流程
2.1查看部门列表(详细流程)

①先将编写的html页面拷贝到web目录下

1682043738430

【编码建议】

从前端->后端/后端->前端皆可,根据程序执行过程编写最佳。

  • 在此以前段->后端为例

②更改前端路径为XxxServle中的映射路径

<!--前端路径需要带项目名-->
<a href="/oa/dept/list">查看部门列表</a>

③设置we.xml中XxxServlet类的映射路径

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">
    <servlet>
        <servlet-name>deptServlet</servlet-name>
        <servlet-class>com.oa.servlet.DeptListServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>deptServlet</servlet-name>
<!--       web.xml中后端映射的servlet路径,以“/”开始,但不带项目名-->
        <url-pattern>/dept/list</url-pattern>
    </servlet-mapping>
</web-app>

④编写DBUtils工具类,因为需要连接数据库,对数据库进行一系列增删改查操作

  • DBUtils工具类中,即JDBC的一系列操作,包含数据库的增加、删除、修改等。

  • 数据库连接驱动 driver

  • 数据库连接路径 url

  • 数据库用户名 user

  • 数据库密码 password

  • 以上四者一般不设置为固定值,而采用编写配置文件,然后在类中加载数据库

    • jdbc.properties配置文件

      #jdbc连接驱动
      driver=com.mysql.jdbc.Driver
      #数据库连接路径
      url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC
      #数据库用户名和密码
      user=root
      password=root
    • DButils工具类加载配置文件

    //加载jdbc配置文件
    private static ResourceBundle bundle = ResourceBundle.getBundle("resource.jdbc");
    private static String dirver = bundle.getString("driver");
    private static String url = bundle.getString("url");
    private static String user = bundle.getString("user");
    private static String password = bundle.getString("password");
  • DButils.java工具类完整代码:

public class DBUtils {
    //资源文件绑定对应的属性名
    // 数据库驱动程序
    private static String driver= "com.mysql.cj.jdbc.Driver";
    // 数据库连接信息
    private static String url = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false";
    // 连接数据库的用户名
    private static String user= "root";
    // 连接数据库的密码
    private static String password = "d17379703772";
    //注册驱动只需要注册一次,所以放在静态代码块中,DBUtil类加载时执行
   static{
       try {
           //com.mysql.jdbc.Driver时数据库驱动,不能写死
           //因为当连接其他数据库时,还需要修改代码,违反了DCP开闭原则
           //OCP开闭原则:对扩展开放,对修改关闭
           //即功能拓展时,不需要修改java源代码
           Class.forName(driver);
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       }
   }

    /**
     * 获取数据库连接对象
     * @return conn 连接对象
     * @throws SQLException
     */

   public static Connection getConnection() throws SQLException {
       //获取连接
       Connection conn = DriverManager.getConnection(url,user,password);
       return conn;
   }

    /**
     * 关闭数据库连接
     * @param conn  数据库连接对象
     * @param ps  执行语句
     * @param rs   返回结果集
     * @throws SQLException
     */
    public  static void close(Connection conn, PreparedStatement ps, ResultSet rs){
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

⑤编写XxxServlet类,继承HttpServlet类,根据请求类型,重写对应的doGet()/doPost()方法

  • 在此是超链接,所以重写doGet()方法
public class DeptListServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //编写Servlet类代码
    }
}

⑥然后就可以在其中进行相关代码编写

  • 1️⃣在DeptListServlet类,doGet方法中连接数据库

  • 2️⃣找到需要动态显示的内容,通过查询数据库得到动态数据

    • 要从数据库中获取数据,需借助JDBC技术
    • 而JavaEE中常利用DBUtils工具类(里面包含一系列JDBC操作:连接数据库连接,关闭数据库连接....)工具类
  • 3️⃣同时在XxxServlet类中输出html标签到浏览器中,将动态显示的部分变为变量

    ❗:要使用response.getWriter()获得的out对象,才可以输出到浏览器中,否则只会在控制台输出

    //防止中文乱码
    response.setContentType("text/html;charset=UTF-8");
    //输出到浏览器
    PrintWriter out = response.getWriter();
    //输出内容语句
    out.print("<!DOCTYPE html>");
  • 后台输出的html标签的属性,双引号(“ ”)->单引号(‘ ’)

  • 此时没有使用jsp技术,只能通过后台输出html代码的形式

  • DeptListServlet类代码如下:

public class DeptListServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置响应内容以及字符集,防止中文乱码
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter out = resp.getWriter();
        //获取项目路径
        String contextPath = req.getContextPath();
        //连接数据库,查询所有部门
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //获取数据库驱动
            conn = DBUtils.getConnection();
            //获取预编译的数据库操作对象
            String sql = "select deptno,dname,loc from dept";
            //编译sql语句
            ps = conn.prepareStatement(sql);
            //执行SQL语句,返回结果集
            rs = ps.executeQuery();
            //遍历结果集
            while( rs.next()){
                int i =0;
                //获取
                //列名按照执行sql语句后获得的表列名而定
                String deptno = rs.getString("deptno");
                String dname = rs.getString("dname");
                String loc = rs.getString("loc");
                    out.println("<tr>");
                    out.println("<td>"+(++i)+"</td>");
                    out.println("<td>"+deptno+"</td>");
                    out.println("<td>"+dname+"</td>");
                    out.println("<td>");
        out.println("<a href='"+contextPath+"/dept/delete?deptno="+deptno+"'>刪除</a>");
        out.println("<a href='"+contextPath+"/dept/edit?deptno="+deptno+"'>修改</a>");
        out.println("<a href='"+contextPath+"/dept/detail?deptno="+deptno+"'>详情</a>");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            //关闭数据库连接,释放资源
            DBUtils.close(conn,ps,rs);
        }
    }
}
2.2增加部门
2.3修改部门
2.4删除部门
  • 在超链接中,href="javascript:void(0)",表示设置用户可点击超链接,但并不跳转。

  • 同时删除时,利用js代码进行相应的删除弹框提示

    • 从前端向后端发请求有以下四种写法

      document.location.href="请求路径";
      document.location = "请求路径";
      window.location.href="请求路径";
      window.location = "请求路径";
<!--提示弹框-->
<a href="javascript:void(0)" οnclick="del()" ></a>
<script>
  function del(id){
    var ok = window.confirm("是否确认删除");
    if(ok){//点击确认删除,将对应部门id传给后端,进行删除
      window.location.href="请求路径"+id;
    }
  }
</script>
2.5查看详情

思路如下:

//第一步:获取部门编号
//第二步:根据部门编号查询数据库,获取该部门编号的部门信息
//第三步:将数据响应到浏览器上
/*编写每一步,建议同步进行测试,即利用System.out.println(打印变量值)*/
三、总结
3.1工程结构

以上可知,仅使用Servlet进行页面开发时,核心工程结构如下:

  • 此时要展示数据库读取的动态数据,只能利用继承的HttpServlet类
    • response.getWriter()生成的out对象,输出html标签到浏览器上显示
-项目名
  -src
    -com
       -公司名/团队名
       -servlet:存放servlet类代码
       -utils:存放工具类,如:DBUtils数据库连接工具类
    -resources:存放自行编写的配置文件
  -web
    -WEB-INF
      -lib:存放项目所需的jar包
      web.xml文件:Tomcat配置文件,常用于设置Servlet类与请求路径的映射关系
    html文件/CSS文件等静态资源

1682063157696

1682062955107

3.2获取项目路径

因为前端的< a >超链接中路径,以“/”开头,需添加项目名

  • 因而就可以在Servlet类中需要通过request.getContextPath去获取当前项目路径
String contextPath = request.getContextPath();
3.3获取路径中传来的参数

javaEE中可采取以下传递数据

  • 表单提交
  • 路径后利用 ? key=value&key=value

两者都可以用request对象的getParameter("属性名")来获得:

❗:路径中获取的参数,都是字符串类型

String name = request.getParameter("name");
  • 利用纯粹的Servlet代码进行开发时,发现许多的前端代码,都写在了后端里,同时只能使用字符串拼接的方法输出变量,因而就引出了jsp技术,帮助程序员将后端请求输出在前端页面中
3.4跳转到另一个请求

利用request对象的getRequestDispatcher("资源路径").forward();

  • 资源路径:
    • 以web的根开始向下找
    • servlet类按web.xml中映射的路径为主
request.getRequestDispatcher("资源路径").forward(request,response);//进行转发

【存在的问题】

从一个Servlet类跳转到另一个Servlet类时,要注意跳转时发出的请求类型是Post,还是Get

若发出的请求类型为Post、而跳转后的Servlet类只重写了doGet()方法,就会报错!

如:InsertServelt类插入数据【post请求】(通过表单提交信息,插入到数据库中)后需要跳转到ListServlet列表页面【get请求】(点击超链接,查询数据库表信息)

  • InsertServelt仅重写doPost()方法

  • ListServelt仅重写doGet()方法

  • 但利用请求转发器跳转时仍然为post请求,因为转发是一次请求,之前是post之后也会是post请求,而ListServelt只重写了doGet()请求,就会报错:提示不支持POST

    1682077209719

【解决方法】

在跳转后的Servlet类中,添加doPost方法,再在doPost()中调用doGet()方法

 @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
     @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //列表信息执行的代码
    }
3.5获取数据库中查询的字段

纯servlet中利用DBUtils工具类来连接数据库,其中执行Sql语句,利用rs.getString()得到数据库中的字段值

【注意】:利用rs.getString("xxx"),其中的xxx不一定是数据库的字段名,取决于执行Sql语句后,得到的结果表。

1682078892806

十四、JavaBean

①javabean常放在项目src目录下的bean包中

1682080866712

14.1JavaBean的概念
  • java是咖啡,bean是豆子,javabean表示咖啡豆
  • 寓意:咖啡是由咖啡豆研磨而成,即Java程序是由一个个JavaBean组成的
  • 一个JavaBean一般有指定规范:
14.2JavaBean规范

一个JavaBean由如下规范:

①有无参数的构造方法

②属性私有化

③对外提供setter和getter方法

④重写toString()

⑤重写hashCode+ equals

⑥实现java.io.Serializable接口

  • 范例:

    package com.oa.servlet;
    
    import java.io.Serializable;
    
    public class User implements Serializable {
        private String id;
        private String name;
        public User() {
    
        }
    
        @Override
        public int hashCode() {
            return super.hashCode();
        }
    
        @Override
        public boolean equals(Object obj) {
            return super.equals(obj);
        }
    
        @Override
        public String toString() {
            return super.toString();
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    }
    

十五、Servlet注解式开发

15.1存在问题

当编写一个Servlet类后,都需要在web.xml中进行配置,而且web.xml中的配置很少被修改。

15.2解决方案

因而我们可以采取注解的形式替代web.xml中< servlet>标签的形式。

  • Servlet3.0版本后,推出了各种Servlvet基于注解式开发
15.3优点
  • 开发效率高,不需要编写大量配置信息
    • 直接在java类中使用注解进行标注
  • web.xml文件体积更小
    • 注意:并非采用注解开发后,web.xml不再使用,常变化的信息,依旧配置在web.xml中
    • 不常变化->建议使用注解
    • 常变化->建议使用配置文件
    • 常用都为 注解+配置文件的开发模式
15.4@WebServlet注解
  • 注解类型:类注解

  • 注解作用:用于设置当前Servlet类的映射路径等属性

  • 注解属性:

    ①**name属性:**用于指定Servlet的名字。

    • 等同于

    ②**value属性:**用于指定Servelt的映射路径

    • 类型:String[]

    • 等同于urlPatterns属性

    • 不同:

    • 1️⃣当数组中只有一个元素,{}可省略

    • 2️⃣属性名为value可省略

      @WebServlet("/welcome")

    urlPatterns属性:用于指定Servlet的映射路径。

    • 类型:String[]

    • 可指定多个字符串 {" "," "," "}

    • 等同于

      @WebServlet(urlPatterns={"/welcome"})

    ④**loadOnStartUp属性:**用来指定服务器启动阶段是否加载该Servlet

    • 类型:int

    • 等同于

    • 1表示加载,0表示不加载

    ⑤**initParams属性:**用来指定初始化参数

    • 类型: WebInitParam[](注解)

    • {@WebInitParam(name="username",value="root"),(...)}
    • 可通过getIntiParameterNames()拿到所有name

    • 通过遍历+以下,获得所有初始化参数值

    • nextElement()拿到name值

    • getInitParameter(name)拿到name对应的value值

    1682230475935

  • 注意:

    不用将所有属性都写上,需要什么写什么

    属性是一个数组,数组只有一个元素时,大括号可以省略

  • 【各注解使用】:

1682229969085

15.5注解对象的使用格式

【格式】@注解名称(属性名=属性值,属性名=属性值...)

十六、获取一个类是否有注解

  • 通过以下代码,可以获取一个类是否存在注解,同时也可以取到该注解对象的各属性值:
public static void main(String[] args) throws Exception{
  //获取类Class对象
  Class<?> welcomeServletClass = Class.forName("Servlet类所在目录");
  
  //获取类上的注解对象
  if(WelcomeServletClass.isAnnotationPresent(WebServlet.class)) {
  //获取该类上的注解对象
  WebServlet annotation = welcomeServletClass.getAnnotatiton(WebServlet.class);
  //获取注解中的value属性值
  String[] value = webServletAnnotation.value();
  for(int i =; i<value.length;i++){
   System.out.println(value[i]); 
  }
  }
}

十七、使用模板方法设计模式

原先的oa系统,利用Servlet注解式开发后,解决了配置文件繁琐的问题,但oa系统仍存在以下问题:

17.1存在问题

【存在问题】

  • 一个单表的增删改查,就需要编写五个Servlet类

    1682235212800

17.2解决方法

【解决方法】

使用模板方法设计模式

  • 以前:一个请求一个Servelt类。1000个请求对应1000个Servlet类

    ①一个请求一个Servelt类
    dept/list->DeptList
    dept/edit->DeptEdit
  • 如今:一个业务对应一个Servlet,一个请求对应一个方法

    ①业务相同合并一个Serlvet类
    @WebServlet("/xx/*")
    ②不同请求后缀对应对应一个方法
    request.getServletPath();//只获取路径
    equals方法//匹配调用不同方法

    ①第一步:在@WebServlet中采取模糊匹配

    //@WebServlet({"/dept/list","/dept/save","/dept/edit","/dept/modify","/dept/delete"})
    @WebServlet("/dept/*")//表示以dept开头的请求路径

    ②第二步:重写service()方法

    • 获取Servlet路径,让不同路径匹配不同方法
        @Override
        protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            String servletPath = request.getServletPath();
            if("/dept/list".equals(servletPath)){
                doList(request,response);
            }else if("/dept/save".equals(servletPath)){
                doSave(request,response);
            }else if("/dept/edit".equals(servletPath)){
                doEdit(request,response);
            }else if("/dept/modify".equals(servletPath)){
                doModify(request,response);
            }else if("/dept/delete".equals(servletPath)){
                doDelete(request,response);
            }
        }

    ③第三步:编写各请求的不同方法

    • 然后在编写的方法中编写对应代码即可
        //编写doList方法
        private void doList(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
        }
        //编写doSave方法
        private void doSave(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
        }
        //编写doList方法
        private void doEdit(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
        }
        //编写doModift方法
        private void doModify(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
        }
17.3【只能重写service方法吗?】

在业务类中重写doGet、doPost方法也可以,但必须在doGet中调用doPost/doPost中调doGet,因为不知道发来的请求是何类型。

①写法一:

在doGet里面调用doPost/doGet里面调用doPost

@WebServlet("/dept/*")//表示以dept开头的请求路径
public class DeptServlet extends HttpServlet { 
@Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String servletPath = request.getServletPath();
        if("/dept/list".equals(servletPath)){
            doList(request,response);
        }else if("/dept/save".equals(servletPath)){
            doSave(request,response);
        }else if("/dept/edit".equals(servletPath)){
            doEdit(request,response);
        }else if("/dept/modify".equals(servletPath)){
            doModify(request,response);
        }else if("/dept/delete".equals(servletPath)){
            doDelete(request,response);
        }
    }
}

②写法二:

直接重写service方法

@WebServlet("/dept/*")//表示以dept开头的请求路径
public class DeptServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String servletPath = request.getServletPath();
        if("/dept/list".equals(servletPath)){
            doList(request,response);
        }else if("/dept/save".equals(servletPath)){
            doSave(request,response);
        }else if("/dept/edit".equals(servletPath)){
            doEdit(request,response);
        }else if("/dept/modify".equals(servletPath)){
            doModify(request,response);
        }else if("/dept/delete".equals(servletPath)){
            doDelete(request,response);
        }
    }
}
17.2 什么是模版方法设计模式?

①模板类:【抽象类】

将固定的步骤和方法,写在模板类中

②模版方法:【抽象方法】

模板类中的方法

  • 将不确定具体实现的方法,定义为抽象方法,由子类决定怎么实现

③什么是模版方法设计模式?

  • 定义核心的算法骨架,具体的实现步骤可以延迟到子类中。
  • 使得核心算法被保护的同时,不被改变,同时也能重复使用

【如】:学生和老师一天做的事类似,但其中doSome方法不同,因而就可以将其他重复的方法,放在模版类中,而不确定如何实现的方法设为模版方法,让子类继承模板类,再去重写模版方法的具体实现是怎样。

  • 模板类Person(抽象类):
public abstract class Person{
   //定义为final,子类无法重写
   public final void day(){
     chiZaoCan();
     doSome();
     chiWuCan();
     chiWanCan();
   }
   //固定的步骤,可以写在模板类中,子类继承实现复用
   public void chiZaoCan(){
    System.out.println("吃早餐");
   }
   //设为抽象方法,由子类去实现
   public abstract void doSome();
   
   public void chiWuCan(){
    System.out.println("吃午餐");
   }
   
   public void chiWanCan(){
   System.out.println("吃晚餐");
   }
   
}
  • 子类-学生
public class Student extend Person{
   public void doSome(){
   System.out.println("去上学");
   }
}
  • 子类-老师
public class Teacher extend Person{
   public void doSome(){
     System.out.println("去教课");
   }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邓六日

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值