JavaSE
javaSE包括:
- java环境搭建
- java基础语法
- 数组
- 常用类
- 异常
- 集合
- 多线程
- IO流
- 反射机制
- 注解Ananotation
- ....
MySQL数据库
JDBC(java语言链接数据库)
WEB后端
- Servlet
- JSP
- AJAX
- jQuery
- MyBatis
- Spring
- SpringMVC
- SpringBoot
- SpringCloud
- ...
JavaEE
一、关于系统架构
-
系统架构有哪几种?
- C/S架构
- B/S架构
-
C/S架构?
-
Client / Server(客户端/服务端)
-
C/S架构的软件或系统有哪些?
- QQ(此类需要安装到本机的软件)
-
优点:
- 速度快
- 软件中的数据大部分集成在客户端软件中,仅少量需要服务端传送
- 体验好
- 界面炫酷
- 使用专门的语言去实现界面,更灵活,不想网页端仅仅是html css js
- 服务器压力小
- 因为大量数据存于客户端软件,少量才需要从服务器传输而来
- 安全
- 大部分数据在客户端上都有缓存,即便服务端出故障,数据也不易丢失
- 速度快
-
缺点:
-
升级维护麻烦,成本高
每一个客户端都需要升级,同时部分软件安装较为麻烦
-
-
-
B/S架构
-
B/S(Brower / Server,浏览器/服务器)
-
B/S架构的有哪些?
-
百度网页版、淘宝网页版都是
(此类无需安装,利用网址访问即可的)
-
-
B/S架构系统是不是一个特殊的C/S架构?
- B/S本质还是一个C/S架构,不过C变成了一个固定不变的浏览器软件
-
优点:
-
升级维护方便,成本较低
(只需升级服务器端即可)
-
用户访问方便
(只需打开网址访问即可)
-
-
缺点
- 速度慢
- 因为大量数据都存于服务器上,用户的请求都需要由服务器响应数据,在网络中传输的数据量也很大
- 体验差
- 界面相对不酷炫,只支持html/css/js三种语言
- 不安全
- 数据都在服务器上,若服务器出故障,大量数据都会丢失
- 速度慢
-
-
两者,哪个好?
- 在不同的使用场景下,每个架构都有各自的优点
- 如:娱乐性的软件-->C/S架构
- 界面酷炫,体验感好
- 公司内部企业级业务软件-->B/S架构
- 维护成本低
- 如:娱乐性的软件-->C/S架构
- 在不同的使用场景下,每个架构都有各自的优点
-
❗:开发B/S架构,其实就是开发网站,开发一个WEB
- 开发WEB系统需要哪些技术?
- WEB前端
- HTML
- CSS
- JavaScript
- WEB后端
- Java
- JavaWEB核心规范:Servlet【Servlet Applet服务器端的Java小程序】
- C也可
- C++也可
- Python也行
- PHP也可
- .....
- Java
- WEB前端
- 开发WEB系统需要哪些技术?
1.1 B/S结构系统通信原理
-
WEB系统的访问过程
- 第一步:打开浏览器
- 第二步:找到地址栏
- 第三步:输入合法的url地址
- 第四步:回车
- 第五步:在浏览器上展示相应的结果
-
域名
- http://www.baidu.com/(网址)
- www.baidu.com(域名)
- 在地址栏输入域名敲下回车,域名解析器会将域名转化为一个具体ip地址、端口号等信息
- 解析结果类似:http://110.245.68.10/index.html
-
ip地址是?==服务器位置
- 类似于每个计算机的身份证,同一网段中,IP地址唯一
- 两个计算机要通信,需要知道IP地址,才可以通信
-
端口号是?==软件位置
- 一个端口代表一个应用、一个服务
- 每个软件启动后都有一个端口号
- 同一计算机中,端口号唯一
-
资源路径是?==访问页面
- 通过资源路径确定,从服务器对应的软件中访问了哪个资源
- 如index.html、login.html
-
WEB系统通信原理
- 第一步:用户输入网址(URL:http://www.baidu.com)
- URL统一资源定位符
- 第二步:域名解析器进行域名解析
- 将域名->ip地址
- http://110.242.63.3:80/index.html
- 第三步:浏览器软件在网络中搜索该IP地址(110.242.63.3)的主机,直至找到
- 第四步:根据端口号,定位该IP地址主机80端口上的软件
- 第五步:80端口对应的服务器软件由资源路径得知想要的资源名:(index.html)
- 第六步:服务器软件找到该资源(index.html),并将其内容输出相应到浏览器上
- 第七步:浏览器接收服务器的HTML、CSS、JS代码
- 第八步:浏览器执行HTML、CSS、JS,渲染显示
- 第一步:用户输入网址(URL:http://www.baidu.com)
通过ip找到对应的服务器位置,端口号确定服务器软件,最后资源路径获取要访问的资源,将其从服务器获取到页面中,页面再对html/css/js进行解析,最后渲染出页面效果。
- 什么是请求,什么是响应?
- 请求和响应实际上是数据的流向不同
- 从Browser端发送数据到Servlet端,叫请求 (request)
- 从Server端发送数据到Browser端,叫响应 (repsonse)
- B-->S (请求)
- S -->B (响应)
二、JavaEE是什么?
- Java包括三大块:
- JavaSE
- Java标准版,是一套他人编写好的类库,但该类库为标准类库,无论学习EE、ME,都是以此为基础。
- JavaEE(WEB方向、WEB系统)
- Java企业级,也是一套他人写好的类库,专门用于企业内部提供一套/多套解决方案的类库。
- 如:淘宝、京东此类网站...
- JavaME
- Java微型版,同样是一套类库,专门用于解决电子微型设备内核程序的开发
- 如:机顶盒内核、吸尘器内核程序、电冰箱内核程序等....
- JavaSE
- JavaEE有13种规范,其中Servlet是规范之一。
三、WEB服务器软件
-
WEB服务器软件有?(他人开发好的)
- Tomcat (WEB服务器)
- jetty (WEB服务器)
- Jboss (应用服务器)
- WebLogic (应用服务器)
-
应用服务器和WEB服务器的关系?
- 应用服务器实现了javaEE的所有规范
- jvaEE有13种不同规范
- WEB服务器只实现了JAVAEE中Servlet + JSP 两个核心规范
- 即应用服务器包含WEB服务器
- 应用服务器实现了javaEE的所有规范
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下载
②选择Dowload下需要的版本
③选择Window版的,zip进行下载
④将压缩包自行放在D盘的一个文件夹下
- 在此我习惯放D盘,因为C盘太容易满
- 同时与java开发相关的工具放在同一文件夹下,方便管理
⑤Tomcat绿色版解压缩即安装
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方法
-
2️⃣但startup.bat启动等同于执行catalina.bat,需先配置CATALINA_HOME环境变量
-
使其能找到catalina.bat
-
否则运行startup.bat会出现如下报错:
配置CATALINA_HOME环境变量
①编辑环境变量->新建系统变量
- 变量名:CATALINA_HOME
- 变量值:Tomcat的根(tomcat在你本机上解压缩的位置)
3️⃣同时catalina.bat中还需要获取 JAVA_HOME
- 即告知Tomcat的jdk在何位置
- 否则运行startup.bat会出现如下报错:
配置JAVA_HOME环境变量
②再次编辑环境变量->新建系统变量
- 变量名:JAVA_HOME
- 变量值:JDK的根(jdk解压缩后在本机中存放的位置)
配置PATH环境变量
PATH=%JAVA_HOME%\bin;
%CATALINA_HOME%\bin;
4️⃣完成两者配置后,切换至tomcat中startup.bat存放的bin目录下,在此调起cmd
同时当JAVA_HOME和CATALINE_HOME都在系统变量中配置完成后
在系统环境变量的Path中,可以将原先本机路径的根--->环境变量
将如下:
变为如下形式:
执行startup.bat
出现Tomcat的执行窗口,Tomcat被启动
- 乱码暂时不用管
关闭startup.bat
最好不要直接关闭弹框,通过cmd,执行目录下的shutdown.bat文件关闭
- 运行shutdown.bat文件:shutdown.bat
- 注意shutdown是关机命令,如果怕误敲,可以直接将shutdown文件更名为stop文件
- 之后直接输入stop就可以关闭Tomcat
测试Tomcat是否启动成功
打开浏览器,在浏览器的地址栏上输入URL:
- http://ip地址:端口号
- 本机ip地址 127.0.0.1 或 localhost
- 端口号8080
- http://127.0.0.1:8080
出现该页面则证明,Tomcat真正启动成功!!
四、编写第一个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地址
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应用的参与角色,协议
- 有哪些角色(整个BS架构参与的人)
- 浏览器软件的开发团队
- 谷歌浏览器、火狐浏览器....
- WEB Server的开发团队
- Tomcat、Jetty....
- DB Server的开发团队
- Oracle、MySQL.....
- webapp的开发团队
- javaWEB程序员开发应用
- 浏览器软件的开发团队
- 角色和角色之间需要遵守哪些规范、哪些协议
- webapp的团队 和 WEB Server的开发团队有一套规范:Servlet规范
- 其中之一是Servlet
- Servlet规范作用是什么?
- WEB Server 和 webapp解耦合
- 其中之一是Servlet
- Brower 和 WebServer的传输协议:HTTP协议
- HTTP协议:超文本传输协议
- webapp的开发团队 和 DB Server的开发团队有一条规范:JDBC规范
- webapp的团队 和 WEB Server的开发团队有一套规范:Servlet规范
六、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开发只需要:
- 编写类实现XxxServlet接口
- 编写.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文件,打开
Servlet
一、Servlet是什么?
- Servlet并非一个简单的接口
- Servlet规范中规定了:
- 一个合格的webapp应该是怎样的目录结构
- 一个合格的webapp应该有一个怎样的配置文件
- 一个合格的webapp配置文件路径放在哪里
- 一个合格的webapp配置文件有哪些内容
- 一个合格的webapp中java程序放在哪里
- 以上都是Servlet规范中规定的
- 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>
-
基础目录如下:
-
**第六步:**编写一个实现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文件
-
同时注意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目录外面
-
-
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类
-
编写的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
修改工程名和路径,一般保持一致
- 此处的javaweb是随便取的
提示是否创建一个新目录,create即可
-
第二步:新建模块(File->new-->Module)
- 创建一个普通的javaSE模块
- 为模块命名,建议Module name的名称与下方都一致
- 创建后的Module会自动放在新建的空工程中
-
第三步:让该模块变为JavaEE的模块
-
右键模块->Add Framework Support 添加框架支持
- 让Module变为符合webapp规范的模块
-
选择JavaEE下的Web Application->Ok
-
添加完框架支持后,IDEA会自动生成一个符合Servlet规范的webapp目录结构
【重点】:IDEA中根据Web Application模版生成的目录有一个web目录,代表webapp的根
-
-
-
第四步:引入Servlet所需的jar包
-
因为此时创建的是一个普通的javaSE工程
-
只有jdk,而Servlet并不在jdk中,而在Tomcat中
-
找到Tomcat/lib目录中servlet-api.jar和jsp-api.jar ,添加到类路径( IDEA的classpath )中
-
点击File->Project Structure查看工程结构
-
点击Modules->选择工程servlet01->+号->JARs or Directories添加jar包
- 添加library库也可以,但会将Tomcat中所有都添加
- 添加jar更精准
-
找到本地Tomcat解压位置的lib目录下
选中jsp-api.jar和servlet-api.jar,点击ok应用
-
选中两个添加的jar,apply即可
-
最后看到工程中External Libraies下显示jsp-api.jar和servlet-api.jar即可
-
-
-
第五步:编写XxxServlet类
-
该XxxServlet类需要实现Servlet接口
public class XxxServlet implements Servlet{ }
-
如何快速继承Servlet的方法
光标放在Servlet上,alt+enter后,点击Implement methods
全选后,点击ok即可
-
-
第六步:在XxxServlet类中,实现Servlet接口的五个方法
- 仅在service()方法中编写业务代码
- 日常使用,多把servletRequest和servletResponse两个参数改为request和response
- 仅在service()方法中编写业务代码
@Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { }
-
第七步:连接数据库
-
在工程WEB-INF目录下新建lib目录,将数据库驱动的jar包放入
-
连接数据库后显示的数据是动态的
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
-
点击+,找到Tomcat Server->local
-
点击Server ,进入配置页面 【基本不用修改,默认即可】
-
1️⃣Name:服务器名称
- 配置页面设置的name 和 左侧的服务名对应,可任取
-
2️⃣Application server ->Configure 选择本机Tomcat所在位置
-
3️⃣Open brower:设置浏览器打开
- After launch 勾选即Tomcat运行自行打开xxx浏览器
- URL:为访问地址,localhost=172.0.0.1:表示本机
-
4️⃣VM opeions:指定虚拟机参数
- 5️⃣On 'Update' action :代码改动时,默认为重启服务器
- 6️⃣JRE:使用的jdk版本
- 7️⃣Tomcat Server Settings :服务器配置(端口号....)
-
-
点击Deployment,部署web应用
- +号,选择Aritifact..
-
修改Application context: 项目名
- ❗项目名要和html页面超链接href处的项目名一致
-
最后ok,完成所有部署
-
-
第十一步:启动Tomcat服务器
-
点击右上角的绿色箭头或绿色小虫子
- 绿色箭头,直接启动
- 绿色小虫子,采用debug模式启动服务器
-
若出现以下报错,说明IDEA的jdk版本过低,点击File->Project Structure->SDKs->选择更高版本的jdk即可
【在此我是Tomcat10,所以用了最新的jdk17,报错就完美解决了!!】
类文件具有错误的版本 55.0, 应为 52.0 请删除该文件或确保该文件位于正确的类路径子目录中。
IDEA出现Tomcat状态栏,即Tomcat启动成功
-
第十二步:打开浏览器,在地址栏输入url:http://localhost:8080/项目名/index.html
-
localhost与172.0.0.1一样,是本机地址
点击图书信息超链接
Tomcat会根据超链接的路径发起请求,先去web.xml文件中,找到对应的Servlet类名和全限定包名,在工程中找到该类,运行后,向数据库读取数据,显示在页面
- 第一步:New Project 新建空工程
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
-
【改进一】:使用适配器模式
(问题)原先一个所有类都继承同一个接口,但其中常用的方法只有一个,其他方法都不使用,其他类要重写方法,会将所有方法都重写过来
(解决)定义适配器,将其中常用的方法定义为abstract抽象方法,同时适配器类为抽象类,继承接口
-
【适配器】
抽象类,实现有许多方法浪费的接口
常用的方法,设为抽象方法
-
其他类 extends 适配器抽象类,即可使用
再让类去继承适配器类即可,不必再都继承于同一个接口,而是各自重写使用频率最多的方法
-
6.1 利用适配器模式改造Servlet
-
第一步:编写一个标准通用的Servlet,起名:GenericServlet
- 让之后所有的类都继承GenericServlet,而不直接实现Servlet接口
-
第二步:将GenericSerlvet中的Service变为抽象方法,让类改为abstract抽象类
-
第三步:其他的XxxServlet直接继承GenericServlet
- 而不再实现Servlet
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
-
【解决二】:与此同时,利用方法重写再定义一个init()方法,同时在初始的init()方法中调用它
- 由此让子类可重写该无参的init()方法
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
如今写法:继承GenericServlet
七、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对象,然后打印输出
- 【结论】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>标签的值
②public Enumeration< String> getInitParameterNames()方法
---获取初始化参数的名字 ,获取< param-name></ param-name>标签的值
以上两方法可获取web.xml中的初始化参数配置信息
- 在web.xml配置init-param的配置信息如下👇:
③public ServletContext getServletContext()方法
④public String getServletName()方法
--获取web.xml文件中< servlet-name> </ servlet-name>标签的内容
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接口
-
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值【局部配置】
- 如下:该配置一般用于放需要共享的配置信息
②获取应用上下文的根
- 根路径,即请求时的项目名
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所对的目录
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://localhost:8080/servlet05/index.html 是URL
- /servlet05/index.html 是URI
- 第三部分:HTTP协议版本号
- 第一部分:请求方式(7种)
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,通过该面板可查看协议具体内容
-
其中标头含有 请求报文 和 响应报文 的信息。
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协议下的开发
- 它是一个模版类
- 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()
-
-
③执行有参的init(),同时执行无参的init
- 便于子类重写
-
④执行service()方法
-
XxxSerlvet中没有service()方法,会调用HttpServlet中最原始的Service()方法
- 不可能调用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请求发送的数据存储在请求行上,无需设置字符集
-
从浏览器路径中传入中文参数,响应到后台并不会现乱码问题
- 因为Tomcat8以及之后,其中的< Connector>标签中的URIEncoding属性(统一资源标识符的编码方式),默认字符集为UTF-8
- URL: http://172.0.0.1:80/项目名/资源路径?username=张三
- URI:/项目名/资源路径?username=张三
-
【注意】
-
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
-
-
形式上的区别:
-
转发(一次请求)
-
转发后地址栏仍为转发前的地址路径
-
-
重定向(两次请求)
-
重定向后地址栏为重定向后的地址路径(因为发送了一次新请求)
重定向前为http://localhost:8080/servlet/a,重定向后为http://localhost:8080/servlet/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对象
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
【存在问题】
当要向数据库插入数据,同时转发跳转到成功页面时,若多次刷新成功页面,会导致发送多次插入的请求,因为转发是一次请求,刷新成功页面,相当于重新发送一次插入数据请求。
【解决方法】
此时就应该要使用重定向!!!
12.2重定向
浏览器发送请求oa项目的AServlet,然后重定向到BServlet,此时服务器会将BServlet的请求路径响应到浏览器中,而浏览器自发以BServlet请求路径再次请求oa项目中的BServlet,最后将BServlet的结果响应到浏览器中。
- 重定向是两次请求,重定向后地址发生改变
- 重定向前后资源请求域不同
- AServlet中使用的request、response对象和BServlet中使用的request、response对象并非同一个,是两份
12.2.1如何重定向
-
利用response对象的sendRedirect()方法,将重定向的路径传入,路径需带项目名。
- 该路径为动态资源时,路径为在web.xml中绑定的Servlet路径,加项目名
- 该路径也可以是静态资源,路径从web根下开始,加项目名
- 利用request.getContextPath()获取项目名
-
第一步:调用response对象的sendRedirect()方法进行重定向
response.sendRedirect(request.getContextPath()+"/b");
-
为什么路径需要带项目名?
- 因为重定向时,response对象会将路径响应给浏览器,浏览器会自发的向服务器发送一次全新的请求
- 重定向前,发送第一次请求:http://localhost:8080/servlet/a
- 重定向后,又发送一次请求:http://localhost:8080/servlet/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应用
②添加JavaEE框架支持
-
第二步:向weapp中添加连接数据库的jar包
③在WEB-INF下创建lib目录,存放jar包
-
第三步:JDBC的工具类
在com.oa.utils包下放入DBUtils工具类
-
第四步:添加jdbc的配置文件
①引入JDBC连接的DBUtils,其中mysql驱动不能写死,因而采用配置文件的方式
#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
二、正式流程
2.1查看部门列表(详细流程)
①先将编写的html页面拷贝到web目录下
【编码建议】
从前端->后端/后端->前端皆可,根据程序执行过程编写最佳。
- 在此以前段->后端为例
②更改前端路径为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文件等静态资源
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
【解决方法】
在跳转后的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语句后,得到的结果表。
十四、JavaBean
①javabean常放在项目src目录下的bean包中
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值
-
注意:
不用将所有属性都写上,需要什么写什么
属性是一个数组,数组只有一个元素时,大括号可以省略
-
【各注解使用】:
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类
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("去教课"); } }