Servet-JSP
Servet-JSP
CAMELLIA第一部分 Servlet
1. 关于系统架构
- 系统架构包括什么形式?
- C/S架构
- B/S架构
- C/S架构?
- Client / Server(客户端 / 服务器)
- C/S架构的软件或者说系统有哪些呢?
- QQ(通过安装特定的软件客户端才能使用的软件)
- C/S架构的特点:需要安装特定的客户端软件。
- C/S架构的系统优点和缺点分别是什么?
- 优点:
- 速度快(软件中的数据大部分都是集成到客户端软件当中的,很少量的数据从服务器端传送过来,所以C/S结构的系统速度快)
- 体验好(速度又快,界面又酷炫,当然体验好了。)
- 界面酷炫(专门的语言去实现界面的,更加灵活。)
- 服务器压力小(因为大量的数据都是集成在客户端软件当中,所以服务器只需要传送很少的数据量,当然服务器压力小。)
- 安全(因为大量的数据是集成在客户端软件当中的,并且客户端有很多个,服务器虽然只有一个,就算服务器那边地震了,火灾了,服务器受损了,问题也不大,因为大量的数据在多个客户端上有缓存,有存储,所以从这个方面来说,C/S结构的系统比较安全。)
- …..
- 缺点:
- 升级维护比较差劲。(升级维护比较麻烦。成本比较高。每一个客户端软件都需要升级。有一些软件不是那么容易安装的。)
- 优点:
- B/S架构?
- B/S(Browser / Server,浏览器 / 服务器)
- http://www.baidu.com
- http://www.jd.com
- http://www.126.com
- B/S结构的系统是不是一个特殊的C/S系统?
- 实际上B/S结构的系统还是一个C/S,只不过这个C比较特殊,这个Client是一个固定不变浏览器软件。
- B/S结构的系统优点和缺点是:
- 优点:
- 升级维护方便,成本比较低。(只需要升级服务器端即可。)
- 不需要安装特定的客户端软件,用户操作极其方便。只需要打开浏览器,输入网址即可。
- 缺点:
- 速度慢(不是因为带宽低的问题,是因为所有的数据都是在服务器上,用户发送的每一个请求都是需要服务器全身心的响应数据,所以B/S结构的系统在网络中传送的数据量比较大。)
- 体验差(界面不是那么酷炫,因为浏览器只支持三个语言HTML CSS JavaScript。在加上速度慢。)
- 不安全(所有的数据都在服务器上,只要服务器发生火灾,地震等不可抗力,最终数据全部丢失。)
- ….
- 优点:
- C/S和B/S结构的系统,哪个好,哪个不好?
- 不同结构的系统在不同的业务场景下有不同的适用场景。
- 娱乐性软件建议使用?
- C/S 结构
- 公司内部使用的一些业务软件建议使用?
- 公司内部使用的系统,需要维护成本低。
- 公司内部使用的系统,不需要很酷炫。
- 公司内部使用的企业级系统主要是能够进行数据的维护即可。
- B/S 结构。
- 注意了:开发B/S结构的系统,其实就是开发网站,其实就是开发一个WEB系统。
- 开发一个WEB系统你需要会哪些技术?
- WEB前端(运行在浏览器上的程序。)
- HTML
- CSS
- JavaScript
- WEB后端(WEB服务器端的程序。)
- Java可以(Java做WEB开发我们称为JavaWEB开发。JavaWEB开发最核心的规范:Servlet【Server Applet服务器端的Java小程序。】)
- C语言也可以
- C++也可以
- Python也行
- PHP也可以
- ….
- WEB前端(运行在浏览器上的程序。)
- 开发一个WEB系统你需要会哪些技术?
- JavaEE是什么?
- Java包括三大块:
- JavaSE
- Java标准版(一套类库:别人写好的一套类库,只不过这个类库是标准类库,走EE,或者走ME,这个SE一定是基础,先学。)
- JavaEE(WEB方向,WEB系统。)
- Java企业版(也是一套类库:也是别人写好的一套类库,只不过这套类库可以帮助我们完成企业级项目的开发,专门为企业内部提供解决方案的一套(多套)类库。)
- 别人写好的,你用就行了,用它可以开发企业级项目。
- 可以开发web系统。
- Java比较火爆的就是这个JavaEE方向。
- JavaME
- Java微型版(还是一套类库,只不过这套类库帮助我们进行电子微型设备内核程序的开发)
- 机顶盒内核程序,吸尘器内核程序,电冰箱内核程序,电饭煲内核程序。。。。。
- JavaSE
- JavaEE实际上包括很多种规范,13种规范,其中Servlet就是JavaEE规范之一。学Servlet还是Java语言。
- Java包括三大块:
2. B/S结构的系统通信原理(没有涉及到Java小程序)
2.1 WEB系统的访问过程
- 第一步:打开浏览器
- 第二步:找到地址栏
- 第三步:输入一个合法的网址
- 第四步:回车
- 第五步:在浏览器上会展示响应的结果。
2.2 关于域名:
- https://www.baidu.com/ (网址)
- www.baidu.com 是一个域名
- 在浏览器地址栏上输入域名,回车之后,域名解析器会将域名解析出来一个具体的IP地址和端口号等。
- 解析结果也许是:http://110.242.68.3:80/index.html
2.3 IP地址是什么?
- 计算机在网络当中的一个身份证号。在同一个网络当中,IP地址是唯一的。
- A计算机要想和B计算机通信,首先你需要知道B计算机的IP地址,有了IP地址才能建立连接。
2.4 端口号是什么?
- 一个端口代表一个软件(一个端口代表一个应用,一个端口仅代表一个服务)。
- 一个计算机当中有很多软件,每一个软件启动之后都有一个端口号。
- 在同一个计算机上,端口号具有唯一性。
2.5 一个WEB系统的通信原理
- 第一步:用户输入网址(URL)
- 第二步:域名解析器进行域名解析:http://110.242.68.3:80/index.html
- 第三步:浏览器软件在网络中搜索110.242.68.3这一台主机,直到找到这台主机。
- 第四步:定位110.242.68.3这台主机上的服务器软件,因为是80端口,可以很轻松的定位到80端口对应的服务器软件。
- 第五步:80端口对应的服务器软件得知浏览器想要的资源名是:index.html
- 第六步:服务器软件找到index.html文件,并且将index.html文件中的内容直接输出响应到浏览器上。
- 第七步:浏览器接收到来自服务器的代码(HTML CSS JS)
- 第八步:浏览器渲染,执行HTML CSS JS代码,展示效果。
2.6 什么是URL?
- 统一资源定位符(http://www.baidu.com)
2.7 什么是请求,什么是响应?
- 请求和响应实际上说的是数据的流向不同。
- 从Browser端发送数据到Server端,我们称为请求。英语单词:request
- 从Server端向浏览器Browser端发送数据,我们称为响应。英语单词:response
- B –> S (请求request)
- S –> B (响应response)
Important附录:
3. 关于WEB服务器软件
3.1 Web服务软件
WEB服务器软件(这些软件都是提前开发好的。)
- Tomcat(WEB服务器)
- jetty(WEB服务器)
- JBOSS(应用服务器)
- WebLogic(应用服务器)
- WebSphere(应用服务器)
应用服务器和WEB服务器的关系?
- 应用服务器实现了JavaEE的所有规范。(JavaEE有13个不同的规范。)
- WEB服务器只实现了JavaEE中的Servlet + JSP两个核心的规范。
- 通过这个讲解说明了:应用服务器是包含WEB服务器的。
- 用过JBOSS服务器的同学应该很清楚,JBOSS中内嵌了一个Tomcat服务器。
3.2 TomCat配置
- Tomcat下载
- apache官网地址:https://www.apache.org/
- tomcat官网地址:https://tomcat.apache.org
- tomcat开源免费的轻量级WEB服务器。
- tomcat还有另外一个名字:catalina(catalina是美国的一个岛屿,风景秀丽,据说作者是在这个风景秀丽的小岛上开发了一个轻量级的WEB服务器,体积小,运行速度快,因此tomcat又被称为catalina)
- tomcat的logo是一只公猫(寓意表示Tomcat服务器是轻巧的,小巧的,果然,体积小,运行速度快,只实现了Servlet+JSP规范)
- tomcat是java语言写的。
- tomcat服务器要想运行,必须先又jre(Java的运行时环境)
- Tomcat服务器要想运行,需要先有jre,所以要先安装JDK,配置java运行环境。
JAVA_HOME=C:\Program Files\Java\jdk-17.0.1
PATH=%JAVA_HOME%\bin
- 启动Tomcat服务器只配置path对应的bin目录是不行的,还需要配置两个环境变量
JAVA_HOME=JDK的根
CATALINA_HOME=Tomcat服务器的根
- Tomcat服务器的安装:
- 直接zip包解压即可。
- 启动Tomcat
- bin目录下有一个文件:startup.bat,通过它可以启动Tomcat服务器。
- xxx.bat文件是windows操作系统专用的,bat文件是批处理文件,这种文件中可以编写大量的windows的dos命令,然后执行bat文件就相当于批量的执行dos命令。
- startup.sh,这个文件在windows当中无法执行,在Linux环境当中可以使用。在Linux环境下能够执行的是shell命令,大量的shell命令编写在shell文件当中,然后执行这个shell文件可以批量的执行shell命令。
- tomcat服务器提供了bat和sh文件,说明了这个tomcat服务器的通用性。
- 分析startup.bat文件得出,执行这个命令,实际上最后是执行catalina.bat文件。
- catalina.bat文件中有这样一行配置:
MAINCLASS=org.apache.catalina.startup.Bootstrap
(这个类就是main方法所在的类。) - tomcat服务器就是Java语言写的,既然是java语言写的,那么启动Tomcat服务器就是执行main方法。
- 打开dos命令窗口,在dos命令窗口中输入startup.bat来启动tomcat服务器。
- bin目录下有一个文件:startup.bat,通过它可以启动Tomcat服务器。
- 关于Tomcat服务器的目录
- bin : 这个目录是Tomcat服务器的命令文件存放的目录,比如:启动Tomcat,关闭Tomcat等。
- conf: 这个目录是Tomcat服务器的配置文件存放目录。(server.xml文件中可以配置端口号,默认Tomcat端口是8080)
- lib :这个目录是Tomcat服务器的核心程序目录,因为Tomcat服务器是Java语言编写的,这里的jar包里面都是class文件。
- logs: Tomcat服务器的日志目录,Tomcat服务器启动等信息都会在这个目录下生成日志文件。
- temp:Tomcat服务器的临时目录。存储临时文件。
- webapps:这个目录当中就是用来存放大量的webapp(web application:web应用)
- work:这个目录是用来存放JSP文件翻译之后的java文件以及编译之后的class文件。
启动/关闭TomCat
- 启动Tomcat: startup
- 关闭Tomcat:stop (shutdown.bat文件重命名为stop.bat,为什么?原因是shutdown命令和windows中的关机命令冲突。所以修改一下。)
测试Tomcat服务器
- 打开浏览器,在浏览器的地址栏上输入URL即可:
- http://ip地址:端口号
- 端口号:8080
- 本机的IP地址是:127.0.0.1、或者是localhost。
配置Tomcat服务器需要的环境变量。
- JAVA_HOME=JDK的根
- CATALINA_HOME=Tomcat服务器的根
- PATH=%JAVA_HOME%\bin;%CATALINA_HOME%\bin
3.3 实现一个最基本的web应用
这个web应用中没有java小程序
- 第一步:找到CATALINA_HOME\webapps目录
- 因为所有的webapp要放到webapps目录下。(这是Tomcat服务器的要求。如果不放到这里,Tomcat服务器找不到你的应用。)
- 第二步:在CATALINA_HOME\webapps目录下新建一个子目录,起名:oa
- 这个目录名oa就是你这个webapp的名字。
- 第三步:在oa目录下新建资源文件,例如:index.html
- 编写index.html文件的内容。
- 第四步:启动Tomcat服务器
- 第五步:打开浏览器,在浏览器地址栏上输入这样的URL:
思考一个问题:
在浏览器上直接输入一个URL和超链接一样吗?既然是一样的,我们完全可以使用超链接。
1
2
3
4
5
6<!--注意以下的路径,以/开始,带项目名,是一个绝对路径。不需要添加:http://127.0.0.1:8080-->
<a href="/oa/login.html">user login2</a>
<!--多个层级也没有关系,正常访问即可。-->
<!--注意:我们目前前端上的路径都以“/”开始的,都是加项目名的。-->
<a href="/oa/test/debug/d.html">d page</a>
http://127.0.0.1:8080/oa/userList.html
- 访问这个地址,可以展示一个用户列表页面。但是这个用户列表页面是写死在HTML文件当中的。这种资源我们称为静态资源。怎么能变成动态资源。显然需要连接数据库。
- 连接数据库需要JDBC程序,也就是说需要编写Java程序连接数据库,数据库中有多少条记录,页面上就显示多少条记录,这种技术被称为动态网页技术。(动态网页技术并不是说页面中有flash动画。动态网页技术是说页面中的数据是动态的,根据数据库中数据的变化而变化。)
4. 动态web应用请求/响应过程中角色与协议分析
4.1 有哪些角色(在整个BS结构的系统当中,有哪些角色参与)
- 浏览器软件的开发团队(谷歌浏览器、火狐浏览器、IE浏览器….)
- WEB Server的开发团队(Tomcat、Jetty、WebLogic、JBOSS、WebSphere….)
- DB Server的开发团队(Oracle、MySQL…..)
- webapp的开发团队(WEB应用是Java Web程序员开发的)
4.2 角色和角色之间需要遵守哪些规范,哪些协议
- webapp的开发团队和WEB Server的开发团队之间有一套规范: Java EE规范之一的Servlet规范。
- Servlet规范的作用是什么?
- WEB Server和webapp解耦合。
- Servlet规范的作用是什么?
- Browser 和 Web Server之间有一套传输协议:HTTP协议(超文本传输协议)。
- webapp开发团队和DB Server的开发团队之间有一套规范:JDBC规范。
4.3 Servlet规范是一个什么规范?
- 遵循Servlet规范的webapp,这个webapp就可以放在不同的WEB服务器中运行。(因为这个webapp是遵循Servlet规范的。)
- Servlet规范包括什么呢?
- 规范了哪些接口
- 规范了哪些类
- 规范了一个web应用中应该有哪些配置文件
- 规范了一个web应用中配置文件的名字
- 规范了一个web应用中配置文件存放的路径
- 规范了一个web应用中配置文件的内容
- 规范了一个合法有效的web应用它的目录结构应该是怎样的。
- …..
- Servlet规范包括什么呢?
5. Servlet:模拟Servlet本质
5.1 模拟Servlet的代码
充当SUN公司的角色,制定Servlet规范
javax.servlet.Servlet
接口1
2
3
4
5
6
7
8
9package javax.servlet;
/*
我们现在充当的角色是SUN公司。
SUN公司把Servlet接口/规范制定出来了。
*/
public interface Servlet{
// 一个专门提供服务的方法
void service();
}
充当Tomcat服务器的开发者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46package org.apache;
import java.util.Scanner;
import java.util.Properties;
import java.io.FileReader;
import javax.servlet.Servlet;
// 充当Tomcat服务器的开发者
public class Tomcat {
public static void main(String[] args) throws Exception {
System.out.println("Tomcat服务器启动成功,开始接收用户的访问。");
/* 简单的使用Scanner来模拟一下用户的请求
用户访问服务器是通过浏览器上的“请求路径”
也就是说用户请求路径不同,后台执行的Servlet不同。
/userList UserListServlet
/login UserLoginServlet
/bank BankServlet
......*/
System.out.print("请输入您的访问路径:");
Scanner s = new Scanner(System.in);
// 用户的请求路径 /bbbb
String key = s.nextLine(); // Tomcat服务器已经获取到了用户的请求路径了。
// Tomcat服务器应该通过用户的请求路径找对应的XXXServlet
// 请求路径和XXXServlet之间的关系应该由谁指定呢?
// 对于Tomcat服务器来说需要解析配置文件
FileReader reader = new FileReader("web.properties");
Properties pro = new Properties();
pro.load(reader);
reader.close();
// 通过key获取value
String className = pro.getProperty(key);
// 通过反射机制创建对象
Class clazz = Class.forName(className);
Object obj = clazz.newInstance(); // obj的类型对于Tomcat服务器开发人员来说不知道。
// 但是Tomcat服务器的开发者知道,你写的XXXXServlet一定实现了Servlet接口
Servlet servlet = (Servlet)obj;
servlet.service();
}
}配置文件
1
2
3/aaaa=com.bjpowernode.servlet.UserListServlet
/bbbb=com.bjpowernode.servlet.UserLoginServlet
/cccc=com.bjpowernode.servlet.BankServlet充当Webapp的开发者
BankServlet
implementsServlet
1
2
3
4
5
6
7
8
9
10
11package com.bjpowernode.servlet;
import javax.servlet.Servlet;
// 充当的角色发生了改变:webapp开发者
// 只要是我们webapp开发者写的XXXServlet都要实现Servlet接口
public class BankServlet implements Servlet {
public void service() {
System.out.println("BankServlet's service...");
}
}UserListServlet
implementsServlet
1
2
3
4
5
6
7
8
9
10package com.bjpowernode.servlet;
import javax.servlet.Servlet;
// 充当的角色发生了改变:webapp开发者
public class UserListServlet implements Servlet {
public void service() {
System.out.println("UserListServlet's service...");
}
}UserLoginServlet
implementsServlet
1
2
3
4
5
6
7
8
9
10package com.bjpowernode.servlet;
import javax.servlet.Servlet;
// 充当的角色发生了改变:webapp开发者
public class UserLoginServlet implements Servlet {
public void service() {
System.out.println("UserLoginServlet's service...");
}
}
5.2 分析Servlet本质
- 对于Java Web程序员来说,只需要做两件事:
- 编写一个类实现Servlet接口。
- 将编写的类配置到配置文件中,在配置文件中:指定请求路径和类名的关系。
注意:
- 配置文件的文件名是固定的。
- 配置文件的存放路径是固定的。
- 文件名、文件路径都是SUN公司制定的Servlet规范中的明细。
- 严格意义上来说Servlet其实并不是简单的一个接口,Servlet规范中规定了:
- 一个合格的webapp应该是一个怎样的目录结构。
- 一个合格的webapp应该有一个怎样的配置文件。
- 一个合格的webapp配置文件路径放在哪里。
- 一个合格的webapp中java程序放在哪里。
Tomcat服务器要遵循Servlet规范。Java Web程序员也要遵循这个Servlet规范。这样Tomcat服务器和webapp才能解耦合。
* 6. 开发一个带有Servlet(Java程序)的webapp(重点)
6.1 开发步骤
第一步:在webapps目录下新建一个目录,起名crm(这个crm就是webapp的名字)。
注意:crm就是这个webapp的根
第二步:在webapp的根下新建一个目录:WEB-INF
注意:这个目录的名字是Servlet规范中规定的,必须全部大写,必须一模一样。必须的必须。
第三步:在WEB-INF目录下新建一个目录:classes
注意:这个目录的名字必须是全部小写的classes。这也是Servlet规范中规定的。另外这个目录下一定存放的是Java程序编译之后的class文件(这里存放的是字节码文件)。
第四步:在WEB-INF目录下新建一个目录:lib
注意:这个目录不是必须的。但如果一个Web应用需要第三方的JAR包,那么这个JAR包必须放到lib目录中。这个目录的名称不能随意更改,必须全部小写为lib。例如,Java语言连接数据库时需要数据库驱动的JAR包,那么这个JAR包必须放到lib目录中。这是Servlet规范中规定的。
第五步:在WEB-INF目录下新建一个文件:web.xml
注意:这个文件是必须的,文件名必须为web.xml,并且必须放在指定位置。这个配置文件描述了请求路径和Servlet类之间的映射关系。
这个文件最好从其他的Web应用程序中拷贝,不建议手写,没有必要。
<?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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
6. 第六步:编写一个Java程序,这个Java程序必须实现Servlet接口,不能随意开发。
- Servlet接口不在JDK当中,Servlet接口是Java EE规范的一部分。(因为Servlet属于Java EE,是另一套类库,不属于Java SE。)
- Servlet接口(Servlet.class文件)由Oracle提供。(最初是由Sun公司提供的。)
- Tomcat服务器实现了Servlet规范,因此需要使用Servlet接口。Tomcat服务器的CATALINA_HOME\lib目录下有一个servlet-api.jar文件,解压这个servlet-api.jar后,你会看到里面有一个Servlet.class文件。
- **重点:** 从JakartaEE 9开始,Servlet接口的全名变为:jakarta.servlet.Servlet。
> 注意:编写这个Java程序时,Java源代码的位置无所谓,你可以将其放在任何地方,只需将编译后的class文件放到classes目录下即可。
7. 第七步:编译我们编写的HelloServlet
- 重点:你怎么能让你的HelloServlet编译通过呢?配置环境变量CLASSPATH
`CLASSPATH=.;C:\dev\apache-tomcat-10.0.12\lib\servlet-api.jar`
- 思考问题:以上配置的CLASSPATH和Tomcat服务器运行有没有关系?
- 没有任何关系,以上配置这个环境变量只是为了让你的HelloServlet能够正常编译生成class文件。
8. 第八步:将以上编译之后的HelloServlet.class文件拷贝到WEB-INF\classes目录下。
9. 第九步:在web.xml文件中编写配置信息,让“请求路径”和“Servlet类名”关联在一起。
- 这一步用专业术语描述:在web.xml文件中注册Servlet类。
- ```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>camellia</servlet-name>
<!-- 这个位置必须是带有包名的全限定类名 -->
<servlet-class>com.camellia.servlet.HelloServlet</servlet-class>
</servlet>
<!-- Servlet映射信息 -->
<servlet-mapping>
<!-- 这个名称可以随意指定,但必须与上面的servlet-name一致 -->
<servlet-name>camellia</servlet-name>
<!-- 这里需要一个路径 -->
<!-- 唯一的要求是路径必须以 / 开始 -->
<!-- 当前路径可以随意指定 -->
<url-pattern>/camellia</url-pattern>
</servlet-mapping>
</web-app>
第十步:启动Tomcat服务器
第十一步:打开浏览器,在浏览器地址栏上输入一个url,这个URL必须是:
- http://127.0.0.1:8080/crm/camellia
注意:浏览器上的请求路径不能随便写,这个请求路径必须和web.xml文件中的url-pattern一致。
浏览器上的请求路径和web.xml文件中的url-pattern的唯一区别就是:浏览器上的请求路径带项目名:/crm
- http://127.0.0.1:8080/crm/camellia
6.2 细节部分
浏览器上编写的路径太复杂,可以使用超链接。(非常重要:html页面只能放到WEB-INF目录外面。)
以后不需要我们编写main方法了。tomcat服务器负责调用main方法,Tomcat服务器启动的时候执行的就是main方法。我们javaweb程序员只需要编写Servlet接口的实现类,然后将其注册到web.xml文件中,即可。
总结一下:一个合法的webapp目录结构应该是怎样的?
1
2
3
4
5
6
7
8
9
10webapproot
|------WEB-INF
|------classes(存放字节码)
|------lib(第三方jar包)
|------web.xml(注册Servlet)
|------html
|------css
|------javascript
|------image
....浏览器发送请求,到最终服务器调用Servlet中的方法的过程。(以下这个过程描述的很粗糙。)
- 用户输入URL,或者直接点击超链接:http://127.0.0.1:8080/crm/camellia
- 然后Tomcat服务器接收到请求,截取路径:/crm/camellia
- Tomcat服务器找到crm项目
- Tomcat服务器在web.xml文件中查找/camellia 对应的Servlet是:com.bjpowernode.servlet.HelloServlet
- Tomcat服务器通过反射机制,创建com.camellia.servlet.HelloServlet的对象。
- Tomcat服务器调用com.camellia.servlet.HelloServlet对象的service方法。
6.3 关于JavaEE的版本
- JavaEE目前最高版本是 JavaEE8
- JavaEE被Oracle捐献了,Oracle将JavaEE规范捐献给Apache了。
- Apache把JavaEE换名了,以后不叫JavaEE了,以后叫做 jakarta EE。
- 以后没有JavaEE了。以后都叫做Jakarta EE。
- JavaEE8版本升级之后的”JavaEE 9”,不再是”JavaEE9”这个名字了,叫做JakartaEE9
- JavaEE8的时候对应的Servlet类名是:javax.servlet.Servlet
- JakartaEE9的时候对应的Servlet类名是:jakarta.servlet.Servlet (包名都换了)
- 如果你之前的项目还是在使用javax.servlet.Servlet,那么你的项目无法直接部署到Tomcat10+版本上。你只能部署到Tomcat9-版本上。在Tomcat9以及Tomcat9之前的版本中还是能够识别javax.servlet这个包。
6.4 解决Tomcat服务器在DOS命令窗口中的乱码问题(控制台乱码)
将CATALINA_HOME/conf/logging.properties文件中的内容修改如下:
java.util.logging.ConsoleHandler.encoding = GBK
6.5 向浏览器响应一段HTML代码
1 | public void service(ServletRequest request, ServletResponse response){ |
6.6 在Servlet中连接数据库,怎么做?
- Servlet是Java程序,所以在Servlet中完全可以编写JDBC代码连接数据库。
- 在一个webapp中去连接数据库,需要将驱动jar包放到WEB-INF/lib目录下。(com.mysql.cj.jdbc.Driver 这个类就在驱动jar包当中。)
7. 在集成开发环境当中开发Servlet程序
集成开发工具很多,其中目前使用比较多的是:
- IntelliJ IDEA、Eclipse
7.1 使用IDEA集成开发工具开发Servlet
第一步:New Project(先创建一个Empty Project【空工程】,然后在空工程下新建Module【模块】。(按习惯来))
第二步:新建模块(File –> new –> Module…)
- 这里新建的是一个普通的JavaSE模块(先不要新建Java Enterprise模块)
- 这个Module自动会被放在New Project创建的空工程下面。
- 第三步:让Module变成JavaEE的模块。(让Module变成webapp的模块。符合webapp规范。符合Servlet规范的Module)
- 在help中搜索:Add Framework Support…(添加框架支持)
- 在弹出的窗口中,选择Web Application(选择的是webapp的支持)
- 选择了这个webapp的支持之后,IDEA会自动给你生成一个符合Servlet规范的webpp目录结构。
- 重点,需要注意的:在IDEA工具中根据Web Application模板生成的目录中有一个web目录,这个目录就代表webapp的根
- 第四步(非必须):根据Web Application生成的资源中有index.jsp文件,可以删除。
- 第五步:编写Servlet(StudentServlet)
class StudentServlet implements Servlet
- 将CATALINA_HOME/lib/servlet-api.jar和jsp-api.jar添加到classpath当中(这里的classpath说的是IDEA的classpath)
- File –> Project Structrue –> Modules –> + 加号 –> Add JARS….
- 实现jakarta.servlet.Servlet接口中的5个方法。
第六步:在Servlet当中的service方法中编写业务代码(我在这里连接数据库。)
第七步:在WEB-INF目录下新建了一个子目录:lib(这个目录名可不能随意,必须是全部小写的lib),并且将连接数据库的驱动jar包放到lib目录下。
第八步:在web.xml文件中完成StudentServlet类的注册。(请求路径和Servlet之间对应起来)
1 |
|
- 第九步:给一个html页面,在HTML页面中编写一个超链接,用户点击这个超链接,发送请求,Tomcat执行后台的StudentServlet。
student.html
这个文件不能放到WEB-INF目录里面,只能放到WEB-INF目录外面。
student.html文件的内容
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>student page</title> </head> <body> <!--这里的项目名是 /xmm ,无法动态获取,先写死--> <a href="/xmm/servlet/student">student list</a> </body> </html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
10. 第十步:让IDEA工具去关联Tomcat服务器。关联的过程当中将webapp部署到Tomcat服务器当中。
- IDEA工具右上角,绿色小锤子右边有一个:Add Configuration
- 左上角加号,点击Tomcat Server --> local
- 在弹出的界面中设置服务器Server的参数(基本上不用动)
- 在当前窗口中有一个Deployment(点击这个用来部署webapp),继续点击加号,部署即可。
- 修改 Application context为:/camellia
11. 第十一步:启动Tomcat服务器
- 在右上角有绿色的箭头,或者绿色的小虫子,点击这个绿色的小虫子,可以采用debug的模式启动Tomcat服务器。
- 我们开发中建议适用debug模式启动Tomcat
- 第十二步:打开浏览器,在浏览器地址栏上输入:http://localhost:8080/camellia/index.html
## 8. Servlet对象的生命周期
### 8.1 什么是Servlet对象生命周期?
- Servlet对象什么时候被创建?
- Servlet对象什么时候被销毁?
- Servlet对象创建了几个?
- **Servlet对象的生命周期表示:一个Servlet对象从出生到最终死亡的整个过程是怎样的?**
### 8.2 Servlet对象是由谁来维护的?
- Servlet对象的创建、方法调用和最终销毁,Java Web程序员是无权干预的。
- Servlet对象的生命周期由Tomcat服务器(WEB Server)全权负责。
- Tomcat服务器通常又称为:WEB容器。(这个叫法你要知道【WEB Container】)
- **WEB容器负责管理Servlet对象的生命周期。**
### 8.3 自己new的Servlet对象受WEB容器的管理吗?
- 自己new的Servlet对象是不受WEB容器管理的。
- WEB容器创建的Servlet对象,这些Servlet对象都会被放到一个集合当中(HashMap),只有放到这个HashMap集合中的Servlet才能够被WEB容器管理,自己new的Servlet对象不会被WEB容器管理。(自己new的Servlet对象不在容器当中)
- web容器底层应该有一个HashMap这样的集合,在这个集合当中存储了Servlet对象和请求路径之间的关系
- <img src="https://camelliaxiaohua-1313958787.cos.ap-shanghai.myqcloud.com/asserts_JavaSE/202406151536485.png" style="zoom: 67%;" />
### 8.4 服务器在启动的Servlet对象有没有被创建出来(默认情况下)?
- 在Servlet中提供一个无参数的构造方法,启动服务器的时候测试构造方法是否执行。
- 经过测试得出结论:默认情况下,服务器在启动的时候Servlet对象并不会被实例化。
- 这种设计是合理的。在用户发送请求之前,提前创建所有的Servlet对象会浪费内存资源。如果某些Servlet对象一直未被访问,它们将成为无用的对象,因此没有必要提前创建。
### 8.5 怎么让服务器启动的时候创建Servlet对象呢?
- 在servlet标签中添加<load-on-startup>子标签,在该子标签中填写整数,越小的整数优先级越高。
```xml
<servlet>
<servlet-name>Aservlet</servlet-name>
<servlet-class>servlet.AServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Aservlet</servlet-name>
<url-pattern>/camellia/Aservlet</url-pattern>
</servlet-mapping>
8.6 Servlet对象生命周期
默认情况下服务器启动的时候AServlet对象并没有被实例化
用户发送第一次请求的时候,控制台输出了以下内容:
1
2
3AServlet无参数构造方法执行了
AServlet's init method execute!
AServlet's service method execute!根据以上输出内容得出结论:
用户在发送第一次请求的时候Servlet对象被实例化(AServlet的构造方法被执行了。并且执行的是无参数构造方法。)
AServlet对象被创建出来之后,Tomcat服务器马上调用了AServlet对象的init方法。(init方法在执行的时候,AServlet对象已经存在了。已经被创建出来了。)
用户发送第一次请求的时候,init方法执行之后,Tomcat服务器马上调用AServlet对象的service方法。
用户继续发送第二次请求,控制台输出了以下内容:
1
AServlet's service method execute!
根据以上输出结果得知,用户在发送第二次,或者第三次,或者第四次请求的时候,Servlet对象并没有新建,还是使用之前创建好的Servlet对象,直接调用该Servlet对象的service方法,这说明:
- 第一:Servlet对象是单例的(注意:Servlet对象是单实例的,但Servlet类并不符合单例模式,因此我们称其为“假单例”。单实例的原因在于Servlet对象的创建由Tomcat控制,而不是由Java Web程序员控制。Tomcat只创建一个Servlet对象,因此是单实例,但属于假单例。真正的单例模式要求构造方法私有化。)
- 第二:无参数构造方法、init方法只在第一次用户发送请求的时候执行。也就是说无参数构造方法只执行一次。init方法也只被Tomcat服务器调用一次。
- 第三:只要用户发送一次请求:service方法必然会被Tomcat服务器调用一次。发送100次请求,service方法会被调用100次。
关闭服务器的时候,控制台输出了以下内容:
1
AServlet's destroy method execute!
通过以上输出内容,可以得出以下结论:
- Servlet的destroy方法只被Tomcat服务器调用一次。
- destroy方法是在什么时候被调用的?
- 在服务器关闭的时候。
- 因为服务器关闭的时候要销毁AServlet对象的内存。
- 服务器在销毁AServlet对象内存之前,Tomcat服务器会自动调用AServlet对象的destroy方法。
destroy方法调用的时候,对象销毁了还是没有销毁呢?
- destroy方法执行的时候AServlet对象还在,没有被销毁。destroy方法执行结束之后,AServlet对象的内存才会被Tomcat释放。
Servlet对象更像一个人的一生:
- Servlet的无参数构造方法执行:标志着你出生了。
- Servlet对象的init方法的执行:标志着你正在接受教育。
- Servlet对象的service方法的执行:标志着你已经开始工作了,已经开始为人类提供服务了。
- Servlet对象的destroy方法的执行:标志着临终。有什么遗言,抓紧的。要不然,来不及了。
关于Servlet类中方法的调用次数?
- 构造方法只执行一次。
- init方法只执行一次。
- service方法:用户发送一次请求则执行一次,发送N次请求则执行N次。
- destroy方法只执行一次。
当我们Servlet类中编写一个有参数的构造方法,如果没有手动编写无参数构造方法会出现什么问题?
- 报错了:500错误。
- 注意:500是一个HTTP协议的错误状态码。
- 500一般情况下是因为服务器端的Java程序出现了异常。(服务器端的错误都是500错误:服务器内部错误。)
- 如果没有无参数的构造方法,会导致出现500错误,无法实例化Servlet对象。
- 所以,一定要注意:在Servlet开发当中,不建议程序员来定义构造方法,因为定义不当,一不小心就会导致无法实例化Servlet对象。
思考:Servlet的无参数构造方法是在对象第一次创建的时候执行,并且只执行一次。init方法也是在对象第一次创建的时候执行,并且只执行一次。那么这个无参数构造方法可以代替掉init方法吗?
- 不能。
- Servlet规范中有要求,作为javaweb程序员,编写Servlet类的时候,不建议手动编写构造方法,因为编写构造方法,很容易让无参数构造方法消失,这个操作可能会导致Servlet对象无法实例化。所以init方法是有存在的必要的。
init、service、destroy方法中使用最多的是哪个方法?
- 使用最多就是service方法,service方法是一定要实现的,因为service方法是处理用户请求的核心方法。
- 什么时候使用init方法呢?
- init方法很少用。
- 通常在init方法当中做初始化操作,并且这个初始化操作只需要执行一次。例如:初始化数据库连接池,初始化线程池….
- 什么时候使用destroy方法呢?
- destroy方法也很少用。
- 通常在destroy方法当中,进行资源的关闭。马上对象要被销毁了,还有什么没有关闭的,抓紧时间关闭资源。还有什么资源没保存的,抓紧时间保存一下。
9. GenericServlet
9.1 为什么要使用适配器?
我们编写一个Servlet类直接实现Servlet接口有什么缺点?
- 我们只需要service方法,其他方法大部分情况下是不需要使用的。代码很丑陋。
适配器设计模式Adapter
- 手机直接插到220V的电压上,手机直接就报废了。怎么办?可以找一个充电器。这个充电器就是一个适配器。手机连接适配器。适配器连接220V的电压。这样问题就解决了。
编写一个GenericServlet类,这个类是一个抽象类,其中有一个抽象方法service。
- GenericServlet实现Servlet接口。
- GenericServlet是一个适配器。
- 以后编写的所有Servlet类继承GenericServlet,重写service方法即可。
思考:GenericServlet类是否需要改造一下?怎么改造?更利于子类程序的编写?
思考第一个问题:我提供了一个GenericServlet之后,init方法还会执行吗?
- 还会执行。会执行GenericServlet类中的init方法。
思考第二个问题:init方法是谁调用的?
- Tomcat服务器调用的。
思考第三个问题:init方法中的ServletConfig对象是谁创建的?是谁传过来的?
- 都是Tomcat干的。
- Tomcat服务器先创建了ServletConfig对象,然后调用init方法,将ServletConfig对象传给了init方法。
思考一下Tomcat服务器伪代码:
public class Tomcat { public static void main(String[] args){ // ..... // Tomcat服务器伪代码 // 创建LoginServlet对象(通过反射机制,调用无参数构造方法来实例化LoginServlet对象) Class clazz = Class.forName("com.bjpowernode.javaweb.servlet.LoginServlet"); Object obj = clazz.newInstance(); // 向下转型 Servlet servlet = (Servlet)obj; // 创建ServletConfig对象 // Tomcat服务器负责将ServletConfig对象实例化出来。 // 多态(Tomcat服务器完全实现了Servlet规范) ServletConfig servletConfig = new org.apache.catalina.core.StandardWrapperFacade(); // 调用Servlet的init方法 servlet.init(servletConfig); // 调用Servlet的service方法 // .... } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72### 9.2 适配器的实现
> 思想:直接写类实现Servlet接口会导致多个方法都要实现,代码很混乱。
>
> 但是在中间增加一个适配器(GenericServlet抽象类)让他实现Servlet接口,然后让我们的Servlet适配器。这样就可以按需实现方法。
>
> init方法重载以完成子类重写init方法以满足业务需求非常巧妙。
```java
package servlet;
import jakarta.servlet.*;
import java.io.IOException;
/**
* 写一个标准通用的servlet。<br>
* 以后所有的Servlet类都不要直接实现Servlet接口,继承GenericServlet即可<br>
* GenericServlet就是一个适配器。<br>
*/
public abstract class GenericServlet implements Servlet {
private ServletConfig config;
/**
* init方法中的servletConfig对象是TomCat创建好的。<br>
* servletConfig对象属于局部变量,所以要改造为在service方法上使用。<br>
* @param servletConfig
* @throws ServletException
*/
@Override
final public void init(ServletConfig servletConfig) throws ServletException {
this.config = servletConfig;
this.init();
}
/**
* 为防止子类重写init(ServletConfig servletConfig)方法,导致init(ServletConfig servletConfig)方法不执行,config为空,将其用final修饰。<br>
* 但是,有时我们子类的有业务确实要重写init,此时我们可以写一个init的重载方法,在init(ServletConfig servletConfig)调用这个重载方法。<br>
*/
public void init(){
}
@Override
public ServletConfig getServletConfig() {
return config;
}
/**
* 抽象方法,这个方法最常用,所以要求子类必须实现service方法。
* @param servletRequest
* @param servletResponse
* @throws ServletException
* @throws IOException
*/
@Override
public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse)
throws ServletException, IOException;
@Override
public String getServletInfo() {
return "";
}
@Override
public void destroy() {
}
}
1 | package servlet; |
10. ServletConfig
10.1 ServletConfig 是什么?
- 包名:
jakarta.servlet.ServletConfig
- 角色:Servlet 规范的一部分。
- 类型:一个接口。
- 注释:
jakarta.servlet.Servlet
也是一个接口。
10.2 谁实现了这个接口?
- 实现者:由 Web 服务器实现。
- 示例:Tomcat 通过
org.apache.catalina.core.StandardWrapperFacade implements ServletConfig
。 - 考虑:不同的 Web 服务器(例如 Jetty)可能有不同的类和包名,但它们都实现了
ServletConfig
。
10.3 与 Servlet 的关系
- 关联:每个
Servlet
对象都有一个对应的ServletConfig
对象。 - 一对一:一个
Servlet
对应一个ServletConfig
,100 个Servlet
对象会有 100 个ServletConfig
对象。
10.4 ServletConfig 对象的创建
- 创建者:由 Web 服务器(如 Tomcat)创建。
- 时间点:在创建
Servlet
对象的同时创建ServletConfig
对象。
10.5 ServletConfig 的用途
- 含义:
Config
是 Configuration(配置)的缩写。 - 角色:作为
Servlet
对象的配置信息对象(.xml
)。
10.6 ServletConfig 中包含的信息
包装了web.xml
文件中的 <servlet>
标签。
- 示例:
1
2
3
4<servlet>
<servlet-name>configServlet</servlet-name>
<servlet-class>servlet.ConfigTestServlet</servlet-class>
</servlet> - 封装:Web 服务器(如 Tomcat)解析
web.xml
文件,并将<servlet>
标签中的配置信息封装到ServletConfig
对象中。
10.7 ServletConfig 接口中的方法
修饰符和类型 | 方法 | 描述 |
---|---|---|
java.lang.String |
getInitParameter(java.lang.String name) |
返回包含命名初始化参数的值的 String ,如果参数不存在则返回 null 。 |
java.util.Enumeration<java.lang.String> |
getInitParameterNames() |
返回包含 servlet 初始化参数名称的 Enumeration 对象,如果 servlet 没有初始化参数则返回空 Enumeration 。 |
ServletContext |
getServletContext() |
返回 ServletContext ,引用当前正在执行的 servlet 上下文。 |
java.lang.String |
getServletName() |
返回此 servlet 实例的名称。 |
10.7.1 配置Servlet对象的初始化信息
在
<servlet></servlet>
可以配置Servlet对象的初始化信息
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<servlet>
<servlet-name>configServlet</servlet-name>
<servlet-class>servlet.ConfigTestServlet</servlet-class>
<!--这里是可以配置一个Servlet对象的初始化信息的。-->
<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/bjpowernode</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>root1234</param-value>
</init-param>
<!--以上<servlet></servlet>标签中的<init-param></init-param>是初始化参数。-->
</servlet> - 这些初始化参数信息会自动被 Web 服务器封装到
ServletConfig
对象中。
10.7.2 ServletConfig中的getInitParameterNames()
和getInitParameter()
方法
getServletName()
返回当前 Servlet 实例的名称。getInitParameterNames()
方法返回一个Enumeration<String>
对象,该对象包含当前 Servlet 的所有初始化参数的名称。如果 Servlet 没有初始化参数,则返回一个空的Enumeration
。这个方法通常用于遍历所有初始化参数名称,以便进一步处理它们的值。getInitParameter(String name)
方法返回指定名称的初始化参数的值。如果参数不存在,则返回null
。这个方法通常用于获取特定初始化参数的值。getServletContext()
返回当前 Servlet 实例正在执行的 ServletContext 对象的引用。ServletContext 提供了与 Web 应用程序环境交互的方法,如获取资源、设置和获取属性等。示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28public class ConfigTestServlet extends GenericServlet {
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
res.setContentType("text/html");
PrintWriter out = res.getWriter();
// 获取ServletConfig对象
ServletConfig config = this.getServletConfig();
// 输出该对象
out.println("ServletConfig对象是:"+config);
//ServletConfig对象是:org.apache.catalina.core.StandardWrapperFacade@3cdfe47
//获取<servlet-name></servlet-name>
String servletName = config.getServletName();
out.println("<br>"+servletName);
// 通过ServletConfig对象的两个方法,可以获取到web.xml文件中的初始化参数配置信息。
Enumeration<String> initParameterNames = config.getInitParameterNames();
//遍历集合
while (initParameterNames.hasMoreElements()){
String initParameterName = initParameterNames.nextElement();
String initParameter = config.getInitParameter(initParameterName);
out.println("<br>"+initParameterName+" = "+initParameter);
}
//通过ServletConfig对象获取ServletContext对象。
ServletContext application = config.getServletContext();
out.println("<br>"+application);
}
}
10.7.3 注意事项
[!NOTE]
分析GenericServlet源码可以知道我们根本不必获取ServletConfig对象,因为GenericServlet有提供。
1 | //在init被赋值 |
- 10.7.2示例代码改写:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class ConfigTestServlet3 extends GenericServlet {
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
res.setContentType("text/html");
PrintWriter out = res.getWriter();
out.println("ServletConfig对象:"+this.getServletConfig());
out.println("<br> ServletConfig对象名称:"+this.getServletName());
Enumeration<String> initParameterNames = this.getInitParameterNames();
while (initParameterNames.hasMoreElements()){
String initParameterName = initParameterNames.nextElement();
String initParameter = this.getInitParameter(initParameterName);
out.println("<br>"+initParameterName+" = "+initParameter);
}
ServletContext application1 = this.getServletContext();
out.println("<br>"+application1);
}
}
11. ServletContext
1. ServletContext 是什么?
- ServletContext 是一个接口,是 Servlet 规范的一部分。
2. ServletContext 是由谁实现的?
- Tomcat 服务器(WEB 服务器)实现了 ServletContext 接口。
1
public class org.apache.catalina.core.ApplicationContextFacade implements ServletContext {}
3. ServletContext 对象是由谁创建的?在什么时候创建的?
- ServletContext 对象在 WEB 服务器启动时创建。
- ServletContext 对象是由 WEB 服务器创建的。
- 每个 Web 应用程序只有一个 ServletContext 对象。
- ServletContext 对象在服务器关闭时销毁。
4. 如何理解 ServletContext?
Context 的含义:
- Servlet 对象的环境对象(Servlet 的上下文对象)。
ServletContext 对象对应于整个 web.xml 文件。
想象一个教室有 50 名学生(每名学生代表一个 Servlet),都在同一个教室里。这个教室就相当于 ServletContext 对象。
存放在 ServletContext 中的数据是所有 Servlet 共享的。
- 例如,教室里的空调是所有学生共享的,语文老师也是所有学生共享的。
Tomcat 是一个容器,可以容纳多个 Web 应用程序,每个 Web 应用程序对应一个 ServletContext 对象。
5. ServletContext 接口中的常用方法
1. getInitParameter(String name)
和 getInitParameterNames()
public String getInitParameter(String name);
- 通过初始化参数的 name 获取其 value。
public Enumeration<String> getInitParameterNames();
- 获取所有初始化参数的 name。
1
2
3
4
5
6
7
8
9
10
11<!--以下是ServletContext对象的方法,获取到是什么信息呢?是以下的配置信息-->
<context-param>
<param-name>pageSize</param-name>
<param-value>10</param-value>
</context-param>
<context-param>
<param-name>startIndex</param-name>
<param-value>0</param-value>
</context-param>
<!--注意:以上的配置信息属于应用级的配置信息,一般一个项目中共享的配置信息会放到以上的标签当中。-->
<!--如果你的配置信息只是想给某一个servlet作为参考,那么你配置到servlet标签当中即可,使用ServletConfig对象来获取。-->示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 导包省略
public class AServlet extends GenericServlet {
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
servletResponse.setContentType("text/html");
PrintWriter out = servletResponse.getWriter();
// 获取ServletContext对象
ServletContext application = this.getServletContext();
out.println("ServletContext对象:" + application);
// 获取上下文的初始化参数
Enumeration<String> attributeNames = application.getInitParameterNames();
while (attributeNames.hasMoreElements()) {
String attributeName = attributeNames.nextElement();
String attributeValue = application.getInitParameter(attributeName);
out.println("<br>" + attributeName + ":" + attributeValue);
}
}
}
2. public String getContextPath();
用于获取当前 Web 应用程序的上下文路径(Context Path)。上下文路径是指 Web 应用程序在 Web 服务器中的根路径,它是相对于服务器的根路径的部分。
1 | ServletContext application = this.getServletContext(); |
- 输出:contextPath:/context
4. public String getRealPath(String path)
用于获取指定路径在文件系统中的真实路径(Real Path)。在 Web 应用程序中,通常会使用相对路径来引用资源,例如配置文件、上传文件等。这些相对路径是相对于 Web 应用程序的根目录来确定的。
5. 日志记录相关方法
public void log(String message)
和 public void log(String message, Throwable t)
是 ServletContext
接口提供的两个方法,用于记录日志信息。
**
public void log(String message)
**:- 这个方法用于记录简单的日志消息,通常用于记录一般性的信息或者调试信息。
- 参数
message
是要记录的日志消息内容。
**
public void log(String message, Throwable t)
**:- 这个方法用于记录带有异常信息的日志消息。
- 参数
message
是要记录的日志消息内容。 - 参数
t
是要记录的异常对象,它包含了发生的异常信息。
1 | // 通过ServletContext对象也是可以记录日志的 |
- Tomcat服务器的logs目录下都有哪些日志文件?
- catalina.2021-11-05.log 服务器端的java程序运行的控制台信息。
- local host.2021-11-05.log ServletContext对象的log方法记录的日志信息存储到这个文件中。
- localhost_access_log.2021-11-05.txt 访问日志
6. ServletContext 对象及应用域相关操作
ServletContext对象还有另一个名字:应用域(后面还有其他域,例如:请求域、会话域)
- 如果所有的用户共享一份数据,并且这个数据很少的被修改,并且这个数据量很少,可以将这些数据放到ServletContext这个应用域中
为什么是所有用户共享的数据?
- ServletContext 是所有用户共享数据的容器,因为它在整个应用程序的生命周期内只有一个实例,并且可以存储全局的配置和资源。
为什么数据量要小?
- 因为数据量比较大的话,太占用堆内存,并且这个对象的生命周期比较长,服务器关闭的时候,这个对象才会被销毁。大数据量会影响服务器的性能,占用内存较小的数据量可以考虑放进去。
怎么向ServletContext应用域中存数据
javapublic void setAttribute(String name, Object value);
1
2
3
4
5
5. 怎么从ServletContext应用域中取数据
- ```java
public Object getAttribute(String name);
怎么删除ServletContext应用域中的数据
public void removeAttribute(String name);
1
2
3
4
5
6
7
8
9
10
11
12
### 6. 关于 Servlet 类的继承结构:
以后编写 Servlet 类的时候,实际上是不会去直接继承 GenericServlet 类的,因为我们是基于 HTTP 协议的 B/S 结构系统。在 Servlet 规范中,提供了一个专门为 HTTP 协议准备的 Servlet 类叫做 HttpServlet。我们编写的 Servlet 类应当继承 HttpServlet 类,因为它更便捷地处理 HTTP 协议。
```mark
jakarta.servlet.Servlet(接口)【爷爷】
jakarta.servlet.GenericServlet implements Servlet(抽象类)【儿子】
jakarta.servlet.http.HttpServlet extends GenericServlet(抽象类)【孙子】
我们以后编写的 Servlet 要继承 HttpServlet 类。
7. 关于缓存机制
大家到目前为止都接触过哪些缓存机制了?
- 堆内存中的字符串常量池和整数型常量池。
- “abc” 先在字符串常量池中查找,如果有,直接拿来用。如果没有则新建,然后再放入字符串常量池。
- 堆内存中的整数型常量池。
- [-128 ~ 127] 一共 256 个 Integer 类型的引用,放在整数型常量池中。没有超出这个范围的话,直接从常量池中取。
- 连接池(Connection Cache)和线程池。
- 连接池可以提高用户的访问效率,并保证数据库的安全性。
- Tomcat 服务器本身支持多线程,使用线程池机制提高效率。
- NoSQL 数据库如 Redis,用作缓存数据库。
12. HTTP协议
12.1 HTTP协议概述
什么是协议?
- 协议实际上是某些人,或者某些组织提前制定好的一套规范,大家都按照这个规范来,这样可以做到沟通无障碍。
- 协议就是一套规范,就是一套标准。由其他人或其他组织来负责制定的。
- 我说的话你能听懂,你说的话,我也能听懂,这说明我们之间是有一套规范的,一套协议的,这套协议就是:中国普通话协议。我们都遵守这套协议,我们之间就可以沟通无障碍。
什么是HTTP协议?
- HTTP协议:是W3C制定的一种超文本传输协议。(通信协议:发送消息的模板提前被制定好。)
- W3C:
- 万维网联盟组织
- 负责制定标准的:HTTP HTML4.0 HTML5 XML DOM等规范都是W3C制定的。
- 万维网之父:蒂姆·伯纳斯·李
什么是超文本?
- 超文本说的就是:不是普通文本,比如流媒体:声音、视频、图片等。
- HTTP协议支持:不但可以传送普通字符串,同样支持传递声音、视频、图片等流媒体信息。
这种协议游走在B和S之间。B向S发数据要遵循HTTP协议。S向B发数据同样需要遵循HTTP协议。这样B和S才能解耦合。
什么是解耦合?
- B不依赖S。
- S也不依赖B。
B/S表示:B/S结构的系统(浏览器访问WEB服务器的系统)
浏览器 向 WEB服务器发送数据,叫做:请求(request)
WEB服务器 向 浏览器发送数据,叫做:响应(response)
HTTP协议包括:
请求协议
- 浏览器 向 WEB服务器发送数据的时候,这个发送的数据需要遵循一套标准,这套标准中规定了发送的数据具体格式。
响应协议
WEB服务器 向 浏览器发送数据的时候,这个发送的数据需要遵循一套标准,这套标准中规定了发送的数据具体格式。
HTTP协议就是提前制定好的一种消息模板。
- 不管你是哪个品牌的浏览器,都是这么发。
- 不管你是哪个品牌的WEB服务器,都是这么发。
12.2 HTTP的请求协议(B –> S)
- HTTP的请求协议包括:4部分
- 请求行
- 请求头
- 空白行
- 请求体
12.2.1 HTTP请求协议的具体报文:GET请求
GET /servlet05/getServlet?username=lucy&userpwd=1111 HTTP/1.1 请求行 Host: localhost:8080 请求头 Connection: keep-alive sec-ch-ua: "Google Chrome";v="95", "Chromium";v="95", ";Not A Brand";v="99" 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/95.0.4638.54 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/servlet05/index.html Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 空白行 请求体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#### 12.2.2 HTTP请求协议的具体报文:POST请求
- ```
POST /servlet05/postServlet HTTP/1.1 请求行
Host: localhost:8080 请求头
Connection: keep-alive
Content-Length: 25
Cache-Control: max-age=0
sec-ch-ua: "Google Chrome";v="95", "Chromium";v="95", ";Not A Brand";v="99"
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/95.0.4638.54 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/servlet05/index.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
空白行
username=lisi&userpwd=123 请求体请求行
- 包括三部分:
- 第一部分:请求方式(7种)
- get(常用的)
- post(常用的)
- delete
- put
- head
- options
- trace
- 第二部分:URI
- 什么是URI? 统一资源标识符。代表网络中某个资源的名字。但是通过URI是无法定位资源的。
- 什么是URL?统一资源定位符。代表网络中某个资源,同时,通过URL是可以定位到该资源的。
- URI和URL什么关系,有什么区别?
- URL包括URI
- http://localhost:8080/servlet05/index.html 这是URL。
- /servlet05/index.html 这是URI。
- 第三部分:HTTP协议版本号
- 第一部分:请求方式(7种)
- 包括三部分:
请求头
- 请求的主机
- 主机的端口
- 浏览器信息
- 平台信息
- cookie等信息
- ….
空白行
- 空白行是用来区分“请求头”和“请求体”
请求体
- 向服务器发送的具体数据。
12.2.3 HTTP的响应协议(S –> B)
HTTP的响应协议包括:4部分
- 状态行
- 响应头
- 空白行
- 响应体
HTTP响应协议的具体报文:
HTTP/1.1 200 ok 状态行 Content-Type: text/html;charset=UTF-8 响应头 Content-Length: 160 Date: Mon, 08 Nov 2021 13:19:32 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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#### 12.2.4 HTTP状态行
1. 第一部分:协议版本号(HTTP/1.1)
2. 第二部分:状态码(HTTP协议中规定的响应状态号。不同的响应结果对应不同的号码。)
- 200 表示请求响应成功,正常结束。
- 以4开始的,一般是浏览器端的错误导致的。
- 404表示访问的资源不存在,通常是因为要么是你路径写错了,要么是路径写对了,但是服务器中对应的资源并没有启动成功。总之404错误是前端错误。
- 405表示前端发送的请求方式与后端请求的处理方式不一致时发生:
- 比如:<u>前端是POST请求,后端的处理方式按照get方式进行处理时,发生405</u>
- 比如:前端是GET请求,后端的处理方式按照post方式进行处理时,发生405
- 以5开始的,一般是服务器端的错误导致的。
- 500表示服务器端的程序出现了异常。一般会认为是服务器端的错误导致的。
3. 第三部分:状态的描述信息
- ok 表示正常成功结束。
- not found 表示资源找不到。
- 响应头:
- 响应的内容类型
- 响应的内容长度
- 响应的时间
- ....
- 空白行:
- 用来分隔“响应头”和“响应体”的。
- 响应体:
- 响应体就是响应的正文,这些内容是一个长的字符串,这个字符串被浏览器渲染,解释并执行,最终展示出效果。
- 怎么查看的协议内容?
- 使用chrome浏览器:F12。然后找到network,通过这个面板可以查看协议的具体内容。
- 怎么向服务器发送GET请求,怎么向服务器发送POST请求?
- 到目前为止,只有一种情况可以发送POST请求:使用form表单,并且form标签中的method属性值为:method="post"。
- 其他所有情况一律都是get请求:
- 在浏览器地址栏上直接输入URL,敲回车,属于get请求。
- 在浏览器上直接点击超链接,属于get请求。
- 使用form表单提交数据时,form标签中没有写method属性,默认就是get
- 或者使用form的时候,form标签中method属性值为:method="get"
- ....
- GET请求和POST请求有什么区别?
- get请求发送数据的时候,数据会挂在URI的后面,并且在URI后面添加一个“?”,"?"后面是数据。这样会导致发送的数据回显在浏览器的地址栏上。(get请求在“请求行”上发送数据)
- http://localhost:8080/servlet05/getServlet?username=zhangsan&userpwd=1111
- post请求发送数据的时候,在请求体当中发送。不会回显到浏览器的地址栏上。也就是说post发送的数据,在浏览器地址栏上看不到。(post在“请求体”当中发送数据)
- get请求只能发送普通的字符串。并且发送的字符串长度有限制,不同的浏览器限制不同。这个没有明确的规范。
- get请求无法发送大数据量。
- post请求可以发送任何类型的数据,包括普通字符串,流媒体等信息:视频、声音、图片。
- post请求可以发送大数据量,理论上没有长度限制。
- get请求在W3C中是这样说的:get请求比较适合从服务器端获取数据。
- post请求在W3C中是这样说的:post请求比较适合向服务器端传送数据。
- get请求是安全的。get请求是绝对安全的。为什么?因为get请求只是为了从服务器上获取数据。不会对服务器造成威胁。(get本身是安全的,你不要用错了。用错了之后又冤枉人家get不安全,你这样不好(太坏了),那是你自己的问题,不是get请求的问题。)
- post请求是危险的。为什么?因为post请求是向服务器提交数据,如果这些数据通过后门的方式进入到服务器当中,服务器是很危险的。另外post是为了提交数据,所以一般情况下拦截请求的时候,大部分会选择拦截(监听)post请求。
- get请求支持缓存。
- https://n.sinaimg.cn/finance/590/w240h350/20211101/b40c-b425eb67cabc342ff5b9dc018b4b00cc.jpg
- 任何一个get请求最终的“响应结果”都会被浏览器缓存起来。在浏览器缓存当中:
- 一个get请求的路径a 对应 一个资源。
- 一个get请求的路径b 对应 一个资源。
- 一个get请求的路径c 对应 一个资源。
- ......
- 实际上,你只要发送get请求,浏览器做的第一件事都是先从本地浏览器缓存中找,找不到的时候才会去服务器上获取。这种缓存机制目的是为了提高用户的体验。
- 有没有这样一个需求:我们不希望get请求走缓存,怎么办?怎么避免走缓存?我希望每一次这个get请求都去服务器上找资源,我不想从本地浏览器的缓存中取。
- 只要每一次get请求的请求路径不同即可。
- https://n.sinaimg.cn/finance/590/w240h350/20211101/7cabc342ff5b9dc018b4b00cc.jpg?t=789789787897898
- https://n.sinaimg.cn/finance/590/w240h350/20211101/7cabc342ff5b9dc018b4b00cc.jpg?t=789789787897899
- https://n.sinaimg.cn/finance/590/w240h350/20211101/7cabc342ff5b9dc018b4b00cc.jpg?t=系统毫秒数
- 怎么解决?可以在路径的后面添加一个每时每刻都在变化的“时间戳”,这样,每一次的请求路径都不一样,浏览器就不走缓存了。
- post请求不支持缓存。(POST是用来修改服务器端的资源的。)
- post请求之后,服务器“响应的结果”不会被浏览器缓存起来。因为这个缓存没有意义。
- GET请求和POST请求如何选择,什么时候使用GET请求,什么时候使用POST请求?
- 怎么选择GET请求和POST请求呢?衡量标准是什么呢?你这个请求是想获取服务器端的数据,还是想向服务器发送数据。如果你是想从服务器上获取资源,建议使用GET请求,如果你这个请求是为了向服务器提交数据,建议使用POST请求。
- 大部分的form表单提交,都是post方式,因为form表单中要填写大量的数据,这些数据是收集用户的信息,一般是需要传给服务器,服务器将这些数据保存/修改等。
- 如果表单中有敏感信息,还是建议适用post请求,因为get请求会回显敏感信息到浏览器地址栏上。(例如:密码信息)
- 做文件上传,一定是post请求。要传的数据不是普通文本。
- 其他情况都可以使用get请求。
- 不管你是get请求还是post请求,发送的请求数据格式是完全相同的,只不过位置不同,格式都是统一的:
- name=value&name=value&name=value&name=value
- name是什么?
- 以form表单为例:form表单中input标签的name。
- value是什么?
- 以form表单为例:form表单中input标签的value。
-----
## 13. 模板方法设计模式
-----
模板设计方法(Template Design Pattern)是软件设计模式的一种,它定义了一个算法的骨架,并允许子类在不改变算法结构的情况下重新定义算法的某些步骤。该模式在面向对象编程中很常见,通过基类中的模板方法(Template Method)来控制算法的执行顺序,同时将一些步骤的具体实现延迟到子类中。
### 13.1 模板设计方法的组成部分:
1. **抽象类(Abstract Class)**:
- 定义了一个模板方法(Template Method),该方法包含算法的骨架。
- 包含算法中的一些具体步骤,这些步骤可以是抽象的(必须由子类实现)或者是具体的(可以被子类覆盖)。
2. **具体子类(Concrete Class)**:
- 实现抽象类中定义的抽象方法。
- 可以选择性地覆盖抽象类中已实现的方法。
### 13.2 模板设计方法的主要步骤:
1. 在抽象类中定义模板方法,该方法包含算法的步骤。
2. 在抽象类中定义算法中的关键步骤(抽象或具体)。
3. 在具体子类中实现或覆盖这些关键步骤。
### 13.3 模板设计方法的优点:
- 代码复用:算法的通用部分在抽象类中实现,避免了重复代码。
- 灵活性:允许子类根据需要实现或修改算法的某些步骤。
- 控制算法结构:确保算法的执行顺序一致,同时允许子类实现细节。
### 13.4 示例代码:
```java
package template;
/**
* 1. Person就是模板方法设计模式当中的模板类。
* 2. day()方法就是模板方法设计模式当中的模板方法。
*/
public abstract class Psrson {
// 模板方法
// 添加了final之后,这个方法无法被覆盖,这样核心的算法也可以得到保护。(不是必须要加)
// 模板方法定义核心的算法骨架,具体的实现步骤可以延迟到子类当中去实现。
// 核心算法一方面是得到了保护,不能被改变。另外一方面就是算法得到了重复使用。
// 另外代码也得到了服用,因为算法中某些步骤的代码是固定的。这些固定的代码不会随着子类的变化而变换,这一部分代码可以写到模板类当中。
public final void day(){
getUp();
wash();
eatBreakfast();
doSome();
eatDinner();
sleep();
}
public void getUp(){
System.out.println("起床");
}
public void wash(){
System.out.println("洗漱");
}
public void eatBreakfast(){
System.out.println("吃早餐");
}
//不同的方法设为抽象方法
public abstract void doSome();
public void eatDinner(){
System.out.println("吃晚饭");
}
public void sleep(){
System.out.println("睡觉");
}
}
//Studnet具体子类
public class Student extends Psrson{
@Override
public void doSome(){
System.out.println("学生上学,学习");
}
}
//Teacher具体子类
public class Teacher extends Psrson{
@Override
public void doSome(){
System.out.println("老师教学生");
}
}
在这个示例中,Person是个模版类。它实现了公共的方法,对于表现不同行为的方法定义为抽象方法。这样子类只需重写要表现不同行为的方法即可。如果不使用模板类会导致子类有大量共同的代码,不利于开发。
13. HttpServlet源码分析
HttpServlet类是专门为HTTP协议准备的。比GenericServlet更加适合HTTP协议下的开发。
13.1 Servlet核心接口与HttpServlet相关类
HttpServlet在哪个包下?
jakarta.servlet.http.HttpServlet
到目前为止我们接触了servlet规范中哪些接口?
jakarta.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协议到浏览器的。
- 回忆Servlet生命周期?
- 用户第一次请求
- Tomcat服务器通过反射机制,调用无参数构造方法。创建Servlet对象。(web.xml文件中配置的Servlet类对应的对象。)
- Tomcat服务器调用Servlet对象的init方法完成初始化。
- Tomcat服务器调用Servlet对象的service方法处理请求。
- 用户第二次请求
- Tomcat服务器调用Servlet对象的service方法处理请求。
- 用户第三次请求
- Tomcat服务器调用Servlet对象的service方法处理请求。
- ….
- Tomcat服务器调用Servlet对象的service方法处理请求。
- 服务器关闭
- Tomcat服务器调用Servlet对象的destroy方法,做销毁之前的准备工作。
- Tomcat服务器销毁Servlet对象。
- 用户第一次请求
- HttpServlet源码分析:
1 | public class HelloServlet extends HttpServlet { |
1 |
|
1 | // HttpServlet模板类。 |
通过以上源代码分析:
假设前端发送的请求是get请求,后端程序员重写的方法是doPost
假设前端发送的请求是post请求,后端程序员重写的方法是doGet
会发生什么呢?
发生405这样的一个错误。
405表示前端的错误,发送的请求方式不对。和服务器不一致。不是服务器需要的请求方式。通过以上源代码可以知道:只要HttpServlet类中的doGet方法或doPost方法执行了,必然405.
怎么避免405的错误呢?
后端重写了doGet方法,前端一定要发get请求。
后端重写了doPost方法,前端一定要发post请求。
这样可以避免405错误。
这种前端到底需要发什么样的请求,其实应该后端说了算。后端让发什么方式,前端就得发什么方式。
有的人,你会看到为了避免405错误,在Servlet类当中,将doGet和doPost方法都进行了重写。
这样,确实可以避免405的发生,但是不建议,405错误还是有用的。该报错的时候就应该让他报错。
如果你要是同时重写了doGet和doPost,那还不如你直接重写service方法好了。这样代码还能
少写一点。
*/
我们编写的HelloServlet直接继承HttpServlet,直接重写HttpServlet类中的service()方法行吗?
- 可以,只不过你享受不到405错误。享受不到HTTP协议专属的东西。
到今天我们终于得到了最终的一个Servlet类的开发步骤:
- 第一步:编写一个Servlet类,直接继承HttpServlet
- 第二步:重写doGet方法或者重写doPost方法,到底重写谁,javaweb程序员说了算。
- 第三步:将Servlet类配置到web.xml文件当中。
- 第四步:准备前端的页面(form表单),form表单中指定请求路径即可。
15. 关于一个web站点的欢迎页面
15.1 什么是一个web站点的欢迎页面?
对于一个webapp来说,是可以设置它的欢迎页面的。设置了欢迎页面之后,当你访问这个webapp的时候,或者访问这个web站点的时候,没有指定任何“资源路径”,这个时候会默认访问你的欢迎页面。
一般的访问方式是:
- http://localhost:8080/servlet06/login.html 这种方式是指定了要访问的就是login.html资源。
如果访问的方式是:
- http://localhost:8080/servlet06 如果我们访问的就是这个站点,没有指定具体的资源路径。它会默认会访问你设置的欢迎页面。
15.2 设置欢迎页面步骤
第一步:在IDEA工具的web目录下新建了一个文件login.html
第二步:在web.xml文件中进行了以下的配置
1 | <welcome-file-list> |
[!NOTE]
注意:设置欢迎页面的时候,这个路径不需要以“/”开始。并且这个路径默认是从webapp的根下开始查找。
- 第三步:启动服务器,浏览器地址栏输入地址
15.2.1 如何将webapp子目录下的html设置为欢迎页?
在webapp根下新建html
在html下新建page目录
在page目录下新建page.html页面
在web.xml文件中应该这样配置
1 | <welcome-file-list> |
[!NOTE]
注意:路径不需要以“/”开始,并且路径默认从webapp的根下开始找。
15.2.2 一个webapp是可以设置多个欢迎页面的
1 | <welcome-file-list> |
注意 :越靠上的优先级越高。找不到的继续向下找。
- 当我的文件名设置为index.html的时候,不需要在web.xml文件中进行配置欢迎页面。
- 因为Tomcat服务器已经提前配置好了。
实际上配置欢迎页面有两个地方可以配置:
一个是在webapp内部的web.xml文件中。(在这个地方配置的属于局部配置)
一个是在
CATALINA_HOME/conf/web.xml
文件中进行配置。(在这个地方配置的属于全局配置)
1
2
3
4
5<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>- Tomcat服务器的全局欢迎页面是:
index.html index.htm index.jsp
。如果你一个web站点没有设置局部的欢迎页面,Tomcat服务器就会以index.html index.htm index.jsp作为一个web站点的欢迎页面。注意原则:局部优先原则。(就近原则)
15.2.3 欢迎页可以是一个Servlet
欢迎页就是一个资源,既然是一个资源,那么可以是静态资源,也可以是动态资源。静态资源如:index.html、welcome.html等;动态资源如:Servlet类。
- 第一步:写一个Servlet
1 | public class WelcomeServlet extends HttpServlet { |
第二步:在web.xml文件中配置servlet
1
2
3
4
5
6
7
8<servlet>
<servlet-name>welcomeServlet</servlet-name>
<servlet-class>servlet.WelcomeServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>welcomeServlet</servlet-name>
<url-pattern>/welcome</url-pattern>
</servlet-mapping>第三步:在web.xml文件中配置欢迎页
1
2
3
4
5<!-- 注意:欢迎页面路径不要以/开始-->
<welcome-file-list>
<welcome-file>welcome</welcome-file>
<welcome-file>html/page/page.html</welcome-file>
</welcome-file-list>[!NOTE]
欢迎页面的路径配置方式是这样的:对于静态资源,路径从webapp
目录开始;而对于动态资源,使用对应Servlet的<url-pattern>
路径(不需要以斜杠/
开头)。
16. 关于WEB-INF目录
- 在WEB-INF目录下新建了一个文件:welcome.html
- 打开浏览器访问:http://localhost:8080/servlet07/WEB-INF/welcome.html 出现了404错误。
注意:放在WEB-INF目录下的资源是受保护的。在浏览器上不能够通过路径直接访问。所以像HTML、CSS、JS、image等静态资源一定要放到WEB-INF目录之外。
17. HttpServletRequest接口详解
HttpServletRequest是一个接口,是Servlet规范中的一员。全限定名称:
jakarta.servlet.http.HttpServletRequest
HttpServletRequest接口的父接口:ServletRequest
1 | public interface HttpServletRequest extends ServletRequest {} |
17.1 HttpServletRequest接口的实现类谁写的? HttpServletRequest对象是谁给创建的?
- 通过测试:
org.apache.catalina.connector.RequestFacade
实现了 HttpServletRequest接口
1 | public class RequestFacade implements HttpServletRequest {} |
- 测试结果说明:Tomcat服务器(WEB服务器、WEB容器)实现了HttpServletRequest接口,还是说明了Tomcat服务器实现了Servlet规范。而对于我们javaweb程序员来说,实际上不需要关心这个,我们只需要面向接口编程即可。我们关心的是HttpServletRequest接口中有哪些方法,这些方法可以完成什么功能!!!!
17.2 HttpServletRequest对象中都有什么信息?都包装了什么信息?
HttpServletRequest对象是Tomcat服务器负责创建的。这个对象中封装了HTTP的请求协议。
实际上是用户发送请求的时候,遵循了HTTP协议,发送的是HTTP的请求协议,Tomcat服务器将HTTP协议中的信息以及数据全部解析出来,然后Tomcat服务器把这些信息封装到HttpServletRequest对象当中,传给了我们javaweb程序员。
javaweb程序员面向HttpServletRequest接口编程,调用方法就可以获取到请求的信息了。
17.3 request和response对象的生命周期
17.3.1 Request对象的生命周期
创建:
当客户端(通常是浏览器)发送HTTP请求到服务器时,服务器会为每个请求创建一个新的HttpServletRequest
对象。这个对象包含了请求的所有信息,例如请求参数、头信息、URI等。处理:
服务器将创建的HttpServletRequest
对象传递给对应的Servlet的service
方法,通常是doGet
、doPost
等方法。Servlet通过这个对象来获取请求信息和与客户端进行交互。销毁:
一旦Servlet完成了对请求的处理,服务器会销毁这个HttpServletRequest
对象。对象的销毁意味着它不再有效,服务器会回收其占用的资源。
17.3.2 Response对象的生命周期
创建:
HttpServletRequest
对象类似,当服务器接收到客户端的请求时,它也会创建一个新的HttpServletResponse
对象。这个对象将用于生成和发送响应回客户端。处理:
服务器将创建的HttpServletResponse
对象传递给对应的Servlet的service
方法。Servlet通过这个对象来设置响应的状态码、头信息以及响应的内容。提交和销毁:
当Servlet完成对响应内容的生成,并且调用了response
对象的flushBuffer
方法或返回后,服务器会将响应数据发送给客户端。然后,服务器会销毁这个HttpServletResponse
对象,并回收其占用的资源。
总结来说,request
和response
对象的生命周期是非常短暂的,它们仅在一次请求-响应周期内有效。从客户端发送请求到服务器生成并发送响应的整个过程中,这两个对象会被创建、使用并最终销毁。
17.4 HttpServletRequest接口中有哪些常用的方法?
- 怎么获取前端浏览器用户提交的数据?
1 | Map<String,String[]> getParameterMap() 这个是获取Map |
[!NOTE]
- String getParameter(String name) 获取value这个一维数组当中的第一个元素。这个方法最常用。
- 这四个方法适用于获取用户提交的数据,不要和对请求域的数据方法搞混。
- 方法测试
Map<String,String[]> parameterMap = request.getParameterMap();
1 | //1. 通过getParameterMap()获取参数Map集合 |
Enumeration
String[] values = request.getParameterValues(“name”);
String value = request.getParameter(“name”);
1 | //2. 通过getParameterNames()获取Map集合所有key |
17.5 前端表单提交的数据采用什么样的数据结构存储?
前端提交的数据格式:username=abc&userpwd=111&aihao=s&aihao=d&aihao=tt
采用Map集合来存储:
1
2
3
4
5
6
7
8
9
10Map<String,String>
key存储String
value存储String
key value
---------------------
username abc
userpwd 111
aihao s
aihao d
aihao tt如果采用以上的数据结构存储会发现key重复的时候value覆盖。这样是不行的,因为map的key不能重复。但是将value存储改为String数组即可解决重复覆盖问题。
1
2
3
4
5
6
7
8Map<String, String[]>
key存储String
value存储String[]
key value
-------------------------------
username {"abc"}
userpwd {"111"}
aihao {"s","d","tt"}注意:前端表单提交数据的时候,假设提交了120这样的“数字”,其实是以字符串”120”的方式提交的,所以服务器端获取到的一定是一个字符串的”120”,而不是一个数字。(前端永远提交的是字符串,后端获取的也永远是字符串。)
17.6 “应用域”对象和“请求域”对象
17.7.1 “应用域”对象是什么?
应用域对象通常指的是 ServletContext
对象。在 Java Web 开发中,它表示整个 web 应用的上下文,可以在应用的各个部分之间共享数据。因为它的生命周期与 web 应用一致,所以被称为“应用域”对象。
什么情况下会考虑向ServletContext这个应用域当中绑定数据呢?
- 第一:所有用户共享的数据。
- 第二:这个共享的数据量很小。
- 第三:这个共享的数据很少的修改操作。
在以上三个条件都满足的情况下,使用这个应用域对象,可以大大提高我们程序执行效率。实际上向应用域当中绑定数据,就相当于把数据放到了缓存(Cache)当中,然后用户访问的时候直接从缓存中取,减少IO的操作,大大提升系统的性能,所以缓存技术是提高系统性能的重要手段。
ServletContext当中有三个操作域的方法:
1
2
3
4
5
6
7
8
9void 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这个键值对。
17.7.2 “请求域”对象
“请求域”对象要比“应用域”对象范围小很多。生命周期短很多。请求域只在一次请求内有效。一个请求对象request对应一个请求域对象。一次请求结束之后,这个请求域就销毁了。
请求域对象也有这三个方法:
1
2
3void setAttribute(String name, Object obj); // 向域当中绑定数据。
Object getAttribute(String name); // 从域当中根据name获取数据。
void removeAttribute(String name); // 将域当中绑定的数据移除[!NOTE]
- 和应用域ServletContext使用方法相同,只是后者生命周期短,涉及请求转发。
- 请注意区分这些操作方法与接收前端传来数据的四种常用方法。
请求域和应用域的选用原则?
- 尽量使用小的域对象,因为小的域对象占用的资源较少。
转发(一次请求)
1
2
3
4
5
6// 第一步:获取请求转发器对象
RequestDispatcher dispatcher = request.getRequestDispatcher("/b");
// 第二步:调用转发器的forward方法完成跳转/转发
dispatcher.forward(request,response);
// 第一步和第二步代码可以联合在一起。
request.getRequestDispatcher("/b").forward(request,response);两个Servlet怎么共享数据?
- 将数据放到ServletContext应用域当中,当然是可以的,但是应用域范围太大,占用资源太多。不建议使用。
- 可以将数据放到request域当中,然后AServlet转发到BServlet,保证AServlet和BServlet在同一次请求当中,这样就可以做到两个Servlet,或者多个Servlet共享同一份数据。
转发的下一个资源必须是一个Servlet吗?
- 不一定,只要是Tomcat服务器当中的合法资源,都是可以转发的。例如:html….
- 注意:转发的时候,路径的写法要注意,转发的路径以“/”开始,不加项目名。
关于request对象中两个非常容易混淆的方法:
1
2
3
4
5
6
7
8
9// uri?username=zhangsan&userpwd=123&sex=1
String username = request.getParameter("username");
// 之前一定是执行过:request.setAttribute("name", new Object())
Object obj = request.getAttribute("name");
// 以上两个方法的区别是什么?
// 第一个方法:获取的是用户在浏览器上提交的数据。
// 第二个方法:获取的是请求域当中绑定的数据。[!NOTE]
getParameter是针对获取前端发送的数据,setAttribute是针对请求域存储的数据。
17.8 HttpServletRequest接口的其他常用方法
获取客户端的IP地址
1
2// 获取客户端的IP地址
String remoteAddr = request.getRemoteAddr();设置请求体的字符集。
POST请求处理:当客户端通过POST方法发送请求时,请求的数据通常包含在请求体中。如果请求体中包含中文或其他非ASCII字符,并且服务器未设置正确的字符集,就可能导致乱码问题。通过设置请求体的字符集,比如使用
request.setCharacterEncoding("UTF-8")
来告诉服务器如何解析请求体的数据,可以有效避免乱码问题。GET请求乱码问题:与POST不同,GET请求的参数是通过URL传递的,通常以查询字符串的形式出现在URL中。如果URL中包含中文或其他非ASCII字符,并且服务器未正确解析这些字符,就可能导致乱码问题。设置请求体字符集的方法并不能解决GET请求中的乱码问题,因为这些字符已经包含在URL中,而非请求体中。
处理post请求乱码问题
1
2
3
4// 设置请求体的字符集。(显然这个方法是处理POST请求的乱码问题。这种方式并不能解决get请求的乱码问题。)
// Tomcat10之后,request请求体当中的字符集默认就是UTF-8,不需要设置字符集,不会出现乱码问题。
// Tomcat9前(包括9在内),如果前端请求体提交的是中文,后端获取之后出现乱码。
request.setCharacterEncoding("UTF-8");处理get请求乱码问题
1
2
3
4// get请求发送的时候,数据是在请求行上提交的,不是在请求体当中提交的。
// 方案:修改CATALINA_HOME/conf/server.xml配置文件
<Connector URIEncoding="UTF-8" />
// 注意:从Tomcat8之后,URIEncoding的默认值就是UTF-8,所以GET请求也没有乱码问题了。
- 处理响应乱码问题
1
2
3// 在Tomcat9之前(包括9),响应中文也是有乱码的,怎么解决这个响应的乱码?
response.setContentType("text/html;charset=UTF-8");
// 在Tomcat10之后,包括10在内,响应中文的时候就不在出现乱码问题了。以上代码就不需要设置UTF-8了。
获取应用的根路径
1
2// 获取应用的根路径(动态的获取)
String contextPath = request.getContextPath();获取请求方式
1
2//获取请求方式
String method = request.getMethod();获取请求的URI
1
2// 获取请求的URI
String uri = request.getRequestURI(); // /aaa/testRequest获取servlet path
1
2// 获取servlet path
String servletPath = request.getServletPath(); // /testRequest
18. 使用纯Servlet做一个单表的CRUD操作
使用纯粹的Servlet完成单表【对部门的】的增删改查操作。(B/S结构的。)
实现步骤
第一步:准备一张数据库表。(sql脚本)
# 部门表 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', 'BEIJING'); insert into dept(deptno, dname, loc) values(20, 'YanFaBu', 'SHANGHAI'); insert into dept(deptno, dname, loc) values(30, 'JiShuBu', 'GUANGZHOU'); insert into dept(deptno, dname, loc) values(40, 'MeiTiBu', 'SHENZHEN'); commit; select * from dept;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
- 第二步:准备一套HTML页面(项目原型)【前端开发工具使用HBuilder】
- 把HTML页面准备好
- 然后将HTML页面中的链接都能够跑通。(页面流转没问题。)
- 应该设计哪些页面呢?
- 欢迎页面:index.html
- 列表页面:list.html(以列表页面为核心,展开其他操作。)
- 新增页面:add.html
- 修改页面:edit.html
- 详情页面:detail.html
- 第三步:分析我们这个系统包括哪些功能?
- 什么叫做一个功能呢?
- 只要 这个操作连接了数据库,就表示一个独立的功能。
- 包括哪些功能?
- 查看部门列表
- 新增部门
- 删除部门
- 查看部门详细信息
- 跳转到修改页面
- 修改部门
- 第四步:在IDEA当中搭建开发环境
- 创建一个webapp(给这个webapp添加servlet-api.jar和jsp-api.jar到classpath当中。)
- 向webapp中添加连接数据库的jar包(mysql驱动)
- 必须在WEB-INF目录下新建lib目录,然后将mysql的驱动jar包拷贝到这个lib目录下。这个目录名必须叫做lib,全部小写的。
- JDBC的工具类
- 将所有HTML页面拷贝到web目录下。
- 第五步:实现第一个功能:查看部门列表
- 我们应该怎么去实现一个功能呢?
- 建议:你可以从后端往前端一步一步写。也可以从前端一步一步往后端写。都可以。但是千万要记住不要想起来什么写什么。你写代码的过程最好是程序的执行过程。也就是说:程序执行到哪里,你就写哪里。这样一个顺序流下来之后,基本上不会出现什么错误、意外。
- 从哪里开始?
- 假设从前端开始,那么一定是从用户点击按钮那里开始的。
- 第一:先修改前端页面的超链接,因为用户先点击的就是这个超链接。
- ```html
<a href="/oa/dept/list">查看部门列表</a>第二:编写web.xml文件
<servlet> <servlet-name>list</servlet-name> <servlet-class>com.bjpowernode.oa.web.action.DeptListServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>list</servlet-name> <!--web.xml文件中的这个路径也是以“/”开始的,但是不需要加项目名--> <url-pattern>/dept/list</url-pattern> </servlet-mapping>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 第三:编写DeptListServlet类继承HttpServlet类。然后重写doGet方法。
- ```java
package com.bjpowernode.oa.web.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(“”)这里有一个双引号,容易冲突。
现在写完这个功能之后,你会有一种感觉,感觉开发很繁琐,只使用servlet写代码太繁琐了。
while(rs.next()){ String deptno = rs.getString("a"); 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>"); out.print(" <a href=''>删除</a>"); out.print(" <a href='edit.html'>修改</a>"); out.print(" <a href='detail.html'>详情</a>"); out.print(" </td>"); out.print(" </tr>"); }
- 详情 是需要连接数据库的,所以这个超链接点击之后也是需要执行一段java代码的。所以要将这个超链接的路径修改一下。 - 注意:修改路径之后,这个路径是需要加项目名的。"/oa/dept/detail"1
2
3
4
5
6
7
8
9
- 第六步:查看部门详情。
- 建议:从前端往后端一步一步实现。首先要考虑的是,用户点击的是什么?用户点击的东西在哪里?
- 一定要先找到用户点的“详情”在哪里。找了半天,终于在后端的java程序中找到了
- ```html
<a href='写一个路径'>详情</a>技巧:
out.print("<a href='"+contextPath+"/dept/detail?deptno="+deptno+"'>详情</a>");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- **重点:向服务器提交数据的格式:uri?**name=value&name=value&name=value&name=value
- 这里的问号,必须是英文的问号。不能中文的问号。
- 解决404的问题。写web.xml文件。
- ```xml
<servlet>
<servlet-name>detail</servlet-name>
<servlet-class>com.bjpowernode.oa.web.action.DeptDetailServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>detail</servlet-name>
<url-pattern>/dept/detail</url-pattern>
</servlet-mapping>
编写一个类:DeptDetailServlet继承HttpServlet,重写doGet方法。
package com.bjpowernode.oa.web.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 DeptDetailServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //中文思路(思路来源于:你要做什么?目标:查看部门详细信息。) // 第一步:获取部门编号 // 第二步:根据部门编号查询数据库,获取该部门编号对应的部门信息。 // 第三步:将部门信息响应到浏览器上。(显示一个详情。) } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 在doGet方法当中:连接数据库,根据部门编号查询该部门的信息。动态展示部门详情页。
- 第七步:删除部门
- 怎么开始?从哪里开始?从前端页面开始,用户点击删除按钮的时候,应该提示用户是否删除。因为删除这个动作是比较危险的。任何系统在进行删除操作之前,是必须要提示用户的,因为这个删除的动作有可能是用户误操作。(在前端页面上写JS代码,来提示用户是否删除。)
- ```html
<a href="javascript:void(0)" onclick="del(30)" >删除</a>
<script type="text/javascript">
function del(dno){
if(window.confirm("亲,删了不可恢复哦!")){
document.location.href = "/oa/dept/delete?deptno=" + dno;
}
}
</script>
以上的前端程序要写到后端的java代码当中:
- DeptListServlet类的doGet方法当中,使用out.print()方法,将以上的前端代码输出到浏览器上。
解决404的问题:
web.xml文件
<servlet> <servlet-name>delete</servlet-name> <servlet-class>com.bjpowernode.oa.web.action.DeptDelServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>delete</servlet-name> <url-pattern>/dept/delete</url-pattern> </servlet-mapping>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 编写DeptDelServlet继承HttpServlet,重写doGet方法。
- ```java
package com.bjpowernode.oa.web.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 DeptDelServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 根据部门编号,删除部门。
}
}
删除成功或者失败的时候的一个处理(这里我们选择了转发,并没有使用重定向机制。)
// 判断删除成功了还是失败了。 if (count == 1) { //删除成功 //仍然跳转到部门列表页面 //部门列表页面的显示需要执行另一个Servlet。怎么办?转发。 request.getRequestDispatcher("/dept/list").forward(request, response); }else{ // 删除失败 request.getRequestDispatcher("/error.html").forward(request, response); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
- 第八步:新增部门
- 注意:最后保存成功之后,转发到 /dept/list 的时候,会出现405,为什么?
- 第一:保存用的是post请求。底层要执行doPost方法。
- 第二:转发是一次请求,之前是post,之后还是post,因为它是一次请求。
- 第三:/dept/list Servlet当中只有一个doGet方法。
- 怎么解决?两种方案
- 第一种:在/dept/list Servlet中添加doPost方法,然后在doPost方法中调用doGet。
- 第二种:重定向。
- 第九步:跳转到修改部门的页面
- 第十步:修改部门
## 19. 在一个web应用中应该如何完成资源的跳转
- 在一个web应用中通过两种方式,可以完成资源的跳转:
- 第一种方式:**转发**
- 第二种方式:**重定向**
### 19.1 转发和重定向代码上有什么区别?
- **转发**
```java
// 获取请求转发器对象
RequestDispatcher dispatcher = request.getRequestDispatcher("/dept/list");
// 调用请求转发器对象的forward方法完成转发
dispatcher.forward(request, response);
// 合并一行代码
request.getRequestDispatcher("/dept/list").forward(request, response);
// 转发的时候是一次请求,不管你转发了多少次。都是一次请求。
// AServlet转发到BServlet,再转发到CServlet,再转发到DServlet,不管转发了多少次,都在同一个request当中。
// 这是因为调用forward方法的时候,会将当前的request和response对象传递给下一个Servlet。
重定向
1 | // 注意:路径上要加一个项目名。为什么? |
[!NOTE]
注意:路径上要加一个项目名。
19.2 转发和重定向形式上有什么区别?
- 转发(一次请求
在浏览器地址栏上发送的请求是:http://localhost:8080/servlet10/a ,最终请求结束之后,浏览器地址栏上的地址还是这个。没变。 - 重定向(两次请求)
在浏览器地址栏上发送的请求是:http://localhost:8080/servlet10/a ,最终在浏览器地址栏上显示的地址是:http://localhost:8080/servlet10/b
19.3 转发和重定向的本质区别?
- 转发:是由WEB服务器来控制的。A资源跳转到B资源,这个跳转动作是Tomcat服务器内部完成的。
- 重定向:是浏览器完成的。具体跳转到哪个资源,是浏览器说了算。
19.4 转发和重定向应该如何选择?什么时候使用转发,什么时候使用重定向?
- 如果在上一个Servlet当中向request域当中绑定了数据,希望从下一个Servlet当中把request域里面的数据取出来,使用转发机制。
- 剩下所有的请求均使用重定向。(重定向使用较多。)
19.5 跳转的下一个资源有没有要求呢?必须是一个Servlet吗?
不一定,跳转的资源只要是服务器内部合法的资源即可。包括:Servlet、JSP、HTML…..
转发会存在浏览器的刷新问题。
20. Servlet注解,简化配置
分析oa项目中的web.xml文件
- 现在只是一个单标的CRUD,没有复杂的业务逻辑,很简单的一丢丢功能。web.xml文件中就有如此多的配置信息。如果采用这种方式,对于一个大的项目来说,这样的话web.xml文件会非常庞大,有可能最终会达到几十兆。
- 在web.xml文件中进行servlet信息的配置,显然开发效率比较低,每一个都需要配置一下。
- 而且在web.xml文件中的配置是很少被修改的,所以这种配置信息能不能直接写到java类当中呢?可以的。
Servlet3.0版本之后,推出了各种Servlet基于注解式开发。优点是什么?
- 开发效率高,不需要编写大量的配置信息。直接在java类上使用注解进行标注。
- web.xml文件体积变小了。
并不是说注解有了之后,web.xml文件就不需要了:
- 有一些需要变化的信息,还是要配置到web.xml文件中。一般都是 注解+配置文件 的开发模式。
- 一些不会经常变化修改的配置建议使用注解。一些可能会被修改的建议写到配置文件中。
我们的第一个注解:
jakarta.servlet.annotation.WebServlet
这会立即使Session失效,并且清除Session中的所有数据。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
- 在Servlet类上使用:@WebServlet,WebServlet注解中有哪些属性呢?
- name属性:用来指定Servlet的名字。等同于:<servlet-name>
- urlPatterns属性:用来指定Servlet的映射路径。可以指定多个字符串。<url-pattern>
- loadOnStartUp属性:用来指定在服务器启动阶段是否加载该Servlet。等同于:<load-on-startup>
- value属性:当注解的属性名是value的时候,使用注解的时候,value属性名是可以省略的。
- 注意:不是必须将所有属性都写上,只需要提供需要的。(需要什么用什么。)
- 注意:属性是一个数组,如果数组中只有一个元素,使用该注解的时候,属性值的大括号可以省略。
- 注解对象的使用格式:
- @注解名称(属性名=属性值, 属性名=属性值, 属性名=属性值....)
## 21. 关于B/S结构系统的会话机制(session机制)
### 21.1 什么是会话?
用户打开浏览器,进行一系列操作,然后最终将浏览器关闭,这个整个过程叫做:一次会话。会话在服务器端也有一个对应的java对象,这个java对象叫做:session。
用户在浏览器上点击了一下,然后到页面停下来,可以粗略认为是一次请求。请求对应的服务器端的java对象是:request。
一个会话当中包含多次请求。(一次会话对应N次请求。),在java的servlet规范当中,session对应的类名:HttpSessio`(jarkata.servlet.http.HttpSession)`
session机制属于B/S结构的一部分。如果使用php语言开发WEB项目,同样也是有session这种机制的。session机制实际上是一个规范。然后不同的语言对这种会话机制都有实现。
Session对象在Web开发中的主要作用是保存用户的会话状态。通过Session对象,可以在用户登录成功后持久化保存登录状态,使得用户在整个会话期间(通常是用户打开浏览器直到关闭浏览器)都能保持登录状态,而无需每次操作都重新验证身份。这种方式能够有效地管理用户的登录状态,提升用户体验并确保安全性。
### 21.2 为什么需要session对象来保存会话状态呢?
HTTP协议是一种无状态协议,意味着<u>**每次请求与响应之间是相互独立的**</u>。在请求时,浏览器和服务器建立连接,但请求结束后连接即断开。这种设计有利于降低服务器的负担,因为连接瞬间的处理是有限的,而连接断开后,服务器不再维护与客户端的状态。
因此,<u>**服务器在处理HTTP请求时不会知道客户端浏览器的状态变化**</u>,比如浏览器关闭。每个用户通过浏览器访问服务器时,服务器会为每个会话生成一个独立的Session对象,用于存储和维护该用户在会话期间的状态信息。例如,张三和李四分别打开浏览器A和B访问服务器后,服务器会分别为他们创建专属的Session对象,以便在会话期间持久化保存各自的状态信息。
### 21.3 为什么不使用request对象/ServletContext对象保存会话状态?
**HttpServletRequest**(请求对象):通过`request.setAttribute()`存储数据,通过`request.getAttribute()`获取数据。它的作用域是一次请求内,即数据只在当前请求中有效。
**HttpSession**(会话对象):通过`session.setAttribute()`存储数据,通过`session.getAttribute()`获取数据。它的作用域是用户会话期间,在用户打开浏览器到关闭浏览器期间数据有效。
**ServletContext**(应用对象):通过`ServletContext.setAttribute()`存储数据,通过`ServletContext.getAttribute()`获取数据。它的作用域是整个Web应用的生命周期内,所有用户共享同一份数据。
总结这些对象的作用域从小到大为:`request` < `session` < `ServletContext`。这些作用域的选择取决于数据需要的生命周期和共享范围。
整理如下:
### 21.4 Session对象实现原理:
当用户第一次访问时,服务器生成一个唯一的Session ID,并将其作为一个Cookie(通常名为JSESSIONID)发送给浏览器。
同时,服务器端维护一个Session列表(通常是一个Map),用来存储所有活跃的Session对象,键为Session ID,值为Session对象本身。
当用户再次请求时,浏览器会自动将之前存储的Session ID发送给服务器。
服务器根据这个ID在Session列表中查找对应的Session对象,从而实现跨请求的数据共享和状态保持。
如果浏览器关闭,Session ID存储在内存中的Cookie也随之消失,会话结束。
### 21.5 Cookie禁用时的影响:
- 如果浏览器禁用了Cookie,Session机制仍然可以工作,但需要依赖URL重写机制。
- URL重写会在每个URL后面附加Session ID,如 `http://localhost:8080/servlet12/test/session;jsessionid=19D1C99560DCBF84839FA43D58F56E16`。
- 这种方式会增加开发和维护的复杂性,因此大多数网站倾向于要求浏览器启用Cookie以便正常使用Session机制。
### 21.6 域对象总结:
- **HttpServletRequest**(请求对象):
- 请求域,数据仅在当前请求中有效。
- **HttpSession**(会话对象):
- 会话域,数据在用户会话期间有效,跨多个请求。
- **ServletContext**(应用对象):
- 应用域,数据在整个Web应用的生命周期内有效,所有用户共享。
#### 21.6.1 域对象的大小关系:
- `request` < `session` < `application`
#### 21.6.2 公共方法:
- 三种域对象均提供以下方法:
- `setAttribute(key, value)`:向域对象中存储数据。
- `getAttribute(key)`:从域对象中获取数据。
- `removeAttribute(key)`:从域对象中移除数据。
#### 21.6.3 登录问题解决方案:
- 登录成功后,将用户信息存储在Session中。
- 后续页面通过判断Session中是否存在用户信息来确定用户是否登录。
#### 21.6.4 销毁Session对象:
- 若要销毁Session,可以调用:
```java
session.invalidate();
22. Cookie
22.1 Session实现原理
session的实现原理中,每一个session对象都会关联一sessionid: JSESSIONID=41C481F0224664BDB28E95081D23D5B8
以上的这个键值对数据其实就是cookie对象。
对于session关联的cookie来说,这个cookie是被保存在浏览器的“运行内存”当中。只要浏览器不关闭,用户再次发送请求的时候,会自动将运行内存中的cookie发送给服务器。
例如,这个Cookie: JSESSIONID=41C481F0224664BDB28E95081D23D5B8
就会再次发送给服务器。服务器就是根据41C481F0224664BDB28E95081D23D5B8
这个值来找到对应的session对象的。
哦,关于cookie的问题,我可以帮你解答:
22.2 如何生成Cookie?
在Java Web开发中,可以使用Servlet API来生成和操作Cookie。通常是通过创建一个Cookie
对象,并将其添加到HTTP响应中来生成Cookie。例如:
1 | Cookie cookie = new Cookie("username", "john_doe"); |
22.3 Cookie保存在哪里?
Cookie保存在客户端,也就是用户的浏览器中。每当用户访问服务器时,浏览器会自动发送相关的Cookie信息给服务器。
22.4 Cookie的作用是什么?
Cookie主要用于在客户端存储少量数据,比如用户的登录信息、偏好设置等。它们可以跟踪用户的状态,使得Web应用程序可以更个性化地响应用户请求。
22.5 浏览器何时发送Cookie?
浏览器在发送HTTP请求时,如果与当前网站相关联的Cookie存在,会自动将这些Cookie信息发送给服务器。这些Cookie信息通常包括服务器之前设置的所有Cookie,但会根据每个Cookie的属性(如过期时间、路径等)来决定是否发送。
22.6 cookie的经典案例
京东商城,在未登录的情况下,向购物车中放几件商品。然后关闭商城,再次打开浏览器,访问京东商城的时候,购物车中的商品还在,这是怎么做的?我没有登录,为什么购物车中还有商品呢?将购物车中的商品编号放到cookie当中,cookie保存在硬盘文件当中。这样即使关闭浏览器。硬盘上的cookie还在。下一次再打开京东商城的时候,查看购物车的时候,会自动读取本地硬盘中存储的cookie,拿到商品编号,动态展示购物车中的商品。京东存储购物车中商品的cookie可能是这样的:productIds=xxxxx,yyyy,zzz,kkkk。注意:cookie如果清除掉,购物车中的商品就消失了。
26邮箱中有一个功能:十天内免登录。这个功能也是需要cookie来实现的。怎么实现的呢?用户输入正确的用户名和密码,并且同时选择十天内免登录。登录成功后。浏览器客户端会保存一个cookie,这个cookie中保存了用户名和密码等信息,这个cookie是保存在硬盘文件当中的,十天有效。在十天内用户再次访问126的时候,浏览器自动提交126的关联的cookie给服务器,服务器接收到cookie之后,获取用户名和密码,验证,通过之后,自动登录成功。
怎么让cookie失效?十天过后自动失效。或者改密码。或者在客户端浏览器上清除cookie。
1 | // 获取浏览器发送的cookie |
cookie机制和session机制其实都不属于java中的机制,实际上cookie机制和session机制都是HTTP协议的一部分。php开发中也有cookie和session机制,只要是你是做web开发,不管是什么编程语言,cookie和session机制都是需要的。HTTP协议中规定:任何一个cookie都是由name和value组成的。name和value都是字符串类型。
在java的servlet中,对cookie提供了哪些支持呢?提供了一个Cookie类来专门表示cookie数据。jakarta.servlet.http.Cookie;java程序怎么把cookie数据发送给浏览器呢?response.addCookie(cookie);在HTTP协议中是这样规定的:当浏览器发送请求的时候,会自动携带该path下的cookie数据给服务器。(URL。)
关于cookie的有效时间怎么用java设置cookie的有效时间
cookie.setMaxAge(60 * 60); 设置cookie在一小时之后失效。
没有设置有效时间:默认保存在浏览器的运行内存中,浏览器关闭则cookie消失。
只要设置cookie的有效时间 > 0,这个cookie一定会存储到硬盘文件当中。
设置cookie的有效时间 = 0 呢?
- cookie被删除,同名cookie被删除。
设置cookie的有效时间 < 0 呢?
- 保存在运行内存中。和不设置一样。
关于cookie的path,cookie关联的路径:
假设现在发送的请求路径是“http://localhost:8080/servlet13/cookie/generate”生成的cookie,如果cookie没有设置path,默认的path是什么?默认的path是:http://localhost:8080/servlet13/cookie 以及它的子路径。也就是说,以后只要浏览器的请求路径是http://localhost:8080/servlet13/cookie 这个路径以及这个路径下的子路径,cookie都会被发送到服务器。
手动设置cookie的path。cookie.setPath(“/servlet13”); 表示只要是这个servlet13项目的请求路径,都会提交这个cookie给服务器。浏览器发送cookie给服务器了,服务器中的java程序怎么接收?
1 | Cookie[] cookies = request.getCookies(); // 这个方法可能返回null |
第二部分 JSP
我的第一个JSP程序:
- 在WEB-INF目录之外创建一个index.jsp文件,然后这个文件中没有任何内容。
将上面的项目部署之后,启动服务器,打开浏览器,访问以下地址:
- http://localhost:8080/jsp/index.jsp 展现在大家面前的是一个空白。
- 实际上访问以上的这个:index.jsp,底层执行的是:index_jsp.class 这个java程序。
- 这个index.jsp会被tomcat翻译生成index_jsp.java文件,然后tomcat服务器又会将index_jsp.java编译生成index_jsp.class文件
- 访问index.jsp,实际上执行的是index_jsp.class中的方法。
JSP实际上就是一个Servlet。
- index.jsp访问的时候,会自动翻译生成index_jsp.java,会自动编译生成index_jsp.class,那么index_jsp 这就是一个类。
- index_jsp 类继承 HttpJspBase,而HttpJspBase类继承的是HttpServlet。所以index_jsp类就是一个Servlet类。
- jsp的生命周期和Servlet的生命周期完全相同。完全就是一个东西。没有任何区别。
- jsp和servlet一样,都是单例的。(假单例。)
jsp文件第一次访问的时候是比较慢的,为什么?
- 为什么大部分的运维人员在给客户演示项目的时候,为什么提前先把所有的jsp文件先访问一遍。
- 第一次比较麻烦:
- 要把jsp文件翻译生成java源文件
- java源文件要编译生成class字节码文件
- 然后通过class去创建servlet对象
- 然后调用servlet对象的init方法
- 最后调用servlet对象的service方法。
- 第二次就比较快了,为什么?
- 因为第二次直接调用单例servlet对象的service方法即可。
JSP是什么?
- JSP是java程序。(JSP本质还是一个Servlet)
- JSP是:JavaServer Pages的缩写。(基于Java语言实现的服务器端的页面。)
- Servlet是JavaEE的13个子规范之一,那么JSP也是JavaEE的13个子规范之一。
- JSP是一套规范。所有的web容器/web服务器都是遵循这套规范的,都是按照这套规范进行的“翻译”
- 每一个web容器/web服务器都会内置一个JSP翻译引擎。
对JSP进行错误调试的时候,还是要直接打开JSP文件对应的java文件,检查java代码。
开发JSP的最高境界:
- 眼前是JSP代码,但是脑袋中呈现的是java代码。
JSP既然本质上是一个Servlet,那么JSP和Servlet到底有什么区别呢?
- 职责不同:
- Servlet的职责是什么:收集数据。(Servlet的强项是逻辑处理,业务处理,然后链接数据库,获取/收集数据。)
- JSP的职责是什么:展示数据。(JSP的强项是做数据的展示)
- 职责不同:
JSP的基础语法
在jsp文件中直接编写文字,都会自动被翻译到哪里?
- 翻译到servlet类的service方法的out.write(“翻译到这里”),直接翻译到双引号里,被java程序当做普通字符串打印输出到浏览器。
- 在JSP中编写的HTML CSS JS代码,这些代码对于JSP来说只是一个普通的字符串。但是JSP把这个普通的字符串一旦输出到浏览器,浏览器就会对HTML CSS JS进行解释执行。展现一个效果。
JSP的page指令(这个指令后面再详细说,这里先解决一下中文乱码问题),解决响应时的中文乱码问题:
- 通过page指令来设置响应的内容类型,在内容类型的最后面添加:charset=UTF-8
- <%@page contentType=”text/html;charset=UTF-8”%>,表示响应的内容类型是text/html,采用的字符集UTF-8
- <%@page import=”java.util.List,java.util.ArrayList”%>
- 通过page指令来设置响应的内容类型,在内容类型的最后面添加:charset=UTF-8
怎么在JSP中编写Java程序:
- <% java语句; %>
- 在这个符号当中编写的被视为java程序,被翻译到Servlet类的service方法内部。
- 这里你要细心点,你要思考,在<% %>这个符号里面写java代码的时候,你要时时刻刻的记住你正在“方法体”当中写代码,方法体中可以写什么,不可以写什么,你心里是否明白呢?
- 在service方法当中编写的代码是有顺序的,方法体当中的代码要遵循自上而下的顺序依次逐行执行。
- service方法当中不能写静态代码块,不能写方法,不能定义成员变量。。。。。。
- 在同一个JSP当中 <%%> 这个符号可以出现多个。
- <%! %>
- 在这个符号当中编写的java程序会自动翻译到service方法之外。
- 这个语法很少用,为什么?不建议使用,因为在service方法外面写静态变量和实例变量,都会存在线程安全问题,因为JSP就是servlet,servlet是单例的,多线程并发的环境下,这个静态变量和实例变量一旦有修改操作,必然会存在线程安全问题。
- JSP的输出语句
- 怎么向浏览器上输出一个java变量。
- <% String name = “jack”; out.write(“name = “ + name); %>
- 注意:以上代码中的out是JSP的九大内置对象之一。可以直接拿来用。当然,必须只能在service方法内部使用。
- 如果向浏览器上输出的内容中没有“java代码”,例如输出的字符串是一个固定的字符串,可以直接在jsp中编写,不需要写到<%%> 这里。
- 如果输出的内容中含有“java代码”,这个时候可以使用以下语法格式:
- <%= %> 注意:在=的后面编写要输出的内容。
- <%= %> 这个符号会被翻译到哪里?最终翻译成什么?
- 翻译成了这个java代码: out.print();
- 翻译到service方法当中了。
- 什么时候使用<%=%> 输出呢?输出的内容中含有java的变量,输出的内容是一个动态的内容,不是一个死的字符串。如果输出的是一个固定的字符串,直接在JSP文件中编写即可。
- <% java语句; %>
在JSP中如何编写JSP的专业注释
- <%–JSP的专业注释,不会被翻译到java源代码当中。–%>
JSP基础语法总结:
- JSP中直接编写普通字符串
- 翻译到service方法的out.write(“这里”)
- <%%>
- 翻译到service方法体内部,里面是一条一条的java语句。
- <%! %>
- 翻译到service方法之外。
- <%= %>
- 翻译到service方法体内部,翻译为:out.print();
- <%@page contentType=”text/html;charset=UTF-8”%>
- page指令,通过contentType属性用来设置响应的内容类型。
- JSP中直接编写普通字符串
使用Servlet + JSP完成oa项目的改造。
使用Servlet处理业务,收集数据。 使用JSP展示数据。
将之前原型中的html文件,全部修改为jsp,然后在jsp文件头部添加page指令(指定contentType防止中文乱码),将所有的JSP直接拷贝到web目录下。
完成所有页面的正常流转。(页面仍然能够正常的跳转。修改超链接的请求路径。)
- <%=request.getContextPath() %> 在JSP中动态的获取应用的根路径。
Servlet中连接数据库,查询所有的部门,遍历结果集。
- 遍历结果集的过程中,取出部门编号、部门名、位置等信息,封装成java对象。
- 将java对象存放到List集合中。
- 将List集合存储到request域当中。
- 转发forward到jsp。
在JSP中:
- 从request域当中取出List集合。
- 遍历List集合,取出每个部门对象。动态生成tr。
思考一个问题:如果我只用JSP这一个技术,能不能开发web应用?
- 当然可以使用JSP来完成所有的功能。因为JSP就是Servlet,在JSP的<%%>里面写的代码就是在service方法当中的,所以在<%%>当中完全可以编写JDBC代码,连接数据库,查询数据,也可以在这个方法当中编写业务逻辑代码,处理业务,都是可以的,所以使用单独的JSP开发web应用完全没问题。
- 虽然JSP一个技术就可以完成web应用,但是不建议,还是建议采用servlet + jsp的方式进行开发。这样都能将各自的优点发挥出来。JSP就是做数据展示。Servlet就是做数据的收集。(JSP中编写的Java代码越少越好。)一定要职责分明。
JSP文件的扩展名必须是xxx.jsp吗?
jsp文件的扩展名是可以配置的。不是固定的。
在CATALINA_HOME/conf/web.xml,在这个文件当中配置jsp文件的扩展名。
<servlet-mapping> <servlet-name>jsp</servlet-name> <url-pattern>*.jsp</url-pattern> <url-pattern>*.jspx</url-pattern> </servlet-mapping>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
- xxx.jsp文件对于小猫咪来说,只是一个普通的文本文件,web容器会将xxx.jsp文件最终生成java程序,最终调用的是java对象相关的方法,真正执行的时候,和jsp文件就没有关系了。
- 小窍门:JSP如果看不懂,建议把jsp翻译成java代码,就能看懂了。
- 同学问:包名bean是什么意思?
- javabean(java的logo是一杯冒着热气的咖啡。javabean被翻译为:咖啡豆)
- java是一杯咖啡,咖啡又是由一粒一粒的咖啡豆研磨而成。
- 整个java程序中有很多bean的存在。由很多bean组成。
- 什么是javabean?实际上javabean你可以理解为符合某种规范的java类,比如:
- 有无参数构造方法
- 属性私有化
- 对外提供公开的set和get方法
- 实现java.io.Serializable接口
- 重写toString
- 重写hashCode+equals
- ....
- javabean其实就是java中的实体类。负责数据的封装。
- 由于javabean符合javabean规范,具有更强的通用性。
- 完成剩下所有功能的改造。
- 当前的oa应用存在的问题:
- 任何一个用户都可以访问这个系统,都可以对这个系统当中的数据进行增删改这些危险的操作。我只想让合法的用户去使用这个系统,不合法的用户不能访问这个系统,怎么办?
- 加一个登录功能。登录成功的可以访问该系统,登录失败不能访问。
- 实现登录功能:
- 步骤1:数据库当中添加一个用户表:t_user
- t_user表当中存储的是用户的登录信息,最基本的也包括:登录的用户名和登录的密码。
- 密码一般在数据库表当中存储的是密文。一般不以明文的形式存储。(这里先使用明文方式。)
- 向t_user表中插入数据。
- 步骤2:再实现一个登录页面。
- 登录页面上应该有一个登录的表单。有用户名和密码输入的框。
- 用户点击登录,提交表单,提交用户名和密码。form是post方式提交。
- 步骤3:后台要有一个对应的Servlet来处理登录的请求。
- 登录成功:跳转到部门列表页面。
- 登录失败:跳转到失败的页面。
- 步骤4:再提供一个登录失败的页面。
- 登录功能实现了,目前存在的最大的问题:
- 这个登录功能目前只是一个摆设,没有任何作用。只要用户知道后端的请求路径,照样可以在不登录的情况下访问。
- 这个登录没有真正起到拦截的作用。怎么解决?
- JSP的指令
- 指令的作用:指导JSP的翻译引擎如何工作(指导当前的JSP翻译引擎如何翻译JSP文件。)
- 指令包括哪些呢?
- include指令:包含指令,在JSP中完成静态包含,很少用了。(这里不讲)
- taglib指令:引入标签库的指令。这个到JJSTL标签库的时候再学习。现在先不管。
- page指令:目前重点学习一个page指令。
- 指令的使用语法是什么?
- <%@指令名 属性名=属性值 属性名=属性值 属性名=属性值....%>
- 关于page指令当中都有哪些常用的属性呢?
- ```
<%@page session="true|false" %>
true表示启用JSP的内置对象session,表示一定启动session对象。没有session对象会创建。
如果没有设置,默认值就是session="true"
session="false" 表示不启动内置对象session。当前JSP页面中无法使用内置对象session。
<%@page contentType="text/json" %> contentType属性用来设置响应的内容类型 但同时也可以设置字符集。 <%@page contentType="text/json;charset=UTF-8" %>
1
2
3
4
- ```
<%@page pageEncoding="UTF-8" %>
pageEncoding="UTF-8" 表示设置响应时采用的字符集。<%@page import="java.util.List, java.util.Date, java.util.ArrayList" %> <%@page import="java.util.*" %> import语句,导包。
1
2
3
4
5
- ```
<%@page errorPage="/error.jsp" %>
当前页面出现异常之后,跳转到error.jsp页面。
errorPage属性用来指定出错之后的跳转位置。<%@page isErrorPage="true" %> 表示启用JSP九大内置对象之一:exception 默认值是false。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
- JSP的九大内置对象
- jakarta.servlet.jsp.PageContext pageContext 页面作用域
- jakarta.servlet.http.HttpServletRequest request 请求作用域
- jakarta.servlet.http.HttpSession session 会话作用域
- jakarta.servlet.ServletContext application 应用作用域
- pageContext < request < session < application
- 以上四个作用域都有:setAttribute、getAttribute、removeAttribute方法。
- 以上作用域的使用原则:尽可能使用小的域。
- java.lang.Throwable exception
- jakarta.servlet.ServletConfig config
- java.lang.Object page (其实是this,当前的servlet对象)
- jakarta.servlet.jsp.JspWriter out (负责输出)
- jakarta.servlet.http.HttpServletResponse response (负责响应)
# 第三部分 EL表达式
- EL表达式是干什么用的?
- Expression Language(表达式语言)
- EL表达式可以代替JSP中的java代码,让JSP文件中的程序看起来更加整洁,美观。
- JSP中夹杂着各种java代码,例如<% java代码 %>、<%=%>等,导致JSP文件很混乱,不好看,不好维护。所以才有了后期的EL表达式。
- EL表达式可以算是JSP语法的一部分。EL表达式归属于JSP。
- EL表达式出现在JSP中主要是:
- 从某个作用域中取数据,然后将其转换成字符串,然后将其输出到浏览器。这就是EL表达式的功效。三大功效:
- 第一功效:从某个域中取数据。
- 四个域:
- pageContext
- request
- session
- application
- 第二功效:将取出的数据转成字符串。
- 如果是一个java对象,也会自动调用java对象的toString方法将其转换成字符串。
- 第三功效:将字符串输出到浏览器。
- 和这个一样:<%= %>,将其输出到浏览器。
- EL表达式很好用,基本的语法格式:
- ${表达式}
- EL表达式的使用:
- ```jsp
<%
// 创建User对象
User user = new User();
user.setUsername("jackson");
user.setPassword("1234");
user.setAge(50);
// 将User对象存储到某个域当中。一定要存,因为EL表达式只能从某个范围中取数据。
// 数据是必须存储到四大范围之一的。
request.setAttribute("userObj", user);
%>
<%--使用EL表达式取--%>
${这个位置写什么????这里写的一定是存储到域对象当中时的name}
要这样写:
${userObj}
等同于java代码:<%=request.getAttribute("userObj")%>
你不要这样写:${"userObj"}
面试题:
${abc} 和 ${"abc"}的区别是什么?
${abc}表示从某个域中取出数据,并且被取的这个数据的name是"abc",之前一定有这样的代码: 域.setAttribute("abc", 对象);
${"abc"} 表示直接将"abc"当做普通字符串输出到浏览器。不会从某个域中取数据了。
${userObj} 底层是怎么做的?从域中取数据,取出user对象,然后调用user对象的toString方法,转换成字符串,输出到浏览器。
<%--如果想输出对象的属性值,怎么办?--%>
${userObj.username} 使用这个语法的前提是:User对象有getUsername()方法。
${userObj.password} 使用这个语法的前提是:User对象有getPassword()方法。
${userObj.age} 使用这个语法的前提是:User对象有getAge()方法。
${userObj.email} 使用这个语法的前提是:User对象有getEmail()方法。
EL表达式中的. 这个语法,实际上调用了底层的getXxx()方法。
注意:如果没有对应的get方法,则出现异常。报500错误。
${userObj.addr222.zipcode}
以上EL表达式对应的java代码:
user.getAddr222().getZipcode()
EL表达式优先从小范围中读取数据。
- pageContext < request < session < application
EL表达式中有四个隐含的隐式的范围:
- pageScope 对应的是 pageContext范围。
- requestScope 对应的是 request范围。
- sessionScope 对应的是 session范围。
- applicationScope 对应的是 application范围。
EL表达式对null进行了预处理。如果是null,则向浏览器输出一个空字符串。
EL表达式取数据的时候有两种形式:
- 第一种:. (大部分使用这种方式)
- 第二种:[ ] (如果存储到域的时候,这个name中含有特殊字符,可以使用 [ ])
- request.setAttribute(“abc.def”, “zhangsan”);
- ${requestScope.abc.def} 这样是无法取值的。
- 应该这样:${requestScope[“abc.def”]}
掌握使用EL表达式,怎么从Map集合中取数据:
- ${map.key}
掌握使用EL表达式,怎么从数组和List集合中取数据:
- ${数组[0]}
- ${数组[1]}
- ${list[0]}
page指令当中,有一个属性,可以忽略EL表达式
<%@page contentType="text/html;charset=UTF-8" isELIgnored="true" %> isELIgnored="true" 表示忽略EL表达式 isELIgnored="false" 表示不忽略EL表达式。(这是默认值) isELIgnored="true" 这个是全局的控制。 可以使用反斜杠进行局部控制:\${username} 这样也可以忽略EL表达式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
- 通过EL表达式获取应用的根:
- ${pageContext.request.contextPath}
- EL表达式中其他的隐式对象:
- pageContext
- param
- paramValues
- initParam
- EL表达式的运算符
- 算术运算符
- +、-、*、/、%
- 关系运算符
- [ ] == eq != > >= < <=
- 逻辑运算符
- [ ] ! && || not and or
- 条件运算符
- [ ] ? :
- 取值运算符
- [ ]和.
- empty运算符
- [ ] empty运算符的结果是boolean类型
- [ ] ${empty param.username}
- [ ] ${not empty param.username}
- [ ] ${!empty param.password}
# 第四部分 JSTL标签库
- 什么是JSTL标签库?
- Java Standard Tag Lib(Java标准的标签库)
- JSTL标签库通常结合EL表达式一起使用。目的是让JSP中的java代码消失。
- 标签是写在JSP当中的,但实际上最终还是要执行对应的java程序。(java程序在jar包当中。)
- 使用JSTL标签库的步骤:
- 第一步:引入JSTL标签库对应的jar包。
- tomcat10之后引入的jar包是:
- jakarta.servlet.jsp.jstl-2.0.0.jar
- jakarta.servlet.jsp.jstl-api-2.0.0.jar
- 在IDEA当中怎么引入?
- 在WEB-INF下新建lib目录,然后将jar包拷贝到lib当中。然后将其“Add Lib...”
- 一定是要和mysql的数据库驱动一样,都是放在WEB-INF/lib目录下的。
- 什么时候需要将jar包放到WEB-INF/lib目录下?如果这个jar是tomcat服务器没有的。
- 第二步:在JSP中引入要使用标签库。(使用taglib指令引入标签库。)
- JSTL提供了很多种标签,你要引入哪个标签????重点掌握核心标签库。
- ```
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
这个就是核心标签库。
prefix="这里随便起一个名字就行了,核心标签库,大家默认的叫做c,你随意。"
第三步:在需要使用标签的位置使用即可。表面使用的是标签,底层实际上还是java程序。
JSTL标签的原理
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 以上uri后面的路径实际上指向了一个xxx.tld文件。 tld文件实际上是一个xml配置文件。 在tld文件中描述了“标签”和“java类”之间的关系。 以上核心标签库对应的tld文件是:c.tld文件。它在哪里。 在jakarta.servlet.jsp.jstl-2.0.0.jar里面META-INF目录下,有一个c.tld文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- 源码解析:配置文件tld解析
- ```
<tag>
<description>对该标签的描述</description>
<name>catch</name> 标签的名字
<tag-class>org.apache.taglibs.standard.tag.common.core.CatchTag</tag-class> 标签对应的java类。
<body-content>JSP</body-content> 标签体当中可以出现的内容,如果是JSP,就表示标签体中可以出现符合JSP所有语法的代码。例如EL表达式。
<attribute>
<description>
对这个属性的描述
</description>
<name>var</name> 属性名
<required>false</required> false表示该属性不是必须的。true表示该属性是必须的。
<rtexprvalue>false</rtexprvalue> 这个描述说明了该属性是否支持EL表达式。false表示不支持。true表示支持EL表达式。
</attribute>
</tag>
<c:catch var="">
JSP....
</c:catch>jstl中的核心标签库core当中有哪些常用的标签呢?
c:if
- <c:if test=”boolean类型,支持EL表达式”></c: if>
c:forEach
- <c:forEach items=”集合,支持EL表达式” var=”集合中的元素” varStatus=”元素状态对象”> ${元素状态对象.count} </c: forEach>
- <c:forEach var=”i” begin=”1” end=”10” step=”2”> ${i} </c: forEach>
c:choose c:when c:otherwise
<c:choose> <c:when test="${param.age < 18}"> 青少年 </c:when> <c:when test="${param.age < 35}"> 青年 </c:when> <c:when test="${param.age < 55}"> 中年 </c:when> <c:otherwise> 老年 </c:otherwise> </c:choose>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
## 改造OA
- 使用什么技术改造呢?
- Servlet + JSP + EL表达式 + JSTL标签。进行改造。
- 在前端HTML代码中,有一个标签,叫做base标签,这个标签可以设置整个网页的基础路径。
- 这是Java的语法,也不是JSP的语法。是HTML中的一个语法。HTML中的一个标签。通常出现在head标签中。
- < base href="http://localhost:8080/oa/">
- 在当前页面中,凡是路径没有以“/”开始的,都会自动将base中的路径添加到这些路径之前。
- < a href="ab/def"></ a>
- 等同于:< a href="http://localhost:8080/oa/ab/def"></ a>
- 需要注意:在JS代码中的路径,保险起见,最好不要依赖base标签。JS代码中的路径最好写上全路径。
- ```
<base href="${pageContext.request.scheme}://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/">
第五部分 Filter过滤器
Filter是什么,有什么用,执行原理是什么?
Filter可以在Servlet这个目标程序执行之前添加代码。也可以在目标Servlet执行之后添加代码。之前之后都可以添加过滤规则。
一般情况下,都是在过滤器当中编写公共代码。
一个过滤器怎么写呢?
第一步:编写一个Java类实现一个接口:jarkata.servlet.Filter。并且实现这个接口当中所有的方法。
- init方法:在Filter对象第一次被创建之后调用,并且只调用一次。
- doFilter方法:只要用户发送一次请求,则执行一次。发送N次请求,则执行N次。在这个方法中编写过滤规则。
- destroy方法:在Filter对象被释放/销毁之前调用,并且只调用一次。
第二步:在web.xml文件中对Filter进行配置。这个配置和Servlet很像。
<filter> <filter-name>filter2</filter-name> <filter-class>com.bjpowernode.javaweb.servlet.Filter2</filter-class> </filter> <filter-mapping> <filter-name>filter2</filter-name> <url-pattern>*.do</url-pattern> </filter-mapping>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
- 或者使用注解:@WebFilter({"*.do"})
- 注意:
- Servlet对象默认情况下,在服务器启动的时候是不会新建对象的。
- Filter对象默认情况下,在服务器启动的时候会新建对象。
- Servlet是单例的。Filter也是单例的。(单实例。)
- 目标Servlet是否执行,取决于两个条件:
- 第一:在过滤器当中是否编写了:chain.doFilter(request, response); 代码。
- 第二:用户发送的请求路径是否和Servlet的请求路径一致。
- chain.doFilter(request, response); 这行代码的作用:
- 执行下一个过滤器,如果下面没有过滤器了,执行最终的Servlet。
- 注意:Filter的优先级,天生的就比Servlet优先级高。
- /a.do 对应一个Filter,也对应一个Servlet。那么一定是先执行Filter,然后再执行Servlet。
- 关于Filter的配置路径:
- /a.do、/b.do、/dept/save。这些配置方式都是精确匹配。
- /* 匹配所有路径。
- *.do 后缀匹配。不要以 / 开始
- /dept/* 前缀匹配。
- 在web.xml文件中进行配置的时候,Filter的执行顺序是什么?
- 依靠filter-mapping标签的配置位置,越靠上优先级越高。
- 过滤器的调用顺序,遵循栈数据结构。
- 使用@WebFilter的时候,Filter的执行顺序是怎样的呢?
- 执行顺序是:比较Filter这个类名。
- 比如:FilterA和FilterB,则先执行FilterA。
- 比如:Filter1和Filter2,则先执行Filter1.
- Filter的生命周期?
- 和Servlet对象生命周期一致。
- 唯一的区别:Filter默认情况下,在服务器启动阶段就实例化。Servlet不会。
- Filter过滤器这里有一个设计模式:
- 责任链设计模式。
- 过滤器最大的优点:
- 在程序编译阶段不会确定调用顺序。因为Filter的调用顺序是配置到web.xml文件中的,只要修改web.xml配置文件中filter-mapping的顺序就可以调整Filter的执行顺序。显然Filter的执行顺序是在程序运行阶段动态组合的。那么这种设计模式被称为责任链设计模式。
- 责任链设计模式最大的核心思想:
- 在程序运行阶段,动态的组合程序的调用顺序。
- 使用过滤器改造OA项目。
# 第六部分 Listener监听器
- 什么是监听器?
- 监听器是Servlet规范中的一员。就像Filter一样。Filter也是Servlet规范中的一员。
- 在Servlet中,所有的监听器接口都是以“Listener”结尾。
- 监听器有什么用?
- 监听器实际上是Servlet规范留给我们javaweb程序员的特殊时机。
- 特殊的时刻如果想执行这段代码,你需要想到使用对应的监听器。
- Servlet规范中提供了哪些监听器?
- jakarta.servlet包下:
- ServletContextListener
- ServletContextAttributeListener
- ServletRequestListener
- ServletRequestAttributeListener
- jakarta.servlet.http包下:
- HttpSessionListener
- HttpSessionAttributeListener
- 该监听器需要使用@WebListener注解进行标注。
- 该监听器监听的是什么?是session域中数据的变化。只要数据变化,则执行相应的方法。主要监测点在session域对象上。
- HttpSessionBindingListener
- 该监听器不需要使用@WebListener进行标注。
- 假设User类实现了该监听器,那么User对象在被放入session的时候触发bind事件,User对象从session中删除的时候,触发unbind事件。
- 假设Customer类没有实现该监听器,那么Customer对象放入session或者从session删除的时候,不会触发bind和unbind事件。
- HttpSessionIdListener
- session的id发生改变的时候,监听器中的唯一一个方法就会被调用。
- HttpSessionActivationListener
- 监听session对象的钝化和活化的。
- 钝化:session对象从内存存储到硬盘文件。
- 活化:从硬盘文件把session恢复到内存。
- 实现一个监听器的步骤:以ServletContextListener为例。
- 第一步:编写一个类实现ServletContextListener接口。并且实现里面的方法。
- ```
void contextInitialized(ServletContextEvent event)
void contextDestroyed(ServletContextEvent event)
第二步:在web.xml文件中对ServletContextListener进行配置,如下:
<listener> <listener-class>com.bjpowernode.javaweb.listener.MyServletContextListener</listener-class> </listener>
当然,第二步也可以不使用配置文件,也可以用注解,例如:@WebListener
注意:所有监听器中的方法都是不需要javaweb程序员调用的,由服务器来负责调用?什么时候被调用呢?
- 当某个特殊的事件发生(特殊的事件发生其实就是某个时机到了。)之后,被web服务器自动调用。
思考一个业务场景:
- 请编写一个功能,记录该网站实时的在线用户的个数。
- 我们可以通过服务器端有没有分配session对象,因为一个session代表了一个用户。有一个session就代表有一个用户。如果你采用这种逻辑去实现的话,session有多少个,在线用户就有多少个。这种方式的话:HttpSessionListener够用了。session对象只要新建,则count++,然后将count存储到ServletContext域当中,在页面展示在线人数即可。
- 业务发生改变了,只统计登录的用户的在线数量,这个该怎么办?
- session.setAttribute(“user”, userObj);
- 用户登录的标志是什么?session中曾经存储过User类型的对象。那么这个时候可以让User类型的对象实现HttpSessionBindingListener监听器,只要User类型对象存储到session域中,则count++,然后将count++存储到ServletContext对象中。页面展示在线人数即可。
实现oa项目中当前登录在线的人数。
- 什么代表着用户登录了?
- session.setAttribute(“user”, userObj); User类型的对象只要往session中存储过,表示有新用户登录。
- 什么代表着用户退出了?
- session.removeAttribute(“user”); User类型的对象从session域中移除了。
- 或者有可能是session销毁了。(session超时)
- 什么代表着用户登录了?