Servlet
webapp:[服务器端的java小程序]
1.关于系统架构
(1)系统架构包括什么形式
- C/S形式
- B/S形式
(2)C/S架构
-
Client / Server (客户端 / 服务器)
-
CS架构的软件或者系统有哪些呢?
-
QQ (先去腾讯官网下载一个QQ软件,然后把客户端软件安装上去,
然后输入QQ号以及密码,登录之后就可以使用
-
很多数据集成到客户端里边了
-
-
C/S架构的特点:需要安装特定的客户端软件
-
C/S架构的系统:
- 优点:
- 速度快 (软件中的数据大部分是集成在客户端软件中)
- 体验好 (速度快,界面炫酷,体验就好了)
- 服务器压力小 (例如游戏客户端: 大部分数据在本地,3D效果等,当然服务器压力小)
- 安全 (应为大量数据集成在客户端软件当中,即使服务器端出现致命问题,对数据影响也不大,因为大量数据在本地有缓存)
- 界面炫酷 (专门的语言去实现界面的,更加灵活)
- 缺点:
- 升级维护比较差劲:(升级维护比较麻烦,每一个客户端软件都需要升级,有些软件不是那么容易安装的(比如说银行系统,安全系数高,只能运维人员去升级,成本高))
- 优点:
(3)B/S架构
-
B / S ( Browser / Server ,浏览器/服务器)
-
B / S 结构的系统是不是一个特殊的C/S系统?
- 实际上BS结构的系统还是一个C/S,只不过这个C比较特殊,这个Client是一个固定不变的浏览器软件
-
B / S 结构的系统优点和缺点是
- 优点:
- 升级维护方便,成本比较低 (只需要升级服务器端就可以了)
- 不需要安装特定的客户端软件,用户操作及其简单,用户只用输入网站就可以使用
- 缺点:
- 速度慢: (不是因为带宽低的问题,是因为所以的数据都是在服务器,用户发送的每一个请求都是需要服务器响应数据 ,所以B/S结构的系统在网络中传递的数据量比较大)
- 体验差: (界面不是那么炫酷 , 因为浏览器只支持三种语言 HTML, CSS ,JavaScript,再加上速度慢)
- 不安全: (所有数据都在服务器上,只要服务器出现问题所有数据都会丢失)
- 优点:
(4)C/S和B/S结构使用场景:
- 在不同的场景下有不同的优缺点
- 娱乐性软件:
- 建议使用C/S
- 公司内部使用的系统,
- 公司内部的系统,需要维护成本低
- 公司内部不需要华丽的界面
- 公司内部的系统主要是进行数据的维护即可
- B/S结构
- 娱乐性软件:
(5)开发技术
开发B/S结构的系统,其实就是开发网站,其实就是开发一个WEB系统
- 开发一个WEB系统要会的知识
- WEB前端 (运行在浏览器的程序)
- HTML
- CSS
- JavaScript
- WEB后端 (WEB服务器端的程序)
- Java可以 (java做WEB开发我们称为JavaWEB开发,JavaWEB开发最核心的规范:Servlet [服务器端的java小程序])
- C语言也可以
- C++
- Python
- PHP
- …
- WEB前端 (运行在浏览器的程序)
2.javaEE是什么?
①javaSE(基础)
- java标准版(一套类库:别人写好的一套类库,只不过这个类库是标准类库,是基础)
②javaEE (WEB方向)
- java企业版(一套类库,只不过这套类库可以帮助我们进行企业级项目的开发,专门为企业内部提供解决方案的多套类库)
- 用它可以开发企业级项目
- 可以开发WEB系统
- java比较火的就是JavaEE方向
③javaME:(微型设备)
-
Java微型版(还是一套类库,只不过这套类库帮助我们进行电子微型设备内核程序的开发)
-
机顶盒内核程序,吸尘器,电冰箱…等
-
javaEE实际上包括很多种规范:13种规范,其中Servlet就是java规范之一,学Servlet还是学java语言
3.B/S结构的系统通信原理
(1)关于域名:
-
https://www.baidu.com/ (网址)
-
www.baidu.com 是一个域名
-
在浏览器地址栏输入域名后,回车之后
域名解析器会解析出来一个具体的IP地址和端口号
-
解析结果也许是:http://39.156.66.14/index.html
-
IP地址:
- 计算机在网络中的身份证号,在同一个网络中,IP地址是唯一的
- A计算机想和B计算机通信,首先需要知道B计算机的IP地址,有了IP地址才能建立连接
-
端口号:
- 一个端口代表一个软件(一个端口代表一个应用,一个端口仅代表一个服务)
- 一个计算机有很多软件,每个启动之后都有一个端口号
- 在同一台计算机上,端口号具有唯一性
(2)一个WEB系统的通信原理:
- 步骤:
- 第一步:用户输入网址( URL )
- 第二步:域名解析器进行解析:http://39.156.66.14/index.html
- 第三步:浏览器软件在网络中搜索IP地址为39.156.66.14这台主机,直接找到这台主机
- 第四步:定位39.156.66.14这台主机上的服务器软件,根据端口号,可以轻松定位到对应的服务器软件
- 第五步:端口号对应的服务器软件得知浏览器想要的资源名是:index.html
- 第六步:服务器软件找到index.html文件,并且将index.html文件中的内容直接输出响应到浏览器上
- 第七步:浏览器接收到来自服务器的代码(HTML , CSS ,JS)
- 第八步:浏览器渲染,执行(HTML , CSS ,JS)代码,展示效果
①URL:统一资源定位符
②请求 ( request )
③响应 ( response )
4.关于web服务器软件(Tomcat)
(1)web服务器软件都有哪些呢?
- (这些软件都是以前开发好的)
- Tomcat (Apache基金会的)
- jetty
- JBOSS (应用服务器)
- WebLogic (应用服务器)
- WebSphere (应用服务器)
(2)应用服务器和WEB服务器的关系?
- 应用服务器实现了JavaEE的所有规范 ( 十三个不同的规范 )
- WEB服务器只实现了JavaEE的Servlet和JSP两个核心规范
- 应用服务器是包含了WEB服务器的
(3)Tomcat 下载
- apache官网:https://www.apache.org/
- tomcat官网:https://tomcat.apache.org/
- tomcat开源免费的轻量级WEB服务器 (又被称为catalina)
- tomcat服务器是轻量级的,体积小,运行速度快,只实现了Servlet+JSP规范
- tomcat是java语言写的,tomcat服务器要是想要运行需要先有jre,
- 所以要先配置java环境
- JAVA_HOME=D:\JAVA\jdk-17.0.1
- PATH=%JAVA_HOME%\bin
(4)Tomcat 服务器安装和运行:
-
直接zip包解压即可
-
启动Tomcat服务器:startup.bat (重命名为OK.bat)
-
关闭服务器:shutdown.bat
///重命名为NO.bat (因为shutdown和windows关机命令重复,害怕输入错误)
-
bin目录下有个startup.bat文件,通过他可以启动Tomcat服务器
-
xxx.bat文件时windows操作系统专用的,bat文件是批处理文件,这种文件中可以编写大量的windows的dos命令,然后执行bat文件就相当于批量的执行dos命令
-
startup.sh :这个文件在windows当中无法执行,在linux环境中能够执行的是shell命令,大量的shell命令编写在shell文件当中.然后执行这个shell文件就可以批量执行shell命令
-
Tomcat服务器提供了两套文件,说明可以在windows和linux中使用
-
分析startup.bat文件,执行这个命令,实际上最后执行的是catalina.bat文件
-
catalina.bat文件中有这样一段代码:
-
MAINCLASS=org.apache.catalina.startup.Bootstrap
这个类就是main方法所在的类
-
-
Tomcat服务器是java语言写的,那么启动服务器就是执行main方法
-
-
我们打开dos命令窗口,输入startup.bat来启动Tomcat服务器
-
启动Tomcat服务器只配置path对应的bin目录是不行的,有两个环境变量需要配置:
- (1)JAVA_HOME=jdk的根
- (2)CATALINA_HOME=Tomcat的根
- (3)PATH=%JAVA_HOME%\bin;%CATALINA_HOME%\bin;
-
(5)测试Tomcat服务器是否启动成功
-
打开浏览器,在浏览器上输入URL即可:
-
http://IP地址:端口号
-
端口号是8080
-
IP是本机IP
本机IP是192.168.0.103
-
-
(6)查看Tomcat服务器版本:
- cmd输入 : catalina version
(7)关于Tomcat服务器的目录
- bin:这个目录是Tomcat服务器的命令文件存放目录:比如:启动Tomcat ,关闭Tomcat等
- conf : 这个目录是Tomcat服务器的配置文件存放目录
- conf中有一个web.xml 是所有项目的父配置文件
- lib : 目录是Tomcat服务器的核心程序目录,因为Tomcat服务器是用java语言写的,这里的jar包里面都是class文件
- logs : Tomcat服务器日志目录,Tomcat服务器启动等信息都会在这个目录下生成日志文件
- temp : Tomcat服务器临时文件目录存储临时文件
- webapps : 这个目录就是用来存放大量的webapp(web application :web应用)
- work : 这个是用来存放JSP文件翻译之后的java文件以及编译之后的class文件
(8)解决Tomcat服务器在DOS命令窗口中乱码的问题(控制台乱码)
(1)打开Tomcat服务器根下的conf文件夹下的logging.properties日志文件
将内容修改如下: 将UTF-8改为GBK (因为Windows的DOS命令窗口是GBK的编码方式)
![](https://gitee.com/aisichen/sichen/raw/master/img/1641872643847.png)
5.实现一个简单的web应用,(没有java小程序):
-
第一步:找到CATALINA_HOME下的webapps目录
- 因为所有的webapp要放到webapps目录下
-
第二步:在CATALINA_HOME下的webapps目录下新建一个子目录 oa
- 这个目录名oa就是你这个webapp的名字
-
第三步:在oa目录下新建资源文件,例如index.html
-
第四步:启动服务器
-
第五步:浏览器搜索资源
- http://192.168.0.103:8080/oa/index.html
- 思考:
- 我们在地址栏输入一个网址和点击超链接是一样的
- 用户点击超链接相当于在浏览器输入地址
- 思考:
- http://192.168.0.103:8080/oa/index.html
*在超链接中IP地址和端口号是可以省略的
-
/oa/index.html 绝对路径
-
<!--注意以下的路径,以../开始,是一个绝对路径,不需要加http://192.168.0.103:8080--> <a href="../../Tomcat/apache-tomcat-10.0.14/webapps/oa/index.html"></a> <!--多个层级没有关系,直接访问即可-->
6.静态资源/动态资源
(1)静态资源:
- 展示的页面数据都是写死在html上的,这种资源我们称为静态资源,
- 怎么能变成动态资源?显然需要java小程序
(2)动态资源:
- 连接数据库需要JDBC程序,也就是说要编写java程序来连接数据库,数据库中有多少条记录,页面上就展示多少条记录,这种技术称为动态网页技术(动态网页是说网页中的数据是动态的,随着数据库中数据的变化而变化)
需要知道的是:
后端到底要执行那个java小程序,取决于你前端浏览器发送的请求路径,一个路径对应一个Servlet小程序
(3)角色和角色之间需要遵守那些规范,那些协议?
-
有哪些角色(在整个BS结构的系统中,有哪些角色参与)
- 浏览器软件的开发商 (谷歌浏览器,火狐浏览器…)
- WEB Server的开发团队 (WEB Server这个软件也是太多了 Tomcat , jetty , WebLogin , JBOSS , WebSphere…)
- DB Server的开发团队 (Oracle , MySQL…)
- webapp的开发团队 (WEB应用是我们作为javaWEB程序员开发的)
-
webapp的开发团队和WEB Server的开发团队之间有一套规范:javaEE规范之一:Servlet规范
-
Servlet规范的作用是什么?
- WEB Server 和 webapp之间解耦合
-
浏览器(Browser)和WebServer之间有一套传输协议: HTTP协议: (超文本传输协议)
-
webapp开发团队 和 DB Server的开发团队之间有一套协议:JDBC
-
7.Servlet规范:
严格意义上来说Servlet其实并不是简单的一个接口:
- Tomcat服务器要遵守Servlet规范,javaWeb程序员也要遵循这个Servlet规范,
这样Tomcat服务器和webapp才能解耦合
- 遵循了Servlet规范的webapp,这个webapp就可以放在不同的WEB服务器中运行 (因为这个webapp是遵循Servlet规范的)
- Servlet规范中规定了:
- 规范了那些接口
- 规范了哪些类
- 规范了一个web应用中应该有哪些配置文件
- 规范了配置文件的名字
- 一个合格的webapp应该是一个怎样的目录结构
- 一个合格的webapp配置文件路径放在哪里
- 一个合格的webapp中java程序放在哪
- Servlet规范中规定了:
8.开发一个带Servlet的webapp
(1)开发步骤是什么(重点)
-
**第一步:**在webapps目录下新建一个目录,起名sichen(这个sichen就是项目的名字) ,也可以设置其他名字
- 注意:sichen就是这个webapp的根
-
**第二步:**在项目目录下,新建一个目录: WEB-INF
- 注意:这个目录的名字是Servlet规范中的规范,必须全部大写,必须一模一样
-
**第三步:**在WEB-INF目录下新建一个目录: classes
- 注意:这个目录的名字必须是全部小写的classes,这也是Servlet规范中规定的,
- 这个目录下存放的是java程序编译之后的class文件 (这里存放的是字节码文件)
-
**第四步:**在WEB-INF目录下新建一个目录 : lib (存放jar包的)
- 注意:这个目录不是必须的,但是如果一个webapp需要第三方的jar包,这个jar包就要放在这个lib目录下,例如java语言连接数据库需要的数据库jar包 ,那么这个jar包就要放在这个lib目录下,这是Servlet规定的
-
**第五步:**在WEB-INF目录下新建一个文件叫 web.xml
-
注意:这个文件是必须的 , 这个文件必须叫做 web.xml ,这个文件必须放在这里 ,一个合法的webapp, web.xml文件是必须的,这个文件就是配置文件 ,这个文件描述了请求路径和Servlet类之间的对照关系
-
这个文件最好从其他的webapp文件中复制,不要自己写复制粘贴即可
- web.xml
-
<?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" metadata-complete="true"> </web-app>
-
-
**第六步:**编写一个java程序,这个小java程序也不能随便开发,必须实现Servlet接口
-
重点:编写java小程序的时候,java源代码放在哪里都可以,但是java文件的class文件必须放在项目下的classes目录下
-
注意:这个Servlet接口不在jdk当中(因为Servlet不是javaSE了,Servlet属于JavaEE属于另外一套类库)
-
Servlet接口 (Servlet.class文件) 是Oracle提供的
-
Servlet接口是JavaEE规范中的一员
-
Tomcat服务器实现了Servlet规范,所以Tomcat服务器也需要使用Servlet接口,(所以Tomcat服务器中应该有这个接口的jar包)
-
Tomcat服务器的根目录下 --> lib -->
-
解压这个servlet-api.jar 在这个路径下会看到一个Servlet.class文件
-
-
从jakartaEE9开始,Servlet接口的包全名变了,叫
jakarta.servlet.Servlet;
-
-
**第七步:**编译我们编写的HelloServlet.java文件
-
重点:你怎么能让你的HelloServlet编译通过呢?配置环境变量CLASSPATH==
.;D:\Tomcat\apache-tomcat-10.0.14\lib\servlet-api.jar
-
思考问题:让配置的CLASSPATH和Tomcat服务器运行有没有关系?
- 没有任何关系,以上配置这个环境变量只是为了让你的HelloServlet能够正常编译生成class文件
-
HelloServlet.java
-
package com.sichen.servlet; import jakarta.servlet.Servlet; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.ServletConfig; import java.io.IOException; public class HelloServlet implements Servlet{ //五个方法: public void init(ServletConfig config) throws ServletException{ } public void service(ServletRequest request , ServletResponse response) throws ServletException , IOException{ System.out.println("Hello,World"); } public void destroy(){ } public String getServletInfo(){ return ""; } public ServletConfig getServletConfig(){ return null; } }
-
-
**第八步:**将以上编译后的HelloServlet的class文件,连带包名拷贝在webapp文件夹下的classes文件夹下
-
**第九步:**在web.xml文件中编写配置文件,让"请求路径"和"Servlet类名" 关联在一起
- 这一步用专业的术语描述叫做: 在web.xml文件中注册Servlet类
- web.xml
<?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" metadata-complete="true"> <!-- Servlet描述信息 --> <!-- 任何一个servlet都对应一个servlet-mapping --> <servlet> <servlet-name>sichen</servlet-name> <!-- 这个位置必须是带有包名的全限定类名java小程序的包名+类名 就是package后边的加上类名--> <servlet-class>com.sichen.servlet.HelloServlet</servlet-class> </servlet> <!-- servlet映射信息 --> <servlet-mapping> <!-- 这个地方也可以随便写,不过这里写的内容要和上面写的一样 --> <servlet-name>sichen</servlet-name> <!-- 这里需要一个路径 --> <!-- 这个路径唯一的要求就是必须是斜杠/开头 --> <!-- 当前这个路径可以随便写,这个路径就是最后浏览器输入的时候,webapp后边的资源路径,参照第十一步 --> <url-pattern>/ai/sichen</url-pattern> </servlet-mapping> <!-- 执行流程是: 服务器通过 <url-pattern> 找到 对应的<servlet-name> 在通过<servlet-name>找到对应的<servlet-class>--> <display-name>Welcome to Tomcat</display-name> <description> Welcome to Tomcat </description> </web-app>
-
**第十步:**启动Tomcat服务器
-
**第十一步:**打开浏览器,在地址栏上输入一个URL,这个URL必须是
-
http://192.168.0.103:8080/sichen/ai/sichen
-
非常重要一点:浏览器上的请求路径不能随便写,这个请求路径必须是和web.xml文件中的url-pattern一致
-
注意:浏览器上的请求路径和web.xml文件中的url-pattern中的请求路径唯一的区别是
- 浏览器上的请求路径前边要加(项目名)
-
-
浏览器上编写的路径太长,可以使用超链接(非常重要:html项目只能放在WEB_INF文件夹之外)
-
index.html
-
<!doctype html> <html> <head> <title>sichen</title> </head> <body> <!-- 这里不用加http://IP:端口号 但是要加项目名 --> <a href="/sichen/ai/sichen">思尘</a> </body> </html>
-
-
以后我们不需要编写main方法了,Tomcat服务器负责调用main方法,
Tomcat服务器在启动时,调用的就是main方法
java程序员只需要编写Servlet接口的实现类,然后将其注册到web.xml文件中即可
-
总结:一个合法的webapp的目录结构是:
-
webapp |------WEB-INF |------classes (存字节码,java编译后的文件) |------lib (第三方jar包) |------web.xml (注册Servlet) |------html |------CSS |------javaScript |------image |---.........
-
-
浏览器发送请求,到最终服务器调用Servlet中的方法,是怎样一个过程(以下这个过程很粗糙)
- 用户输入URL,或者直接点击超链接http://192.168.0.103:8080/sichen/ai/sichen
- 然后Tomcat服务器接收到请求后,截取路径:/sichen/ai/sichen
- Tomcat 服务器找到sichen项目
- Tomcat服务器在web.xml文件中查找/ai/sichen 对应的Servlet是 com.sichen.servlet.HelloServlet
- Tomcat服务器通过反射机制,创建com.sichen.servlet.HelloServlet的对象
- Tomcat服务器调用com.sichen.servlet.HelloServlet这个类
*关于JavaEE的版本:
- 关于javaEE的版本 目前最高版本是JavaEE8
- javaEE被Oracle捐献了,Oracle将JavaEE规范捐献给了Apache
- Apache将javaEE改名了,以后就不叫JavaEE了,
- 叫做 jakartaEE
- javaEE版本升级之后的"JavaEE9",不叫这个名字了,叫 jakartaEE9
- javaEE的时候对应的Servlet类名是: javax.servlet.Servlet;
jakartaEE9
的时候对应的Servlet类名是: jakarta.servlet.Servlet (包名都换了)- 注意:如果你之前的项目还是在使用javax.servlet.Servlet,那么你的项目无法直接部署到Tomcat10+版本上,你能部署到Tomcat9,以及9之前的版本中
9.Servlet中的五种方法:
package com.sichen.servlet;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.ServletConfig;
import java.io.IOException;
import java.io.PrintWriter;
public class HelloServlet implements Servlet{
//五个方法:
//init方法被翻译成初始化
//init方法只执行一次
//在AServlet对象第一次被创建之后执行
//init方法通常是完成初始化操作的
//init方法在执行的时候Aservlet对象已经被创建了
public void init(ServletConfig config) throws ServletException{
}
//service方法:是处理用户请求的核心方法
//只要用户发送一次请求,service方法必然会执行一次
//发送几次,就执行几次
public void service(ServletRequest request , ServletResponse response)
throws ServletException , IOException{
//向控制台打印
System.out.println("Hello,World");
}
//destroy方法只执行一次
//Tomcat服务器在销毁AServlet对象之前会调用一次destroy方法
//destroy在执行的时候.AServlet对象的内存还没有被销毁,即将被销毁
//destroy方法内可以编写销毁前的准备
//比如说:服务器关闭的时候,AServlet对象开启了一些设备,这些资源可能是流,可能是数据库连接
//要关闭这些流和数据库,可以把代码写在这个方法当中
public void destroy(){
}
public String getServletInfo(){
return "";
}
public ServletConfig getServletConfig(){
return null;
}
}
(1)向浏览器响应一段html代码(*)
import java.io.PrintWriter;
public void service(ServletRequest request , ServletResponse response)
throws ServletException , IOException{
//向控制台打印
System.out.println("Hello,World");
//设置响应的内容类型是普通文本或HTML
//设置响应的内容类型时,要在获取流对象之前设置,有效
response.setContentType("text/html");
//怎么将一个数据直接输出在浏览器上:
//需要使用ServletResponse接口:response
//response表示响应,从服务器端发送数据到浏览器端叫响应
//获取浏览器响应流
PrintWriter out = response.getWriter();
out.print("HelloServlet , You Are my first Servlet");
//这是一个输出流,负责输出数据到浏览器,这个输出流不需要我们刷新,也不需要我们关闭,这些都有Tomcat来维护
//可以识别html内容
out.print("<h1>Hello servlet</h1>");
}
(2)在Servlet中连接数据库:(重点)
-
在代码中直接编写JDBC即可
-
不要忘了在web.xml中绑定路径
-
lib中添加mysql的jar包
-
StudentList.java
-
package com.sichen.servlet; //注意包名尽量一致,这样到时候往classes中放的时候更容易 import jakarta.servlet.Servlet; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.ServletConfig; import java.io.IOException; import java.io.PrintWriter; import java.sql.*; public class StudentList implements Servlet{ //五个方法: public void init(ServletConfig config) throws ServletException{ } public void service(ServletRequest request , ServletResponse response) throws ServletException , IOException{ //这条语句直接写在最前边 response.setContentType("text/hmtl"); //流:在浏览器页面上响应数据 PrintWriter pw = response.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/aisichen"; String user = "root"; String password = "acpl159357"; conn = DriverManager.getConnection(url,user,password); //获取预编译对象 String sql = "select id,name,dizhi from t_student"; ps = conn.prepareStatement(sql); //执行sql rs = ps.executeQuery(); //处理查询结果集 while(rs.next()){ String id = rs.getString("id"); String name = rs.getString("name"); String dizhi = rs.getString("dizhi"); //将信息打印在浏览器中 //可以java和html语言联合使用 pw.print("id:"+id+"姓名:"+name+"地址:"+dizhi+"<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 destroy(){ } public String getServletInfo(){ return ""; } public ServletConfig getServletConfig(){ return null; } }
10.在集成开发工具IDEA开发Servlet程序(重点)
(1)使用IDEA集成开发工具:
-
第一步:创建一个空的工程; Servlet
-
第二步:新建模块
- 这里创建一个普通的javaSE模块(先不要创建javaEnterprise模块)
- 这个Module自动会被放在Servlet的project下面
- 这个module名字叫sichen
-
第三步:让这个Module变成javaEE的模块 (让模块变成webapp的模块,符合Servlet规范的Module)
- 在Module上点击右键 Add Framework Support…(添加框架支持)
- 在弹出的窗口中,选择Web Application (选择的是webapp的支持)
- 选择了这个webapp的支持之后,IDEA会自动生成一个符合Servlet规范的webapp目录结构
- 重点: 需要注意的是:在IDEA工具中根据Web Application模板生成的目录中
**有一个web目录,这个目录就代表webappd的根**
-
第四步:(非必须): 根据Web Application生成的资源中有一个index.jsp的文件 这里可以删除它
-
第五步: 编写Servlet (StudentServlet)
public class StudentServlet implements Servlet
- 这时发现没有Servlet.class文件没有,怎么办? 将servlet-api.jar和jsp-api.jar包添加到IDEA的classpath中
- File–>Project Structrue --> Modules --> +加号 -->Add JARS
- 这时发现没有Servlet.class文件没有,怎么办? 将servlet-api.jar和jsp-api.jar包添加到IDEA的classpath中
-
第六步:在Servlet当中的servlet方法当中编写业务代码
-
第七步:在WEB-INF目录下新建一个lib目录,将连接mysql数据库的jar包放进去
-
第八步:在web.xml文件中完成StudentServlet类的注册: (请求路径)
-
<?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>sichen</servlet-name> <servlet-class>com.sichen.servlet.StudentServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>sichen</servlet-name> <url-pattern>/servlet/student</url-pattern> </servlet-mapping> </web-app>
-
第九步:给一个html页面,在html页面中编写一个超连接,用户点击这个超连接,发送请求,Tomcat执行后台的StudentServlet
-
index.html
-
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>思尘</title> </head> <body> <!-- 这里路径是写死的,不能动态更换 --> <a href="/sichen/servlet/student">student</a> </body> </html>
-
-
第十步:让IDEA工具关联Tomcat服务器,关联的过程当中将webapp部署到Tomcat服务器当中
-
左上角加号 —>点击Tomcat Server —> local
-
在弹出的界面上设置服务器的参数 ,(基本上不用动)
-
在当前窗口中有一个Deployment(点击这个用来部署webapp) —> 加号 —> 工件
-
-
这里的上下文设置为index.html文件中写的项目名
-
-
修改Application context 为: /sichen
-
-
第十一步:启动Tomcat服务器
- 以DeBug模式启动Tomcat服务器
-
第十二步:打开浏览器,在地址栏输入: http://localhost:8080/sichen/index.html
11.Servlet对象的生命周期
(1)什么是Servlet对象的生命周期:
- Servlet对象什么时候被创建,
- Servelt对象什么时候被销毁,
- Servlet对象创建了几个?
- Servlet对象的生命周期表示:一个Servlet对象从出生到最后死亡,整个过程是什么?
(2)Servlet对象是由谁来维护的?
- Servlet对象的创建,对象上方法的调用,对象最终的销毁,javaweb程序员是无权干预的
- Servelt对象的生命周期是由Tomcat服务器(WEB Server)全权负责的
- Tomcat服务器通常我们又称为: WEB容器 (这个叫法你要知道[WEB Container])
- WEB容器来管理Servlet对象的生命周期
(3)思考:我们自己new 的Servlet对象受WEB容器的管理吗?
-
我们自己new的Servlet对象是不受WEB容器的管理,
-
WEB容器创建的Servlet对象,都会被放到一个集合当中 (HashMap),只有放到这个HashMap集合当中的Servlet才能被WEB容器管理,自己new的Servlet不会被WEB容器管理 (相当于不在容器当中)
-
WEB容器底层有一个HashMap这样的集合,在这个集合当中存储了Servlet对象和请求路径之间的关系
(4)研究:服务器启动时,Servlet对象有没有被创建出来(默认情况下)?
- 在Servlet中提供一个无参构造方法,启动服务器的时候,看构造方法是否执行
- 经过测试得出结论: 默认情况下,服务器在启动的时候Servlet对象并不会被实例化
- 这个设计是合理的,用户没有发送请求之前,如果提前创建出来所有的Servlet对象,必然是耗费内存的.并且创建出来的Servlet如果一直没有用户访问,显然这个Servlet是一个废物,没必要先创建
(5)怎么让服务器启动的时候创建Servlet对象:
-
在web.xml文件对应的地方添加:
-
(正整数),
-
在此标签中填写整数,整数越小优先级越高
-
<servlet> <servlet-name>aservlet</servlet-name> <servlet-class>com.sichen.javaweb.servlet.AServlet</servlet-class> <!-- 在这里加一个这个标签,中间跟一个正整数,这个整数决定了创建时的顺序,数字越小优先级越高,最小是0 --> <load-on-startup>(正整数)</load-on-startup> </servlet> <servlet-mapping> <servlet-name>aservlet</servlet-name> <url-pattern>/lujing/a</url-pattern> </servlet-mapping>
-
注意:通常情况下,在服务器启动时,是不创建Servlet对象的
(6)Servlet的生命周期: (用户发送请求之后)
- 默认情况下服务器启动的时候,Aservlet对象并没有被实例化,
- 用户发送请求后Servlet对象被实例化 (一次)
- 执行init方法 (一次)
- 执行service方法 (多次,用户发送一次请求执行一次)
- 关闭服务器 , 执行destroy方法 (一次)
- 服务器销毁Servlet对象的内存
①用户发送第一个请求后,控制台输出了以下内容:
-
AServlet无参构造方法执行了 Aservlet's init method execute! Aservlet's service method execute!
-
根据以上输出的内容得出结论:
-
用户在发送第一次请求的时候,Servlet对象被实例化,(AServlet的构造方法被执行了并且执行的是无参构造方法)
-
AServlet对象被创建出来之后,Tomcat服务器马上调用了AServlet对象的init方法
(init方法在执行的时候,AServlet的对象已经被创造出来了)
-
init方法执行后,Tomcat服务器立刻执行service方法
-
②用户发送第二次请求:控制台输出以下内容:
-
Aservlet's service method execute!
-
根据以上输出结果可知,用户在发送第二次,或者多次的时候:Servlet的对象并没有新建,还是使用之前创建好的Servlet对象,直接调用Servlet对象的service方法,这说明:
-
第一: Servlet对象是单例的 (单实例的 , 但是要注意Servlet对象是单实例的 ,
但是Servlet类并不符合单例模式 , 我么称之为假单例
之所以单例是因为Servlet对象的创建我们程序员管不着,这个对象的创建只能是Tomcat说了算 Tomcat只创建了一个,所以导致了单例,但是属于假单例)
真单例模式,构造方法是私有化的
-
第二: 无参数构造方法和init方法只在第一次用户请求的时候执行,也就是说,无参数构造方法执行一次,init方法也只被Tomcat服务器调用一次
-
第三: 只要用户发送一次请求,service方法必然会被Tomcat服务器调用一次,发送一百次请求,调用一百次
-
③关闭服务器的时候,控制台输出了以下内容:
-
Aservlet's destroy method execute!
-
通过以上输出可以得出以下结论:
-
Servlet中的destroy方法只被Tomcat服务器执行一次
-
destroy方法是在什么时候被调用的?
- 是在服务器关闭的时候被调用
- 因为服务器关闭的时候,要销毁AServlet对象的内存
- 服务器在销毁AServlet对象内存之前,Tomcat服务器会自动调用AServlet对象的destroy方法
-
研究:destroy方法调用的时候,对象销毁了还是没有销毁?
-
destroy方法执行的时候AServlet对象还没有被销毁
destroy方法执行结束之后,AServlet对象的内存才会被销毁
-
-
-
-
Servlet对象更像人的一生:
- Servlet的无参构造方法执行: 人出生了
- Servlet对象的init方法执行: 标志着你正在接受教育
- Servlet对象的service方法执行: 标志着你已经开始工作了
- Servlet对象的destroy方法执行: 标志着你已经快死了,写遗言
12.GenericServlet
//GenericServlet实现了Servlet , ServletConfig和 java.io.Serializable
GenericServlet implements Servlet, ServletConfig, java.io.Serializable
(1)当我们Servlet类中编写了一个有参数的构造方法,如果没有手动编写无参数构造方法,会出现什么问题?(报500错误)
- 报错了: 500错误
- 注意: 500是一个HTTP协议的错误状态码.
- 500一般情况下是因为服务器端的java程序出现了异常,(服务器端的错误是500错误,服务器内部错误)
- 如果没有无参数的构造方法,会导致出现500错误,无法实例化Servlet对象
- 所以,一定要注意,在Servlet开发中,不建议程序员来定义构造方法,因为定义不当一不小心就会导致无法实例化Servlet对象
思考:Servlet的无参构造方法是在对象第一次创建的时候执行,并且只执行一次,而init方法也是在第一次创建的时候执行,并且只执行一次,那么这个无参数构造方法可以代替掉无参数构造方法吗:
- 不能
- Servlet规范中有要求,作为javaweb程序员来说,编写Servlet类的时候,不建议手动编写构造方法,很容易让无参构造方法消失,这个操作可能会导致Servlet对象无法实例化,所以init方法是有存在的必要的
- init , service ,destroy方法中使用量最多的是哪个方法?
- 使用最多的方法是service
- 什么时候使用init方法呢?
- 通常在init方法中进行初始化,并且这个初始化只需要初始化一次,例如:初始化数据库连接池,初始化线程池,
- 什么时候使用destroy方法呢?
- destroy方法也很少用,
- 通常在destroy方法中进行资源的关闭,还有什么资源没保存的,保存一下
(2)我们编写一个Servlet类直接实现Servlet接口每次都要重写五个方法很繁琐,怎么解决?(适配器模式)
-
我们只需要service方法,其他方法大部分情况下是不需要使用的,代码很丑陋
-
所以使用适配器模式 Adapter:
-
例子:
-
(1)先创建一个接口 MyInterface
-
public interface MyInterface { void m1(); void m2(); void m3(); void m4(); void m5(); void m6(); void m7(); void core(); }
-
(2)然后创建接口的配适器
-
GenericServlet 一个抽象类,里边有一个抽象方法core()是我们常用的方法
-
public abstract class GenericServlet implements MyInterface{ public abstract void core(); @Override public void m1() { } @Override public void m2() { } @Override public void m3() { } @Override public void m4() { } @Override public void m5() { } @Override public void m6() { } @Override public void m7() { } }
-
(3)最后创建类继承UserAdapter
-
这样只用实现一个接口的方法即可
-
public class User extends GenericServlet{ @Override public void core() { } }
-
-
总结:以后所有的Servlet类都不要直接实现Servlet接口
-
我们可以创建一个标准通用的Servlet , 起名 : GenericServlet
-
以后所有的Servlet类都要继承GenericServlet类
-
GenericServlet就是一个适配器
-
以后编写的所有Servlet类继承GenericServlet,重写service方法即可
(3)思考:GenericServlet类是否需要改进以下,更利于子类程序的编写
-
思考第一个问题,我提供的GenericServlet之后,init方法还会执行吗?
- 还会执行,会执行GenericServlet类中的init方法
-
思考第二个问题:这个init方法是谁调用的呢?
- Tomcat服务器调用的
-
思考第三个问题:init方法中的ServletConfig方法是谁传过来的?
- 都是Tomcat干的
- Tomcat服务器先创建了Servlet对象,然后调用init方法,将ServletConfig对象传给了init方法
(4)Tomcat服务器的伪代码:
-
public class Tomcat { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { //..... //Tomcat服务器伪代码 //创建loginServlet对象(通过反射机制,调用无参构造方法来实例化loginServlet对象) Class clazz = Class.forName("com.sichen.javaweb.servlet.loginServlet"); Object obj = clazz.newInstance(); //向下转型 Servlet servlet = (Servlet)obj; //创建servletConfig对象 //Tomcat服务器要将ServletConfig对象实例化 ServletConfig servletConfig = new ServletConfig(); //调用servlet的init方法 //小猫咪创建的servletConfig对象org.apache.catalina.core.StandardWrapperFacade@6af935f9 //多态(Tomcat服务器完整的实现了Servelt规范) servlet.init(servletConfig); //调用servlet的service方法 //.... }
(5)整章重点重要 (模板设计模式)
模板设计模式
- GenericServlet抽象类
package com.sichen.javaweb.servlet;
import jakarta.servlet.*;
import java.io.IOException;
public abstract class GenericServlet implements Servlet {
//创建一个成员变量
private ServletConfig servletConfig;
/**
* init方法中的ServletConfig对象是Tomcat创建好的
* 这个对象目前在init方法的参数上,是一个局部变量
* 那么ServletConfig对象肯定以后要在service方法上使用,那么怎么才能保证
* ServletConfig对象在service方法中能够使用呢?
* @param servletConfig
* @throws ServletException
*/
//在这里加一个final子类就不能重写init方法,也就不能破坏我写的结构
@Override
public final void init(ServletConfig servletConfig) throws ServletException {
// System.out.println("小猫咪创建的servletConfig对象"+servletConfig);
//使用this给成员变量赋值 = 传过来的servletConfig
this.servletConfig = servletConfig;
//在这里调用init方法
this.init();
}
//解决子类重写init方法的问题,创建一个init方法,让子类可以重写
//模板设计模式
public void init(){
}
/**
* 抽象方法,这个方法是最常用的所以要求此类必须实现service方法
* @param servletRequest
* @param servletResponse
* @throws ServletException
* @throws IOException
*/
@Override
public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException;
@Override
public void destroy() {
System.out.println("Aservlet's destroy method execute!");
}
@Override
public ServletConfig getServletConfig() {
//这里的getServletConfig方法返回servletConfig,这样子类继承的时候就可以获取这个servletConfig
return servletConfig;
}
@Override
public String getServletInfo() {
return null;
}
}
-
GenericServlet抽象类的子类
-
package com.sichen.javaweb.servlet; import jakarta.servlet.ServletConfig; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import java.io.IOException; public class Vip extends GenericServlet{ //思考一个问题: //有没有一种可能,需要我在Vip类中重写init方法 //当然有可能,于是重写init方法 // public void init(ServletConfig servletConfig) throws ServletException{ // System.out.println("重写的init方法"); // } //父类将init方法final了,子类没有办法重写这个init方法 //如果这个时候还是想要重写init方法怎么办 //在父类中写一个init方法,让原init方法调用init(),这边子类就可以重写了 //模板设计模式 //这里边的init方法也只执行一次,因为是重写的父类方法,而父类方法是在父类中的init中执行的,所以只执行一次 @Override public void init(){ System.out.println("这是子类init方法"); } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("vip可以享受更好的服务..."); //这里就可以使用get方法获取ServletConfig ServletConfig config = this.getServletConfig(); System.out.println("servletConfig对象"+config); } }
13.ServletConfig
(1)ServletConfig是什么? (是一个规范 / 接口)
- jakarta.servlet.ServletConfig
- 很显然ServletConfig是Servlet规范中的一员
- ServletConfig是一个接口, (jakarta.servlet.Servlet也是一个接口)
(2)谁去实现这个接口呢? (web服务器实现)
- ServletConfig对象是:org.apache.catalina.core.StandardWrapperFacade@fb8b8e6
- 结论:Tomcat服务器实现了这个 ServletConfig 接口
- 思考:如果Tomcat服务器换成了jetty服务器,输出ServletConfig对象的时候还是这个结果吗?
- 不一定是,包名类名可能和Tomcat的不一样,但是他们都是实现了ServletConfig这个规范
(3)Servlet/ServletCondfig的关系
- 一个Servlet对象中有一个ServletConfig对象, (Servlet/ServletCondfig对象是一对一)
- 一百个Servlet对象,就应该有一百个ServletConfig对象
(4)ServletConfig对象是谁创建的呢?
- 参照12-(4)Tomcat服务器的伪代码:
- Tomcat服务器(Web服务器)创建的ServletConfig对象
- 创建Servlet对象的时候,同时创建了ServletConfig对象
(5)ServletConfig是干什么的?
- config = 全名是 Configuration
- ServletConfig 对象被翻译为: Servlet对象的配置信息对象
- 一个Servlet对象就有一个Servlet的配置信息对象ServletConfig
(6)ServletConfig对象中到底包含了什么信息呢?
-
web.xml文件中的标签中的配置信息
-
<servlet> <servlet-name>configServlet</servlet-name> <servlet-class>com.sichen.javaweb.servlet.ConfigtextServlet</servlet-class> </servlet>
-
Tomcat服务器解析web.xml,将文件中的标签中的配置信息自动包装到ServletConfig对象中
(7)ServletConfig中有哪些方法?
-
以下的四个方法,在自己编写的Servlet类当中也可以用this去调用,(这个Servlet继承了GenericServlet)
-
(1)getServletName() : 获取Servlet的name
-
(2)getServletContext() : 获取ServletContext对象
-
通过下面两个方法可以获取到web.xml文件中的初始化参数配置信息
-
(3)getInitParameter(String name) : 通过初始化参数的name获取value (也就是值 )
-
String driver = config.getInitParameter("driver"); out.print(driver); //输出com.mysql.cj.jdbc.Driver
-
(4)getInitParameterNames() : 获取所有的初始化参数的name
-
Enumeration<String> initParameterNames = config.getInitParameterNames(); //遍历集合 while (initParameterNames.hasMoreElements()){//是否有更多元素 String s = initParameterNames.nextElement();//取元素 out.print(s);//取name out.print("<br>"); }
-
两个方法组合使用:
-
//循环取内容 Enumeration<String> initParameterNames = config.getInitParameterNames(); //遍历集合 while (initParameterNames.hasMoreElements()){//是否有更多元素 String s = initParameterNames.nextElement();//取元素 String initParameter = config.getInitParameter(s);//通过name取value out.print(s +"="+ initParameter); out.print("<br>"); }
初始化参数配置信息
-
<servlet> <servlet-name>configServlet</servlet-name> <servlet-class>com.sichen.javaweb.servlet.ConfigtextServlet</servlet-class> <init-param> <param-name>driver</param-name> <param-value>com.mysql.cj.jdbc.Driver</param-value> </init-param> <init-param> <param-name>url</param-name> <param-value>jdbc:mysql://localhost:3306/emp</param-value> </init-param> <init-param> <param-name>user</param-name> <param-value>root</param-value> </init-param> <init-param> <param-name>password</param-name> <param-value>acpl159357</param-value> </init-param> </servlet>
-
因为GenericServlet接口继承了ServletConfig接口
-
所以继承这个GenericServlet接口后,不用再获取ServletConfig对象,直接用this使用getInitParameterNames()方法和getInitParameter()方法即可
14.ServletContext
(1)通过ServletConfig对象获取ServletContext对象
-
//获取ServletContext对象 //第一种方式: ServletContext application = config.getServletContext(); out.print("<br>"+ application); //第二种方式: ServletContext application2 = this.getServletContext(); out.print("<br>" + application2);
(2) ServletContext是什么?
- ServletContext是一个接口,是Servlet规范中的一员
- Servlet对象的环境对象 (Servlet对象的上下文对象)
- ServletContext对象对应的其实是整个web.xml文件
(3)ServletContext是谁实现的?
-
Tomcat服务器(Web服务器)实现了这个接口
-
public class org.apache.catalina.core.ApplicationContextFacade implements ServletContext{}
(4)ServletContext对象是谁创建的?在什么时候创建的?
- ServletContext在Web服务器启动的时候创建
- ServletContext是Web服务器创建的
- 对于一个webapp来说,ServletContext对象只有一个
(5)ServletContext对象在服务器关闭的时候被销毁
(6)总结
- Tomcat是一个容器,一个容器可以放多个webapp,一个webapp对应一个ServletContext对象
- 50个学生,每个学生都是一个Servlet,这50个学生都在同一个教室,那么这个教室相当于一个ServletContext对象
- 放在ServletContext对象中的数据,所有Servlet一定是共享的
- 比如:一个教室的空调是所有学生共享的
(7)ServletContext接口中有哪些常用方法?
①上下文初始化参数:
-
web.xml
-
<!-- 上下文的初始化参数:是应用级的配置信息,一般一个项目共享的配置信息 --> <context-param> <param-name>pageSize</param-name> <param-value>10</param-value> </context-param> <context-param> <param-name>starIndex</param-name> <param-value>0</param-value> </context-param>
-
public String getInitParameter(String name);//通过上下文参数的name获取value public Enumeration<String> getInitPerameterNames(); //获取上下文参数的所有name //以上两个方法是ServletContext对象的方法,这个方法获取的是什么信息?是上下文初始化参数的配置信息
上下文初始化参数和初始化参数信息区别
- 上下文初始化参数是全局属性:
- 这些参数属于应用级的配置信息,一般一个项目中共享的配置信息会放到以上标签中
- 初始化参数信息是局部属性
- 如果你的配置信息只是想给一个Servlet作为参考,那么你放置到Servlet标签当中去即可,使用ServletConfig对象来获取
②动态获取应用的根路径(非常重要)
-
因为在java源代码当中有一些地方可能会需要应用的根路径,这个方法可以动态的获取根路径
-
在java源码中,永远不要把项目的根路径写死!
-
public String getContextPath(); //方法的使用: String contextPath = servletContext.getContextPath(); out.print(contextPath);//输出 /servlet03 //应用的根路径 /servlet03
③动态获取文件的绝对路径 (文件的服务器路径)
public String getRealPath(String path);
//方法的使用;
//获取文件的绝对路径
//String realPath = appLication.getRealPath("index.html");不加 / 也行
//这个/代表着Web文件夹的根,不加/默认也是从这个根下找
String realPath = servletContext.getRealPath("/index.html");
out.print("文件的绝对路径是:" + realPath);
//输出为:
//文件的绝对路径D:\IDEA\Servlet\out\artifacts\servlet03_war_exploded\index.html
//获取Web目录下的其他文件夹里的文件绝对路径:
String realPath1 = servletContext.getRealPath("/common/common.html");
④写项目的日志
//通过ServletContext的log方法也是可以记录日志的
//这个日志原始是是记录在Tomcat下的 D:\Tomcat\apache-tomcat-10.0.14\logs 文件夹里
//但是使用IDEA工具后就存储在和idea相关的文件下了
public void log(String message);
//方法使用:
servletContext.log("大家好我是思尘!");
public void log(String message , Throwable t);
//方法使用,可以定义一个条件,在日志文件中输出类似非法记录
int age = 17;
//当年龄小于十七岁的时候显示非法记录日志
if (age < 18) {
servletContext.log("对不起,您未成年,请绕行:" ,
new RuntimeException("小屁孩快走开,不适合你"));
}
⑤Tomcat的日志文件
-
localhost_access_log.2022-01-13.txt :访问日志文件 catalina.2022-01-13.log :Tomcat控制台运行日志 //这两个启动服务器就会创建 localhost.2022-01-13.log :调用log方法输出的日志 //第一次访问的时候才会创建
⑥ServletContext又叫"应用域"(存取删数据)
-
ServletContext还有另一个名字: 应用域 (后面还有其他域 例如:请求域,会话域)
-
如果所有用户共享一份数据,并且这个数据很少的被修改,并且这个数据量不多,可以将这些数据放到ServletContext这个应用域中
-
为什么是所有用户共享的数据?
- 不是共享的没有意义,因为servletContext这个对象只有一个,只有共享的数据放进去才有意义
-
为什么数据量要很小?
- 因为数据量太大的话,太占用堆内存
- 并且这个对象的生命周期很长,服务器关闭的时候,这个对象才会被销毁,所以说占用内存的较小的数据量可以存储进去
-
为什么这些共享的数据很少修改?或者说几乎不修改?
- 因为所有用户共享的数据,如果涉及到修改操作,必然会引起线程并发所带来的安全问题,所以放在ServletContext对象中的数据一般不修改
-
所有用户共享,数据量小,又不修改,这样的数据放到ServletContext这个应用域当中,会大大提高效率,因为应用域相当于一个缓存,放到缓存中的数据,下次在使用的时候,不需要从数据库中再次查找,大大提高效率
-
怎么存数据
-
public void setAttribute(String name , Object value); //相当于map.put(k,v); //使用 //这里是创建一个类,两个参数,name,password User user = new User("sichen" , "123"); servletContext.setAttribute("userobj" , user); //这里的value什么类型都能存
-
-
怎么取数据
-
public Object getAttribute(String name); //相当于 Object v = map.get(k); //使用 Object userobj = servletContext.getAttribute("userobj"); out.print(userobj + "<br>"); //输出是:因为User类中写了toString方法,所以输出 // User{name='sichen', password='123'}
-
-
怎么删除数据
-
public void removeAttribute(String name); //相当于 map.remove(k);
-
⑦获取MIME类型
-
MIME类型 : 在互联网通信过程中定义的一种文件数据类型 ,
- 格式 : 大类型/小类型
- 例如 : text/html image/jpeg
-
方法 :
-
String getMimeType(String file) //其实是通过文件的后缀类型来获取 //传一个文件的名字
综合案例 : 下载文件
- 文件下载需求
- 页面显示一个超连接
- 点击超链接之后 , 弹出下载提示框
- 完成图片文件下载
分析 :
- 超链接指向的资源如果能够被浏览器解析 , 则在浏览器上展示 , 如果不能解析则弹出下载提示框 , 不能满足需求
- 任何资源都必须弹出下载框
- 使用响应头设置 资源的打开方式 attachment
- content-disposition:attachment
问题: 中文文件名的问题
在下载提示框以及下载后的文件中 , 中文是不能被显示的,
-
解决思路 :
-
1.获取客户端使用的浏览器版本信息
-
//1. 获取user-agent请求头 String agent = request.getHeader("user-agent");
-
2.根据不同的版本信息 , 设置filename的编码方式
-
找到一个DownLoadUtils.java的包 , 里边有一个方法
-
-
String getFileName(String agent, String filename) //将上边获取的agent , 和文件名称filename分别传进去 , 返回一个filename
-
DownLoadUtils.java
-
import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Base64; public class DownLoadUtils { public static String getFileName(String agent, String filename) throws UnsupportedEncodingException { if (agent.contains("MSIE")) { // IE浏览器 filename = URLEncoder.encode(filename, "utf-8"); filename = filename.replace("+", " "); } else if (agent.contains("Firefox")) { // 火狐浏览器 Base64.Encoder encoder = Base64.getEncoder(); filename = "=?utf-8?B?" +encoder.encodeToString(filename.getBytes("utf-8")) + "?="; } else { // 其它浏览器 filename = URLEncoder.encode(filename, "utf-8"); } return filename; } }
步骤:
-
1.定义页面 , 编辑超链接的href属性 , 属性值指向一个servlet , 传递资源的名称filename
-
<a href="/login/downloadServlet?filename=21-0.png">图片一</a> <a href="/login/downloadServlet?filename=1.avi">视频一</a>
-
2.定义downloadServlet.java
-
package cn.sichen.web.servlet; import jakarta.servlet.*; import jakarta.servlet.http.*; import jakarta.servlet.annotation.*; import java.io.FileInputStream; import java.io.IOException; @WebServlet(name = "downloadServlet", value = "/downloadServlet") public class downloadServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request , response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1.获取请求参数 : 文件名称 String filename = request.getParameter("filename"); //2.使用字节输入流加载文件进内存 //2.1 找到文件服务器路径 ServletContext servletContext = this.getServletContext(); String realPath = servletContext.getRealPath( "/img/" + filename); //2.2 用字节流关联 FileInputStream fis = new FileInputStream(realPath); //2.3 指定response的响应头 //2.3.1 设置响应头类型 content-type //获取文件的MIME类型的 String mimeType = servletContext.getMimeType(filename); //这里要传两个参数 , 一个是要设置的属性名 , 一个是值 response.setHeader("content-type" , mimeType); //2.3.2 设置响应头打开方式 : content-disposition response.setHeader("content-disposition" , "attachment;filename="+filename); //3 将输入流的数据写出到输出流中 //获取输出流 ServletOutputStream outputStream = response.getOutputStream(); byte[] buff = new byte[1024*8]; int len = 0; while ((len = fis.read(buff)) != -1){ outputStream.write(buff , 0 ,len); } fis.close(); } }
-
15.HTTP协议
(1)什么是协议?
- 协议是某些人,某些组织制定的规范,大家都是用这个规范,可以无障碍交流
- 协议就是一套标准
(2)什么是HTTP协议?
-
http协议: 是W3C制定的一种超文本传输协议 (通信协议)
-
这种协议游走在B和S之间,B和S之间发送数据,都要使用http协议,
这样B和S之间才能解耦合 (B不依赖S,S不依赖B)
(3)什么是超文本?
-
超文本说的就是: 不是普通文本, 比如流媒体:声音, 视频 ,图片等
-
HTTP协议支持: 不但可以传送普通文本,同样支持传递 声音,视频,图片等流媒体
-
W3C
-
万维网联盟组织
-
负责制定标准的: HTTP; HTML; HTML4.0;HTML5; xml; DOM…
-
万维网之父: 蒂姆 伯纳斯 李
-
-
浏览器向WEB服务器发送数据 : 叫做 :请求 (request)
-
WEB服务器向浏览器发送数据: 叫做 :响应 (response)
-
HTTP协议包括:
- 请求协议
- 浏览器向服务器发送数据的时候,这个发送的数据需要遵循的一套标准
- 响应协议
- 服务器向浏览器发送数据的时候,这个发送的数据需要遵循的一套标准
- 请求协议
-
HTTP协议就是提前制定好的一种消息模板
- 不管你是哪个品牌的浏览器,都是这么发
- 不管你是哪个品牌的web服务器,都是这么发
(4)HTTP的请求协议(B->S)(详细分析)
-
怎么查看协议的内容
-
使用浏览器 : F12 然后找到network,通过这个面板可以查看协议的内容
-
http的请求协议包括: (4部分)
- 请求行
- 请求头
- 空白行
- 请求体
-
http请求协议的具体报文 GET请求 (用户输入数据会在请求头里边)
-
GET /servlet04/getservlet?username=jack&userpwd=123 HTTP/1.1 //请求行 Host: localhost:8080 //请求头 Connection: keep-alive sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Referer: http://localhost:8080/servlet04/index.html Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cookie: Idea-cf4f63f1=866f0263-8599-4304-a94b-acdb719e6fbb 空白行 请求体
-
http请求协议的具体报文 POST请求
-
POST /servlet04/postservlet HTTP/1.1 请求行 Host: localhost:8080 请求头 Connection: keep-alive Content-Length: 18 Cache-Control: max-age=0 sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" Upgrade-Insecure-Requests: 1 Origin: http://localhost:8080 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Referer: http://localhost:8080/servlet04/index.html Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cookie: Idea-cf4f63f1=866f0263-8599-4304-a94b-acdb719e6fbb 空白行 username=jack&userpwd=123 请求体
- 请求行
- 包括三部分
- 第一部分 : 请求方式 (七种)
- get (常用)
- post (常用)
- delete
- put
- head
- options
- trace
- 第二部分 : URI : 统一资源标识符
- URI和URL什么关系,
- URL是包含URI的
- http://localhost:8080/servlet04/index.html 这是URL
- /servlet04/index.html 这是URI
- 有什么区别
- URI: 统一资源标识符:代表网络中某个资源的名字 , 但是通过URI是无法定位资源的
- URL: 统一资源定位符:代表网络中某个资源,.同时,通过URL是可以定位到该资源的
- URI和URL什么关系,
- 第三部分 : 协议版本号
- 第一部分 : 请求方式 (七种)
- 包括三部分
- 请求头
- 请求的主机
- 主机的端口
- 浏览器的信息
- 平台信息
- cookie信息
- …
- 空白行
- 是用来区分请求头和请求体的
- 请求体
- 向服务器发送的具体数据
- 请求行
-
get请求和post请求的区别
-
get请求在请求行上发送 (在请求行的URI后边,以一个"?"隔开)
- get请求发送数据的时候,数据会挂在URI的后面,并且在URI的后面添加一个"?" "?"后面是数据这样会导致发送的数据回显在浏览器的地址栏
- http://localhost:8080/servlet04/getservlet?username=jack&userpwd=123
- get请求只能发送普通的字符串,并且这个字符串的长度有限制的,这个不同浏览器有不同的限制
- get请求不能发送大数据量
- get请求发送数据的时候,数据会挂在URI的后面,并且在URI的后面添加一个"?" "?"后面是数据这样会导致发送的数据回显在浏览器的地址栏
-
post请求在请求体中发送
- 不会回显到浏览器的地址栏上,也就是说post的数据在地址栏上看不到
- post可以发送任何类型数据,包括普通字符串,流媒体等信息:视频,图片,声音等…
- post请求可以发送大数据量,理论上没有长度限制
-
不管你是get请求还是post请求,发送数据的格式是完全相同的,只是位置不同,格式都是
-
name=value&name=value&name=value
-
username=jack&userpwd=123
-
name是什么?
-
以form表单为例: form表单中的input标签的name
-
<form action="/servlet04/getservlet" method="get"> <!-- name就是这里起的名字 --> 用户名<input type="text" name="username" /><br> 密码 <input type="text" name="userpwd" /><br> <input type="submit" value="login"/> </form>
-
-
value是什么?
- 以form表单为例: form表单中的input标签的value (也就是用户输入的)
-
-
-
-
什么时候使用get请求,什么时候使用post请求
-
get请求在W3C中是这样说的: get请求比较适合向服务器端获取数据
-
post请求在W3C中是这样说的: post请求比较适合向服务器端传送数据
-
get请求是绝对安全的 , 因为get请求只是向服务器获取数据 不会对服务器造成危险
-
post请求是危险的 , 因为post请求是向服务器提交数据的,如果这些数据以后门的的方式进入到服务器当中,服务器是很危险的 , 另外post是提交数据 , 所以一般情况下,大部分会拦截 (监听) post请求
-
get请求支持缓存 ,
- 任何一个get请求最终的"响应结果" 都会被浏览器缓存起来,在浏览器缓存中
- 一个get请求的路径,对应 一个资源
- 一个get请求的路径 对应 一个资源
- …
- 实际上你只要发送一个get请求,浏览器第一件事就是先从缓存中找,如果浏览器缓存中没有,再从服务器上去获取
- 任何一个get请求最终的"响应结果" 都会被浏览器缓存起来,在浏览器缓存中
-
post是不支持缓存的
- post请求之后 , 服务器响应的结果不会在浏览器上缓存起来,因为这样没有意义
-
s
- 大部分的form表单提交 , 都是post方式 , 因为form表单中药填写大量信息 , 这些数据是收集用户信息 , 一般都是要传递给服务器 , 服务器将这些数据保存 / 修改等
- 如果表单中有敏感信息 , 还是建议使用post请求 , 因为get请求会回显到浏览器地址栏
- 做文件上传 . 一定是post请求 , 要传的数据不是不同文本
- 其他情况可以使用get请求
-
-
有没有这么一种情况,我希望get请求不缓存?每次get都去服务器获取资源
- 只要每一次get请求的请求路径不同即可.
- 可以在路径后边添加一个每时每刻都在变化的"时间戳"
-
怎么先服务器发送get请求,怎么向服务器发送post请求?
-
到目前为止,只有一种情况可以发送post请求
-
使用form表单,并且form表单中的method的属性值为post
-
<form action="/servlet04/postservlet" method="post"> 用户名<input type="text" name="username" /><br> 密码 <input type="password" name="userpwd" /><br> <input type="submit" value="login"/> </form>
-
其他所有的情况一律是get请求
- 在浏览器的地址栏上输入URL,敲回车,属于get请求
- 在浏览器上直接点击超链接的时候,属于get请求
- 使用form表单提交数据的时候,没有method属性,默认是get
- …
-
(5)HTTP的响应协议(S->B)(详细分析)
-
怎么查看协议的内容
-
使用浏览器 : F12 然后找到network,通过这个面板可以查看协议的内容
-
http响应协议包括: (4部分)
- 状态行
- 响应头
- 空白行
- 响应体
-
http响应协议的具体报文
-
HTTP/1.1 200 ok //状态行 Content-Type: text/html;charset=UTF-8 //响应头 Content-Length: 111 Date: Fri, 14 Jan 2022 13:47:01 GMT Keep-Alive: timeout=20 Connection: keep-alive //响应头 //空白行 <!doctype html> //响应体 <html> <head> <title>from get servlet</title> </head> <body> <h1>from get servlet </h1> </body> </html>
- 状态行
- 三部分组成
- 第一部分: 协议版本号 (HTTP/1.1)
- 第二部分: 状态码 (200) http协议中规定的响应状态号,不同的响应结果对应不同的号码
- 200: 表示请求响应成功,正常结束
- 404: 表示访问的资源不存在,通常是因为要么是你路径写错了,要么是路径写对了,但是服务器中对应的资源没有启动成功,总之404一般是前端错误
- 405: 表示前端发送的请求方式和后端请求的处理方式不一致所致的
- 比如:前端的请求是POST,后端的处理方式是get方式进行处理,发生405
- 比如:前端的请求是get,后端的处理方式是post方式进行处理,发生405
- 500: 表示服务器端的程序出现异常,一般会认为是服务器端的错误导致
- 1XX开始的是是说 服务器接收浏览器消息 , 但是没有完成 , 等待一段时间后 , 发送 1XX的状态码 , 询问客户端还要不要发送数据
- 2XX开始 一般表示成功
- 3XX开始
- 一般表示重定向(302)
- 访问缓存 (304)
- 4XX开始的一般是浏览器端的错误
- 请求路径没有对应的资源 (404)
- 请求方式没有对应的doXxx方法 (405)
- 5XX开始的一般是服务器端的错误
- 服务器内部出现异常 (500)
- 第三部分: 状态的描述信息
- ok表示正常成功结束
- not found 表示资源找不到
- 三部分组成
- 响应头
- 响应的内容类型
- 相应的内容长度
- 相应的时间
- …
- 空白行
- 用来分隔响应头和响应体
- 响应体
- 响应体就是响应的正文,这些内容是一个长的字符串,这个字符串被浏览器渲染,解释并执行,最终展示出效果
- 状态行
16.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请求接口 (接口) - jakarta.servlet.ServletResponse //Servlet响应接口 (接口) - jakarta.servlet.ServletException //Servlet异常 (类) - jakarta.servlet.GenericServlet //标准通用的Servlet类 (抽象类)
-
-
http包下都有那些接口和类呢 jakarta.servlet.http 包下
-
- jakarta.servlet.http.HttpServlet (HTTP协议专用的Servlet类) (抽象类) - jakarta.servlet.http.HttpServletRequest (HTTP协议专用的请求对象) - jakarta.servlet.http.HttpServletResponse (HTTP协议专用的相应对象)
-
-
注意HttpServletRequest对象中封装了那些信息?
- HttpServletRequest 简称 request对象
- HttpServletRequest 中封装了请求协议的全部内容
- Tomcat服务器 (WEB服务器) 将’请求协议’中的数据全部解析出来 , 然后将这些数据封装到了request对象里边了
- 也就是说 , 我们只需要面向HttpServletRequest对象 , 就可以获取请求协议中的数据
-
HttpServletResponse对象是专门用来响应HTTP协议到浏览器的
(1)HttpServlet源码分析
-
package com.sichen.javaweb.servlet; import jakarta.servlet.http.HttpServlet; //调用service方法 public abstract class helloServlet extends HttpServlet { //用户第一次请求 , 创建HelloServlet对象的时候 , 会执行这个无参数构建方法 public HelloServlet(){ } } //-------------------------------------------------------------------------------- // 创建对象后 调init方法 //没有init方法 , 那么就会执行父类HttpServlet中的init方法 //HttpServlet中也没有init方法 , 那么就会继续执行父类的父类 GenericServlet中的init方法 public abstract class GenericServlet implements Servlet, ServletConfig, Serializable { //用户第一次请求的时候 , HelloServlet对象第一次创建的时候 public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); } //用户第一次请求的时候 , 带有参数的init方法会调用 这个没有参数的init方法 public void init() throws ServletException { } } //-------------------------------------------------------------------------------- //init方法执行完后 , 调用service方法 //这个service就是一个模板类 public abstract class HttpServlet extends GenericServlet { //用户发送第一次请求的时候 , 这个service方法会执行 //用户执行一次请求 , 这个service方法就会执行一次 public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { //将ServletRequest 和 ServletResponse 向下转型为带有Http的HttpServletRequest 和 HttpServletResponse request = (HttpServletRequest)req; response = (HttpServletResponse)res; } catch (ClassCastException var6) { throw new ServletException(lStrings.getString("http.non_http")); } //调用重载的service方法 this.service(request, response); } //这个service方法的两个参数都是带有Http的 //这个service是一个模板方法 //在该方法中定义核心骨架 , 具体的实现步骤延迟到子类去完成 protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取请求方式 //这个请求方式最终可能是 : "" //注意 : req.getMethod() 方法获取的是请求方法 , 可能是七种之一 // GET , HEAD , POST , PUT , DELETE , OPTIONS , TRACE 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); } } //请求是get请求就调用doGet方法 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String msg = lStrings.getString("http.method_get_not_supported"); this.sendMethodNotAllowed(req, resp, msg); } //请求是post方法,就调用post方法 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String msg = lStrings.getString("http.method_post_not_supported"); this.sendMethodNotAllowed(req, resp, msg); } } //------------------------------------------------------------------------------- /* 通过以上代码分析: 假设前端发送的是get请求 , 后端程序员重写的是doPost() 假设前端发送的是post请求 , 后端程序员重写的是doGet() 会发生什么? 子类中没有这个方法,就会调用父类的方法 , 就会报405的错误 405表示前端的错误 , 发送的请求和后端的方式不对 , 不是服务器需要的请求方法 通过以上源代码可以知道: 只要HttpServlet类中的doGet方法 , 或是Dopost方法执行了 , 必然405 怎么避免405的错误呢 后端重写了doGet方法 , 前端一定要发送get请求 后端重写了doPost方法 , 前端一定要发送post请求 这样可以避免405错误 这样前端要发送什么请求 , 是后端说了算 , 后端让发送什么请求 , 前端必须发送什么请求 , 不然就报错 */
-
我们编写的HelloServlet 直接继承HttpServlet ,直接重写HttpServlet类中的 service()方法可以吗?
- 可以 , 只不过你享受不到405错误 , 享受不到HTTP协议专属的服务
- 405错误是一种保护机制
- 一般不建议重写service方法
(2)注意: 编写Servlet类最终步骤
-
我们以后编写Servlet类的时候,实际上是不会直接继承GenericServlet的,因为我们是B/S结构的系统,这种系统是基于HTTP超文本传输协议的,在Servlet规范中,提供了一个类叫做HttpServlet,他是专门为HTTP协议准备的一个Servlet类,我们编写的Servlet类要继承HttpServlet (HttpServlet是HTTP协议专用的), 使用HttpServlet处理HTTP协议更便捷,但是你要知道他们之间的继承结构:
-
jakarta.servlet.Servlet (接口) [爷爷] jakarta.servlet.GenericServlet implements Servlet (抽象类) [儿子] jakarta.servlet.http.HttpServlet extends GenericServlet (抽象类) [孙子] //我们以后编写Servlet类直接继承HttpServlet就行了
-
到今天我们终于得到了Servlet类的开发步骤
-
第一步 : 编写一个Servlet类 , 直接继承HttpServlet
-
第二步 : 重写doGet方法或者重写doPost方法 , 到底重写谁 , java程序员说了算
-
第三步 : 将Servlet类配置到web.xml文件当中
-
第四步 : 准备前端的页面 ( form表单 ) , form表单中指定请求路径
-
注意 : 表单中要加项目名
-
项目名 web.xml文件中的跳转路径 <form action="/servlet06/he" method="post"> <input type="submit" value="提交">
-
(3)HttpServletRequest
-
out.print(request); //org.apache.catalina.connector.RequestFacade@1964d5ef
-
HttpServletRequest是一个接口 , 全限定名称 : jakarta.servlet.http.HttpServletRequest
-
HttpServletRequest接口是Servlet规范中的一员
-
HttpServletRequest接口的父接口是 : ServletRequest
-
public interface HttpServletRequest extends ServletRequest {}
-
-
HttpServletRequest接口的实现类谁写的 ? HttpServletRequest对象是谁给创建的 ?
-
通过测试 : org.apache.catalina.connector.RequestFacade
-
public class RequestFacade implements HttpServletRequest {}
-
测试结果 : Tomcat服务器(WEB服务器)实现了HttpServletRequest接口 , 还是说明了Tomcat服务器实现了Servlet规范
-
而对于我们java程序员来说 , 实际上不需要关心这个 , 我们只需要面向接口编程即可 , 我们关心的是HttpServletRequest接口中有哪些方法 , 这些方法可以完成哪些功能?
-
-
HttpServletRequest对象中都有什么信息 ? 都包装了什么信息 ?
-
HttpServletRequest对象是Tomcat服务器负责创建的 ,
-
这个对象中封装了HTTP请求协议的全部内容
-
实际上用户发送请求的时候 , 遵循了HTTP协议 , 发送的是HTTP的请求协议 ,
Tomcat服务器将HTTP协议中的信息以及数据解析出来 , 然后Tomcat服务器把这些信息封装到HttpServletRequest对象当中 , 传给了我们Java程序员
-
java程序员面向HttpServletRequest接口编程 , 调用方法就可以获取请求信息了
-
-
request对象和response对象的生命周期 :
- request对象和response对象 一个是请求对象 , 一个是响应对象 , 这两个对象只在当前请求中有效
- 一次请求对应一个request.
- 一次响应对应一个response.
①HttpServletRequest接口中有哪些常用的方法 ?
String getParameter(String name)//通过name获取value这个一维数组的第一个元素,(这个方法最常用)
Map<String,String[]> getParameterMap() //这个是获取整个map集合
Enumeration<String> getParameterNames() //这个是获取map中的name
String[] getParameterValues(String name) //这个是通过name获取value , 一般用在复选框的地方 , 获取整个一维数组
//以上的四个方法和获取用户输入的数据有关
-
思考 : 如果是你 , 前端的form表单提交了数据之后 , 你准备怎么储存这些数据 , 你准备采用什么样的数据结构去存储这些数据呢 ?
-
前端提交的数据格式是 :
-
username=jack&password=123456&aihao=s&aihao=d&aihao=t
-
注意 : 前端表单提交的数据 , 假设提交了120这样的数字 , 其实是以字符串的形式提交的 , 所以前端提交的是字符串 , 后端获取的也永远是一个字符串
-
我会采用Map集合来存储
-
Map<String , String> //这是不正确的 //key存储String //value存储String //这种方法是不对的 , //如果采用以上的数据结构存储会发现key重复的时候value覆盖 key value //------------------- username jack password 123456 aihao s aihao d (后边会覆盖前边的) aihao t (后边会覆盖前边的) //----------------------------------------------------- //用这种方法存储 Map<String , String[]> key存储String value存储String[] key value //------------------- username {"jack"} password {"123456"} aihao {"s", "d", "t"}
-
-
注意 : 通常是这样使用的 :
-
package com.sichen.javaweb.servlet; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.Enumeration; public class RequestTextServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); //获取的是所有的name Enumeration<String> parameterNames = request.getParameterNames(); while (parameterNames.hasMoreElements()){ String s = parameterNames.nextElement(); //这里获取的是对应name中的所有value , 然后下边通过增强for循环遍历所有的value String[] parameterValues = request.getParameterValues(s); out.print(s +"="); //通过增强for循环遍历所有的value for (String values : parameterValues){ out.print(values+","); } out.println(""); } } }
-
-
② request对象实际上又被称为 “请求域对象”
-
请求域对象 :
-
要比应用域对象范围小很多 , 生命周期短很多
-
只在一次请求中存活
-
请求域对象也有三个方法 :
-
void setAttribute(String name ,Object obj); //向域当中绑定数据 Object getAttribute(String name); //从域当中根据name获取数据 void removeAttribute(String name); //将域当中绑定的数据移除
-
请求域和应用域的选用原则
- 尽量使用小的的域对象 , 因为小的域对象占用的资源较少
③Servlet中的转发机制 (RequestDispatcher)
-
执行了Aservlet之后 , 跳转到Bservlet (这个跳转可以使用Servlet的转发机制)
-
//这样可以吗? 在Aservlet中new一个Bservlet对象 , // 然后调用Bservlet对象的doGet方法 , 把request对象传过去? Bservlet bservlet = new Bservlet(); bservlet.doGet(request , response); //不行 , 因为你自己new的对象不受服务器的管理 , 违背了Servlet规范 //
-
-
怎么进行转发:
-
第一步 : 获取请求转发器对象
-
RequestDispatcher getRequestDispatcher(java.lang.String path) //相当于吧"/b"这个路径包装到请求转发器中 , 实际上是把下一个跳转的资源路径通知给Tomcat服务器了 RequestDispatcher dispatcher = request.getRequestDispatcher("/b"); //括号中放要转发的资源的路径
-
第二步 : 调用请求转发器RequestDispatcher的 forward方法 ,进行转发
-
注意 : 转发的时候这两个参数很重要
-
void forward(ServletRequest request, ServletResponse response) //将这个传过去 dispatcher.forward(request , response);
-
//两步合一 request.getRequestDispatcher("/b").forward(request , response);
-
-
这么做有什么好处呢?
-
将数据放到ServletContext应用域中 , 当然是可以的 , 但是应用域范围太大 , 占用资源太多,不建议使用
-
可以将数据放到request域当中 , 然后Aservlet转发到Bservlet中 , 在保证Aservlet和Bservlet在同一次请求当中 , 这样就可以做到两个Servlet ,或者多个Servlet共享一份数据
-
//Aservlet中在request对象中绑定数据 , 转发给Bservlet public class Aservlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获取当前系统时间 Date date = new Date(); //向request域中绑定数据 request.setAttribute("systime",date); //获取转发器对象 RequestDispatcher dispatcher = request.getRequestDispatcher("/b"); //转发参数 dispatcher.forward(request , response); } } //Bservlet 得到数据 ,并输出 public class Bservlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Object systime = request.getAttribute("systime"); response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("request域当中获取的系统时间" + systime ); } }
-
-
转发的下一个资源必须是一个Servlet吗?
-
不一定 , 只要是Tomcat服务器当中的合法资源 , 都是可以转发的 例如 : html…
-
注意 : 转发的时候路径要注意 : 转发的路径以"/"开始 , 不添加项目名
-
request.getRequestDispatcher("/index.html").forward(request , response);
-
-
关于request对象中两个非常容易混淆的方法
-
String username = request.getParameter("username"); Object obj = request.getAttribute("name"); //以上两个方法的区别是什么? //第一个方法 : 获得的是用户浏览器上提交的数据 //第二个方法 : 获得的是请求域当中绑定的数据
-
HttpServletRequest的其他常用方法
①获取客户端IP地址,远程主机和远程端口
-
java.lang.String getRemoteAddr() //获取IP地址 request.getRemoteAddr(); //输出 127.0.0.1 因为我是本机访问 java.lang.String getRemoteHost() //获取远程主机 int getRemotePort() //获取远程端口
②解决post请求(get请求) 请求体中文编码问题(Tomcat 9及之前)
-
//设置请求体的字符集 (处理psot请求的请求体中文乱码问题 , 这种方式并不能解决get请求的乱码问题) //Tomcat10之后的字符集默认就是 UTF-8 //Tomcat9之前(包括9) 如果前端请求体提交的数据是中文 , 就会出现乱码现象 , 怎么解决,就用下边的代码 void setCharacterEncoding(java.lang.String env) request.setCharacterEncoding("UTF-8");
-
Get请求乱码怎么解决?
-
修改CATALINA_HOME/conf/server.xml配置文件 增加下边的代码即可
-
-
注意 : Tomcat 8之前Get请求会出现乱码的 , 但是Tomcat8之后Get请求就解决了乱码问题 , 默认就是UTF-8,不用再增加上边的代码了
④动态获取根路径 (虚拟目录)
-
//也是使用contextpath(); 这个方法 和ServletContext中的方法一样 String getcontextPath(); //使用 String request.getcontextPath(); // 输出: /servlet08
⑤获取请求方式
-
String getMethod(); //使用 String request.getMethod(); // 输出: 例子: POST
⑥获取请求的URI(统一资源标识符)
-
String getRequestURI(); //使用 request.getRequestURI(); //输出: /servlet08/request
⑦获取Servlet 路径
-
String getServletPath(); //使用 request.getServletPath(); //输出: /request
(4)HttpServletResponse
①设置响应状态码
setStatus(int sc)
②设置响应头
setHeader(String name , String value)
③重定向
//方法一
public void sendRedirect(String location)
//使用
response.sendRedirect("/项目名/b");
//方法二
//设置响应状态码
response.setStatus(302);
//设置响应头
response.setHeader("location" , "/虚拟目录/路径")
**注意:**跳转的资源只要是服务器中的合法资源即可跳转
④获取输出流
(1)字符输出流 : PrintWriter getWriter()
PrintWriter pw = response.getWriter();
//这个流不用关闭
//这里使用 pw.write(String s)方法 来输出字符
//因为使用这个流不需要刷新 , 它是response调用的方法 , 因为response在一次请求之后就会销毁 , 这个流也随之销毁
这个流还有一个方法 : print() , 这个方法输出的对象可以自动刷新
(2)字节输出流 : ServletOutputStream getOutputStream
response.setContentType("text/html;charset=utf-8");
ServletOutputStream outputStream = response.getOutputStream();
//这里要注意,在输出的时候 , 要在后边加.getBytes("utf-8")
outputStream.write("你好".getBytes("UTF-8"));
⑤解决响应乱码(中文输出乱码)问题 (Tomcat 9及之前)
-
response.setContextType("text/html"); PrintWriter out = response.getWriter(); out.print("我是一个Java菜鸟"); //在Tomcat 9 及之前的版本 , 这样输出到浏览器 , 中文会乱码 , 解决方法如下 //response.setContextType("text/html;charset=UTF-8");
-
注意 :
-
2020年2月28日 Tomcat10发布之后Tomcat就在 conf/web.xml 中使用
-
和
-
将默认请求和响应字符编码设置为 UTF-8。 , 不再需要进行配置即可直接输入输出中文 , 以及大多数语言
(5)跳转和重定向的区别
-
在一个web应用中通过两种方式 , 可以进行资源的跳转
- 第一种方式 : 转发
- 第二种方式 : 重定向
-
转发和重定向的区别?
-
代码上有什么区别?
-
转发 (是一次请求!)
-
//获取请求转发器 RequestDispatcher dispatcher = request.getRequestDispatcher("/转发到的路径"); //调用转发器的forward方法 , 完成转发 dispatcher.forward(request,response); //合并一行代码 request.getRequestDispatcher("/转发到的路径,以斜杠"/"开始 , 不用加项目名").forward(request,response); //传这两个参数是为了 : 将数据绑定到request中 , 可以多个Servlet共享数据 , 这多个Servlet是一个请求域 , 相当于是一次请求
-
-
重定向
-
//重定向时的路径当中需要以项目名开始 //原因是 response具有响应能力 , 他可以把这个路径重新响应给浏览器 , 浏览器能够自发的向服务器发送一个请求 //response对象将这个路径 "/servlet10/b"响应给了浏览器 //浏览器又自发的向浏览器发送了一次全新的请求 http://localhost:8080/servlet10/b //所以浏览器一共发送了两次请求 //第一次 : http://localhost:8080/servlet10/a //第二次 : http://localhost:8080/servlet10/b //最终浏览器地址栏的地址会显示最后一次请求的路径 , 所以会导致浏览器地址栏的路径改变 response.sendRedirect("项目名/b");
-
-
-
形式上有什么区别?
- 转发 (一次请求)
- 在浏览器上发送的请求是 : http://localhost:8080/servlet10/a 最终请求结束之后 , 浏览器地址栏上的地址还是这个 , 没变
- 重定向 (两次请求)
- 将重定向中的路径反馈给浏览器 , 浏览器自发的重新发送一次请求 给浏览器 , 所以是两次请求 , 最终浏览器的地址栏会发生改变
- 转发 (一次请求)
-
转发和重定向的本质区别 :
- 转发 : 是由Web服务器来控制的 , A资源转发到B资源 , 这个跳转动作是由Tomcat服务器内部来进行操作的
转发只能在内部资源内跳转
-
重定向 : 是由浏览器控制的 , 具体跳转到那个页面 , 是由浏览器说了算 可有重定向到其他服务器的资源
-
转发和重定向应该如何选择?
- 什么时候使用转发?
- 如果在上一个Servlet对象中向Servlet域当中绑定了数据 , 希望从下一个Servlet当中把request域当中的数据取出来 , 使用转发机制
- 什么时候使用重定向?
- 其他情况一般都使用重定向 , 重定向使用较多 ,
- 比如 , 新增数据 , 删除数据的时候 , 要用doPost方法 , 但是返回新增成功后 , 跳转回之前页面的时候 , 之前页面中只有doGet方法 , 这样会报405错误 ,
- 使用重定向就可以解决这个问题
- 什么时候使用转发?
-
转发会出现浏览器的刷新问题
- 就是浏览器在点击刷新的时候 , 会重新发送一次 最近的请求 , 如果使用转发的话 , 会重复执行多次请求 , 而这些请求会重新执行编写的程序中的方法 ,
- 比如新增数据 , 每刷新一次 , 就会执行一次新增操作 ,造成在数据库中增加很多数据
-
17.关于一个web站点的欢迎页面
(1)什么是一个web站点的欢迎页面?
- 对于一个webapp来说 , 我们是可以设置它的欢迎页面的
- 设置了欢迎页面之后 , 当你访问这个webapp的时候 , 没有指定任何资源路径 , 这个时候会默认访问你的欢迎页面
- 我们一般的访问方式是:
- http://localhost:8080/servlet06/login.html 这种方式指定了要访问的就是login.html资源
- 如果我访问的方式是:
- http://localhost:8080/servlet06 如果我们访问的就是这个站点 , 没有指定具体的资源路径 , 它默认访问谁呢?
- 默认访问你的欢迎页面 .
(2)怎么设置你的欢迎页面
-
第一步: 在idea的WEB目录下新建一个login.html
-
第二步 : 在web.xml文件中进行以下配置
-
<welcome-file-list> <welcome-file>login.html</welcome-file> </welcome-file-list>
-
第三步 : 启动服务器 , 浏览器地址栏输入地址
-
http://localhost:8080/ + 项目名
如果在webapp根目录下设置一个文件夹 , 文件夹中在设置一个文件夹 , 文件夹中是欢迎页面 , 那么怎么找到这个欢迎页面呢?
-
<welcome-file-list> <welcome-file>page1/page2/index.html</welcome-file> </welcome-file-list>
-
注意 : 路径不需要以 “/” 开始 , 并且路径默认从webapp的根下开始找
-
一个webapp是可以设置多个欢迎页的 , 越靠上的优先级越高 ,找不到的继续向下找 ,
都找不到就报404
-
<welcome-file-list> <welcome-file>login.html</welcome-file> <welcome-file>page1/page2/index.html</welcome-file> </welcome-file-list>
-
注意 : 在WEB的根下设置一个index.html 就不用在web.xml文件下重新设置欢迎页面 ,
这是因为Tomcat服务器默认是以index.html 为初始页面的
-
实际上配置欢迎页面有两个地方可以配置:
- 一个是在webapp内部的web.xml文件中 (在这个地方配置的属于局部变量)
- 一个是在CATALINA_HOME/conf/web.xml文件中进行配置 (在这个地方配置属于全局变量)
-
注意 : 局部优先
(3)欢迎页可以是一个Servlet吗?
-
可以
-
静态资源 : XXX.html
-
动态资源 : Servlet
-
<servlet> <servlet-name>welcome</servlet-name> <servlet-class>com.sichen.javaweb.servlet.welcomeServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>welcome</servlet-name> <url-pattern>/welcome</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>welcome</welcome-file> </welcome-file-list>
18.关于WEB-INF目录
- 在WEB-INF目录下新建一个文件 : welcome.html
- 打开浏览器访问 : http://localhost:8080/servlet07/WEB-INF/welcome.html 出现了404错误
- 注意 : 放在WEB-INF目录下的资源是受到保护的 , 在浏览器上不能够通过路径直接访问
- 所以像HTML , CSS , js , image等静态资源一定要放在WEB-INF目录外边
19.多线程处理
Tomcat服务器是支持多线程的
大部分时间我们我们要考虑的是代码放在多线程的环境下,数据的安全的呢
20.大家目前位置都接触过那些缓存机制?
- 堆内存当中的字符串常量池
- 例如: "abc"先是在字符串常量池中查找,如果有,直接拿来用,如果没有则新建,然后再放入字符串常量池中
- 堆内存中的整数型常量池
- [-128 ~ 127] 一共256个Integer类型的引用,放在整数型常量池中,没有超出这个范围的话,直接从常量池中获取
- 连接池 (Connection Cache)
- 这里所说的连接池是说:java语言连接数据库的连接对象:java.sql.Connection对象
- JVM是一个进程,MYSQL数据库是一个进程,进程和进程之间打开通道是很费劲的,是很耗费资源的,怎么办?
- 可以先创建好N个Connection连接对象,再将连接对象放到一个集合当中,我们把这个放油Connection对象的集合称为;连接池,每一次用户连接的时候,不需要新建连接对象,省去了新建的环节,直接从连接池中获取连接对象,大大提升访问效率
- 连接池:
- 效率高,安全,
- 最大连接数,最小连接数,
- 用户访问只能从连接池中获取对象,无法重新创建,防止了大量用户连接.,到达一定量就不能连接了
- 线程池
- Tomcat服务器本身就是支持多线程的
- Tomcat服务器是在用户发送一次请求后就,新建一个Thread线程对象吗?
- 当然不是,实际上是在Tomcat服务器启动的时候,会先创建好N多个线程Thread对象,然后将线程对象放到集合中去,称为线程池,用户发送请求之后,需要有一个对应的线程对象,来处理这个请求,这个时候线程对象就会直接从线程池中拿,效率较高
- 所有的web服务器(或者说是应用服务器)都是支持多线程的,都有线程池机制
- redis
- NoSQL数据库,非关系型数据库,缓存数据库
- 向ServletContext这个应用域中存放数据,也等同于将数据存放到缓存池当中了
- 第一 : 所有用户共享的数据
- 第二 : 这个共享的数据量很小
- 第三 : 这个共享的数据很少的修改
- 在以上三个条件都满足的情况下 , 使用这个应用域对象 , 可以大大提高我们程序的执行效率
- 实际上向应用域中绑定数据 , 就相当于把数据放到了缓存(Cache)当中 , 然后用户访问的时候直接从缓存中取 , 减少io操作 , 大大提升系统的性能 , 所以缓存技术是提高系统性能的重要手段
13.transient修饰的属性不会被序列化(最后了解下)
表示可以被序列化
java.io.Serializable
区分类的序列号: 只要版本号一样就认为是一个class
private static final long serialVersionUID = 1L;
21.练习:使用纯Servlet实现单表的CRUD操作
-
使用纯粹的Servlet完成单表 [对部门的] 的增删改查操作 (B/S结构的)
-
实现步骤:
-
第一步 : 准备一个数据库表
-
写一个sql脚本 (执行sql脚本 , 在cmd中 , 这个数据库下 ,source 加)
-
drop table if exists dept; create table dept( deptno int primary key, dname varchar(255), loc varchar(255) ); insert into dept(deptno,dname,loc) values(10,'xiaoshoubu','shanghai'); insert into dept(deptno,dname,loc) values(20 ,'yanfabu','beijing'); insert into dept(deptno,dname,loc) values(30 ,'jishubu','guangzhou'); insert into dept(deptno,dname,loc) values(40 ,'meitibu','shenzhen'); commit; select * from dept;
-
-
-
第二步: 准备一套HTML页面 , (项目原型) [前端工具HBuilder]
- 把HTML页面准备好
- 然后将HTML页面中的链接都跑通 (页面流转没问题)
- 应该设计那些页面呢?
- 欢迎页面 welcome.html
- 列表页面 list.html (以列表页面为核心 , 展开其他操作)
- 新增页面 add.html
- 修改页面 edit.html
- 删除页面
- 查询页面 detail.html
readonly 只读 , 解决那些属性不能修改的东西
-
第三步 : 分析我们系统包括那些功能
- 什么叫做一个功能?
- 只要你这个操作连接了数据库 , 就是一个独立的功能
- 包括那些功能
- 查看部门列表
- 新增部门
- 删除部门
- 查看部门详情
- 跳转到修改页面
- 修改部门
- 什么叫做一个功能?
-
第四步 : 在idea当中搭建开发环境
- 创建一个webapp
- 向webapp中添加MySQL驱动
- 必须在WEB-INF目录下必须新建一个lib包放mysql驱动
- jdbc的工具类
- 将所有的HTML页面拷贝到web目录下
-
第五步 : 实现第一个功能 :查看部门列表
-
我们应该怎么去实现一个功能呢?
-
建议 : 你可以从后端往前端一步一步写 , 也可以从前端一步一步往后端写
-
不要想起来什么写什么 , 你写代码的过程 ,最好是程序的执行过程 , 程序执行哪里,你就写哪里 , 这样一个顺序流下来之后 , 不会出现什么错误 , 意外
-
从那里开始?
- 假设从前段开始 , 那么一定是从用户点击按钮开始的
-
第一 : 先修改前端页面的超链接
-
<a href="/oa/dept/list" >查看部门列表</a>
-
-
第二 : 编写xml文件
-
<servlet> <servlet-name>list</servlet-name> <servlet-class>com.sichen.oa.action.DeptListServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>list</servlet-name> <!-- 注意web.xml文件中的路径也是以"/"开始的 , 但是不需要加项目名--> <url-pattern>/dept/list</url-pattern> </servlet-mapping>
-
-
第三 : 编写DeptListServlet类继承HttpServlet ,重写doget方法 (因为超链接是get请求)
-
package com.sichen.oa.action; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; public class DeptListServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }
-
-
第四 : 在DeptListServlet类中doGet方法中连接数据库 , 查询所有的部门 , 动态的展示部门列表
-
分析list.html页面中的哪部分是固定死的 , 哪部分是要动态展示的 list.html页面中的内容所有的双引号要替换成单引号 , 因为out.print("")中有一个双引号,会发生冲突
-
DeptListServlet.java
package com.sichen.oa.action; import com.sichen.oa.utils.DButil; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class DeptListServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //设置响应类型以及字符集 防止中文乱码问题 response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); out.print(" <!DOCTYPE html>"); out.print(" <html>"); out.print(" <head>"); out.print(" <meta charset='utf-8'>"); out.print(" <title>部门列表页面</title>"); out.print(" </head>"); out.print(" <body>"); out.print(" <h1 align='center' >部门列表</h1>"); out.print(" <hr>"); out.print(" <table border='1px' align='center' width='50%'>"); out.print(" <tr>"); out.print(" <th>序号</th>"); out.print(" <th>部门编号</th>"); out.print(" <th>部门名称</th>"); out.print(" <th>部门地址</th>"); out.print(" <th>操作</th>"); out.print(" </tr>"); //上边一部分是死的 //动态输出一个页面 //连接数据库 , 查询所有的部门 Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { //获取连接 conn = DButil.getconnection(); //获取预编译的数据库操作对象 String sql = "select deptno,dname,loc from ceshi"; ps = conn.prepareStatement(sql); //执行SQL语句 rs = ps.executeQuery(); //处理结果集 int i = 0; while (rs.next()){ String deptno = rs.getString("deptno"); String dname = rs.getString("dname"); String loc = rs.getString("loc"); //这一部分是动态的 out.print(" <tr>"); out.print(" <td>"+(++i)+"</td>"); out.print(" <td>"+deptno+"</td>"); out.print(" <td>"+dname+"</td>"); out.print(" <td>"+loc+"</td>"); out.print(" <td>"); out.print(" <a href=''>删除</a>"); out.print(" <a href=''>修改</a>"); out.print(" <a href=''>详情</a>"); out.print(" </td>"); out.print(" </tr>"); } } catch (SQLException e) { e.printStackTrace(); }finally{ //释放资源 DButil.close(conn , ps , rs); } //下边一部分是死的 out.print(" </table>"); out.print(" <hr >"); out.print(" <a href='add.html' >新增部门</a>"); out.print(" </body>"); out.print(" </html>"); } }
-
写完之后会发现只使用Servlet开发代码很繁琐
-
-
第六步 : 实现详情功能
-
假设从前端开始 , 那么一定是从用户点击详情按钮开始的
-
<a href='写一个路径'>详情</a>
详情是需要连接数据库的 , 所以这个超连接点击之后也是需要执行java代码的 , 所以要将这个超连接的路径修改一下
-
/*注意 :这个路径是加项目名的*/ <a href='/oa/dept/detail'>详情</a>
-
-
技巧 : get请求的格式要记住 URL后边加一个"?"后边加请求传递的名字和值
-
out.print("<a href='"+contextPath+"/dept/detail?deptno="+deptno+"'>详情</a>");
-
DeptDetailServlet.java
package com.sichen.oa.action; import com.sichen.oa.utils.DButil; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class DeptDetailServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获取请求传过来的deptno String deptno = request.getParameter("deptno"); response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); out.print(" <!DOCTYPE html>"); out.print(" <html>"); out.print(" <head>"); out.print(" <meta charset='utf-8'>"); out.print(" <title>部门详情</title>"); out.print(" </head>"); out.print(" <body>"); out.print(" <h1>部门详情</h1>"); out.print(" <hr >"); //编写数据库 Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { //获取连接对象 conn = DButil.getconnection(); //获取预操作对象 String sql = "select dname,loc from ceshi where deptno=?; ps = conn.prepareStatement(sql); ps.setString(1,deptno); rs = ps.executeQuery(); //处理结果集 //这里用if 而不用while是因为deptno在数据库中是主键 , 只能有一条记录,不用循环了 if (rs.next()){ String dname = rs.getString("dname"); String loc = rs.getString("loc"); out.print(" 部门编号 : "+deptno+" <br>"); out.print(" 部门名称 : "+dname+" <br>"); out.print(" 部门地址 : "+loc+" <br> "); } } catch (SQLException e) { e.printStackTrace(); }finally{ //释放资源 DButil.close(conn,ps,rs); } out.print(" <input type='button' value='后退' οnclick='window.history.back()' />"); out.print(" </body>"); out.print(" </html>"); } }
-
-
第七步 : 删除部门
-
怎么开始? 从哪里开始 ? 从前端页面开始 , 用户点击删除按钮的时候 , 应该提示用户是否删除 , 因为删除操作是比较危险的 , 任何系统在进行删除操作之前是必须提醒用户的 , 因为这个删除操作可能是用户误操作的 , (在前端页面上写js代码 , 来提示用户是否删除)
-
在DeptListServlet.java中加上这一段代码
-
//在删除中修改为以下代码 out.print("<a href='javascript:void(0)' οnclick='del("+deptno+")'>删除</a>");
-
out.print("<script type='text/javascript'>"); out.print("function del(dno){"); out.print("var ok = window.confirm('亲 , 删除了不可恢复哦');" ); out.print("if (ok){"); out.print("document.location.href = '"+contextPath+"/dept/delete?deptno='+dno ;"); out.print("}"); out.print("}"); out.print("</script>");
-
然后编写DeptDeleteServlet.java
-
package com.sichen.oa.action; import com.sichen.oa.utils.DButil; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; public class DeptDeleteServlet extends HttpServlet { //定义的这个值是后边判断删除影响的数量的 int s = 0; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String deptno = request.getParameter("deptno"); Connection conn = null; PreparedStatement ps = null; try { conn = DButil.getconnection(); //数据库操作删除 , 修改的时候最好开启事务 ,这是设置事务不自动提交 conn.setAutoCommit(false); String sql = "delete from ceshi where deptno=?"; ps = conn.prepareStatement(sql); ps.setString(1 , deptno); s = ps.executeUpdate(); //提交事务 conn.commit(); } catch (SQLException e) { //这个代码要放在catch中 , 如果出现异常 , conn不为空说明是有操作的 , 但是出现异常,那么就把数据回滚到事务开始之前的状态 , 所以要放在异常处理的里边 if (conn!=null){ try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); }finally{ DButil.close(conn,ps,null); } //最后判断删除是否成功 , 然后跳转页面 if (s==1) { //删除成功 //仍然跳转到部门列表页面 //部门列表页面显示需要执行另一个Servlet , 怎么办 ,转发机制 request.getRequestDispatcher("/dept/list").forward(request , response); }else { //删除失败 request.getRequestDispatcher("/deletefalse.html").forward(request , response); } } }
- 删除成功或者失败的时候的一个处理 , (这里我们使用的是转发机制 , 因为我们到这里还没有学重定向机制) - ```java //最后判断删除是否成功 , 然后跳转页面 if (s==1) { //删除成功 //仍然跳转到部门列表页面 //部门列表页面显示需要执行另一个Servlet , 怎么办 ,转发机制 //转发的下一个资源不仅能是一个java程序 , 还可以是一个静态网页 request.getRequestDispatcher("/dept/list").forward(request , response); }else { //删除失败 request.getRequestDispatcher("/deletefalse.html").forward(request , response); }
-
-
第八步 :新增部门
-
在list页面中 ,点击新增部门
-
add.html
-
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>新增部门</title> </head> <body> <h1>新增部门</h1> <hr> <form action="/oa/dept/add" method="post"> 部门编号<input type="text" name="deptno" /><br> 部门名称<input type="text" name="dname"/><br> 部门位置<input type="text" name="loc" /><br> <input type="submit" value="保存"/> </form> </body> </html>
-
DeptAddServlet.java
-
package com.sichen.oa.action; import com.sichen.oa.utils.DButil; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class DeptAddServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int count = 0; //获取部门信息 , //注意乱码问题 , (Tomcat10之后不会出现这个问题) request.setCharacterEncoding("UTF-8"); String deptno = request.getParameter("deptno"); String dname = request.getParameter("dname"); String loc = request.getParameter("loc"); //连接数据库,执行insert语句 Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = DButil.getconnection(); String sql = "insert into ceshi(deptno,dname,loc) values(?,?,?)"; ps = conn.prepareStatement(sql); ps.setString(1,deptno); ps.setString(2,dname); ps.setString(3,loc); count = ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); }finally{ DButil.close(conn,ps,rs); } if (count == 1){ //执行成功 , 跳转到列表页面 //转发是一次请求 , 他转发的时候也是一个post请求 , 但是list中是doGet方法 , 会出现问题 //目前解决方法只能是在list中添加一个doPost方法 , 在doPost方法中调doGet方法 request.getRequestDispatcher("/dept/list").forward(request,response); }else { //保存失败 , 跳转错误页面 request.getRequestDispatcher("/deletefalse.html").forward(request,response); } } }
-
注意 : 最后的保存成功之后 , 转发到/dept/list的时候 , 会出现405 , 为什么?
- 第一 : 保存的是post请求 , 底层执行的是doPost方法.
- 第二 : 转发是一次请求 , 之前是post , 之后还是post , 因为它是一次请求
-
第三 : /dept/list 中只有一个doGet方法
-
怎么解决? 两种方法
- 第一种 : 在.dept/list 中添加doPost方法 , 然后在doPost方法中调用doGet方法
-
第二种 : 使用重定向
-
-
第九步 : 修改部门
-
DeptEditServlet.java
-
package com.sichen.oa.action; import com.sichen.oa.utils.DButil; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class DeptEditServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); //获取应用根路径 String contextPath = request.getContextPath(); out.print("<!DOCTYPE html>"); out.print("<html>"); out.print("<head>"); out.print("<meta charset='utf-8'>"); out.print("<title>修改页面</title>"); out.print("</head>"); out.print("<body>"); out.print("<h1>修改部门</h1>"); out.print("<hr>"); //只要是向服务器提交数据 , 就用post请求 out.print("<form action='"+contextPath+"/dept/modify' method='post'>"); //获取部门编号 String deptno = request.getParameter("deptno"); //连接数据库 , 根据部门编号查询部门信息 Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = DButil.getconnection(); String sql = "select dname,loc from ceshi where deptno=? "; ps = conn.prepareStatement(sql); ps.setString(1,deptno); rs = ps.executeQuery(); //这个结果集中只有一条记录 if (rs.next()){ //rs.getString里边的参数 , 是查询结果的列名 , String dname = rs.getString("dname"); String loc = rs.getString("loc"); out.print("部门编号<input type='text' name='deptno' value='"+deptno+"' readonly/><br>"); out.print("部门名称<input type='text' name='dname' value='"+dname+"'/><br>"); out.print("部门位置<input type='text' name='loc' value='"+loc+"'/><br>"); } } catch (SQLException e) { e.printStackTrace(); }finally{ DButil.close(conn,ps,rs); } out.print("<input type='submit' value='修改'/>"); out.print("</form>"); out.print("</body>"); out.print("</html>"); } }
-
跳转到DeptModifyServlet.java 去执行修改操作
-
package com.sichen.oa.action; import com.sichen.oa.utils.DButil; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; public class DeptModifyServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String contextPath = request.getContextPath(); //解决请求体的中文乱码问题 request.setCharacterEncoding("UTF-8"); //获取表单中的数据 String deptno = request.getParameter("deptno"); String dname = request.getParameter("dname"); String loc = request.getParameter("loc"); //连接数据库 , 执行更新语句 Connection conn = null; PreparedStatement ps = null; int count = 0; try { conn = DButil.getconnection(); String sql = "update ceshi set dname=?,loc=? where deptno=?"; ps = conn.prepareStatement(sql); ps.setString(1,dname); ps.setString(2,loc); ps.setString(3,deptno); count = ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); }finally { DButil.close(conn, ps, null); } if (count == 1){ request.getRequestDispatcher("/dept/list").forward(request,response); }else{ request.getRequestDispatcher("/dept/deletefalse.html").forward(request,response); } } }
-
-
-
22.路径格式 :
超链接中的路径 : /项目名/具体路径
xml文件中的路径 : /具体路径
浏览器访问时的路径 : /项目名/具体路径
idea中的这个路径就是 /项目名
转发中的路径 /具体路径即可
重定向中的路径 /项目名/具体路径
1.相对路径 : 通过相对路径 , 不可以确定唯一资源
-
如 : ./index.html 不以 / 开头 , 而是以 . 开头
-
注意 :
规则 : 找到访问的当前资源和目标资源之间的相对位置关系
- . / : 当前目录
- …/ : 后退一级目录
2.绝对定位 : 通过绝对路径可以确定唯一资源
- 如 : http://location/sichen/index.html --> 化简写法 : /sichen/index.html
- 以 / 开头的路径
![1642514744106](https://gitee.com/aisichen/sichen/raw/master/img/1642514744106.png)
23.javaBean
构成
package com.sichen.javaweb.bin;
import java.io.Serializable;
import java.util.Objects;
/**
* 一个普通的javabean
* 什么是javabean
* java咖啡
* bean 豆子
* javabean 咖啡豆
* 咖啡是由咖啡豆研磨而成
* 寓意 java是由一个一个javabean组成的
* 一个javabean一般是有规范的
* (1)类必须被public修饰
* (2)无参数的构造方法
* (3)属性私有化(private)
* (4)对外提供setter和getter方法
* 重写toString
* 重写Hashcode 加 equals
* 实现java.io.Serializable接口 (可序列化)
*
*/
public class user implements Serializable {
private String id;
private String name;
public user() {
}
public user(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
user user = (user) o;
return Objects.equals(id, user.id) && Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
@Override
public String toString() {
return "user{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
'}';
}
}
作用 :
一般是用来封装数据的
概念:
(1)成员变量
(2) 属性 : setter 和 getter方法截取后的产物
例如 : getUsername() --> Username --> username
在一般情况下 , 属性名和变量名一样
方法 :
(1) getProperty() 获取属性
(2) setProperty() 设置属性
(3) populate() 将获取的数据集合封装为一个javabean对象
BeanUtils工具类
对于参数较多的类 , 如果还使用下边的方法显得十分麻烦
//2.获取请求参数
String name = req.getParameter("name");
String password = req.getParameter("password");
//3.封装user对象
User loginuser = new User();
loginuser.setName(name);
loginuser.setPassword(password);
这个时候 , 可以使用 BeanUtils 工具类
- 需要导入两个依赖
//2.获取所有请求参数
Map<String, String[]> map = req.getParameterMap();
//3.创建User对象
User loginuser = new User();
//3.2使用BeanUtils封装
try {
//这个方法 populate可以将获取的数据map集合封装到User对象中
BeanUtils.populate(loginuser , map);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
24.验证码(防止恶意注册)
①先将验证码的图片放在本地文件夹中 ,然后使用的时候从中间选一张出来
②使用随机生成的验证码 (常用)
package cn.sichen.web.servlet;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
@WebServlet(name = "yanzhengServlet", value = "/yanzhengServlet")
public class yanzhengServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request , response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int width = 100;
int height = 50;
//1.创建一个对象 , 能在内存中画图(验证码的画图对象)
//这里要传三个参数 , 宽 高 和图片类型 , 一般使用RGB的图片类型
BufferedImage image = new BufferedImage(width , height , BufferedImage.TYPE_INT_RGB);
//2.美化图片
//2.1填充背景色 , 获取画笔对象
Graphics g = image.getGraphics();
//设置画笔颜色
g.setColor(Color.pink);
//填充 四个参数 , 从那个位置开始 , 填充大小
g.fillRect(0,0,width , height);
//2.2 画边框 drawRect()
g.setColor(Color.blue);
g.drawRect(0,0,width-1,height-1);
//验证码的全部字符
String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
//生成随机脚标
Random random = new Random();
//创建一个字符串缓冲区
StringBuilder sb = new StringBuilder();
for (int x = 1; x <= 4; x++) {
int i = random.nextInt(str.length());
//获取字符
char c = str.charAt(i);//随机字符
//将字符放入缓冲区
sb.append(c);
//2.3 写验证码
g.drawString(c+"" , width/5*x , height/2);
}
//将缓冲区字符转为String类型的
String checkCode_session = sb.toString();
HttpSession session = request.getSession();
session.setAttribute( "checkCode_session",checkCode_session);
//2.4 画干扰线
g.setColor(Color.GREEN);
//g.drawLine(1,1,30,30);
//随机生成坐标点
for (int x = 1 ; x<=5 ; x++) {
int x1 = random.nextInt(width);
int x2 = random.nextInt(width);
int y1 = random.nextInt(height);
int y2 = random.nextInt(height);
g.drawLine(x1 , y1 , x2 , y2);
}
//3.将这个图片输出到页面展示(response)
ImageIO.write(image , "jpg" , response.getOutputStream());
}
}
register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>register</title>
<script>
/*1.点击超链接 或者图片需要换一张
给图片和超链接绑定单击事件
* */
window.onload = function (){
//获取图片对象
var img = document.getElementById("checkCode");
//绑定单击事件
img.onclick = function (){
//这里只写这个的话 , 会因为浏览器换存导致图片无法切换 , 可以加一个?传一个时间戳
// img.src = "/login/yanzhengServlet";
var date = new Date().getTime();
img.src = "/login/yanzhengServlet?"+date;
}
var a = document.getElementById("change");
a.onclick = function (){
var date = new Date().getTime();
img.src = "/login/yanzhengServlet?"+date;
}
}
</script>
</head>
<body>
<img id="checkCode" src="/login/yanzhengServlet"/>
<a id="change" href="">看不清换一张?</a>
</body>
</html>
Servlet的注解 :
- @WebServlet
- name 属性 : 用来指定Servlet的名字 , 等同于 :
- urlPatterns 属性 : 用来指定Servlet的映射路径 , 等同于 : 可以写多个路径 . 解决类爆炸问题
- load
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
---
# 24.验证码(防止恶意注册)
①先将验证码的图片放在本地文件夹中 ,然后使用的时候从中间选一张出来
②使用随机生成的验证码 (常用)
```java
package cn.sichen.web.servlet;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
@WebServlet(name = "yanzhengServlet", value = "/yanzhengServlet")
public class yanzhengServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request , response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int width = 100;
int height = 50;
//1.创建一个对象 , 能在内存中画图(验证码的画图对象)
//这里要传三个参数 , 宽 高 和图片类型 , 一般使用RGB的图片类型
BufferedImage image = new BufferedImage(width , height , BufferedImage.TYPE_INT_RGB);
//2.美化图片
//2.1填充背景色 , 获取画笔对象
Graphics g = image.getGraphics();
//设置画笔颜色
g.setColor(Color.pink);
//填充 四个参数 , 从那个位置开始 , 填充大小
g.fillRect(0,0,width , height);
//2.2 画边框 drawRect()
g.setColor(Color.blue);
g.drawRect(0,0,width-1,height-1);
//验证码的全部字符
String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
//生成随机脚标
Random random = new Random();
//创建一个字符串缓冲区
StringBuilder sb = new StringBuilder();
for (int x = 1; x <= 4; x++) {
int i = random.nextInt(str.length());
//获取字符
char c = str.charAt(i);//随机字符
//将字符放入缓冲区
sb.append(c);
//2.3 写验证码
g.drawString(c+"" , width/5*x , height/2);
}
//将缓冲区字符转为String类型的
String checkCode_session = sb.toString();
HttpSession session = request.getSession();
session.setAttribute( "checkCode_session",checkCode_session);
//2.4 画干扰线
g.setColor(Color.GREEN);
//g.drawLine(1,1,30,30);
//随机生成坐标点
for (int x = 1 ; x<=5 ; x++) {
int x1 = random.nextInt(width);
int x2 = random.nextInt(width);
int y1 = random.nextInt(height);
int y2 = random.nextInt(height);
g.drawLine(x1 , y1 , x2 , y2);
}
//3.将这个图片输出到页面展示(response)
ImageIO.write(image , "jpg" , response.getOutputStream());
}
}
register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>register</title>
<script>
/*1.点击超链接 或者图片需要换一张
给图片和超链接绑定单击事件
* */
window.onload = function (){
//获取图片对象
var img = document.getElementById("checkCode");
//绑定单击事件
img.onclick = function (){
//这里只写这个的话 , 会因为浏览器换存导致图片无法切换 , 可以加一个?传一个时间戳
// img.src = "/login/yanzhengServlet";
var date = new Date().getTime();
img.src = "/login/yanzhengServlet?"+date;
}
var a = document.getElementById("change");
a.onclick = function (){
var date = new Date().getTime();
img.src = "/login/yanzhengServlet?"+date;
}
}
</script>
</head>
<body>
<img id="checkCode" src="/login/yanzhengServlet"/>
<a id="change" href="">看不清换一张?</a>
</body>
</html>
Servlet的注解 :
- @WebServlet
- name 属性 : 用来指定Servlet的名字 , 等同于 :
- urlPatterns 属性 : 用来指定Servlet的映射路径 , 等同于 : 可以写多个路径 . 解决类爆炸问题
- load