资料来源:https://www.bilibili.com/video/BV1vb41187jH?p=3
https://www.bilibili.com/video/BV1Z3411C7NZ?p=9
一、BS架构访问过程
1、以上图形很形象的描述了B/S架构程序的访问流程。请从上面图形中分别找出Browser、WebServer、DBServer(我们称作3tier)。
2、分析小张和小王的访问有什么区别?
小张:它在浏览器地址栏上输入的请求路径是http://202.108.251.34:8080/egov/login.html,
访问原理是:浏览器在广域网中搜索ip地址是202.108.251.34这个计算机,找到这台计算机之后,再去这台计算机上搜索8080相关的服务,就找到了Tomcat服务器(Web Server、Web Container、Web容器),Tomcat服务器正在处于接收用户请求的状态,接收到请求之后解析请求路径,知道小张访问的资源是egov应用(web app)中的login.html资源(纯静态页面),Tomcat服务器负责在Web容器搜索到该资源,并且Tomcat服务器负责将该资源以响应的方式发送给浏览器客户端(小张)。在整个过程中不需要执行egov应用(web app)中的任何java程序,web app的开发者(java程序员)也不需要编写任何java程序,只要编写一个login.html页面放在egov应用中即可。
小王:它在浏览器地址栏上输入的请求路径是http://202.108.251.34:8080/oa/delete?empno=7369, 访问原理是:浏览器在广域网中搜索ip地址是202.108.251.34这个计算机(硬件服务器),找到这台计算机之后,再去这台计算机上搜索8080相关的服务,就找到了Tomcat服务器(Web Server、Web Container、Web容器),Tomcat服务器正在处于接收用户请求的状态,接收到请求之后解析请求路径,知道小张访问的资源是oa应用(web app)中的delete资源,这个delete资源不是一个静态的HTML页面,而需要执行一段小java程序去处理用户这次的请求,注意:用户的请求路径/delete和小java程序一定是互相绑定的。这段小java程序再去连接数据库(JDBC),数据库负责删除该empno=7369的数据,并且将删除结果返回给小java程序,小java程序在负责将删除结果响应给浏览器客户端(小王)。
小张和小王的区别:
1)小张访问的资源是WEB服务器中的一个静态资源,以.html结尾。不需要web app中执行一段java程序
2)小王访问的资源是WEB服务器中的一个动态资源,不是以.html结尾的,这个时候就需要Tomcat服务器为我们执行一段对应的小java程序。所以web app中必须有一段小java程序。
以上所描述的小java程序指的就是Servlet,Servlet(Server let)表示WEB服务器端小java程序。
3、 请分析B/S架构中涉及到的产品、角色、协议以及协议的制定者?
1)B/S架构中涉及到的所有产品、角色:
i. 浏览器(IE,FireFox, Chrome, Opera等)
ii. WEB服务器、WEB Server、WEB Container。(Tomcat、JBOSS、GlassFish等)
iii. DB 服务器(Oracle、Mysql、SqlServer等)
iv. Web App的开发者(Servlet程序员、我们)
2)B/S架构中涉及到的所有的协议、标准、规范:
i. 浏览器和WEB服务器之间遵循HTTP协议,这个协议的制定者是W3C,这个协议是通信协议。HTTP协议包括请求协议和响应协议两个,这两个协议只是方向不同,从浏览器向服务器发送数据叫做请求协议,从服务器向浏览器发送数据我们叫做响应协议,这里的HTTP协议我们用的是HTTP1.1版本(以后详细介绍HTTP协议。)
ii. WEB服务器和web app的开发者(我们)之间有一些规范,例如:Servlet、JSP规范,这些规范的制定者是SUN。
iii. Web app开发者(我们)和数据库服务器之间有一个规范,叫做JDBC规范,这个规范的制定者是SUN。
注意:我们程序依赖于规范的制定,J2EE规范很多,包括:JDBC、Servlet、JSP、EJB等。
二、模拟servlet本质
1、制定Servlet规范
2、服务端Java程序开发
3、Web服务器开发
4、真实场景开发服务端Java程序
1)不需要再编写main方法,main方法在Tomcat服务器中。Tomcat服务器启动就是main方法执行
2)编写一个Servlet类实现Servlet接口,实现service方法
3)将Servlet类和请求路径绑定在一起,编写web.xml文件
三、Tomcat服务器的使用
Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。由于有了Sun 的参与和支持,最新的Servlet 和JSP 规范总是能在Tomcat 中得到体现,Tomcat 5 支持最新的Servlet 2.4 和JSP 2.0 规范。因为Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应用服务器。目前最新版本是8.0
Apache Tomcat 6.x 在汲取Tomcat 5.5.x优点的基础上,实现了Servlet 2.5和JSP 2.1等特性的支持
Tomcat最初是由Sun的软件构架师詹姆斯·邓肯·戴维森开发的。后来他帮助将其变为开源项目,并由Sun贡献给Apache软件基金会。由于大部分开源项目O’Reilly都会出一本相关的书,并且将其封面设计成某个动物的素描,因此他希望将此项目以一个动物的名字命名。因为他希望这种动物能够自己照顾自己,最终,他将其命名为Tomcat。而O’Reilly出版的介绍Tomcat的书籍的封面也被设计成了一个猫的形象。而Tomcat的Logo兼吉祥物也被设计成了一只猫。
1、Tomcat服务器的安装
1)首先确保自己开发的计算机上已经安装了JRE,这里统一安装JRE1.7。因为Tomcat服务器是java语言开发的,服务器要想运行必须有java的运行环境。
2)将tools/tomcat7/apache-tomcat-7.0.57.zip压缩包直接解压缩到C:\目录下。这里我们使用的是免安装绿色版tomat服务器。
3)解压之后apache-tomcat-7.0.57修改为tomcat7
2、Tomcat服务器的配置
1)由于Tomcat服务器底层是java语言实现的,所以需要java的运行环境,也就是需要JVM,我们在这里只需要配置一个环境变量即可,这个环境变量供Tomcat服务器找到java的运行环境,如下所示:
JAVA_HOME=C:\java\jdk6 (这个环境变量是必须要配置的)
2)打开CATALINA_HOME(C:\tomcat7)下的bin文件夹,找到startup.bat批处理文件双击启动Tomcat服务器。如果出现下图则表示服务器启动成功。下面窗口开启表示tomat服务器启动,JVM处于运行状态,可以理解为main方法在运行。点击该窗口右上角的关闭按钮退出JVM,JVM退出之后tomat服务器也就退出了,因为tomcat服务器是java实现的。(尽量不要采用这种方式关闭服务器,有的时候会出问题)
3)打开CATALINA_HOME(C:\tomcat7)下的bin文件夹,找到shutdown.bat批处理文件双击关闭Tomcat服务器。(这是一种正规的关闭方式,不会出现服务器没有关闭的现象。)
4)为了方便我们启动和关闭服务器推荐把启动和关闭服务器的命令配置到环境变量PATH中。这样我们就可以随时随地使用命令开启和关闭服务器了。如下所示:
PATH= C:\tomcat7\bin (在原先的PATH后面添加即可,不是必须配置的)
CATALINA_HOME= C:\tomcat7 (不是必须的)
由于在执行关闭Tomcat服务器命令的时候shutdown.bat和windows操作系统关闭计算机的命令冲突了,推荐将tomcat服务器的shutdown.bat修改名为stop.bat
3、Tomcat服务器相关目录
- CATALINA_HOME指的是Tomcat服务器安装的根目录,例如:C:\apache-tomcat-8.0.50
- CATALINA_HOME/bin :存放了一些Tomat服务器相关的命令,例如启动Tomcat服务器需要使用startup.bat,关闭Tomcat服务器需要shutdown.bat。.bat后缀的文件我们称作批处理文件,批处理文件中有大量的命令。执行批处理文件就是批量执行一些相关的命令。在该目录下以.sh结尾的文件是在unix、Linux操作系统中使用的。例如在Unix操作系统中执行startup.sh,关闭服务器的时候使用shutdown.sh。这些文件叫做shell文件,在这些shell文件中有批量的shell命令。
- CATALINA_HOME/conf :存放了一些Tomcat服务器相关的配置文件。有.properties结尾的属性配置文件,有.xml结尾的配置文件,例如:catalina.properties文件、server.xml文件(该文件是一个Tomcat服务器级别的配置文件,配置了相关的服务器端口号等信息)、web.xml(该文件是一个“web应用”相关的模板配置文件)文件、tomcat-users.xml(配置Tomcat服务器用户相关的用户名和密码等信息)文件。
- CATALINA_HOME/lib :存放了tomcat服务器的核心程序,所有文件都是以.jar结尾的,这种文件我们称作jar包,在jar包中存放了大量的.class文件。Tomcat服务器的运行主要依赖了这些class文件。其中servlet-api.jar、jsp-api.jar都是SUN制定的servlet、jsp相关的规范,这里的规范就是接口、标准。而其它的jar包中的class都实现了这些接口或者面向了这些接口进行了调用。
- CATALINA_HOME/logs :该目录下存储了Tomcat服务器相关的日志信息,由于在DOS窗口中显示文本有限,程序发生异常之后我们无法详细查看所有的信息,这个时候我们可以打开相关的日志文件进行查看。
- CATALINA_HOME/temp :临时活页夹,Tomcat服务器运行时用来存储临时文件的。
- CATALINA_HOME/webapps :这是一个非常重要的目录,Tomcat服务器规定,所有程序员开发的WEB应用、WEB站点必须存放在该目录下,不然Tomcat服务器无法找到并提供web服务。在webapps目录下存放的一个文件夹代表一个web app(web application)
- CATALINA_HOME/work :该目录下存放了Tomcat服务器运行JSP的时候生成的 .java文件以及对应的.class文件。(该目录可以暂时放过,等JSP之后再看)
四、开发第一个webapp
1、开发项目
login.html
2、部署项目
3、启动并访问项目
4、FirstWebApp的开发步骤
1)在CATALINA_HOME/webapps/ 目录下新建文件夹,起名FirstWebApp,这个目录的名字就是webapp的名字。C:\apache-tomcat-6.0.32路径是Tomcat服务器的根目录,C:\apache-tomcat-6.0.32\webapps\FirstWebApp是webapp的根目录。
2)在webapp的根目录下新建一个html文件login.html,简单编写该HTML页面。
3)启动Tomcat服务器,打开浏览器在浏览器地址栏上输入URL访问该资源:
- http://127.0.0.1:8080/(对应C:\apache-tomcat-6.0.32)
- http://127.0.0.1:8080/FirstWebApp(对应C:\apache-tomcat-6.0.32\webapps\FirstWebApp)
- http://127.0.0.1:8080/FirstWebApp/login.html(访问login.html页面)
- http://:通信协议,是W3C制定的,包括请求协议和响应协议,这个协议就是一套数据格式
- 127.0.0.1:IP地址,是计算机在网络中的身份证号,独一无二的。
- 8080:PORT端口号,Tomcat默认端口8080,端口是这台计算机上服务的唯一标志,常用端口:1521(Oracle)、3306(mysql)、21(FTP服务器默认端口)、80(WEB的默认端口)
- /FirstWebApp/login.html:资源的路径,FirstWebApp是webapp的根,login.html是根下的一个文件,要求这个文件必须在FirstWebApp文件夹下。
4)向服务器发送请求除了在浏览器地址栏上直接输入URL的方式之外,还可以使用超链接的方式向服务器发送请求,所以在web app的根下在新建一个first.html,在该文件中编写超链接,当用户点击超链接的时候向web服务器发送请求,超链接如下所示:http://127.0.0.1:8080/FirstWebApp/login.html
5)在webapp的根目录下新建html文件夹,在该文件夹中编写second.html文件,如果想访问该资源,需要编写以下的URL:http://127.0.0.1:8080/FirstWebApp/html/second.html
6)站在用户的角度如何访问web服务器中的资源:当服务器启动之后,只能打开浏览器,在浏览器中输入URL或者点击超链接向web服务器发送请求进行资源的访问,用户没有权利直接打开“服务器”上的某个html文件,因为浏览器和服务器在不同的地理位置上。
7)在向web服务器发送请求的时候请求路径中的IP地址和port端口号可以省略,如下所示:
- 跳转到登录页面
- 跳转到HTML目录中的页面
- 以上路径使用的是绝对路径,必须以“/”开始,第一个“/”代表Tomcat服务器的根。路径前面的IP地址和port端口会自动添加。
5、FirstServletWebApp的开发步骤
1)在CATALINA_HOME/webapps/目录下新建FirstServletWebApp文件夹,该文件夹就是该webapp的根。
2)在webapp的根下新建文件夹起名WEB-INF,必须全部大写,而且必须完全相同,这个文件夹是SUN制定的规范,名字是规范,位置也是规范。
3)在WEB-INF目录下新建一个文件夹必须叫做classes,这个文件夹的名字以及位置也是SUN制定的规范,这个文件夹下将来存放javaweb程序员开发的java的字节码文件。
4)在WEB-INF目录下必须有一个配置文件叫做web.xml,这个配置文件可以从其他项目中拷贝。该文件在Tomcat服务器启动的时候解析,所以这个文件如果编写的不合语法,启动Tomcat服务器的时候会出现XML文件的解析异常。如果这个文件解析失败,表示该webapp启动失败(Tomcat可以启动成功),一个webapp对应一个web.xml文件。一个合法的web.xml文件应该具备以下信息: ```xml <?xml version=”1.0” encoding=”ISO-8859-1”?>
5)编写java源程序,该java源程序可以在任意位置编写,只要将其编写java源代码编译生成字节码文件就可以。最终服务器执行的是字节码程序。执行的是classes目录中的字节码。(此时在WEB-INF目录下新建src文件夹,这不是必须的,不是SUN制定的规范)<br />6)编写HelloServlet实现javax.servlet.Servlet接口并且重点实现service方法。<br />7)将servlet-api.jar配置到环境变量classpath中。(只是让java源程序编译生成正常的字节码文件)<br />8)将HelloServlet.java源程序编译生成字节码,将字节码拷贝到classes目录下。<br />9)开始编写web.xml文件,如下所示:(web.xml文件中配置的标签是SUN制定的规范,标签不能随便写,因为Tomcat服务器已经编写了web.xml文件的解析程序,Servlet规范包括文件的名称、文件的位置、类、接口、配置文件编写方式等。)
```java
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<servlet>
<servlet-name>firstServlet</servlet-name>
<servlet-class>HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>firstServlet</servlet-name>
<url-pattern>/servlet/hello</url-pattern>
<url-pattern>/a/b/c/d/e/f</url-pattern>
</servlet-mapping>
</web-app>
注:一个servlet标签对应一个servlet-mapping标签,servlet标签和servlet-mapping标签通过servlet-name进行联系,所以servlet-name标签中的文本可以随意编写,只要一致即可。servlet-class标签配置servlet的完整类名,如果这个类有包名,必须添加包名。url-pattern主要编写请求路径,这个请求路径必须以“/”开始,只要以“/”开始即可,后续的具体路径可以随意(但是这个路径一旦定下来,浏览器向服务器发送请求的时候,超链接发送的请求路径必须和url-pattern中配置的路径一致),web.xml文件的配制主要作用是将“Servlet类名”和“请求路径”绑定在一起。
10)启动Tomcat服务器,打开浏览器在地址栏上直接输入URL访问Servlet,URL如下所示:
Ø http://192.168.1.100:8080/FirstServletWebApp/servlet/hello
Ø http://192.168.1.100:8080/FirstServletWebApp/a/b/c/d/e/f
11)当然我们也可以编写HTML,在HTML中使用超链接发送请求,超链接的编写方式如下所示:
Ø 访问HelloServlet
Ø 访问HelloServlet
12)路径总结:到目前为止路径共编写了两个:
Ø 超链接中的路径
Ø web.xml文件中的路径
Ø 它们路径都是以“/”开始,都是使用了绝对路径,其中超链接上的路径比web.xml文件中的路径多一个“项目名称”
13)如果希望向浏览器输出HTML代码执行以下程序:
Ø 设置响应的内容类型以及字符编码方式,解决响应中的中文乱码问题(必须在获取响应流之前设置)
i. response.setContentType(“text/html;charset=GB18030”);
Ø 获取响应流,该响应流执行特定的浏览器客户端(这个响应流不需要程序员手动关闭,Tomcat服务器管理。)
ii. PrintWriter out = response.getWriter();
Ø 打印
iii. out.print(); 或者out.println(); ,后者是将HTML源代码换行,如果希望网页中换行,需要使用
l webapp的固定目录如下所示:
五、Servlet对象详解
1、Servlet对象生命周期
1、Servlet的本质是什么?
Servlet是服务器端的小java程序,这个小java程序不能随意编写,必须实现SUN制定的javax.servlet.Servlet接口,实现其中的方法。Servlet是一个满足java规范的java类。Servlet既然满足Servlet规范,Tomcat服务器我们可以叫做“WEB容器(Container)”,那么Servlet 就可以叫做容器中的“组件(Component)”
2、Servlet对象的生命周期是什么样的,什么时候创建,创建几次,什么时候销毁?
假设客户端向web服务器发送的请求是/login请求,当用户向web服务器第一次发送/login请求的时候,Tomcat在容器中搜索/login对象的Servlet对象,但是没有找到该对象,此时会从web.xml文件中获取/login对象的完整Servlet类名,通过反射机制,调用Servlet的无参数构造方法创建Servlet对象,马上调用init方法完成Servlet对象的初始化操作,然后调用service方法提供核心服务。
当用户第2+次再发送/login请求的时候,Tomcat还是在容器中搜索/login对象的Servlet对象,此时可以找到该对象,直接调用该对象的service方法提供核心服务。
当用户长时间没有访问该Servlet,或者服务器关闭,或者项目重新部署的时候,Tomcat容器会去回收Servlet对象所占用的内存,在回收该内存之前调用该Servlet对象的destroy方法,完成回收之前的准备。
结论:
- Servlet对象只创建一次(构造函数只执行一次)
- Servlet对象的init方法在对象创建之后马上执行(只执行一次完成初始化操作)
- Servlet对象service方法,只要用户访问一次则执行一次。
- Servlet对象的destroy方法,也是只执行一次,执行之前对象没有销毁,即将销毁。
3、Servlet对象的生命周期由Tomcat容器管理,对象的创建,对象内存的释放,还有对象中的方法调用都是由Tomcat容器完成,程序员不能干涉,只能编写该类实现Servlet接口,将其配置在web.xml文件中。
4、Servlet对象是单实例的,并且是在多线程的环境下运行,可能存在线程并发带来的安全问题。
5、什么情况下在init方法中编写程序?什么情况下在service方法中编写程序?什么情况下在destroy方法中编写程序?
- init方法是SUN规范中为程序员提供的一个对象初始化时机,这是一个特定的时刻,有的时候我们需要在这个特定的时刻执行一段特定的程序,此时将该程序写入init方法,例如:项目经理要求在Servlet对象创建时刻记录日志,请将该程序编写到init方法中。对象第一次创建的时候执行记录日志,并且只执行一次,记录一次日志信息。(init方法一般很少用)
- Service方法是Servlet的核心业务方法,核心业务都在该方法中完成,只要编写一个Servlet,service方法是一定要编写代码的,完成业务的处理。(常用)
- destroy方法init方法相同,只不过是一个不同的时机。(destroy方法一般很少使用)
2、web服务器启动加载Servlet
在web.xml文件的标签内部添加: 1
该标签表示在服务器启动阶段加载Servlet,完成实例化和初始化。
数字越小,优先级越高。
3、Servlet对象
4、Servlet对象中的方法
5、Servlet线程安全问题
1、Servlet线程安全问题及解决方案 (Servlet单实例多线程)
Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的。当客户端第一次请求某个Servlet时,Servlet容器将会根据web.xml配置文件实例化这个Servlet类。当有新的客户端请求该Servlet时,一般不会再实例化该Servlet类,也就是有多个线程在使用这个实例。这样的话,当两个或多个线程同时访问同一个Servlet时,可能会发生多个线程同时访问同一资源的情况,数据可能会变得不
一致,所以就很容易造成一系列的一些安全性问题。
为了保证数据的安全性可以采用下列方式:
同步对共享数据的操作
使用synchronized 关键字能保证一次只有一个线程可以访问被保护的区段,同步后的代码如下: ```java public class XXXServlet extends HttpServlet {
synchronized (this){XXXX}…………
}
**避免使用实例变量、静态变量**<br />线程安全问题还有些是由实例变量、静态变量造成的,只要在Servlet里面的任何方法里面都不使用实例变量、静态变量,那么该Servlet就是线程安全的。<br />对上面的两种种方法进行测试,可以表明用它们都能设计出线程安全的Servlet程序。但是在程序中使用同步来保护要使用的共享的数据,会使系统的性能大大下降。这是因为被同步的代码块在同一时刻只能有一个线程执行它,使得其同时处理客户请求的**吞吐量**降低,而且很多客户处于阻塞状态。另外为保证主存内容和线程的工作内存中的数据的一致性,要频繁地刷新缓存,这也会大大地影响系统的性能。所以在实际的开发中也应避免或最小化Servlet 中的同步代码;在Serlet中避免使用实例变量是保证Servlet线程安全的最佳选择。从Java 内存模型也可以知道,方法中的临时变量是在栈上分配空间,而且每个线程都有自己私有的栈空间,所以它们不会影响线程的安全。
**2、小结**<br />Servlet的线程安全问题只有在大量的并发访问时才会显现出来,并且很难发现,因此在编写Servlet程序时要特别注意。线程安全问题主要是由实例变量造成的,因此在Servlet中应避免使用实例变量。如果应用程序设计无法避免使用实例变量,那么使用同步来保护要使用的实例变量,但为保证系统的最佳性能,应该同步可用性最小的代码路径。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643895738696-209a276a-30b6-413a-8413-237db9f6b3c2.png#clientId=ud4401b47-14b4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=368&id=uf3b6c252&margin=%5Bobject%20Object%5D&name=image.png&originHeight=368&originWidth=1054&originalType=binary&ratio=1&rotation=0&showTitle=false&size=129405&status=done&style=none&taskId=ubeb49174-bd0a-41a4-b625-e4935b23098&title=&width=1054)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643896313714-4b9a0f78-b046-40c4-83e3-11ce765f6cb8.png#clientId=ud4401b47-14b4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=585&id=u98fa1184&margin=%5Bobject%20Object%5D&name=image.png&originHeight=585&originWidth=1385&originalType=binary&ratio=1&rotation=0&showTitle=false&size=313745&status=done&style=none&taskId=u5d3d3f23-12ac-4b9f-bf07-670ed7ae4f4&title=&width=1385)
<a name="hJbIx"></a>
# 六、ServletConfig对象详解
![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643899883025-1264177c-cc0a-42c3-bf06-0da56306c738.png#clientId=ud4401b47-14b4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=756&id=xqnie&margin=%5Bobject%20Object%5D&name=image.png&originHeight=756&originWidth=1597&originalType=binary&ratio=1&rotation=0&showTitle=false&size=862521&status=done&style=none&taskId=u280c218c-1ba1-47d1-9aea-d10f5c8b550&title=&width=1597)<br />**1、javax.servlet.ServletConfig是SUN制定的接口**,apache对ServletConfig接口的实现类的完整类名是:org.apache.catalina.core.StandardWrapperFacade,但是作为程序员不需要关心具体的类型,只要面向javax.servlet.ServletConfig接口调用即可,程序运行阶段执行的是apache的ServletConfig的实现类中的方法。<br />**2、Servlet对象的创建以及方法的调用过程**
```java
Class c = Class.forName("com.bjpowernode.javaweb.servlet.ServletConfigTest");
Servlet servlet = (Servlet)c.newInstance();
ServletConfig config = new org.apache.catalina.core.StandardWrapperFacade();
servlet.init(config);
ServletRequest request = 创建了apache的一个request对象;
ServletResponse response = 创建了apache的一个response对象;
servlet.service(request,response);
servlet.service(request,response);
servlet.service(request,response);
servlet.service(request,response);
………………
servlet.destroy();…
3、Servlet和ServletConfig之间的关系?
一个Servlet对象会对应一个ServletConfig对象
4、ServletConfig对象的本质是什么?
ServletConfig实际上是一个Servlet对象相关的配置信息对象。一个Servlet在web.xml文件中
5、ServletConfig接口中常用的方法
// 通过初始化参数的name获取初始化参数的value
String value = config.getInitParameter(String name);
// 获取所有初始化参数的name
Enumeration<String> names = config.getInitParameterNames();
// 获取ServletContext对象
ServletContext application = config.getServletContext();
传递ServletConfig对象的方法:将局部变量config赋值给实例变量config
七、ServletContext对象详解
1、javax.servlet.ServletContext是SUN制定的接口,apache对ServletContext接口的实现类完整类名:org.apache.catalina.core.ApplicationContextFacade,但是程序员不需要关心具体的类名,直接面向ServletContext接口调用方法即可。
2、获取ServletContext对象的方法
// 通过ServletConfig对象获取ServletContext对象
ServletContext application = config.getServletContext();
获取ServletConfig对象的方法:
3、ServletContext代表什么?什么时候被创建?什么时候被销毁?
- ServletContext在Tomcat服务器启动阶段解析webapp中的web.xml文件的时候创建ServletContext对象
- 在Tomcat服务器关闭的时候ServletContext对象被销毁。
- 对于同一个webapp来说,ServletContext对象只有一个。
- ServletContext代表“Servlet上下文”。ServletContext上下文指的是“所有Servlet对象共享的一个四周环境”对象。在同一个webapp中,所有的Servlet对象共享一个ServletContext对象。存储在ServletContext对象中的数据所有的Servlet共享。ServletContext对象可以完成多个Servlet之间数据的传递。
4、ServletContext接口中常用的方法
// 向Servlet上下文中存储数据
application.setAttribute(String name,Object obj);
// 从Servlet上下文中读取数据
Object obj = application.getAttribute(String name);
// 删除Servlet上下文中的数据
application.removeAttribute(String name);
// 获取Servlet上下文参数所有的name
Enumeration<String> names = application.getInitParameterNames();
// 通过Servlet上下文参数的name获取value
String value = application.getInitParameter(String name);
// 通过ServletContext获取文件的绝对路径真实路径
String realPath = application.getRealPath(“/index.html”); 注意:必须保证webapp的根下有index.html
5、什么样的数据适合存储到ServletContext对象中?
- 很少的数据量
- 所有用户共享的数据
- 这些共享数据不涉及到修改操作,或者很少涉及到修改操作。
6、多线程环境下,什么情况下需要考虑线程并发带来的安全问题?
- 多线程环境下运行程序
- 有共享的数据
- 共享数据涉及到修改操作
7、解决线程安全问题有两种解决方案?
- 使用局部变量替代成员变量、静态变量,局部变量在栈中存储,栈内存不共享,成员变量在堆内存的java对象内部存储,堆内存是共享的,静态变量在方法区中存储,方法区内存也是多线程共享
使用线程同步机制:synchronized语法支持线程同步机制。(会使并发量降低,吞吐量降低)
Servlet、ServletConfig、ServletContext对象的内存关系
Servlet、ServletConfig、ServletContext三个对象的代码关系如下所示: ```java public class Servlet{
private ServletConfig config; public ServletConfig getServletConfig(){ return config; } }
public class ServletConfig{
private ServletContext servletContext;
public ServletContext getServletContext(){
return servletContext;
}
}
**_Servlet、ServletConfig、ServletContext三个对象内存关系图:_**<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643942324724-1d4faf5b-7bfa-44f6-97e0-daa79479c4ca.png#clientId=u1ff6156f-86fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=380&id=uf970dc3f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=443&originWidth=833&originalType=binary&ratio=1&rotation=0&showTitle=false&size=45175&status=done&style=none&taskId=ub7dca203-1baf-4817-8da3-ab611cac72f&title=&width=715)<br />**结论:一个Servlet对应一个ServletConfig对象,所有的Servlet对象共享一个ServletContext对象。**<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1644076107854-64b178df-2ade-4e19-af79-d0ec9e925edd.png#clientId=u600da005-286f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=85&id=u5d49e759&margin=%5Bobject%20Object%5D&name=image.png&originHeight=102&originWidth=844&originalType=binary&ratio=1&rotation=0&showTitle=false&size=119210&status=done&style=none&taskId=u803e0c38-bf44-4d57-b361-55d5d0ccf3a&title=&width=703)
![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643901941905-cb717be8-cc7d-4b8c-a163-33057972ef31.png#clientId=ud4401b47-14b4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=354&id=u27a64be8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=354&originWidth=1130&originalType=binary&ratio=1&rotation=0&showTitle=false&size=221130&status=done&style=none&taskId=uca6d31aa-a699-42a5-a97a-ad6e8e21b1f&title=&width=1130)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643902267911-3d216dbc-db8c-4c85-a89f-eb60162bf08a.png#clientId=ud4401b47-14b4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=901&id=ud087510b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=901&originWidth=1576&originalType=binary&ratio=1&rotation=0&showTitle=false&size=739478&status=done&style=none&taskId=u94ed3969-ca42-415b-b177-59d6df7f207&title=&width=1576)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643903491992-b85b9e5d-4c6f-45da-968c-41f57e1350ea.png#clientId=ud4401b47-14b4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=514&id=u8e584dd4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=514&originWidth=848&originalType=binary&ratio=1&rotation=0&showTitle=false&size=270127&status=done&style=none&taskId=ubc12d63f-0bf9-4ced-9130-45957c9debb&title=&width=848)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643903394758-fa2a7a4a-4f0e-4928-93ab-fa1f2ce3cd45.png#clientId=ud4401b47-14b4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=925&id=ub894a9fb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=925&originWidth=1223&originalType=binary&ratio=1&rotation=0&showTitle=false&size=711534&status=done&style=none&taskId=u685ea133-3e94-47a1-9f90-c36c4cc617e&title=&width=1223)
<a name="TGza1"></a>
# 八、GenericServlet对象
![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643941918578-7498f8b4-7972-4a4d-a5fb-f4cf3e0812e3.png#clientId=u1ff6156f-86fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=906&id=ua1efd65a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=942&originWidth=734&originalType=binary&ratio=1&rotation=0&showTitle=false&size=437649&status=done&style=none&taskId=uda3cac46-b113-4072-9901-0c1305a1488&title=&width=706)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643941676860-fd92335f-6d95-4315-a3ae-fb5bff48e027.png#clientId=u1ff6156f-86fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=335&id=u47dcf7c2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=440&originWidth=952&originalType=binary&ratio=1&rotation=0&showTitle=false&size=269879&status=done&style=none&taskId=u9afd76d9-4aa7-41f4-9873-8fe567597c8&title=&width=725)
<a name="Dlzi1"></a>
# 九、HTTP协议
<a name="ImXqr"></a>
## 1、HTTP协议详解
![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643944233407-1b12014c-64dd-47cc-8863-20925ae483b9.png#clientId=u1ff6156f-86fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=578&id=lDCiy&margin=%5Bobject%20Object%5D&name=image.png&originHeight=885&originWidth=1071&originalType=binary&ratio=1&rotation=0&showTitle=false&size=279999&status=done&style=none&taskId=u4893fc84-dbcf-448a-9ba3-14fd812d7a9&title=&width=699)
<a name="GbV3Z"></a>
### 1.1 基于GET请求
![图片2.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643945291518-3c80de55-c683-4927-894a-f123a737a85f.png#clientId=u1ff6156f-86fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=279&id=ub2b59ef1&margin=%5Bobject%20Object%5D&name=%E5%9B%BE%E7%89%872.png&originHeight=388&originWidth=964&originalType=binary&ratio=1&rotation=0&showTitle=false&size=14351&status=done&style=none&taskId=u98c69ce8-b6cf-449a-9066-5d5fc4d2070&title=&width=693)<br />**1)请求行**<br />① GET(描述该请求采用了什么请求方法),HTTP协议中包含8种请求方法:
- **GET 请求获取Request-URI 所标识的资源 **
- **POST 在Request-URI 所标识的资源后附加新的数据 **
- HEAD 请求获取由Request-URI 所标识的资源的响应消息报头
- PUT 请求服务器存储一个资源,并用Request-URI 作为其标识
- DELETE 请求服务器删除Request-URI 所标识的资源
- TRACE 请求服务器回送收到的请求信息,主要用于测试或诊断
- CONNECT 保留将来使用
- OPTIONS 请求查询服务器的性能,或者查询与资源相关的选项和需求
② URI(请求WEB服务器的资源名称)
- URI:统一资源标识符(代表这个资源的名称)
如上图中的 /PrjTheHttpProtocol/test?username=admin&userpassword=123<br />**说明:HTTP协议规定GET请求发送数据在URI中发送,**<br />**格式:uri?name=value&name=value&name=value…..**
- URL:统一资源定位符(不但代表这个资源的名称,而且通过它还可以找到该资源),
如:http://ip:port/URI<br />③ HTTP1.1(当前使用的HTTP协议版本)<br />**2)请求报头**<br />① 告诉web服务器浏览器接收的语言版本<br />② 请求web服务器的IP地址和端口号<br />③ Cookies等信息<br />**3)空白行**<br />分割请求报头和请求体的专用行<br />**4)请求体**<br />由于当前使用的请求方式是GET请求方式,所以请求体中不传送任何数据
<a name="uDP7s"></a>
### 1.2 基于POST请求
![图片1.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643945274513-72511b61-b767-4d00-ab90-117359427cdd.png#clientId=u1ff6156f-86fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=349&id=u9eb4c324&margin=%5Bobject%20Object%5D&name=%E5%9B%BE%E7%89%871.png&originHeight=496&originWidth=978&originalType=binary&ratio=1&rotation=0&showTitle=false&size=17932&status=done&style=none&taskId=uf6153984-c5bf-4898-bb0d-3a75b0e38f4&title=&width=689)<br />**1)请求行**<br />① 上图采用POST方式发送请求。<br />② 上图URI后边没有任何数据,这是因为采用POST方式提交的缘故。<br />③ HTTP1.1(当前使用的HTTP协议版本)<br />**2)请求报头**<br />由于请求是POST请求,所以报头中显示:Cache-Control:no-cache)<br />**3)空白行**<br />分割请求报头和请求体的专用行<br />**4)请求体**<br />由于当前使用的请求方式是POST请求方式,所以数据在请求体中发送,<br />并且格式是:name=value&name=value&name=value……
<a name="Cehg0"></a>
### 1.3 响应协议
![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643945219596-2c7e94d1-e7a7-42a0-a8dd-84434e8081a8.png#clientId=u1ff6156f-86fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=564&id=u79410efc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=564&originWidth=385&originalType=binary&ratio=1&rotation=0&showTitle=false&size=37514&status=done&style=none&taskId=u8de7f7b3-7770-4229-ae3e-932d0c0c4fc&title=&width=385)<br />**1)状态行**<br />① HTTP1.1:HTTP协议版本号<br />② 200:响应状态号<br />**状态代码有三位数字组成,第一个数字定义了响应的类别,且有五种可能取值:**<br />**1xx:指示信息--表示请求已接收,继续处理 **<br />**2xx:成功--表示请求已被成功接收、理解、接受 **<br />**3xx:重定向--要完成请求必须进行更进一步的操作 **<br />**4xx:客户端错误--请求有语法错误或请求无法实现 **<br />**5xx:服务器端错误--服务器未能实现合法的请求 **<br />**常见状态代码、状态描述、说明:**<br />**200 OK 客户端请求成功 **<br />**400 Bad Request 客户端请求有语法错误,不能被服务器所理解 **<br />**401 Unauthorized 请求未经授权**<br />**403 Forbidden 服务器收到请求,但是拒绝提供服务 **<br />**404 Not Found 请求资源不存在**<br />**500 Internal Server Error 服务器发生不可预期的错误 **<br />**503 Server Unavailable 服务器当前不能处理客户端的请求,一段时间后, 可能恢复正常 **<br />**405 浏览器客户端发送的请求和底层的方法doPost/doGet不一致导致的。**<br />③ OK:对响应结果的描述<br />**2)响应报头**<br />a) WEB服务器版本信息<br />b) 内容类型以及字符编码方式<br />c) 内容长度,响应回来的总字符数<br />d) 响应时间<br />e) Cookies等信息<br />**3)空白行**<br />分割响应报头和响应正文的专用行<br />**4)响应正文**<br />从WEB服务器端响应回来的HTML代码
<a name="jv9is"></a>
## 2、GET和POST请求的区别
![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643944926316-ea09b07e-fdca-4fbe-8cda-b6afcbbe7ced.png#clientId=u1ff6156f-86fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=889&id=bKwLE&margin=%5Bobject%20Object%5D&name=image.png&originHeight=889&originWidth=1139&originalType=binary&ratio=1&rotation=0&showTitle=false&size=614171&status=done&style=none&taskId=u7df1fcd8-bafa-4187-a159-927e20e3d49&title=&width=1139)<br />**2.1 HTTP请求的两种方式,GET和POST请求的表面形式上的区别: **
1. GET请求通过URL(请求行)提交数据,在URL中可以看到所传参数。POST通过“请求体”传递数据,参数不会在url中显示 。
1. GET请求提交的数据有长度限制(1024或2048),POST请求没有限制(或限制80KB)。
1. GET请求返回的内容可以被浏览器缓存起来。而每次提交的POST,浏览器在你按下F5的时候会跳出确认框,浏览器不会缓存POST请求返回的内容。
**2.2 以上描述都是GET,POST两者区别表现形式,是浏览器对这两种请求的处理方式。作为Web开发人员,更应该看清的是它们的本质区别是什么HTTP协议是这样解释GET和POST的:GET请求不应该做读取数据之外的事情(原文:Requests using GET SHOULD NOT have the significance of taking an action other than retrieval)。而如果一个请求,修改了服务器资源或者使用了服务器资源(如发邮件,使用打印机等),那么应当使用POST。所以,GET和POST的本质区别是使用场景的区别,简单的说,GET是只读,POST是写。浏览器对两种请求的不同处理方式也是基于这两个不同的场景:**
1. GET:查询往往需要的上传的数据量比较小,查询参数也往往不需要保密,所以放在url里比较高效。HTTP协议要求同一URL的多个请求应该返回同样的结果,所以浏览器可以把返回结果缓存起来,以提高性能。至于参数长度的限制,这个是和浏览器url的长度限制相关的,1024也好,2048也好,其实没有太大的意义,参数超长往往是错误使用GET方法的结果。
1. POST:修改数据需要支持大数据量表单的提交,数据也常常包含用户的私人信息,所以数据放在请求的消息体中传递。相同的POST请求可能会对服务器端产生不同的影响,比如两次POST可能创建两条不同的数据,所以对POST返回结果的缓存是没有意义的。
**2.3 用GET,还是用POST?**<br />如果回答“因为POST的参数长度不受限制,所以我用POST”,就有点本末倒置了。两者之间如何选择,首先要看是不是修改或者使用了服务器资源,其次要看请求或者响应中的数据是不是包含了敏感信息,如果是,那么应该选择POST,同时处于安全性的考虑,服务器端应该只接受POST,**拒绝GET**。比如数据的增加和修改,认证信息的提交,是一定要用POST的。如果只是简单查询,用GET就可以了。
**2.4 POST请求是不是比GET请求更安全?**<br />有人说“POST比GET安全,因为GET的参数都明文写在url上了”,从个人信息安全的角度上说,这句话是对的,但这种安全机制是“防君子不防小人”的,有各种工具能够获取POST请求的数据。如前面所说,两者有截然不同的使用场景,如果是该用POST的地方用了GET,又说GET不安全,那GET也太冤枉了。其实,HTTP协议中提到GET是安全的方法(safe method),其意思是说GET方法不会改变服务器<br />端数据,所以不会产生副作用。这是建立在Web开发人员正确使用GET方法的基础上的,如果修改数据的请求却使用了GET方法,显然是非常危险的。
**2.5 GET与POST的误用有什么危害?**<br />应该使用GET的地方用了POST:性能受损,浏览器不会缓存。 <br />应该使用POST的地方用了GET:每一个这样的地方都是一个漏洞,有可能被黑客利用。如果是一个对安全要求很高的网站,一定不要忽视。<br />不仅仅是在前端要正确的使用GET和POST,**同时还需要后端代码的支持**,比如后端应当在需要POST请求的时候拒绝GET请求,从而切断黑客利用GET请求攻击的途径,更高级别的,还需要对POST请求进行过滤,以确保所有的POST请求都来自可信任的地址。
**2.6 如何判断当前请求是GET请求还是POST请求?**
- 在浏览器地址栏上直接编写URL提交的请求一定是GET请求。
- 使用热链接向服务器发送的请求一定是GET请求。
- 使用form表单提交数据的时候,如果method属性没有编写,或者method属性值被指定是GET,这样发送的请求属于GET请求。
- 使用form表单提交数据的时候,如果method属性值被手动指定为POST,那么该请求属于POST请求。
**2.7 思考:我们在做javaweb开发的时候所有的Servlet都要继承HttpServlet类,并且负责重写doGet和doPost方法,假设当前请求是POST请求,而没有重写doPost方法为什么会出现以下异常?**<br /> ![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643942679925-19b9bf51-9973-4159-9426-7e1b61790614.png#clientId=u1ff6156f-86fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=315&id=bncbV&margin=%5Bobject%20Object%5D&name=image.png&originHeight=315&originWidth=596&originalType=binary&ratio=1&rotation=0&showTitle=false&size=65992&status=done&style=none&taskId=ue6319a0f-3677-4611-b0f6-8e2cda8b387&title=&width=596)<br />底层是doPost方法表明希望客户端发送的请求是POST请求,如果此时发送的请求是GET请求,则会执行HtttpServlet类中的doGet方法,这样这个方法会报错(上面就是错误)。<br />该用POST的时候,java的Servlet服务器端代码进行了控制,客户端只能发送POST请求,不能发送GET,你只要发GET就报错。<br />记住:不要随意的编写doPost和doGet,是POST就编写doPost方法,是GET就编写doGet方法。**在重写doGet方法和doPost方法的时候一定记住不要再调用super.doGet或者super.doPost等方法。**
<a name="rKAX2"></a>
## 3、保证前后端请求方式一致
![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643946583282-9d83926e-0d19-4ec2-8e4a-419d51b6308e.png#clientId=u1ff6156f-86fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=850&id=uc2c36e28&margin=%5Bobject%20Object%5D&name=image.png&originHeight=850&originWidth=1205&originalType=binary&ratio=1&rotation=0&showTitle=false&size=781386&status=done&style=none&taskId=u5698c2cc-e5a1-4474-b69e-3a1d20cf987&title=&width=1205)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643946295873-192b7bae-0e29-4498-8a68-7a360ac9abbf.png#clientId=u1ff6156f-86fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=462&id=u64e2acb3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=626&originWidth=881&originalType=binary&ratio=1&rotation=0&showTitle=false&size=500809&status=done&style=none&taskId=u82b5474e-a65a-4500-a53c-e5827d8df27&title=&width=650)
<a name="h2Mlr"></a>
# 十、HttpServlet接口
![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643948264267-f19c1ad6-1eeb-4743-884c-91d38bb88cd2.png#clientId=u1ff6156f-86fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=224&id=u999eff1e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=374&originWidth=1212&originalType=binary&ratio=1&rotation=0&showTitle=false&size=331077&status=done&style=none&taskId=u1e943de0-435e-4a61-8200-0d478399510&title=&width=726)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643952875301-ccf168b0-233e-4915-ad5b-90b931fccde3.png#clientId=u1ff6156f-86fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=333&id=ud6ad848f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=867&originWidth=1788&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1444348&status=done&style=none&taskId=u43e7694b-984c-491c-8ae2-cfa565a5c6b&title=&width=686)
![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643953642377-e7209311-e34d-4112-b5f8-c5c4825c3099.png#clientId=u1ff6156f-86fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=594&id=u007f0d8a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=778&originWidth=834&originalType=binary&ratio=1&rotation=0&showTitle=false&size=440421&status=done&style=none&taskId=u68ca03c4-4027-474c-93e8-856d002d94f&title=&width=637)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643953804240-3c0c5ec5-41a6-43e2-abf8-5b214646d80f.png#clientId=u1ff6156f-86fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=468&id=u00b670e2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=875&originWidth=1172&originalType=binary&ratio=1&rotation=0&showTitle=false&size=598331&status=done&style=none&taskId=ud89f8990-273b-49a3-a4cf-f95c1b7893a&title=&width=627)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643954006604-223134fd-2c5a-4ca7-9d16-59181984632d.png#clientId=u1ff6156f-86fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=434&id=udd416d64&margin=%5Bobject%20Object%5D&name=image.png&originHeight=557&originWidth=910&originalType=binary&ratio=1&rotation=0&showTitle=false&size=244389&status=done&style=none&taskId=ue042c32d-e7f1-4b45-a91d-e0555c298be&title=&width=709)
<a name="SVwbn"></a>
# 十一、HttpServletRequest对象
![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643957055431-cd1f117c-45db-40e6-8c8c-e8108544ca29.png#clientId=u1ff6156f-86fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=658&id=CPzpn&margin=%5Bobject%20Object%5D&name=image.png&originHeight=658&originWidth=1465&originalType=binary&ratio=1&rotation=0&showTitle=false&size=644054&status=done&style=none&taskId=u8fdce37f-10ae-49c0-9915-bb7699416a2&title=&width=1465)<br />1、javax.servlet.http.HttpServletRequest是SUN制定的Servlet规范,是一个接口。表示**请求**,“HTTP请求协议”的完整内容都被封装到request对象中,HttpServletRequest接口的父接口是javax.servlet.ServletRequest。<br />2、Apache软件基金会开发的“Tomcat容器”对javax.servlet.http.HttpServletRequest接口的实现类完整类名是org.apache.catalina.connector.RequestFacade,但是我们javaweb程序员不需要关心具体的请求对象类型,不需要关心是哪个容器,我们只需要面向HttpServletRequest接口调用方法即可。<br />3、一次请求对应一个请求对象,请求开始,请求对象被创建,请求结束,请求对象被回收,下一次请求的时候是一个新的请求对象。如何理解一次请求:从用户发送请求开始,到网页最终停下来,这是一次完整的请求(**除重定向之外**)<br />4、HttpServletRequest接口中常用的方法
```java
// 获取参数Map
Map<String,String[]> parameterMap = request.getParameterMap();
// 获取参数Map的所有key,获取所有参数的name
Enumeration<String> parameterNames = request.getParameterNames();
// 通过参数Map集合的key获取参数Map集合的value(value是一个字符串类型的一维数组)用于获取复选框提交的数据
String[] parameterValues = request.getParameterValues(String name);
// 通过用户提交的参数name获取参数value(最常用的方法)
String value = request.getParameter(String name);
// 向HttpServletRequest对象中存储数据(绑定)
request.setAttribute(String name, Object obj);
// 从HttpServletRequest对象中获取数据(读取数据)
Object obj = request.getAttribute(String name);
// 移除HttpServletRequest对象中的数据
request.removeAttribute(String name);
// 使用request对象完成转发(转发是一次请求,一次请求跨越多个Servlet)
// 转发的代码下边不能再编写转发的程序
// 转发的下一个资源可能是:JSP、Servlet、HTML等。
request.getRequestDispatcher(“资源路径”).forward(request, response);
// 获取客户端的IP地址
String clientIP = request.getRemoteAddr();
// 获取URI
String uri = request.getRequestURI();
// 获取URL
StringBuffer url = request.getRequestURL();
// 获取Servlet Path
String servletPath = request.getServletPath();
// 获取应用程序的根路径,获取应用上下文路径
String contextPath = request.getContextPath();
// 解决请求体中的乱码问题(在从request对象中获取任何数据之前设置有效)
request.setCharacterEncoding(“GB18030”);
// 在服务器端获取用户发送的请求中的所有Cookie
Cookie[] cookies = request.getCookies();
// 获取会话对象session
HttpSession sessoin = request.getSessoin(); 获取session对象,如果获取不到则开启新session
HttpSession sessoin = request.getSession(true); 获取session对象,如果获取不到则开启新session
HttpSession sessoin = request.getSession(false); 获取session对象,如果获取不到则返回null
5、HttpServletRequest对象是一个请求级别的对象,一次请求一个对象,所以request对象只能完整在同一次请求中进行数据的传递,可以跨越多个Servlet进行数据的传递,但是必须使用转发机制。如果request对象和ServletContext对象都可以完成此功能,我们优先选择request范围。(到此为止我们已经讲过两个范围对象:ServletContext、HttpServletRequest)。request不能完成跨用户传递数据。只能完成在一次请求中传递数据。
6、路径总结:
1、向web服务器发送请求时:
<a href=”/PrjStudyServlet01/servlet/hello”>Hello Servlet</a>(第一个“/”代表Tomcat服务器根)
2、web.xml文件中:
<url-pattern>/servlet/hello</url-pattern>
3、通过ServletContext获取文件绝对路径:
String realPath = application.getRealPath(“/index.html”);(保证webapp根下有index.html文件)
4、转发涉及到的路径:
request.getRequestDispatcher(“/servlet/hello”).forward(request,response);
总结:目前我们涉及到的所有路径都是以“/”开始,只有在向web服务器发送请求的时候才需要添加“项目名”,其他的路径都是不需要添加项目名。
十二、HttpServletResponse对象
1、javax.servlet.ServletResponse是接口,是SUN制定的响应对象,专门完成向浏览器的响应动作。
2、javax.servlet.http.HttpServletResponse父接口是ServletResponse
3、获取响应流:PrintWriter out = response.getWriter();
4、解决响应中的中文乱码问题(在获取响应流之前设置,设置响应的内容类型以及字符编码方式):response.setContentType(“text/html;charset=UTF-8”);内容类型编写错误会导致文件下载。
5、关于PrintWriter中的方法:
- println方法:输出到浏览器的HTML源码换行
- print方法:输出到浏览器的HTML源码不换行
- 如果想在网页中做到换行效果,需要使用HTML识别的
标签。
6、向特定的浏览器客户端发送Cookie:response.addCookie(cookie);
public class WelcomeServlet implements Servlet{
public void init(ServletConfig config) throws ServletException{
}
public void service(ServletRequest request,ServletResponse response) throws ServletException,IOException{
// 解决响应的时候中文乱码问题
// 设置响应的内容类型和字符编码方式
// 响应内容类型采用text/html
// 字符编码方式采用UTF-8
// 获取响应流之前设置才有效果
response.setContentType("text/html;charset=UTF-8");
// 将消息输出到浏览器上
// 输出到浏览器属于响应,和response对象有关系
// 查阅ServletResponse的帮助文档
// 1、获取标准输出流,该输出流可以响应文本到指定客户端
PrintWriter out = response.getWriter();
//response.setContentType("text/html;charset=UTF-8"); // 此时才设置没有效果
// 2、调用PrintWriter中的print方法打印文本
// 提醒:浏览器只能执行html css javascript,所以我们Servlet响应的文本也应该是html css javascript
out.print("<html>");
out.print("<head>");
out.print("<title>welcome servlet</title>");
out.print("<script type='text/javascript'>function sayHello(){alert('hello world');}</script>");
out.print("</head>");
out.print("<body>");
out.print("<h1 align='center'><font color='red'>welcome servlet!</font></h1>");
out.print("<h1 align='center'><font color='red'>中文有事吗?</font></h1>");
out.print("<input type='button' value='hello' onclick='sayHello();'/>");
// 在HTML中做到换行效果,需要使用<br>标签
out.print("abc");
out.print("<br>");
out.print("def");
out.print("</body>");
out.print("</html>");
/*
// PrintWriter的println方法作用:将源代码换行
// 这样反而体积会增大,不建议使用
out.println("<html>");
out.println("<head>");
out.println("<title>welcome servlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1 align='center'><font color='red'>welcome servlet!</font></h1>");
out.println("<h1 align='center'><font color='red'>中文有事吗?</font></h1>");
out.println("abc");
out.println("def");
out.println("</body>");
out.println("</html>");
*/
// 注意:PrintWriter标准输出流不需要手动关闭,也不需要手动刷新
}
public void destroy(){
}
public String getServletInfo(){
return "";
}
public ServletConfig getServletConfig(){
return null;
}
}
十三、程序中乱码解决方案
1、乱码出现在这么几个位置上
数据传递过程中的乱码
数据保存过程中的乱码
数据展示过程中的乱码
2、数据传递过程中的乱码
假如使用表单form向web服务器发送请求提交数据,如果数据中有简体中文,在服务器端Servlet中使用“String value = request.getParameter(String name);”则一定会出现中文乱码问题。因为在网络传输数据的过程中,不能直接传送中文,浏览器是先将中文采用ISO-8859-1的方式进行预先编码,这种编码方式是不支持中文的,所以在服务器端如果不做任何处理,直接从request对象中获取的简体中文一定是乱码。解决方案包括以下两种方式:
我们可以将从request中获取的数据采用ISO-8859-1的方式进行解码,让这个数据再次回到正常的byte[]数组状态,然后再采用一种支持中文的编码方式进行编码,这种方式是一种万能的方式,适合所有的情况,但是前提是必须直到原先采用的原始的编码方式是什么,关键是能够让我们的数据再回到原始状态(byte[]数组),但是这种方式的缺点就是太麻烦,如果表单中提交的数据量比较大,编码量也就随着庞大起来。
调用request对象的一个特定的方法就可以完成乱码的处理:“request.setCharacterEncoding(“GB18030”);”但是这行代码必须出现在从request对象中获取任何数据之前设置才起作用。这个方法只处理请求体中的乱码问题,对请求行中的乱码不起作用。(处理POST请求中的乱码问题)
关于GET请求中的乱码问题,主要是告诉Tomcat服务器请求行中采用什么样字符编码方式,我们可以去修改TOMCAT服务器中的server.xml文件,将server.xml文件中配置端口号的标签末尾添加URIEncoding=”GB18030”,这样就可以解决GET请求中的中文乱码问题。
3、数据保存中出现的乱码问题
数据保存指的是数据库中的乱码问题,有的时候我们在数据库表中存储的中文无法正常显示,显示为乱码,为什么我们当前的这个ORACLE数据存储中文不会出现乱码问题呢?因为我们在安装Oracle数据库的时候指定了字符的编码方式是支持简体中文的。大家以后在使用mysql数据库的时候,安装之前必须手动设置该数据库采用的字符编码方式,如果采用mysql数据库默认的字符编码方式的话,是会出现中文乱码问题的。大家一定要注意手动修改mysql数据库的字符编码方式。还有一种情况本身数据库是支持简体中文的,但是在保存之前数据已经是乱码,保存之后必然还是乱码。
4、数据展示过程中的乱码
Servlet使用HttpServletResponse这个响应对象向浏览器客户端响应HTML代码,默认情况下,如果响应中有中文的话,会出现乱码问题,我们需要在获取响应流之前设置响应的内容类型以及字符编码方式,使用这种方式可以解决数据展示过程中的乱码问题:
“response.setContentType(“text/html;charset=GB18030”);”需要注意的是这段代码必须在获取响应流out之前设置才起作用。响应的内容类型不要写错了,如果写错了,则是文件下载。
十四、转发与重定向的区别(资源跳转)
代码上的实现:
转发:request.getRequestDispatcher(“/servletPath”).forward(request,response);
重定向:response.sendRedirect(“/webcontextPath/servletPath”);
注意资源路径:在转发中不需要编写web应用的根路径名称。但是在重定向的时候需要编写web应用的根路径名称,假设web应用的根路
径名称是PrjStudyServlet,该web应用的根下有a资源,如果是转发则路径写 /a,如果是重定向则 /PrjStudyServlet/a
1、forward方法只能将请求转发给同一个WEB应用中的资源(这个资源可能是Servlet、JSP、HTML等),而HttpServletResponse.sendRedirect 方法不仅可以重定向到当前应用程序中的其他资源,还可以重定向到另一个WEB应用中的资源
2、调用HttpServletResponse.sendRedirect方法重定向的访问过程结束后,浏览器地址栏中显示的URL会发生改变,由初始的URL地址变成重定向的目标URL;而调用RequestDispatcher.forward 方法的请求转发过程结束后,浏览器地址栏保持初始的URL地址不变。
3、HttpServletResponse.sendRedirect方法对浏览器的请求直接作出响应,响应的结果就是告诉浏览器去重新发出对另外一个URL的 访问请求,这个过程好比有个绰号叫“浏览器”的人写信找张三借钱,张三回信说没有钱,让“浏览器”去找李四借,并将李四现在的通信地址告诉给了“浏览器”。于是,“浏览器”又按张三提供通信地址给李四写信借钱,李四收到信后就把钱汇给了“浏览器”。可见,“浏览器”一共发出了两封信和收到了两次回复, “浏览器”也知道他借到的钱出自李四之手。RequestDispatcher.forward方 法在服务器端内部将请求转发给另外一个资源,浏览器只知道发出了请求并得到了响应结果,并不知道在服务器程序内部发生了转发行为。这个过程好比绰号叫“浏览器”的人写信找张三借钱,张三没有钱,于是张三找李四借了一些钱,甚至还可以加上自己的一些钱,然后再将这些钱汇给了“浏览器”。可见,“浏览器”只发 出了一封信和收到了一次回复,他只知道从张三那里借到了钱,并不知道有一部分钱出自李四之手。
4、RequestDispatcher.forward方法的调用者与被调用者之间共享相同的request对象和response对象,它们属于同一个访问请求和响应过程;而HttpServletResponse.sendRedirect方法调用者与被调用者使用各自的request对象和response对象,它们属于两个独立的访问请求和响应过程。对于同一个WEB应用程序的内部资源之间的跳转,特别是跳转之前要对请求进行一些前期预处理,并要使用HttpServletRequest.setAttribute方法传递预处理结果,那就应该使用RequestDispatcher.forward方法。不同WEB应用程序之间的重定向,特别是要重定向到另外一个WEB站点上的资源的情况,都应该使用HttpServletResponse.sendRedirect方法。
5、无论是转发还是重定向,转发和重定向后面不能再有转发和重定向相关的代码。(转发和重定向在同一个Servlet中只能有一次。)
怎么选择是重定向还是转发呢?
通常情况下转发更快,而且能保持request内的对象,所以他是第一选择。但是由于在转发之后,浏览器中URL仍然指向开始页面,此时如果重载当前页面,开始页面将会被重新调用。如果你不想看到这样的情况,则选择转发(页面刷新问题)。 不要仅仅为了把变量传到下一个页面而使用session作用域,那会无故增大变量的作用域,转发也许可以帮助你解决这个问题。
重定向:以前的request中存放的变量全部失效,并进入一个新的request作用域。
转发:以前的request中存放的变量不会失效,就像把两个页面拼到了一起。
什么是一次请求:
浏览器向服务器发送请求到服务器响应结束为一次请求。
但是重定向发送了两次请求,服务器响应了两次。
1、 如果是web应用之间资源的跳转,必须使用重定向。
2、 为了解决页面的刷新问题,必须使用重定向。
3、 如果在Servlet中向request对象中存储了一个数据,希望在下一个Servlet/JSP页面中把request对象中的数据取出来,这个时候必须使用转发,因为重定向是两次请求,request不能跨请求传递数据。其它情况都可以是用重定向。
重定向解决页面刷新问题
十五、Cookie详解
1、Cookie概述
Cookie是由服务器端生成并储存在浏览器客户端上的数据。在javaweb开发中Cookie被当做java对象在web服务器端创建,并由web服务器发送给特定浏览器客户端,并且WEB服务器可以向同一个浏览器客户端上同时发送多个Cookie,每一个Cookie对象都由name和value组成,name和value只能是字符串类型,浏览器接收到来自服务器的Cookie数据之后默认将其保存在浏览器缓存中(如果浏览器关闭,缓存消失,Cookie数据消失),只要浏览器不关闭,当我们下一次发送“特定”请求的时候,浏览器负责将Cookie数据发送给WEB服务器。我们还可以使用特殊的方法,将Cookie保存在客户端的硬盘上。永久性保存。这样关闭浏览器Cookie还是存在的,不会消失,比如:实现两周内自动登录。
2、Cookie在客户端的保存形式和有效时间
- 服务器端默认创建的Cookie,发送到浏览器之后,浏览器默认将其保存在缓存中,当浏览器关闭之后Cookie消失。
- 服务器创建Cookie对象之后,调用setMaxAge方法设置Cookie的有效时间,
- 如果这个有效时间>0,则该Cookie对象发送给浏览器之后浏览器将其保存到硬盘文件中。
- 如果这个有效时间<0,则该Cookie对象也是被保存在浏览器缓存中,待浏览器关闭Cookie消失。
- 如果这个有效时间=0,则该Cookie从服务器端发过来的时候就已经是一个已过时的Cookie。
3、Cookie和请求路径之间的关系
每一个Cookie和请求路径是绑定在一起的,只有特定的路径才可以发送特定的Cookie。实际上浏览器是这样做的:浏览器在向web服务器发送请求的时候先去对应的请求路径下搜索是否有对应的Cookie,如果有Cookie,并且Cookie没有失效,则发送该Cookie或者多个Cookie到服务器端。请求路径和Cookie的关系是这样对应的: ```xml 场景一: 1、假如获取Cookie时的路径是:http://127.0.0.1:8080/PrjCookies/getCookie 2、将来发送Cookie的路径包括如下路径 : http://127.0.0.1:8080/PrjCookies/getCookie(相同路径) http://127.0.0.1:8080/PrjCookies/xxxx(同目录) http://127.0.0.1:8080/PrjCookies/xxxx/xxxx/xxx(子目录)
场景二: 1、假如获取Cookie时的路径是:http://127.0.0.1:8080/PrjCookies/servlet/getCookie 2、将来发送Cookie的路径包括如下路径 : http://127.0.0.1:8080/PrjCookies/servlet/getCookie(相同路径) http://127.0.0.1:8080/PrjCookies/servlet/xxxxx(同目录) http://127.0.0.1:8080/PrjCookies/servlet/xxxxx/xxxx(子目录)
场景三: 1、我们也可以在创建Cookie对象的时候设置Cookie的关联路径 例如:cookie.setPath(“/PrjCookies/system/login”); 2、那么如下的请求路径浏览器会发送该Cookie: http://127.0.0.1:8080/PrjCookies/system/login(相同路径) http://127.0.0.1:8080/PrjCookies/system/xxx(同一目录) http://127.0.0.1:8080/PrjCookies/system/xxx/xxx(子目录)
<a name="KKQQB"></a>
## 4、浏览器禁用Cookie
![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1643980238250-7a3dcd37-1188-4ceb-adc0-a26341b1d566.png#clientId=u1ff6156f-86fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=520&id=uf97c3830&margin=%5Bobject%20Object%5D&name=image.png&originHeight=520&originWidth=431&originalType=binary&ratio=1&rotation=0&showTitle=false&size=100402&status=done&style=none&taskId=u58866df6-0ce6-40f4-a831-9e341cb8f54&title=&width=431)**IE浏览器禁用Cookie。**<br />当浏览器禁用Cookie之后,服务器还是仍然会将Cookie发送给浏览器,只不过这次浏览器选择了不接收。现在有很多网站的使用都是需要开启接收Cookie的。例如126邮箱。
<a name="Hatv6"></a>
## 5、cookie总结
1、Cookie是什么? Cookie作用? Cookie保存在哪里?<br /> - 翻译过来:曲奇饼干<br /> - Cookie可以保存会话状态,但是这个会话状态是保留在客户端上。<br /> - 只要Cookie清除,或者Cookie失效,这个会话状态就没有了。<br /> - Cookie是保存在浏览器客户端上的<br /> - Cookie可以保存在浏览器的缓存中,浏览器关闭Cookie消失<br /> - Cookie也可以保存在客户端的硬盘文件中,浏览器关闭Cookie还在,除非Cookie失效。
2、Cookie只有在javaweb中有吗?<br /> - Cookie不止是在javaweb中存在<br /> - 只要是web开发,只要是B/S架构的系统,只要是基于HTTP协议,就有Cookie的存在。<br /> - Cookie这种机制是HTTP协议规定的。
3、Cookie实现的功能,常见的有哪些?<br /> - 保留购物车商品的状态在客户端上<br /> - 十天内免登录<br /> .......
4、在java中Cookie被当做类来处理,使用new运算符可以创建Cookie对象,而且Cookie由两部分组成,<br /> 分别是Cookie的name和value,name和value都是字符串类型String。
5、在java程序中怎么创建Cookie?<br /> Cookie cookie = new Cookie(String cookieName, String cookieValue);
6、服务器可以一次向浏览器发送多个Cookie
```java
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//创建Cookie对象
Cookie cookie1 = new Cookie("username","zhangsan");
Cookie cookie2 = new Cookie("password","123");
// response.setContentType(type);
// response.getWriter();
// response.sendRedirect(location);
// 将Cookie对象发送给浏览器客户端
response.addCookie(cookie1);
response.addCookie(cookie2);
}
7、默认情况下,服务器发送Cookie给浏览器之后,浏览器将Cookie保存在缓存当中,只要不关闭浏览器,Cookie永远存在,并且有效。
当浏览器关闭之后,缓存中的Cookie被清除。
8、在浏览器客户端无论是硬盘文件中还是缓存中保存的Cookie,什么时候会再次发送给服务器呢?
- 浏览器会不会提交发送这些Cookie给服务器,和请求路径有关系。
- 请求路径和Cookie是紧密关联的。
- 不同的请求路径会发送提交不同的Cookie
9、默认情况下Cookie会和哪些路径绑定在一起????
/prj-servlet-18/test/createAndSendCookieToBrowser 请求服务器,服务器生成Cookie,并将Cookie发送给浏览器客户端
这个浏览器中的Cookie会默认和“test/”这个路径绑定在一起。
也就是说,以后只要发送“test/”请求,Cookie一定会提交给服务器。
/prj-servlet-18/a 请求服务器,服务器生成Cookie,并将Cookie发送给浏览器客户端
这个浏览器中的Cookie会默认和“prj-servlet-18/”这个路径绑定在一起。
也就是说,以后只要发送“prj-servlet-18/”请求,Cookie一定会提交给服务器。
10、其实路径是可以指定的,可以通过java程序进行设置,保证Cookie和某个特定的路径绑定在一起。
假设,执行了这样的程序:cookie.setPath(“/prj-servlet-18/king”);
那么:Cookie将和”/prj-servlet-18/king”路径绑定在一起
只有发送“/prj-servlet-18/king”请求路径,浏览器才会提交Cookie给服务器。
// 设置Cookie的关联路径
cookie1.setPath(request.getContextPath() + "/king");
cookie2.setPath(request.getContextPath() + "/king");
11、默认情况下,没有设置Cookie的有效时长,该Cookie被默认保存在浏览器的缓存当中,只要浏览器不关闭Cookie存在,只要关闭浏览器Cookie消失,我们可以通过设置Cookie的有效时长,以保证Cookie保存在硬盘文件当中。但是这个有效时长必须是>0的。换句话说,只要设置Cookie的有效时长大于0,则该Cookie会被保存在客户端硬盘文件当中。有效时长过去之后,则硬盘文件当中的Cookie失效。
cookie有效时长 = 0 直接被删除
cookie有效时长 < 0 不会被存储
cookie有效时长 > 0 存储在硬盘文件当中
cookie.setMaxAge(60 * 60); 1小时有效
//创建Cookie对象
Cookie cookie1 = new Cookie("username","zhangsan");
Cookie cookie2 = new Cookie("password","123");
//设置Cookie的有效期
cookie1.setMaxAge(60 * 60);
cookie2.setMaxAge(60 * 60 * 24);
12、浏览器提交Cookie给服务器,服务器怎么接收Cookie?
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 从request对象中获取所有提交的Cookie
Cookie[] cookies = request.getCookies();
if(cookies != null){
for(Cookie cookie : cookies){
String cookieName = cookie.getName();
String cookieValue = cookie.getValue();
System.out.println(cookieName + "=" + cookieValue);
}
}
}
13、浏览器是可以禁用Cookie,什么意思?
- 表示服务器发送过来的Cookie,我浏览器不要,不接收。
- 服务器还是会发送Cookie的,只不过浏览器不再接收。
6、使用Cookie机制,实现10天内免登录
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
<html>
<head>
<title>登录页面</title>
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="this is my page">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<!--<link rel="stylesheet" type="text/css" href="./styles.css">-->
</head>
<body>
<form action="/prj-servlet-19/login" method="post">
用户名
<input type="text" name="username" >
<br>
密码
<input type="password" name="password">
<br>
<input type="checkbox" name="tenDayAutoLoginFlag" value="ok">十天内免登录<br>
<input type="submit" value="登录">
</form>
</body>
</html>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
<html>
<head>
<title>登录失败页面</title>
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="this is my page">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<!--<link rel="stylesheet" type="text/css" href="./styles.css">-->
</head>
<body>
登录失败,用户名不存在或者密码错误,请<a href="/prj-servlet-19/login.html">重新登录</a>
</body>
</html>
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取用户名和密码
request.setCharacterEncoding("UTF-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
// JDBC连接数据库验证用户名和密码
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
boolean loginSuccess = false;
String realName = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3366/bjpowernode", "root", "123");
String sql = "select id,username,password,realname from t_user where username=? and password=?";
ps = conn.prepareStatement(sql);
ps.setString(1, username);
ps.setString(2, password);
rs = ps.executeQuery();
if(rs.next()){
loginSuccess = true;
realName = rs.getString("realname");
}
} catch (Exception e) {
e.printStackTrace();
} finally{
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(ps != null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
if(loginSuccess){
// 登录成功之后,获取用户是否选择了十天内免登录
String tenDayAutoLoginFlag = request.getParameter("tenDayAutoLoginFlag");
if("ok".equals(tenDayAutoLoginFlag)){
// 创建Cookie对象
Cookie cookie1 = new Cookie("username",username);
Cookie cookie2 = new Cookie("password",password);
// 设置有效时间
cookie1.setMaxAge(60 * 60 * 24 * 10);
cookie2.setMaxAge(60 * 60 * 24 * 10);
// 设置关联路径
cookie1.setPath(request.getContextPath());
cookie2.setPath(request.getContextPath());
// 发送Cookie给浏览器
response.addCookie(cookie1);
response.addCookie(cookie2);
}
// 登录成功跳转到成功页面
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.print("<html>");
out.print("<head>");
out.print("<title>欢迎页面</title>");
out.print("</head>");
out.print("<body>");
out.print("欢迎");
out.print(realName);
out.print("访问");
out.print("</body>");
out.print("</html>");
}else{
//登录失败跳转到失败页面
response.sendRedirect(request.getContextPath() + "/login-error.html");
}
}
}
public class CheckLoginStatusServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//从request中获取所有的Cookie
Cookie[] cookies = request.getCookies();
String username = null;
String password = null;
if(cookies != null){
// 遍历Cookie
for(Cookie cookie : cookies){
String cookieName = cookie.getName();
String cookieValue = cookie.getValue();
if("username".equals(cookieName)){
username = cookieValue;
}else if("password".equals(cookieName)){
password = cookieValue;
}
}
}
if(username != null && password != null){
//连接数据库JDBC验证用户名和密码
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
boolean loginSuccess = false;
String realName = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3366/bjpowernode", "root", "123");
String sql = "select id,username,password,realname from t_user where username=? and password=?";
ps = conn.prepareStatement(sql);
ps.setString(1, username);
ps.setString(2, password);
rs = ps.executeQuery();
if(rs.next()){
loginSuccess = true;
realName = rs.getString("realname");
}
} catch (Exception e) {
e.printStackTrace();
} finally{
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(ps != null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
if(loginSuccess){
//登录成功跳转到成功页面
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.print("<html>");
out.print("<head>");
out.print("<title>欢迎页面</title>");
out.print("</head>");
out.print("<body>");
out.print("欢迎");
out.print(realName);
out.print("访问");
out.print("</body>");
out.print("</html>");
}else{
//登录失败跳转到失败页面
response.sendRedirect(request.getContextPath() + "/login-error.html");
}
}else{
//跳转到登录页面
response.sendRedirect(request.getContextPath() + "/login.html");
}
}
}
十六、HttpSession详解(简称session)
1、session概述
在java web中session是一个存储在WEB服务器端的java对象,该对象代表用户和WEB服务器的一次会话(一次会话指的就是用户在浏览某个网站时,从进入网站到浏览器关闭所经过的这段时间,也就是用户浏览这个网站所花费的时间),通过session对象可以完成数据的存取,而放在session对象中的数据都是用户相关的。也就说张三访问WEB服务器,服务器会生成一个张三的session对象,李四去访问WEB服务器,服务器就会生成一个李四的session对象。 系统为每个访问者都设立一个独立的session对象,用以存取数据,并且各个访问者的session对象互不干扰。 session与cookie是紧密相关的。 session的使用要求用户浏览器必须支持cookie,如果浏览器不支持使用cookie,或者设置为禁用cookie,那么将不能使用Session。除非服务器使用了URL重写机制(uri;jsessionid=xxxxxx)。
2、思考以下问题
购物网站中的购物车是一个用户一个购物车,购物车是在服务器端的一个java对象,那么该java对象一定是需要找一个临时的区域存储起来的,因为用户在不断的购物,不断的向WEB服务器发送购物请求。那么这个购物车可以存储在ServletContext对象中吗?或者可以存储在HttpServletRequest对象中吗?
1、购物车是一个用户级别的java对象,不是大家共享的对象,所以购物车千万不能存储在ServletContext对象中。因为ServletContext对象是服务器级别的对象,是大家共享的对象。
2、购物车是一个用户级别的java对象,用户发送“N次”请求完成购物,这N次请求必须使用底层同一个购物车对象,所以购物车不能存储在HttpServletRequest对象中,如果存储在该对象中,那么用户只要发送一次请求就是一个新的购物车对象。
3、分析以下案例
假设有两个用户,一个是北京的张三,一个是南京的李四,都在访问京东商城购物网站,那么在京东WEB服务器中一定会有两个购物车,一个是张三的购物车,一个是属于李四的购物车,大家思考:一个WEB服务器,两个浏览器客户端,为什么张三在购物的时候向购物车中放入的商品一定是放到张三的购物车中,而不会存放到李四的购物车中,也就是说session是怎么实现和某个特定用户绑定的?下面使用图形描述session的工作原理:
4、session的工作原理
当用户第一次访问web服务器的时候,web服务器会为该用户分配一个session对象,并且同时生成一个cookie对象(jsessionid=xxxx),然后web服务器将cookie对象的值和session对象以键值对的方式存储在web服务器端的session列表(Map)中,服务器负责将该cookie数据发送给浏览器,浏览器将cookie信息存储在浏览器的缓存中,只要浏览器不关闭,用户第二次访问服务器的时候会自动发送缓存中存储的cookie数据给服务器,服务器收到cookie数据之后获取cookie的值,然后通过该值在session列表(Map)中搜索对应的session对象。需要注意的是,当浏览器关闭之后,缓存中的cookie消失,这样客户端下次再访问服务器的时候就无法获取到服务器端的session对象了。这就意味着会话已经结束。但是这并不代表服务器端的session对象马上被回收,session对象仍然在session列表中存储,当长时间没有用户再访问这个session对象了,我们称作session超时,此时web服务器才会回收session对象。
什么情况下一次会话结束?
1、浏览器关闭,缓存中的Cookie消失,会话不一定结束,因为服务器端的session对象还没有被销毁。我们还可以通过URL重写机制继续访问Session对象。
2、浏览器没关闭,但是由于长时间没有访问web服务器,服务器判定session超时,将session对象销毁。此时浏览器虽然没关闭,但是这次会话已经结束。
3、session对象所关联的这个Cookie的name有点特殊,这个name必须是jsessionid,必须全部小写,这是HTTP协议中规定的。
4、浏览器禁用了Cookie,可以采用URL重写机制(这样编码的代价比较大,所以一般网站都是不允许禁用Cookie的)。http://ip:port/webapp/servlet/accessSys**;jsessionid=xxxxxx**
注意:B/S架构采用HTTP协议通信,该协议是一个无连接协议,浏览器向服务器发送请求的瞬间是连接状态,等请求结束之后,B和S的连接断开了,服务器是无法监测浏览器的(浏览器关闭服务器是无法监测的)
5、HttpSession对象的相关方法
获取session对象,如果获取不到则新建:
request.getSession(true); request.getSession();
获取session对象,如果获取不到则返回null:
request.getSession(false);
向session中存储数据:
session.setAttribute("name",ObjectValue);
从session中获取数据:
Object value = session.getAttribute("name");
删除session中某个数据:
session.removeAttribute("name");
使session失效:
session.invalidate();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String ip = request.getRemoteAddr();
HttpSession session = request.getSession();
System.out.println(ip + "'s session = " + session);
// 向会话范围中存储一个数据
session.setAttribute("username", "zhangsan");
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession();
// 从session范围中读取数据
Object username = session.getAttribute("username");
System.out.println(username);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取session对象,若没有获取到session对象,则新建session对象
//HttpSession session = request.getSession();
//获取session对象,若没有获取到session对象,则新建session对象
//HttpSession session = request.getSession(true);
//获取session对象,若没有获取到session对象,则返回null
HttpSession session = request.getSession(false);
if(session != null){
//销毁session
session.invalidate();
}
}
6、session对象的超时设置方式
在web.xml中定义:
<session-config>
<session-timeout>30</session-timeout>
</session-config>
7、ServletContext(application)、HttpSession(session)、HttpServletRequest(request)三者的区别与联系
7.1 相同点:都可以用来传递数据,都有相同的存取数据的方法。
1)存:xxx.setAttribute(“name”,ObjectValue);
2)取:Object value = xxx.getAttribute(“name”);
3)删: xxx.removeAttribute(“name”);
7.2 不同点:
ServletContext(appliation)是Servlet上下文对象,在服务器启动阶段解析web.xml文件创建ServletContext对象,在同一个web app中所有的Servlet对象共享同一个ServletContext对象。该对象一旦创建不会被销毁,除非将服务器停掉。所以尽量不要往这个对象中存放大数据,因为这是一个所有用户共享的空间,往该对象中存储的数据在多线程环境下涉及到修改操作的话注意线程安全问题。一般存储在该对象中的数据首先是所有用户共享的,不会被修改的,少量数据。ServletContext对象传递数据可以跨Servlet、跨请求、跨用户(跨会话)传递数据。
HttpSession(session)是会话对象,每一个用户都有一个这样的对象,是一个用户级别的对象,存储在该对象中的数据一般都是该用户专属的数据,例如购物车对象可以存储在session中,HttpSession对象传递数据可以跨Servlet、跨请求(这些请求必须属于同一个会话)、但是不能跨用户传递数据。
HttpServletRequest(request)是请求对象,一次请求一个对象,每一次请求都会新建一个请求对象,是一个请求级别的对象,存储该对象中的数据一般都是请求级别的数据,一次请求之后这个数据就不再使用的数据可以存储在该对象中,HttpServletRequest对象传递数据可以跨Servlet,但是不能跨请求,更不能跨用户传递数据。
使用原则:尽量从小范围向大范围使用。(考虑原则:request< session < application)
8、session总结
1、Session表示会话,不止是在javaweb中存在,只要是web开发,都有会话这种机制。
2、在java中会话对应的类型是:javax.servlet.http.HttpSession,简称session/会话
3、Cookie可以将会话状态保存在客户端,HttpSession可以将会话状态保存在服务器端。
4、HttpSession对象是一个会话级别的对象,一次会话对应一个HttpSession对象。
5、什么是一次会话?
“目前”可以这样理解:用户打开浏览器,在浏览器上发送多次请求,直到最终关闭浏览器,表示一次完整的会话。
6、在会话进行过程中,web服务器一直为当前这个用户维护着一个会话对象/HttpSession
7、在WEB容器中,WEB容器维护了大量的HttpSession对象,换句话说,在WEB容器中应该有一个“session列表”
思考:为什么当前会话中的每一次请求可以获取到属于自己的会话对象? session的实现原理?
- 打开浏览器,在浏览器上发送首次请求
- 服务器会创建一个HttpSession对象,该对象代表一次会话
- 同时生成HttpSession对象对应的Cookie对象,并且Cookie对象的name是JSESSIONID,Cookie的value是32位长度的字符串
- 服务器将Cookie的value和HttpSession对象绑定到session列表中
- 服务器将Cookie完整发送给浏览器客户端
- 浏览器客户端将Cookie保存到缓存中
- 只要浏览器不关闭,Cookie不会消失
- 当再次发送请求的时候,会自动提交缓存当中的Cookie
- 服务器接收到Cookie,验证该Cookie的name确实是:JSESSIONID,然后获取该Cookie的value
- 通过Cookie的value去session列表中检索对应的HttpSession对象。
8、和HttpSession对象关联的这个Cookie的name是比较特殊的,在java中就叫做:jsessionid
9、浏览器禁用Cookie会出现什么问题?怎么解决?
- 浏览器禁用Cookie,则浏览器缓存中不再保存Cookie
- 导致在同一个会话中,无法获取到对应的会话对象
- 禁用Cookie之后,每一次获取的会话对象都是新的
浏览器禁用Cookie之后,若还想拿到对应的Session对象,必须使用URL重写机制,怎么重写URL:
http://localhost/prj-servlet-21/user/accessMySelfSession;jsessionid=D3E9985BC5FD4BD05018BF2966863E94
重写URL会给编程带来难度/复杂度,所以一般的web站点是不建议禁用Cookie的。建议浏览器开启Cookie
10、浏览器关闭之后,服务器端对应的session对象会被销毁吗?为什么?
- 浏览器关闭之后,服务器不会销毁session对象
- 因为B/S架构的系统基于HTTP协议,而HTTP协议是一种无连接/无状态的协议
- 什么是无连接/无状态?
请求的瞬间浏览器和服务器之间的通道是打开的,请求响应结束之后,通道关闭
这样做的目的是降低服务器的压力。
11、session对象在什么时候被销毁?
- web系统中引入了session超时的概念。
- 当很长一段时间(这个时间可以配置)没有用户再访问该session对象,此时session对象超时,web服务器自动回收session对象。
- 可配置:web.xml文件中,默认是30分钟
<session-config>
<session-timeout>120</session-timeout>
</session-config>
12、什么是一次会话呢?
- 一般多数情况下,是这样描述的:用户打开浏览器,在浏览器上进行一些操作,然后将浏览器关闭,表示一次会话结束。
- 本质上的描述:从session对象的创建,到最终session对象超时之后销毁,这个才是真正意义的一次完整会话。
13、关于javax.servlet.http.HttpSession接口中常用方法:
-void setAttribute(String name, Object value)
-Object getAttribute(String name)
-void removeAttribute(String name)
-void invalidate() 销毁session
14、ServletContext、HttpSession、HttpServletRequest接口的对比:
① 以上都是范围对象:
ServletContext application:是应用范围
HttpSession session:是会话范围
HttpServletRequest request:是请求范围
② 三个范围的排序:
application > session > request
③ 其他:
application完成跨会话共享数据、
session完成跨请求共享数据,但是这些请求必须在同一个会话当中、
request完成跨Servlet共享数据,但是这些Servlet必须在同一个请求当中【转发】
④ 使用原则:有小到大尝试,优先使用小范围。
例如:登录成功之后,已经登录的状态需要保存起来,可以将登录成功的这个状态保存到session对象中
登录成功状态不能保存到request范围中,因为一次请求对应一个新的request对象。
登录成功状态也不能保存到application范围中,因为登录成功状态是属于会话级别的,不能所有用户共享。
15、补充HttpServletRequest中的方法:
- HttpSession session = request.getSession(); 获取当前的session,获取不到,则新建session
- HttpSession session = request.getSession(true); 获取当前的session,获取不到,则新建session
- HttpSession session = request.getSession(false); 获取当前的session,获取不到,则返回null
十七、关于url-pattern的编写方式和路径的总结
1、路径的编写形式
- 超链接:<a href="/项目名/资源路径"></a>
- form表单:<form action="/项目名/资源路径"></form>
- 重定向:response.sendRedirect("/项目名/资源路径");
- 转发:request.getRequestDispatcher("/资源路径").forward(request,response);
- 欢迎页面
<welcome-file-list>
<welcome-file>资源路径</welcome-file>
</welcome-file-list>
- servlet路径
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.bjpowernode.javaweb.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/资源路径</url-pattern>
</servlet-mapping>
- Cookie设置path
cookie.setPath("/项目名/资源路径");
- ServletContext
ServletContext application = config.getServletContext();
application.getRealPath("/WEB-INF/classes/db.properties");
application.getRealPath("/资源路径");
2、url-pattern的编写方式
2.1 url-pattern可以编写多个
2.2 精确匹配
<url-pattern>/hello</url-pattern>
<url-pattern>/system/hello</url-pattern>
2.3 扩展匹配
<url-pattern>/abc/*</url-pattern>
2.4 后缀匹配
<url-pattern>*.action</url-pattern>
<url-pattern>*.do</url-pattern>
2.5 全部匹配
<url-pattern>/*</url-pattern>