JavaWeb开发 Servlet技术详解_能够编写servlet单一控制器应用程序。-程序员宅基地

技术标签: 学习  java  笔记  tomcat  servlet  java后端  

目录

一、JavaEE简介

 1.1 什么是JavaEE

1.2 JavaEE缺点

1.3 开源框架优点

1.4 JavaEE版本

二、服务器

2.1 服务器简介

2.2 服务器分类

2.2.1 JavaEE应用服务器(JavaEE Application Server)

2.2.2 Web容器(Web Server)

三、Tomcat

3.1 Tomcat简介 

3.2 Tomcat下载与安装

3.3 Tomcat目录结构与介绍

3.4 Tomcat的启动与关闭

3.4.1 Tomcat启动

3.4.2 Tomcat关闭

3.4.3 访问Tomcat

3.5 Tomcat配置文件介绍

3.6 解决控制台乱码以及修改监听端口

3.7 配置TomcatManager

3.8 Tomcat工作原理

 3.9 Tomcat组件

3.9.1 Server组件

3.9.2 Service组件

3.9.3 Connector组件

3.9.4 Engine组件

3.9.5 Host组件

3.9.6 Context组件

3.10 配置虚拟主机(Host)

3.11 配置Context

四、Servlet

4.1 Servlet简介

4.1.1 Servlet技术特点

4.1.2 Servlet在应用程序中的位置

4.2 编写第一个Servlet

4.3编译servlet

4.4 创建web.xml

4.5 部署运行Servlet

4.6 Tomcat处理请求过程

4.7 Servlet继承结构

4.8 Servlet的生命周期

4.9 Servlet处理请求的原理

4.10 Servlet的作用

4.12 在Idea中创建web工程

 4.13 在web工程中编写Servlet

4.14 Idea中的web项目部署详解

4.15 将web项目部署到Tomcat的webapps中

4.16 获取请求信息

4.16.1 HttpServletRequest对象

 4.16.2 获取请求数据(表单)

 4.16.3 获取复选框(checkbox组件)中的值

 4.16.4 获取所有提交数据的key

 4.17 设置请求编码

4.18 资源访问路径

4.19 获取请求头信息

4.19.1 获取请求头案例

 4.20 HttpServletRequest对象的生命周期

4.21 设置响应类型

4.21.1 HttpServletResponse对象

4.21.2 设置字符型响应

4.21.3设置字节型响应

4.22 设置响应编码(针对响应字符)

4.23 重定向响应

4.24 文件下载

4.25 解决下载时文件名中文乱码问题

4.26 ServletContext对象

ServletContext对象介绍

ServletContext对象的作用

4.26.1 相对路径转绝对路径

 4.26.2 获取容器的附加信息(基本信息)

 4.26.3 获取web.xml中的信息

 4.26.4 全局容器

 4.26.5 ServletContext对象生命周期

4.27 ServletConfig对象

 4.28 Cookie对象与HttpSession对象

4.28.1 Cookie对象的特点

4.28.2 Cookie对象的创建

 4.28.3 获取Cookie中的数据

 4.28.4 Cookie跨域问题

4.28.5 状态Cookie与持久化Cookie

4.28.6 通过Cookie实现客户端与服务端会话的维持

4.28.7 HttpSession对象的特点

4.28.8 HttpSession对象的创建

4.28.9 HttpSession的使用

4.28.10 HttpSession的销毁方式

4.28.11 通过HttpSession实现客户端与服务端会话的维持

4.28.12 HttpSession生命周期

4.28.13 HttpSession对象总结

4.29自启动Servlet

 4.29.1 通过自启动Servlet实现配置信息的读取

4.30 Servlet线程安全问题

4.31 Servlet的url-pattern配置

4.31.1 URL的匹配规则

4.31.2 Servlet的多URL映射方式

4.32 基于注解式开发Servlet

4.32.1 @WebServlet

 4.32.2 @WebInitParam

 4.33 文件上传

 4.34 Filter过滤器

Filter对象的创建

4.34.2在Filter中设置请求编码

4.34.3 FilterConfig对象的使用

4.34.4 FilterChain(过滤器链)

4.34.5 基于注解式开发Filter

 4.34.6 Filter的生命周期

4.35 Listener监听器

4.35.1 ServletContext对象的生命周期监听器

 4.35.2 ServletContext对象的属性操作监听器

 4.35.3 HttpSession对象的生命周期监听器

 4.35.4 HttpSession对象的属性操作监听器

 4.35.5 HttpServletRequest对象的生命周期监听器

 4.35.6 HttpServletRequest对象的属性操作监听器

 4.35.7 基于注解式开发监听器

4.36 Filter与Listener设计模式

4.36.1 Filter的设计模式

4.36.2 Listener的设计模式


一、JavaEE简介

 1.1 什么是JavaEE

JavaEE(Java Enterprise Edition),Java企业版,是一个用于企业级web开发平台,它是一组Specification。最早由Sun公司定制并发布,后由Oracle负责维护。在JavaEE平台规范了在开发企业级web应用中的技术标准。

在JavaEE平台共包含了13个技术规范(随着JavaEE版本的变化所包含的技术点的数量会有增多)。它们分别是:JDBC、JNDI、EJB、RMI、Servlet、JSP、XML、JMS、Java IDL、JPA、JTA、JavaMail和JAF。

1.2 JavaEE缺点

  1. JavaEE技术使用时过于复杂了。

  2. JavaEE技术使用慢,效率过低。

  3. JavaEE技术较重,很多技术需要依赖服务器中间件。

1.3 开源框架优点

  1. 高效:开发变得简单,快速,并且有效。
  2. 成本:很多框架都是免费,并且开发人员编写代码更快,所以客户成本自然 更低。
  3. 支持:框架有文档支持,团队支持,或者大的社区支持,能迅速帮你解决问 题。

1.4 JavaEE版本

年份 版本 对JDK的支持 Servlet版本 JSP版本
1999年12月 1.2 1.1 and later 2.2 1.1
2001年9月 1.3 1.3 and later 2.3 1.2
2003年11月 1.4 1.4 and later 2.4 2.0
2006年5月 5.0 5 and later 2.5 2.1
2009年10月 6.0 6 and later 3.0 2.2
2013年6月 7.0 7 and later 3.1 2.3
2017年8月 8.0 8 and later 4.0 2.3

注意

2017 年 8 月,Java EE 已经正式更名为 Jakarta EE(雅加达)。

二、服务器

2.1 服务器简介

  1. 硬件服务器的构成与一般的PC比较相似,但是服务器在稳定性、安全性、性能等方面都要求更高,因为CPU、芯片组、内存、磁盘系统、网络等硬件和普通PC有所不同。
  2. 软件服务器(英文名称Server),也称伺服器。指一个管理资源并为用户提供服务的计算机软件,通常分为文件服务器、数据库服务器和应用程序服务器。运行以上软件的计算机或计算机系统也被称为服务器。

2.2 服务器分类

2.2.1 JavaEE应用服务器(JavaEE Application Server)

应用服务器是Java EE规范的具体实现, 可以执行/驱动基于JavaEE平台开发的web项目。绝大部分的应用服务器都是付费产品。

常见的应用服务:

Weblogic(BEA Oracle 收费)

Webshpere(IBM 收费)

JBoss(RedHad 收费)

Geronimo(Apache 免费)

2.2.2 Web容器(Web Server)

只实现了JavaEE平台下部分技术标准,如Servlet,Jsp,JNDI,JavaMail。Web容器是开源免费的。

Tomcat(Apache 开源免费)

Jetty(Jetty 开源免费)

三、Tomcat

3.1 Tomcat简介 

Tomcat服务器是Apache的一个开源免费的Web容器。它实现了JavaEE平台下部分技术规范,属于轻量级应用服务器。

Tomcat版本 JDK版本 Servlet版本 JSP版本
10.0.X 8 and later 5.0 3.0
9.0.x 8 and later 4.0 2.3
8.0.x 7 and later 3.1 2.3
7.0.x 6 and later 3.0 2.2
6.0.x 5 and later 2.5 2.1

Tomcat作用

可以在Tomcat中运行我们所编写的Servlet、JSP。

3.2 Tomcat下载与安装

下载地址:Apache Tomcat - Welcome!

配置环境变量

Tomcat是用Java语言开发的Web容器,所以在使用Tomcat时需要在操作系统中正确配置环境变量。

JAVA_HOME:C:\Program Files\Java\jdk1.8.0_171
PATH:%JAVA_HOME%\bin;
CLASS_PATH:%JAVA_HOME%\lib;   (这个一定要补充上,要不然会出现服务器一启动就闪退的情况)

3.3 Tomcat目录结构与介绍

bin

bin目录主要是用来存放tomcat的命令文件,主要有两大类,一类是以.sh结尾的(linux命令),另一类是以.bat结尾的(windows命令)。

conf

conf目录主要是用来存放tomcat的一些配置文件。

lib

lib目录主要用来存放tomcat运行需要加载的jar包。

 logs

logs目录用来存放tomcat在运行过程中产生的日志文件。

temp

temp目录用户存放tomcat在运行过程中产生的临时文件。(清空不会对tomcat运行带来影响)

webapps

webapps目录用来存放应用程序,当tomcat启动时会去加载webapps目录下的应用程序。可以以文件夹、war包的形式发布应用。

work

work目录用来存放tomcat在运行时的编译后文件,例如JSP编译后的文件。

3.4 Tomcat的启动与关闭

Tomcat的启动与关闭需要执行bin目录中的命令脚本。

3.4.1 Tomcat启动

  • 方式一

    运行startup.bat文件。

  • 方式二

    catlina.bat start

    其中catlina.bat是命令文件,start是启动Tomcat参数。

3.4.2 Tomcat关闭

  • 方式一

    运行shutdown.bat文件。

  • 方式二

    catlina.bat stop

    其中catlina.bat是命令文件,stop是关闭Tomcat参数。

  • 方式三

    直接关闭掉控制台窗口。

3.4.3 访问Tomcat

访问Tomcat的URL格式:

http://ip:port

访问本机Tomcat的URL格式:

http://localhost:8080

3.5 Tomcat配置文件介绍

Tomcat 的配置文件由4个xml组成,分别是 context.xml、web.xml、server.xml、tomcat-users.xml。每个文件都有自己的功能与配置方法。

context.xml

context.xml 是 Tomcat 公用的环境配置。 Tomcat 服务器会定时去扫描这个文件。一旦发现文件被修改(时间戳改变了),就会自动重新加载这个文件,而不需要重启服务器 。(其他的文件只要是修改了就要重新启动tomcat)

web.xml

Web应用程序描述文件,都是关于是Web应用程序的配置文件。所有Web应用的 web.xml 文件的父文件。

server.xml

是 tomcat 服务器的核心配置文件,server.xml的每一个元素都对应了 tomcat中的一个组件,通过对xml中元素的配置,实现对 tomcat中的各个组件和端口的配置。

tomcat-users.xml

配置访问Tomcat的用户以及角色的配置文件。

3.6 解决控制台乱码以及修改监听端口

解决控制台乱码

控制台产生乱码的原因是在Tomcat在输出日志中使用的是UTF-8编码,而我们中文的Windows操作系统使用的是GBK编码。由于编码格式不统一,所以出现了乱码。

解决方式:修改conf目录中的logging.properties文件重新指定的编码方式。注意ctrl+s保存。

java.util.logging.ConsoleHandler.encoding = GBK

修改Tomcat监听端口

Tomcat默认监听端口为8080。可以通过修改server.xml文件中的port来改变Tomcat的监听端口。

<Connector port="8080" protocol="HTTP/1.1"
        connectionTimeout="20000"
        redirectPort="8443" />

3.7 配置TomcatManager

什么是Tomcat Manager

Tomcat Manager是Tomcat自带的、用于对Tomcat自身以及部署在Tomcat上的应用进行管理的web应用。默认情况下,Tomcat Manager是处于禁用状态的。准确的说,Tomcat Manager需要以用户角色进行登录并授权才能使用相应的功能,不过Tomcat并没有配置任何默认的用户,因此我们需要先进行用户配置后才能使用Tomcat Manager。

配置Tomcat Manager的访问用户

Tomcat Manager中没有默认用户,我们需要在tomcat-users.xml文件配置。Tomcat Manager的用户配置需要配置两个部分:角色配置、用户名及密码配置。

Tomcat Manager中的角色分类

  • manager-gui角色:

    允许访问HTML GUI和状态页面(即URL路径为/manager/html/*)

  • manager-script角色:

    允许远程访问文本界面和状态页面(即URL路径为/manager/text/*)

  • manager-jmx角色:

    允许访问JMX代理和状态页面(即URL路径为/manager/jmxproxy/*)

  • manager- status角色:

    仅允许访问状态页面(即URL路径为/manager/status/*)

配置用户及角色

修改tomcat-users.xml

<role rolename ="manager-gui"/> 
<user username ="tomcat" password ="tomcat" roles="manager-gui" />

 

解除访问限制

进入Tomcat的webapps目录下,打开webapps/manager/META-INF/context.xml文件,修改下面这段配置

<context antiresourcelocking="false" privileged="true">
<!-- 把下面这段注释掉 -->
<!-- <Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" /> -->
</context>

3.8 Tomcat工作原理

Tomcat是一个能够处理请求并产生响应的应用程序。Tomcat实现了JavaEE平台下的一些技术规范(比如Servlet、JSP技术规范),所以我们可以在Tomcat中运行我们所编写的Servlet、JSP。

 3.9 Tomcat组件

conf/server.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">


 <Service name="Catalina">


    <!-- 连接器监听端口是 8080,默认通讯协议是 HTTP/1.1 -->
  <Connector port="8080" protocol="HTTP/1.1"
        connectionTimeout="20000"
        redirectPort="8443" />
              
    <!-- 名字为 Catalina 的引擎,其默认的虚拟主机是 localhost -->
  <Engine name="Catalina" defaultHost="localhost">
     <!-- 名字为 localhost 的虚拟主机,其目录是 webapps-->
   <Host name="localhost" appBase="webapps"
      unpackWARs="true" autoDeploy="true">
   </Host>
  </Engine>
 </Service>
</Server>

3.9.1 Server组件

启动一个server实例(也就是启动一个tomcat ,即一个JVM进程),它监听在8005端口以接收shutdown命令。Server的定义不能使用同一个端口,这意味着如果在同一个物理机上启动了多个Server实例,必须配置它们使用不同的端口。

<Server port="8005" shutdown="SHUTDOWN">

port: 接收shutdown指令的端口,默认为8005;

shutdown:发往此Server用于实现关闭tomcat实例的命令字符串,默认为SHUTDOWN;

 也就是说,tomcat(或者说是server组件)一启动会监听两个端口一个是8080用于监听客户端请求,一个是8005监听SHUTDOWN命令关闭tomcat。

使用端口关闭存在远程关闭的风险,可以禁用8005端口,此时只能通过关闭敞口来关闭tomcat。当然这是运维的活!

<Server port="-1" shutdown="SHUTDOWN">

3.9.2 Service组件

Service主要用于关联一个引擎和与此引擎相关的连接器,每个连接器通过一个特定的端口和协议接收请求并将其转发至关联的引擎进行处理。困此,Service要包含一个引擎、一个或多个连接器。

<Service name="Catalina">

name:此服务的名称,默认为Catalina;

3.9.3 Connector组件

支持处理不同请求的组件,一个引擎可以有一个或多个连接器,以适应多种请求方式。默认只开启了处理Http协议的连接器。如果需要使用其他协议,需要在Tomcat中配置该协议的连接器。

在Tomcat中连接器类型通常有4种:

  1. HTTP连接器
  2. SSL连接器(也就是https协议链接器)
  3. AJP 1.3连接器
  4. proxy连接器
<Connector port="8888" protocol="HTTP/1.1"
        connectionTimeout="20000"
        redirectPort="8443" />

port:监听的端口

protocol:连接器使用的协议,默认为HTTP/1.1;

connectionTimeout:等待客户端发送请求的超时时间,单位为毫秒;

redirectPort:如果某连接器支持的协议是HTTP,当接收客户端发来的HTTPS请求时,则转发至此属性定义的端口;

maxThreads:支持的最大并发连接数,默认为200个;

3.9.4 Engine组件

 Engine是Servlet处理器的一个实例,即servlet引擎,定义在server.xml中的Service标签中。Engine需要defaultHost属性来为其定义一个接收所有发往非明确定义虚拟主机的请求的Host组件。(Engine就好比是汽车的发动机,Host就相当于是发动机中的刚缸体,里面存放的就是web资源)

name:Engine组件的名称;

defaultHost:Tomcat支持基于FQDN(Fully Qualified Domain Name 全限定域名)的虚拟主机,这些虚拟主机可以通过在Engine容器中定义多个不同的Host组件来实现;但如果此引擎的连接器收到一个发往非明确定义虚拟主机的请求时则需要将此请求发往一个默认的虚拟主机进行处理(也就是当请求的域名无法找到对应的虚拟主机的话,会默认匹配域名为localhost的虚拟主机。),因此,在Engine中定义的多个虚拟主机的主机名称中至少要有一个跟defaultHost定义的主机名称同名;

3.9.5 Host组件

虚拟主机(英语:virtual hosting)或称共享主机(shared web hosting),又称虚拟服务器,是一种在单一主机或主机群上,实现多网域服务的方法,可以运行多个网站或服务的技术。

Host组件位于Engine容器中用于接收请求并进行相应处理的虚拟主机。通过该容器可以运行Servlet或者JSP来处理请求。

<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">

name:虚拟主机的名称,Tomcat通过在请求URL中的域名与name中的值匹配,用于查找能够处理该请求的虚拟主机。如果未找到则交给在Engine中defaultHost指定的主机处理;

appBase:此Host的webapps目录,即指定存放web应用程序的目录的路径;

autoDeploy:在Tomcat处于运行状态时放置于appBase目录中的应用程序文件是否自动进行deploy;默认为true;

unpackWARs:在启用此webapps时是否对WAR格式的归档文件先进行展开;默认为true;(纯Java项目可以打包为jar包,web项目可以打包为war包)

3.9.6 Context组件

Context是Host的子组件,代表指定一个Web应用,它运行在某个指定的虚拟主机(Host)上;每个Web应用都是一个WAR文件,或文件的目录。

<Context path="/test" docBase="D:\bjsxt\itbaizhan.war" />

path:context path既浏览器访问项目的访问路径。

docBase:相应的Web应用程序的存放位置;也可以使用相对路径,起始路径为此Context所属Host中appBase定义的路径;

如果将web项目部署到webapps目录下的话不需要使用context组件,但是要将项目部署到其他的地方的话,需要使用context组件告诉host组件项目的位置。

3.10 配置虚拟主机(Host)

 修改server.xml添加Host配置

 <!-- 默认的虚拟主机 -->
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />

      </Host>
	  
	  
	  <!-- 虚拟主机1-->
	  <Host name="test1"  appBase="webapps1"
            unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />

      </Host>
	 <!-- 虚拟主机2--> 
	  <Host name="test2"  appBase="webapps2"
            unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
      </Host>

修改windows的Host文件

修改Windows系统中的Host文件做域名与IP的绑定。

Host文件位置

C:\Windows\System32\drivers\etc

修改内容

127.0.0.1 test1
127.0.0.1 test2

3.11 配置Context

通过contex组件可以将web应用部署到其他目录中,而不是webapps下。

创建index.html页面。

将index.html资源部署到d盘的demo目录中。

通过test:8888/web/index.html访问虚拟主机,并访问index.html  (表示我要访问test虚拟主机中的web文件下的index)。

配置server.xml文件

	   <Host name="test"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
			
			<Context path="/web" docBase="d:/demo"/>

	  </Host>

name="test" 表示主机的名称叫test

path="/web" 表示资源的访问路径

docBase="d:/demo" 表示资源的位置

修改windows的Host文件

修改Windows系统中的Host文件做域名与IP的绑定。

Host文件位置

C:\Windows\System32\drivers\etc、

修改内容

127.0.0.1 test

四、Servlet

4.1 Servlet简介

Servlet是Server Applet的简称,称为服务端小程序,是JavaEE平台下的技术标准,基于Java语言编写的服务端程序。 Web 容器或应用服务器实现了Servlet标准所以Servlet需要运行在Web容器或应用服务器中。Servlet主要功能在于能够在服务器中执行并生成数据。

4.1.1 Servlet技术特点

4.1.2 Servlet在应用程序中的位置

4.2 编写第一个Servlet

package it.com.servlet;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;


public class HelloWorld extends HttpServlet{
	

    public void doGet(HttpServletRequest request,HttpServletResponse response)throws ServletException,IOException{
        
        PrintWriter pw = response.getWriter();
        pw.println("<!DOCTYPE html>");
        pw.println("<html lang=en>");
        pw.println("<head>");
        pw.println("<meta charset=UTF-8>");
        pw.println("<title>Document</title>");
        pw.println("</head>");
        pw.println("<body>");
    pw.println("<font color=blue>HelloWorld</font>");
        pw.println("</body>");
        pw.println("</html>");
        pw.flush();
        pw.close();
    }
}

4.3编译servlet

Java中需要将源文件(.java)通过Javac编译为.class的字节文件才能运行。

D:\>javac oneServlet.java

出错的原因是servlet是JavaEE实现的规范不是JavaSE实现的,在servlet包中的类全部在Tomcat中有实现。因此servlet程序要放在Tomcat中运行。因此需要指定这些对象的位置(也就是tomcat中servlet包的位置)

D:\>javac -classpath "D:\Java\apache-tomcat-9.0.75\lib\servlet-api.jar" HelloWorld.java

执行后D盘产生字节文件Hello World.class

4.4 创建web.xml

什么是web.xml

Web项目的部署描述文件,是JavaWeb工程的配置文件,通过web.xml文件可以配置servlet、filter等技术。Tomcat启动时会先解析该配置文件获取项目的配置信息。

web.xml文件中的头信息

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
     version="4.0">
</web-app>

在web.xml文件中配置Servlet

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
     version="4.0">


         <!-- 给定servlet的全名,tomcat会根据全名实例化servlet,从而使用servlet对象的方法 -->
         <servlet>
            <servlet-name>HelloWorld</servlet-name>
            <servlet-class>it.com.servlet.HelloWorld</servlet-class>
         </servlet>


         <!-- servlet资源映射,访问/helloworld.do即可访问到servlet资源 -->
         <servlet-mapping>
            <servlet-name>HelloWorld</servlet-name>
            <url-pattern>/helloworld.do</url-pattern>
         </servlet-mapping>
</web-app>

注意路径必须加 /

4.5 部署运行Servlet

Web工程目录结构

访问Servlet

http://localhost:8888/servletdemo/helloworld.do

 注意:没有在host下配置context时资源名默认为项目名。

4.6 Tomcat处理请求过程

  1. 用户访问localhost:8888/test/helloword.do,请求被发送到Tomcat,被监听8888端口并处理 HTTP/1.1 协议的Connector获得。
  2. Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的回应。
  3. Engine获得请求localhost/test/helloword.do,匹配所有的虚拟主机Host。
  4. Engine匹配到名为localhost的Host虚拟主机来处理/test/helloword.do请求(即使匹配不到会请求交给默认Host处理)。
  5. 匹配到的Context获得请求/helloword.do。
  6. 构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用HelloWorld的doGet()或doPost().执行业务逻辑、数据存储等程序。
  7. Context把执行完之后的结果通过HttpServletResponse对象返回给Host。
  8. Host把HttpServletResponse返回给Engine。
  9. Engine把HttpServletResponse对象返回Connector。
  10. Connector把HttpServletResponse对象返回给客户Browser。

4.7 Servlet继承结构

Servlet接口

  1. init(),创建Servlet对象后立即调用该方法完成一些初始化工作。
  2. service(),处理客户端请求,执行业务操作,利用响应对象响应客户端请求。
  3. destroy(),在销毁Servlet对象之前调用该方法,释放资源。
  4. getServletConfig(),ServletConfig是容器向servlet传递参数的载体。(可以获取配置文件web.xml的信息)
  5. getServletInfo(),获取servlet相关信息。

ServletConfig接口

  1. String getServletName(),返回 Servlet 的名字,即 web.xml 中 元素的值。
  2. ServletContext getServletContext(),返回一个代表当前 Web 应用的 ServletContext 对象。
  3. String getInitParameter(String name),根据初始化参数名返回对应的初始化参数值。
  4. Enumeration getInitParameterNames(),返回一个 Enumeration 对象,其中包含了所有的初始化参数名。

GenericServle抽象类

GenericServlet是实现了Servlet接口的抽象类。在GenericServlet中进一步的定义了Servlet接口的具体实现,其设计的目的是为了和应用层协议解耦,在GenericServlet中包含一个Service抽象方法。

HttpServlet类

继承自 GenericServlet,针对于处理 HTTP 协议的请求所定制。在 HttpServlet的service() 方法中已经把 ServletReuqest 和 ServletResponse 转为 HttpServletRequest 和 HttpServletResponse。 直接使用 HttpServletRequest 和 HttpServletResponse, 不再需要强转。实际开发中, 直接继承 HttpServlet, 并根据请求方式复写 doXxx() 方法即可。

Tomcat实现了servlet技术规范,所以在tomcat的lib中的servlet-api.jar中可以查看这些接口和实现类。使用反编译小工具jd

4.8 Servlet的生命周期

Servlet的生命周期是由容器(tomcat,即servlet是由tomcat实例化的)管理的,分别经历三各阶段:

init():初始化

service():服务

destroy():销毁

(注意面试)当客户端浏览器第一次请求Servlet时,容器会实例化这个Servlet,然后调用一次init方法,并在新的线程中执行service方法处理请求。service方法执行完毕后容器不会销毁这个Servlet而是做缓存处理,当客户端浏览器再次请求这个Servlet时,容器会从缓存中直接找到这个Servlet对象,并再一次在新的线程中执行Service方法(单例模式)。当容器在销毁Servlet之前对调用一次destroy方法。

4.9 Servlet处理请求的原理

当浏览器基于get方式请求我们创建Servlet时,我们自定义的Servlet中的doGet方法会被执行。doGet方法能够被执行并处理get请求的原因是,容器在启动时会解析web工程中WEB-INF目录中的web.xml文件,在该文件中我们配置了Servlet与URI的绑定,容器通过对请求的解析可以获取请求资源的URI,然后找到与该URI绑定的Servlet并做实例化处理(注意:只实例化一次,如果在缓存中能够找到这个Servlet就不会再做次实例化处理)。在实例化时会使用Servlet接口类型作为引用类型的定义,并调用一次init方法,由于GenericServlet中重写了该方法所以最终执行的是GenericServlet中init方法(GenericServlet中的Init方法是一个空的方法体),然后在新的线程中调用service方法。由于在HttpServlet中重写了Service方法所以最终执行的是HttpServlet中的service方法。在service方法中通过request.getMethod()获取到请求方式进行判断如果是Get方式请求就执行doGet方法,如果是POST请求就执行doPost方法。如果是基于GET方式提交的,并且在我们的Servlet中又重写了HttpServlet中的doGet方法,那么最终会根据Java的多态特性转而执行我们自定义的Servlet中的doGet方法。

4.10 Servlet的作用

  • 获取用户提交的数据
  • 获取浏览器附加的信息
  • 处理数据(访问数据库或调用接口)
  • 给浏览器产生一个响应
  • 在响应中添加附加信息

4.12 在Idea中创建web工程

三个步骤:

  1. 创建Web工程
  2. 添加servlet-api.jar
  3. 配置Tomcat

新建一个Java项目

 项目名称右键选择第二个命令

 勾选Web Application

 为项目添加依赖

 配置tomcat

 

 4.13 在web工程中编写Servlet

新建MyServlet类,继承HttpServlet,重写doGet();方法。配置web.xml文件。启动tomcat在浏览器访问即可。

public class MyServlet extends HttpServlet {
    //处理get请求
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        PrintWriter pw = resp.getWriter();
        pw.println("<!DOCTYPE html>");
        pw.println("<html lang=en>");
        pw.println("<head>");
        pw.println("<meta charset=UTF-8>");
        pw.println("<title>Document</title>");
        pw.println("</head>");
        pw.println("<body>");
        pw.println("<font color=blue>Hello Servlet</font>");
        pw.println("</body>");
        pw.println("</html>");
        pw.flush();
        pw.close();

    }
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--配置servlet位置信息-->
    <servlet>
        <servlet-name>MyServlet</servlet-name>
        <servlet-class>cn.it.servlet.MyServlet</servlet-class>
    </servlet>

    <!--URL映射-->
    <servlet-mapping>
        <servlet-name>MyServlet</servlet-name>
        <url-pattern>/MyServlet.do</url-pattern>
    </servlet-mapping>
</web-app>

 

4.14 Idea中的web项目部署详解

在Idea中默认的并不会把web项目真正的部署到Tomcat的webapps目录中,而是通过为每个web项目创建一个独立的Tomcat副本并在Tomcat副本中通过的Tomcat的Context组件完成项目的目录指定,在Context组件的docBase属性中会指定Idea对web项目编译后的目录out/artifacts/.....。

默认部署方式

Idea会在C:\Users\Administrator\AppData\Local\JetBrains\IntelliJIdea2020.2\tomcat中为每个Web项目创建一个独立的Tomcat副本。

Idea通过执行Tomcat的catalina.bat启动脚本启动Tomcat,通过启动参数来指定启动Tomcat副本运行指定目录中的web项目。

Idaa在启动Tomcat之前会先在操作系统中设置一些临时环境变量,这些变量会被Tomcat的启动脚本所读取。

4.15 将web项目部署到Tomcat的webapps中

点击项目结构选项

指定输出artifacts的目录为Tomcat的webapps中的demo目录。

启动Tomcat,查看demo目录中的内容。

4.16 获取请求信息

4.16.1 HttpServletRequest对象

HttpServletRequest对象代表客户端浏览器的请求,当客户端浏览器通过HTTP协议访问服务器时,HTTP请求中的所有信息都会被Tomcat所解析并封装在这个对象中,通过这个对象提供的方法,可以获得客户端请求的所有信息。

获取请求信息

req.getRequestURL()

返回客户端浏览器发出请求时的完整URL。

req.getRequestURI()

返回请求行中指定资源部分。

req.getRemoteAddr()

返回发出请求的客户机的IP地址。

req.getLocalAddr()

返回WEB服务器的IP地址。

req.getLocalPort()

返回WEB服务器处理Http协议的连接器所监听的端口。

package cn.it.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

//获取请求信息
public class GetRequestInfoServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取浏览器发出请求的完整的url
        StringBuffer requestURL = req.getRequestURL();
        String s = requestURL.toString();

        //获取请求行中指定的资源部分
        String requestURI = req.getRequestURI();

        //获取发送请求的客户机的IP地址
        String remoteAddr = req.getRemoteAddr();

        //获取服务端的IP地址
        String localAddr = req.getLocalAddr();

        //获取服务端监听的端口
        int localPort = req.getLocalPort();

        PrintWriter writer = resp.getWriter();
        writer.println("浏览器发出请求的完整的url:"+s);
        writer.println("求行中指定的资源部分:"+requestURI);
        writer.println("发送请求的客户机的IP地址:"+remoteAddr);
        writer.println("服务端的IP地址:"+localAddr);
        writer.println("服务端监听的端口:"+localPort);

    }
}
 <servlet>
        <servlet-name>getRequestInfoServlet</servlet-name>
        <servlet-class>cn.it.servlet.GetRequestInfoServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>getRequestInfoServlet</servlet-name>
        <url-pattern>/getRequestInfo.do</url-pattern>
    </servlet-mapping>

 4.16.2 获取请求数据(表单)

根据key获取指定value

req.getParameter("key");

根据key获取对应的value,返回一个字符串。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!-- action="getRequestData.do" 不能加 “/”-->
<form action="getRequestData.do" method="post">
     <input type="text" name="username"/>
     <input type="text" name="password"/>
     <input type="submit" value="确定"/>
</form>
</body>
</html>

注意:html要在web下创建要不然不能直接访问到。action="getRequestData.do" 不能加 “/”

package cn.it.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

//获取表单数据
public class GetRequestData extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");

        PrintWriter writer = resp.getWriter();
        writer.println("username=" + username);
        writer.println("password=" + password);

        writer.flush();
        writer.close();

    }
}
    <servlet>
        <servlet-name>getRequestData</servlet-name>
        <servlet-class>cn.it.servlet.GetRequestData</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>getRequestData</servlet-name>
        <url-pattern>/getRequestData.do</url-pattern>
    </servlet-mapping>

 4.16.3 获取复选框(checkbox组件)中的值

String[] userlikes  =  req.getParameterValues("checkboxkey");

 获取复选框(checkbox组件)中的值,返回一个字符串数组。

        String[] userLikes = req.getParameterValues("userLike");
        List<String> userLikesList = Arrays.asList(userLikes); //将数组转换为集合

 4.16.4 获取所有提交数据的key

req.getParameterNames()

获取请求中所有数据的key,该方法返回一个枚举类型。

Enumeration<String> parameterNames = req.getParameterNames();
 //获取参数的key
        Enumeration<String> parameterNames = req.getParameterNames();
        List<String> parameterNamesList = new ArrayList<String>();
        while (parameterNames.hasMoreElements()) {
            parameterNamesList.add(parameterNames.nextElement());
        }

 4.17 设置请求编码

请求的数据包基于字节在网络上传输,Tomcat接收到请求的数据包后会将数据包中的字节转换为字符。在Tomcat中使用的是ISO-8859-1的单字节编码完成字节与字符的转换,所以数据中含有中文就会出现乱码,可以通过req.setCharacterEncoding("utf-8")方法来对提交的数据根据指定的编码方式重新做编码处理。

req.setCharacterEncoding("UTF-8");

4.18 资源访问路径

绝对路径

绝对路径访问资源表示直接以”/”作为项目的Context Path。该方式适用于以”/”作为项目的Context Path。

<form action="/getInfo.do" method="post">

相对路径

相对路径访问资源表示会相对于项目的Context Path作为相对路径。该方式适用于为项目指定的具体的Context Path。

<form action="getInfo.do" method="post">

4.19 获取请求头信息

获取请求头信息

根据请求头中的key获取对应的value。

String headerValue = req.getHeader("headerKey");

获取请求头中所有的key,该方法返回枚举类型。

Enumeration<String> headerNames = req.getHeaderNames();

4.19.1 获取请求头案例

需求:编写一个Servlet,如果浏览器的语言是zh-CN,显示“你好,聪明的中国人!”,如果浏览器的语言设置为en-US,那么则显示“Hello,American”。

package cn.it.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class Practise extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String header = req.getHeader("Accept-Language");
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter writer = resp.getWriter();
        if (header.startsWith("zh-CN")) {
            writer.println("你好");
        }else if (header.startsWith("en-US")) {
            writer.println("hello");
        }else {
            writer.println("####");
        }
        writer.flush();
        writer.close();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}

 4.20 HttpServletRequest对象的生命周期

当有请求到达Tomcat时,Tomcat会创建HttpServletRequest对象,并将该对象通过参数的方式传递到我们Servlet的方法中,当处理请求处理完毕并产生响应后该对象生命周期结束。

4.21 设置响应类型

4.21.1 HttpServletResponse对象

HttpServletResponse对象代表服务器的响应。这个对象中封装了响应客户端浏览器的流对象,以及向客户端浏览器响应的响应头、响应数据、响应状态码等信息。

设置响应类型

resp.setContentType("MIME")

该方法可通过MIME-Type设置响应类型。默认响应的是text/html页面

Type Meaning
application/msword Microsoft Word document
application/octet-stream Unrecognized or binary data
application/pdf Acrobat (.pdf) file
application/postscript PostScript file
application/vnd.lotus-notes Lotus Notes file
application/vnd.ms-excel Excel spreadsheet
application/vnd.ms-powerpoint PowerPoint presentation
application/x-gzip Gzip archive
application/x-java-archive JAR file
application/x-java-serialized-object Serialized Java object
application/x-java-vm Java bytecode (.class) file
application/zip Zip archive
application/json JSON
audio/basic Sound file in .au or .snd format
audio/midi MIDI sound file
audio/x-aiff AIFF sound file
audio/x-wav Microsoft Windows sound file
image/gif GIF image
image/jpeg JPEG image
image/png PNG image
image/tiff TIFF image
image/x-xbitmap X Windows bitmap image
text/css HTML cascading style sheet
text/html HTML document
text/plain Plain text
text/xml XML
video/mpeg MPEG video clip
video/quicktime QuickTime video clip

4.21.2 设置字符型响应

响应的类型是文本类型的时候可以使用字符响应

常见的字符型响应类型:

设置响应类型为文本型,内容含有html字符串,是默认的响应类型

resp.setContentType("text/html")

设置响应类型为文本型,内容是普通文本。

resp.setContentType("text/plain")
package cn.it.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

//设置字符响应
public class ResponseServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //设置响应类型
        resp.setContentType("text/plain");
        PrintWriter pw = resp.getWriter();
        pw.println("<!DOCTYPE html>");
        pw.println("<html lang=en>");
        pw.println("<head>");
        pw.println("<meta charset=UTF-8>");
        pw.println("<title>Document</title>");
        pw.println("</head>");
        pw.println("<body>");
        pw.println("<font color=red>HelloWorld</font>");
        pw.println("</body>");
        pw.println("</html>");
        pw.flush();
        pw.close();


    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}

 

设置响应类型为JSON格式的字符串。

resp.setContentType("application/json")

4.21.3设置字节型响应

响应图片视频等二进制文件的话需要使用字节响应。

设置响应类型为图片类型,图片类型为jpeg或jpg格式。

resp.setContentType("image/jpeg")
package cn.it.servlet;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;

//设置字节响应
public class ResponseBitServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //读取D盘图片文件
        File file = new File("D:/aa.jpg");
        //创建读取图片的IO流对象
        InputStream in = new FileInputStream(file);
        //图片缓存区
        BufferedInputStream bis = new BufferedInputStream(in);

        //设置响应类型为
        resp.setContentType("image/jpeg");
        //获取字节输出流对象
        BufferedOutputStream bos = new BufferedOutputStream(resp.getOutputStream());
        int temp = 0;
        while ((temp = bis.read()) != -1) {
              bos.write(temp);
        }

        bos.flush();
        bos.close();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}

设置响应类型为图片类型,图片类型为gif格式。 

resp.setContentType("image/gif")

4.22 设置响应编码(针对响应字符)

response.setContentType("text/html;charset=utf-8");

不仅发送到浏览器的内容会使用UTF-8编码,而且还通知浏览器使用UTF-8编码方式进行显示。所以总能正常显示中文

response.setCharacterEncoding("utf-8");

仅仅是发送的浏览器的内容是UTF-8编码的,至于浏览器是用哪种编码方式显示不管。 所以当浏览器的显示编码方式不是UTF-8的时候,就会看到乱码,需要手动指定浏览器编码。

4.23 重定向响应

response.sendRedirect(URL地址)

重定向响应会在响应头中添加一个Location的key对应的value是给定的URL。客户端浏览器在解析响应头后自动向Location中的URL发送请求。

重定向响应特点:

  • 重定向会产生两次请求两次响应。
  • 重定向的URL是由客户端浏览器发送的。
  • 浏览器地址栏会有变化。
package cn.it.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;

//重定向响应
public class RedirectServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        //获取内容
        String search = req.getParameter("search");
        //在使用get请求数据的时候需要将中文进行url编码(转成%十六进制%)
        resp.sendRedirect("https://www.baidu.com/s?tn=39042058_40_oem_dg&ie=utf-8&wd="+ URLEncoder.encode(search,"utf-8"));
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}

4.24 文件下载

在实现文件下载时,我们需要在响应头中添加附加信息。

response.addHeader("Content-Disposition", "attachment; filename="+文件名);

Content-Disposition:attachment

该附加信息表示作为对下载文件的一个标识字段。不会在浏览器中显示而是直接做下载处理。

filename=文件名

表示指定下载文件的文件名。

package cn.it.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;

//文件下载
public class FileDownServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //读取下载文件
        File file = new File("d:/aa.jpg");
        //文件字节输入流
        FileInputStream fis = new FileInputStream(file);
        //文件字节缓冲流
        BufferedInputStream bis = new BufferedInputStream(fis);

        //在响应中添加文件下载的信息
        resp.addHeader("Content-Disposition", "attachment; filename="+file.getName());
        //产生响应 文件字节缓冲输出流
        BufferedOutputStream bos = new BufferedOutputStream(resp.getOutputStream());
        int temp = 0;
        while ((temp = bis.read()) != -1) {
            bos.write(temp);
        }
        bos.flush();
        bos.close();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}

4.25 解决下载时文件名中文乱码问题

  resp.addHeader("Content-Disposition", "attachment; filename="+new String(file.getName() .getBytes("gbk"),"iso-8859-1"));

4.26 ServletContext对象

ServletContext对象介绍

ServletContext官方叫Servlet上下文。服务器会为每一个Web应用创建一个ServletContext对象。这个对象全局唯一,而且Web应用中的所有Servlet都共享这个对象。所以叫全局应用程序共享对象。

ServletContext对象的作用

  • 相对路径转绝对路径
  • 获取容器的附加信息
  • 读取配置信息(web.xml)
  • 全局容器

4.26.1 相对路径转绝对路径

该方法可以将一个相对路径转换为绝对路径,在文件上传与下载时需要用到该方法做路径的转换。转换的目的是为了实现跨平台。

context.getRealPath("path")

 添加目录时最好将该项目所在的tomcat中的位置删除,重新启动tomcat。

        //获取ServletContext对象
        ServletContext servletContext = this.getServletContext();
        //路径转换 相对路径转换为绝对路径
        String realPath = servletContext.getRealPath("/img/哈哈.jpg");
        //读取下载文件
        File file = new File(realPath);

 4.26.2 获取容器的附加信息(基本信息)

返回Servlet容器的名称和版本号

servletContext.getServerInfo()

返回Servlet容器所支持Servlet的主版本号。

servletContext.getMajorVersion()

返回Servlet容器所支持Servlet的副版本号。

servletContext.getMinorVersion()
package cn.it.servlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

//获取容器附加信息
public class GetBaseInfoServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext servletContext = this.getServletContext();
        //获取当前服务器的信息
        String serverInfo = servletContext.getServerInfo();
        //获取主版本号
        int majorVersion = servletContext.getMajorVersion();
        //获取副版本号
        int minorVersion = servletContext.getMinorVersion();

        //响应到浏览器
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter writer = resp.getWriter();
        writer.println("当前服务器的信息:"+serverInfo);
        writer.println("主版本号:"+majorVersion);
        writer.println("副版本号:"+minorVersion);

    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}

 4.26.3 获取web.xml中的信息

<context-param>
  <param-name>key</param-name>
  <param-value>value</param-value>
</context-param>
servletContext.getInitParameter("key")

该方法可以读取web.xml文件中标签中的配置信息。

servletContext.getInitParameterNames()

该方法可以读取web.xml文件中所有param-name标签中的值。

 <context-param>
        <param-name>name</param-name>
        <param-value>张三</param-value>
    </context-param>
    <context-param>
        <param-name>password</param-name>
        <param-value>123</param-value>
    </context-param>
package cn.it.servlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

//读取配置web.xml配置文件中的<context-param>节点信息
public class ContextInfo extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            //获取servletContext对象
            ServletContext servletContext = this.getServletContext();
            Enumeration<String> initParameterNames = servletContext.getInitParameterNames();
            resp.setContentType("text/html;charset=utf-8");
            PrintWriter writer = resp.getWriter();
            while (initParameterNames.hasMoreElements()) {
                String name = initParameterNames.nextElement();
                String initParameter = servletContext.getInitParameter(name);
                writer.println("name:"+name+",value:"+initParameter);
             }
            writer.flush();
            writer.close();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}

 4.26.4 全局容器

servletContext.setAttribute("key",ObjectValue)

向全局容器中存放数据。

servletContext.getAttribute("key")
package cn.it.servlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

//全局容器的使用
public class GlobalContext extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
          //获取全局数据
        ServletContext servletContext = this.getServletContext();
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter writer = resp.getWriter();
        writer.println(servletContext.getAttribute("key1"));
        writer.println(servletContext.getAttribute("key2"));
        writer.flush();
        writer.close();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    public void init() throws ServletException {
        //服务器初始化时添加全局数据
        ServletContext servletContext = this.getServletContext();
        servletContext.setAttribute("key1","张三");
        servletContext.setAttribute("key2","李四");
    }
}

 4.26.5 ServletContext对象生命周期

当容器启动时会创建ServletContext对象并一直缓存该对象,直到容器关闭后该对象生命周期结束。ServletContext对象的生命周期非常长,所以在使用全局容器时不建议存放业务数据。

4.27 ServletConfig对象

ServletConfig对象对应web.xml文件中的<servlet>节点。当Tomcat初始化一个Servlet时,会将该Servlet的配置信息,封装到一个ServletConfig对象中。我们可以通过该对象读取<servlet>节点中的配置信息

<servlet>
  <servlet-name>servletName</servlet-name>
  <servlet-class>servletClass</servlet-class>
  <init-param>
    <param-name>key</param-name>
    <param-value>value</param-value>
  </init-param>
</servlet>

读取web.xml文件中标签中标签中的配置信息 

servletConfig.getInitParameter("key")

该方法可以读取web.xml文件中当前标签中所有标签中的值。 

servletConfig.getInitParameterNames()
    <servlet>
        <servlet-name>GetServletConfig</servlet-name>
        <servlet-class>cn.it.servlet.GetServletConfig</servlet-class>
        <init-param>
            <param-name>key1</param-name>
            <param-value>张三</param-value>
        </init-param>
        <init-param>
            <param-name>key2</param-name>
            <param-value>李四</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>GetServletConfig</servlet-name>
        <url-pattern>/config.do</url-pattern>
    </servlet-mapping>
package cn.it.servlet;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

public class GetServletConfig extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取ServletConfig对象
        ServletConfig servletConfig = this.getServletConfig();
        Enumeration<String> initParameterNames = servletConfig.getInitParameterNames();
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter writer = resp.getWriter();
        while (initParameterNames.hasMoreElements()) {
            String name = initParameterNames.nextElement();
            String initParameter = servletConfig.getInitParameter(name);
            writer.println(name+":"+initParameter);
        }
        writer.flush();
        writer.close();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}

 4.28 Cookie对象与HttpSession对象

Cookie对象与HttpSession对象的作用是维护客户端浏览器与服务端的会话状态的两个对象。由于HTTP协议是一个无状态的协议,所以服务端并不会记录当前客户端浏览器的访问状态,但是在有些时候我们是需要服务端能够记录客户端浏览器的访问状态的,如获取当前客户端浏览器的访问服务端的次数时就需要会话状态的维持。在Servlet中提供了Cookie对象与HttpSession对象用于维护客户端与服务端的会话状态的维持。二者不同的是Cookie是通过客户端浏览器实现会话的维持,而HttpSession是通过服务端来实现会话状态的维持。

4.28.1 Cookie对象的特点

  • Cookie使用字符串存储数据
  • Cookie使用Key与Value结构存储数据
  • 单个Cookie存储数据大小限制在4097个字节
  • Cookie存储的数据中不支持中文,Servlet4.0中支持
  • Cookie是与域名绑定所以不支持跨一级域名访问
  • Cookie对象保存在客户端浏览器内存或系统磁盘中
  • Cookie分为持久化Cooke与状态Cookie
  • 浏览器在保存同一域名所返回Cookie的数量是有限的。不同浏览器支持的数量不同,Chrome浏览器为50个
  • 浏览器每次请求时都会把与当前访问的域名相关的Cookie在请求中提交到服务端。

4.28.2 Cookie对象的创建

通过new关键字创建Cookie对象

Cookie cookie = new Cookie("key","value")

通过HttpServletResponse对象将Cookie写回给客户端浏览器。 

response.addCookie(cookie)
package cn.it.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class CookieServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //创建cookie对象
        Cookie cookie = new Cookie("key1","张三");
        //通过响应对象将cookie写回客户端
        resp.addCookie(cookie);

        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter writer = resp.getWriter();
        writer.println("cookie");
        writer.flush();
        writer.close();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}

 4.28.3 获取Cookie中的数据

浏览器每次请求时都会把与当前访问的域名相关的Cookie在请求中提交到服务端。通过HttpServletRequest对象获取Cookie,返回Cookie数组。在Servlet4.0中的Cookie的Value开始支持中文存储。

Cookie[] cookies = request.getCookies();
package cn.it.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class GetCookiesServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Cookie[] cookies = req.getCookies();
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter writer = resp.getWriter();
        for (Cookie cookie : cookies) {
            writer.print(cookie.getName() + "=" + cookie.getValue());
        }
        writer.flush();
        writer.close();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}

 4.28.4 Cookie跨域问题

域名分类:域名分为顶级域、顶级域名(一级域名)、二级域名。

域名等级的区别:一级域名比二级域名更高级,二级域名是依附于一级域名之下的附属分区域名,即二级域名是一级域名的细化分级。例如:baidu.com 为一级域名,news.baidu.com为二级域名。

Cookie不支持一级域名的跨域,支持二级域名的跨域。

就是在访问百度的域名时,浏览器不会将cookie提交给百战的服务器。百度之间可以实现cookie共享。

4.28.5 状态Cookie与持久化Cookie

状态Cookie:Cookie对象仅会被缓存在浏览器所在的内存中。当浏览器关闭后Cookie对象 也会被销毁。

持久化Cookie:浏览器会对Cookie做持久化处理,基于文件形式保存在系统的指定目录中。在Windows10系统中为了安全问题不会显示Cookie中的内容。

当Cookie对象创建后默认为状态Cookie。可以使用Cookie对象下的cookie.setMaxAge(60)方法设置失效时间,单位为秒。一旦设置了失效时间,那么该Cookie为持久化Cookie,浏览器会将Cookie对象持久化到磁盘中。当失效时间到达后文件删除。

4.28.6 通过Cookie实现客户端与服务端会话的维持

package cn.it.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;


public class WelcomeServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取cookie
        Cookie[] cookies = req.getCookies();
        boolean flag = false;
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals("welcome")){
                    System.out.println(cookie.getValue());
                    flag = true;
                    break;
                }
            }
        }
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter writer = resp.getWriter();
        if (flag){
            writer.println("欢迎回来");
        }else {
            writer.println("第一次访问");
            //状态写回
            Cookie cookie = new Cookie("welcome", "123 ");
            cookie.setMaxAge(60);
            resp.addCookie(cookie);
        }
        writer.flush();
        writer.close();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}

4.28.7 HttpSession对象的特点

  • HttpSession保存在服务端
  • HttpSession使用Key与Value结构存储数据
  • HttpSession的Key是字符串类型,Value则是Object类型
  • HttpSession存储数据大小无限制

4.28.8 HttpSession对象的创建

HttpSession对象的创建是通过request.getSession()方法来创建的。客户端浏览器在请求服务端资源时,如果在请求中没有jsessionid,getSession()方法将会为这个客户端浏览器创建一个新的HttpSession对象,并为这个HttpSession对象生成一个jsessionid,在响应中通过状态Cookie写回给客户端浏览器,如果在请求中包含了jsessionid,getSession()方法则根据这个ID返回与这个客户端浏览器对应的HttpSession对象。

getSession()方法还有一个重载方法getSession(true|false)。当参数为true时与getSession()方法作用相同。当参数为false时则只去根据jsessionid查找是否有与这个客户端浏览器对应的HttpSession,如果有则返回,如果没有jsessionid则不会创建新的HttpSession对象。

 HttpSession session = req.getSession();

4.28.9 HttpSession的使用

session.setAttribute("key",value)

将数据存储到HttpSession对象中


Object value = session.getAttribute("key")

根据key获取HttpSession中的数据,返回Object


Enumeration attributeNames = session.getAttributeNames()

获取HttpSession中所有的key,返回枚举类型


session.removeAttribute("key")

根据key删除HttpSession中的数据


String id = session.getId()

根据获取当前HttpSession的SessionID,返回字符串类型
package cn.it.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;

public class SessionServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         //创建HttpSession对象
        HttpSession session = req.getSession();
        //存放数据
        session.setAttribute("key", "Java");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}
package cn.it.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;

public class GetSessionData extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        //获取数据
        String key = (String) session.getAttribute("key");
        PrintWriter writer = resp.getWriter();
        writer.println(key);
        writer.flush();
        writer.close();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}

 相同浏览器中的session是共享的,不同浏览器之间的session是不共享的。当关闭浏览器的时候,状态cookie会被销毁但是存放在服务端session列表中的session是不会被销毁的。但是再次使用req.getSession创建session对象的时候得到的是一个新的session对象。

4.28.10 HttpSession的销毁方式

HttpSession的销毁方式有两种:

  • 通过web.xml文件指定超时时间
  • 通过HttpSession对象中的invalidate()方法销毁当前HttpSession对象

我们可以在web.xml文件中指定HttpSession的超时时间,当到达指定的超时时间后,容器就会销该HttpSession对象,单位为分钟。该时间对整个web项目中的所有HttpSession对象有效。时间的计算方式是根据最后一次请求时间作为起始时间。只要用户继续访问,服务器就会更新HttpSession的最后访问时间,并维护该HttpSession。用户每访问服务器一次,无论是否读写HttpSession,服务器都认为该用户的HttpSession"活跃(active)"了一次,销毁时间则会重新计算。如果有哪个客户端浏览器对应的HttpSession的失效时间已到,那么与该客户端浏览器对应的HttpSession对象就会被销毁。其他客户端浏览器对应的HttpSession对象会继续保存不会被销毁。

<session-config>
        <session-timeout>2</session-timeout>
    </session-config>
session.invalidate();

4.28.11 通过HttpSession实现客户端与服务端会话的维持

package cn.it.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

public class SessionServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/plain;charset=UTF-8");
         //创建HttpSession对象
        HttpSession session = req.getSession();
        boolean flag = false;
        Enumeration<String> attributeNames = session.getAttributeNames();
        while (attributeNames.hasMoreElements()) {
            String name = attributeNames.nextElement();
            if ("welcome".equals(name)) {
                flag = true;
                break;
            }
        }

        PrintWriter writer = resp.getWriter();
        if (flag){
            writer.println("欢迎再次回来");
        }else {
            writer.println("欢迎第一次访问");
            session.setAttribute("welcome",123);
        }
        writer.flush();
        writer.close();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}

4.28.12 HttpSession生命周期

在HttpSession对象生命周期中没有固定的创建时间与销毁时间。何时创建取决于我们什么时候第一次调用了getSession()或getSession(true)的方法。HttpSession对象的销毁时间取决于超时时间的到达以及调用了invalidate()方法。如果没有超时或者没有调用invalidate()方法,那么HttpSession会一直存储。默认超时时间为30分钟(Tomcat的web.xml文件配置的时间就是默认超时时间)。

4.28.13 HttpSession对象总结

HttpSession与Cookie的区别:

  • cookie数据存放在客户的浏览器或系统的文件中,而HttpSession中的数据存放在服务器中。
  • cookie不安全,而HttpSession是安全的。
  • 单个cookie保存的数据不能超过4K,很多浏览器都限制一个域名保存cookie的数量。而HttpSession没有容量以及数量的限制。

HttpSession的使用建议

HttpSession对象是保存在服务端的,所以安全性较高。我们可以在HttpSession对象中存储数据,但是由于HttpSession对象的生命周期不固定,所以不建议存放业务数据。一般情况下我们只是存放用户登录信息。

4.29自启动Servlet

自动启动Servlet表示在Tomcat启动时就会实例化这个Servlet,他的实例化过程不依赖于请求,而是依赖容器的启动。

可以通过在web.xml中的<servlet>标签中通过<load-on-startup>1</load-on-startup>配置自启动Servlet。(详见servlet生命周期)

<load-on-startup>1</load-on-startup>中值越小的优先级越高。

    <servlet>
        <servlet-name>AutoStartServlet</servlet-name>
        <servlet-class>cn.it.servlet.AutoStartServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>AutoStartServlet</servlet-name>
        <url-pattern>/auto</url-pattern>
    </servlet-mapping>
package cn.it.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class AutoStartServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter writer = resp.getWriter();
        writer.println("autoStart");
        writer.flush();
        writer.close();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    //servlet被实例化后立即调用该方法
    @Override
    public void init() throws ServletException {
        System.out.println("servlet init");
    }
}

tomcat启动后 

 浏览器请求后

 4.29.1 通过自启动Servlet实现配置信息的读取

修改文件下载案例,通过自启动Servlet读取配置信息

文件下载的资源不一定都是在web根目录下的img目录下面。也就是说路径不能写死。

    <servlet>
        <servlet-name>fileDownServlet</servlet-name>
        <servlet-class>cn.it.servlet.FileDownServlet</servlet-class>
        <init-param>
            <param-name>path</param-name>
            <param-value>img</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>fileDownServlet</servlet-name>
        <url-pattern>/down.do</url-pattern>
    </servlet-mapping>
package cn.it.servlet;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;

//文件下载
public class FileDownServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取ServletContext对象
        ServletContext servletContext = this.getServletContext();
        String path = (String) servletContext.getAttribute("path");
        //路径转换 相对路径转换为绝对路径
        String realPath = servletContext.getRealPath(path+"/哈哈.jpg");
        System.out.println(realPath);
        //读取下载文件
        File file = new File(realPath);
        //文件字节输入流
        FileInputStream fis = new FileInputStream(file);
        //文件字节缓冲流
        BufferedInputStream bis = new BufferedInputStream(fis);

        //在响应中添加文件下载的信息
        resp.addHeader("Content-Disposition", "attachment; filename="+new String(file.getName() .getBytes("utf-8"),"iso-8859-1"));
        //产生响应 文件字节缓冲输出流
        BufferedOutputStream bos = new BufferedOutputStream(resp.getOutputStream());
        int temp = 0;
        while ((temp = bis.read()) != -1) {
            bos.write(temp);
        }
        bos.flush();
        bos.close();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    public void init() throws ServletException {
         //获取servletConfig对象读取<servlet>配置信息
         ServletConfig servletConfig = this.getServletConfig();
         String path = servletConfig.getInitParameter("path");
         //将读取的信息放到全局容器中,因为ServletConfig只能获取当前servlet的配置文件信息
         this.getServletContext().setAttribute("path",path);
    }
}

这样只需要改动配置文件再重新启动tomcat即可。文件下载功能照样可以实现。

4.30 Servlet线程安全问题

在Servlet中使用的是多线程方式来执行service()方法处理请求,所以我们在使用Servlet时需要考虑到线程安全问题,在多线程中对于对象中的成员变量是最不安全的,所以不要在Servlet中通过成员变量的方式来存放数据,如果一定要使用成员变量存储数据,在对数据进行操作时需要使用线程同步的方式来解决线程安全问题,避免出现数据张冠李戴现象。

package cn.it.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

//servlet线程安全
public class ThreadSafeServlet extends HttpServlet {

    private PrintWriter writer;

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取用户提交的数据
        String name = req.getParameter("name");
        //响应到客户端
        writer = resp.getWriter();
        //线程休眠
        try {
            Thread.sleep(5000);
            writer.write(name);
            writer.flush();
            writer.close();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       doPost(req, resp);
    }
}

同时使用谷歌浏览器和edge浏览器访问时,会出现后启动的浏览器会先访问到资源就是因为两个线程共享一个成员变量。谷歌浏览器的输出对象被edge浏览器的输出对象覆盖了。

package cn.it.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

//servlet线程安全
public class ThreadSafeServlet extends HttpServlet {

    private PrintWriter writer;

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取用户提交的数据
        String name = req.getParameter("name");
        synchronized (this){
            //响应到客户端
            writer = resp.getWriter();
            //线程休眠
            try {
                Thread.sleep(5000);
                writer.write(name);
                writer.flush();
                writer.close();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       doPost(req, resp);
    }
}

 this代表的当前对象,所有调用当前的servlet中的方法的请求都会由并行变为串行。但是一般情况下不建议使用成员变量而是建议使用局部变量。

4.31 Servlet的url-pattern配置

当浏览器请求url时,服务端会根据url将请求交给能处理该请求的servlet

4.31.1 URL的匹配规则

精确匹配

精确匹配是指<url-pattern>中配置的值必须与url完全精确匹配。

<servlet-mapping>
  <servlet-name>demoServlet</servlet-name>
  <url-pattern>/demo.do</url-pattern>
</servlet-mapping>

http://localhost:8888/demo/demo.do 匹配

http://localhost:8888/demo/suibian/demo.do 不匹配

扩展名匹配

在允许使用统配符作为匹配规则,“*”表示匹配任意字符。在扩展名匹配中只要扩展名相同都会被匹配和路径无关。注意,在使用扩展名匹配时在中不能使用“/”,否则容器启动就会抛出异常。

<servlet-mapping>
  <servlet-name>demoServlet</servlet-name>
  <url-pattern>*.do</url-pattern>
</servlet-mapping>

http://localhost:8888/demo/abc.do 匹配

http://localhost:8888/demo/suibian/haha.do 匹配

http://localhost:8888/demo/abc 不匹配

路径匹配 

根据请求路径进行匹配,在请求中只要包含该路径且该路径在contextpath的后面第一个都匹配。“*”表示任意路径以及子路径。大但是不能写成*/suibian/*

<servlet-mapping>
  <servlet-name>demoServlet</servlet-name>
  <url-pattern>/suibian/*</url-pattern>
</servlet-mapping>

http://localhost:8888/demo/suibian/haha.do 匹配

http://localhost:8888/demo/suibian/hehe/haha.do 匹配

http://localhost:8888/demo/hehe/heihei.do 不匹配

http://localhost:8888/demo/hehe/suibian/heihei.do 不匹配

任意匹配

匹配“/”。匹配所有但不包含JSP页面。

<url-pattern>/</url-pattern>

http://localhost:8888/demo/suibian.do 匹配

http://localhost:8888/demo/addUser.html 匹配

http://localhost:8888/demo/css/view.css 匹配

http://localhost:8888/demo/addUser.jsp 不匹配

http://localhost:8888/demo/user/addUser.jsp 不匹配

匹配所有

<url-pattern>/*</url-pattern>

http://localhost:8888/demo/suibian.do 匹配

http://localhost:8888/demo/addUser.html 匹配

http://localhost:8888/demo/suibian/suibian.do 匹配

优先顺序

当一个url与多个Servlet的匹配规则可以匹配时,则按照 “ 精确路径 > 最长路径 > 扩展名”这样的优先级匹配到对应的Servlet。

考考你

Servlet1 映射到 /abc/*

Servlet2 映射到 /*

Servlet3 映射到 /abc

Servlet4 映射到 *.do

当请求URL为“/abc/a.html”,“/abc/* ”和“/* ”都匹配,Servlet引擎将调用Servlet1。

当请求URL为“/abc”时,“/abc/* ”和“/abc”都匹配,Servlet引擎将调用Servlet3。

当请求URL为“/abc/a.do”时,“/abc/* ”和“ *.do”都匹配,Servlet引擎将调用Servlet1。

当请求URL为“/a.do”时,“/* ”和“*.do”都匹配,Servlet引擎将调用Servlet2。

当请求URL为“/xxx/yyy/a.do”时,“/* ”和“*.do”都匹配,Servlet引擎将调用Servlet2。

4.31.2 Servlet的多URL映射方式

在web.xml文件中支持将多个URL映射到一个Servlet中,但是相同的URL不能同时映射到两个Servlet中。

方式一

<servlet-mapping>
  <servlet-name>demoServlet</servlet-name>
  <url-pattern>/suibian/*</url-pattern>
  <url-pattern>*.do</url-pattern>
</servlet-mapping>

方式二

<servlet-mapping>
  <servlet-name>demoServlet</servlet-name>
  <url-pattern>/suibian/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
  <servlet-name>demoServlet</servlet-name>
  <url-pattern>*.do</url-pattern>
</servlet-mapping>

4.32 基于注解式开发Servlet

在Servlet3.0以及之后的版本中支持注解式开发Servlet。对于Servlet的配置不在依赖于web.xml配置文件,而是使用@WebServlet注解完成Servlet的配置。

4.32.1 @WebServlet

属性名 类型 作用
initParams WebInitParam[] Servlet的init参数
name String Servlet的名称
urlPatterns String[] Servlet的访问URL,支持多个
value String[] Servlet的访问URL,支持多个
loadOnStartup int 自启动Servlet
description String Servlet的描述
displayName String Servlet的显示名称
asyncSupported boolean 声明Servlet是否支持异步操作模式
package cn.it.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;


@WebServlet(urlPatterns = "/ann.do")
public class AnnotationServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter writer = resp.getWriter();
        writer.println("annotation");
        writer.flush();
        writer.close();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}

 4.32.2 @WebInitParam

使用注解配置servlet初始化参数。

属性名 类型 作用
name String param-name
value String param-value
description String description

package cn.it.servlet;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

//配置初始化参数
@WebServlet(urlPatterns = "/init.do",initParams={
        @WebInitParam(name = "key1",value = "WebInitParam"),
        @WebInitParam(name = "key2",value = "Java")
},loadOnStartup = 1)
public class WebInitParamServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletConfig servletConfig = this.getServletConfig();
        String key1 = servletConfig.getInitParameter("key1");
        String key2 = servletConfig.getInitParameter("key2");
        PrintWriter writer = resp.getWriter();
        writer.println(key1);
        writer.println(key2);
        writer.flush();
        writer.close();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    public void init() throws ServletException {
        //读取注解中配置的参数
        ServletConfig servletConfig = this.getServletConfig();
        Enumeration<String> initParameterNames = servletConfig.getInitParameterNames();
        while (initParameterNames.hasMoreElements()) {
            String name = initParameterNames.nextElement();
            String initParameter = servletConfig.getInitParameter(name);
            System.out.println(name + "=" + initParameter);
        }
    }
}

 

 4.33 文件上传

在Servlet3.0之前的版本中如果实现文件上传需要依赖apache的Fileupload组件,在Servlet3.0以及之后的版本中提供了Part对象处理文件上传,所以不在需要额外的添加Fileupload组件。

在Servlet3.0以及之后的版本中实现文件上传时必须要在Servlet中开启多参数配置:

基于web.xml配置文件的配置

<multipart-config>
  <file-size-threshold></file-size-threshold>
  <location></location>
  <max-file-size></max-file-size>
  <max-request-size></max-request-size>
</multipart-config>

基于@MultipartConfig注解的配置

属性名 类型 描述
fileSizeThreshold int 当数据量大于该值时,内容将被写入临时文件。
location String 存放生临时成的文件地址
maxFileSize long 允许上传的文件最大值(byte)。默认值为 -1,表示没有限制
maxRequestSize long 一个 multipart/form-data请求能携带的最大字节数(byte),默认值为 -1,表示没有限制。

Part对象中常用的方法

上传文件的大小

long getSize()

上传文件的原始文件名

String getSubmittedFileName()

获取<input name="upload" ...>标签中name属性值

String getName()

获取上传文件的输入流

InputStream getInputStream()

保存文件至服务器

void write(String path)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>上传文件</title>
</head>
<body>
<!--    上传文件只能post,因为get是字符提交。post既可以是字符提交(默认)也可以是字节提交-->
   <form action="up.do" method="post" enctype="multipart/form-data">
    文件描述   <input type="text" name="desc"/> <br> <!--文件描述是字符型数据-->
    上传文件   <input type="file" name="file"    /><br>  <!--文件是字节型数据。表单提交要么是字节提交,要么是字符提交-->
               <input type="submit" value="提交">
   </form>
</body>
</html>
package cn.it.servlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.UUID;

//文件上传
@WebServlet(urlPatterns = "/up.do")
@MultipartConfig //开启文件上传
public class FileUploadServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        //获取表单的描述信息
        String desc = req.getParameter("desc");
        //获取上传的文件
        Part part = req.getPart("file");
        //将上传文件重命名,防止文件覆盖,但是文件的扩展名要保留。
        String kz = part.getSubmittedFileName().substring(part.getSubmittedFileName().lastIndexOf("."));
        String newName = UUID.randomUUID().toString()+kz;
        //将上传的文件写到项目(路径转换)
        ServletContext servletContext = this.getServletContext();
        String realPath = servletContext.getRealPath("img/" + newName);
        System.out.println(realPath);
        //文件保存
        part.write(realPath);

        //将文件上传的描述返回到浏览器
        resp.setContentType("text/plain;charset=UTF-8");
        PrintWriter writer = resp.getWriter();
        writer.println(desc);
        writer.println("文件上传的位置是:"+realPath);
        writer.flush();
        writer.close();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }
}

 需要注意的是只有将项目的位置放在tomcat中时文件才会存放在tomcat中。workspace中是没有上传的文件的。

 4.34 Filter过滤器

Filter过滤器是Servlet2.3中所提供的一个过滤请求与响应的对象。

Filter过滤器既可以对客户端向服务器端发送的请求进行过滤,也可以对服务器端向客户端产生的响应进行过滤处理。

Filter对象的创建

创建一个Class实现Filter接口,并实现接口中三个抽象方法。

init()方法:初始化方法,在创建Filter后立即调用。可用于完成初始化动作。

doFilter()方法:拦截请求与响应方法,可用于对请求和响应实现预处理。

destroy()方法:销毁方法,在销毁Filter之前自动调用。可用于完成资源释放等动作。

    <filter>
        <filter-name>FilterServlet</filter-name>
        <filter-class>cn.it.filter.FilterServlet</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>FilterServlet</filter-name>
        <url-pattern>/filter.do</url-pattern>  <!--只有请求filter.do时,该请求才会被管理器拦截-->
    </filter-mapping>
package cn.it.filter;

import javax.servlet.*;
import java.io.IOException;

public class FilterServlet implements Filter {

    /*Filter对象在容器启动的时候会被实例化,被实例化后立即调用init方法完成初始化*/
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init filter");
    }

    /*过滤请求与响应,当客户端请求的URL与Filter定义的URL-pattern匹配,那么该请求会进入到该方法*/
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //对请求处理
        System.out.println("请求被过滤");
        //放行请求
        filterChain.doFilter(servletRequest,servletResponse);
        //对响应处理
        System.out.println("响应过滤");
    }

    /*当Filter对象在销毁之前会调用一次destroy*/
    @Override
    public void destroy() {

    }
}

4.34.2在Filter中设置请求编码

 <filter>
        <filter-name>EncodingFilter</filter-name>
        <filter-class>cn.it.filter.EncodingFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>EncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>  <!--所有请求都能进入过滤器-->
    </filter-mapping>
package cn.it.filter;

import javax.servlet.*;
import java.io.IOException;

public class EncodingFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        servletRequest.setCharacterEncoding("UTF-8");
        servletResponse.setCharacterEncoding("UTF-8");//不建议设置响应编码,要视需求而定。
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

    }
}

由于HttpServletResponse对象是在Servlet的doGet或doPost等方法中创建的,因此设置响应编码的代码也通常放在这些方法中。这样可以确保在Servlet的业务逻辑处理后再设置响应编码,以免影响处理单元的逻辑和性能。 

4.34.3 FilterConfig对象的使用

FilterConfig对象是用来读取<filter>中<init-param>初始化参数的对象。该对象通过参数传递到init方法中,用于读取初始化参数。

通过name获取对应的value。 

filterConfig.getInitParameter("name")

返回该Filter中所有中的值。

filterConfig.getInitParameterNames()
    <filter>
        <filter-name>EncodingFilter</filter-name>
        <filter-class>cn.it.filter.EncodingFilter</filter-class>
        <init-param>
            <param-name>code</param-name>
            <param-value>gbk</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>EncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>  <!--所有请求都能进入过滤器-->
    </filter-mapping>
package cn.it.filter;

import javax.servlet.*;
import java.io.IOException;

public class EncodingFilter implements Filter {

    //设置默认编码
    private String defaultEncoding = "UTF-8";

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String code = filterConfig.getInitParameter("code");
        //如果设置了编码方式就用设置的编码方式,没有设置就用默认的编码方式
        if (code != null && code.length() > 0) {
            defaultEncoding = code;
        }
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        servletRequest.setCharacterEncoding(defaultEncoding);
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

    }
}

4.34.4 FilterChain(过滤器链)

Filter技术的特点是在对请求或响应做预处理时,可实现“插拔式”的程序设计。我们可以根据自己需求添加多个Filter,也可以根据需求去掉某个Filter,通过修改web.xml文件即可实现。那么如果有多个过滤器对某个请求及响应进行过滤,那么这组过滤器就称为过滤器链。

Filter执行顺序

则按照在web.xml文件中配置的上下顺序来决定先后。在上的先执行,在下的后执行。

4.34.5 基于注解式开发Filter

Filter支持注解式开发,通过@WebFilter注解替代web.xml中Filter的配置。

属性名 类型 作用
filterName String 指定过滤器的 name 属性
urlPatterns String[] 拦截请求的URL,支持多个
value String[] 拦截请求的URL,支持多个
description String 过滤器的描述
displayName String 过滤器的显示名称
initParams WebInitParam[] 指定一组过滤器初始化参数,等价于 标签。

使用注解式开发Filter时,执行顺序会根据Filter的名称进行排序的结果决定调用的顺序。web.xml配置的优先级要高于注解。

package cn.it.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import java.io.IOException;

@WebFilter(urlPatterns = "/ann.do",initParams = {
        @WebInitParam(name = "key",value = "web"),
        @WebInitParam(name = "key2",value = "java")
})
public class AnnotationFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String key1 = filterConfig.getInitParameter("key1");
        String key2 = filterConfig.getInitParameter("key2");
        System.out.println(key1);
        System.out.println(key2);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("请求处理");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("响应处理");
    }

    @Override
    public void destroy() {

    }
}

 4.34.6 Filter的生命周期

Filter的生命周期是由容器管理的。当容器启动时会实例化Filter并调用init方法完成初始化动作。当客户端浏览器发送请求时,容器会启动一个新的线程来处理请求,如果请求的URL能够被过滤器所匹配,那么则先调用过滤器中 的doFilter方法,再根据是否有chain.doFilter的指令,决定是否继续请求目标资源。当容器关闭时会销毁Filter对象,在销毁之前会调用destroy方法。

4.35 Listener监听器

监听器用于监听web应用中某些对象的创建、销毁、增加,修改,删除等动作的发生,然后作出相应的响应处理。当范围对象的状态发生变化的时候,服务器会自动调用监听器对象中的方法。

监听器分类

按监听的对象划分,可以分为:

  • ServletContext对象生命周期监听器与属性操作监听器;
  • HttpSession对象生命周期监听器与属性操作监听器;
  • ServletRequest对象生命周期监听器与属性操作监听器;

4.35.1 ServletContext对象的生命周期监听器

ServletContextListener接口定义了ServletContext对象生命周期的监听行为。

void contextInitialized(ServletContextEvent sce)

ServletContext对象创建之后会触发该监听方法,并将ServletContext对象传递到该方法中。

void contextDestroyed(ServletContextEvent sce)

ServletContext对象在销毁之前会触发该监听方法,并将ServletContext对象传递到该方法中。

    <!--配置ServletLifeCircleListener-->
    <listener>
        <listener-class>cn.it.listener.ServletLifeCircleListener</listener-class>
    </listener>
package cn.it.listener;


import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class ServletLifeCircleListener implements ServletContextListener {

    //监听ServletContext创建的监听方法(容器启动时创建ServletContext对象,而且只会创建一个)
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("servletContextInitialized:"+sce.getServletContext());
    }

    //监听ServletContext销毁的监听方法(容器关闭时销毁ServletContext对象)
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("servletContextDestroyed:"+sce.getServletContext());
    }
}

 

 4.35.2 ServletContext对象的属性操作监听器

ServletContextAttributeListener接口定义了对于ServletContext对象属性操作的监听行为。

void attributeAdded(ServletContextAttributeEvent scae)

向ServletContext对象中添加属性时会触发该监听方法,并将ServletContext对象传递到该方法中。触发事件的方法servletContext.setAttribute("key","value")。

void attributeRemoved(ServletContextAttributeEvent scae)

当从ServletContext对象中删除属性时会触发该监听方法,并将ServletContext对象传递到该方法中。触发事件方法servletContext.removeAttribute("key")。

void attributeReplaced(ServletContextAttributeEvent scae)

当从ServletContext对象中属性的值发生替换时会触发该监听方法,并将ServletContext对象传递到该方法中。触发事件的方法servletContext.setAttribute("key","value")。

    <!--配置ServletContextAttrListener-->
    <listener>
        <listener-class>cn.it.listener.ServletContextAttrListener</listener-class>
    </listener>
package cn.it.listener;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(urlPatterns = "/context.do")
public class ServletContextAttrServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       //获取ServletContext
        ServletContext servletContext = this.getServletContext();
        servletContext.setAttribute("servletContextKey","123");
        servletContext.setAttribute("servletContextKey",111);
        servletContext.removeAttribute("servletContextKey");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}
package cn.it.listener;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;

public class ServletContextAttrListener implements ServletContextAttributeListener {

    @Override
    public void attributeAdded(ServletContextAttributeEvent scae) {
        System.out.println("-------------addStart-------------------");
        System.out.println("监听添加属性:"+scae.getName()+"="+scae.getValue());
        ServletContext servletContext = scae.getServletContext();
        System.out.println("--------------addend------------------");
    }

    @Override
    public void attributeRemoved(ServletContextAttributeEvent scae) {
        System.out.println("-------------removeStart-------------------");
        System.out.println("监听删除属性:"+scae.getName()+"="+scae.getValue());
        ServletContext servletContext = scae.getServletContext();
        System.out.println("--------------removeEnd------------------");
    }

    @Override
    public void attributeReplaced(ServletContextAttributeEvent scae) {
        System.out.println("-------------replaceStart-------------------");
        System.out.println("监听替换属性:"+scae.getName()+"="+scae.getValue());
        ServletContext servletContext = scae.getServletContext();
        System.out.println("--------------replaceEnd------------------");
    }
}

 4.35.3 HttpSession对象的生命周期监听器

HttpSessionListener接口定义了HttpSession对象生命周期的监听行为。

void sessionCreated(HttpSessionEvent se)

HttpSession对象创建后会触发该监听方法,并将已创建HttpSession对象传递到该方法中。

void sessionDestroyed(HttpSessionEvent se)

HttpSession对象在销毁之前会触发该监听方法,并将要销毁的HttpSession对象传递到该方法中。

    <!--配置HttpSessionLifeCircleListener-->
    <listener>
        <listener-class>cn.it.listener.HttpSessionLifeCircleListener</listener-class>
    </listener>
    <session-config>
        <session-timeout>1</session-timeout>
    </session-config>
package cn.it.listener;

import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

public class HttpSessionLifeCircleListener implements HttpSessionListener {

    //监听HttpSession对象创建的方法
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println("session对象创建:"+se.getSession());
    }

    //监听HttpSession对象销毁的方法
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("session对象销毁:"+se.getSession());
    }
}

 4.35.4 HttpSession对象的属性操作监听器

HttpSessionAttributeListener接口定义了对于HttpSession对象属性操作的监听行为。

void attributeAdded(HttpSessionBindingEvent se)

向HttpSession对象中添加属性时会触发该监听方法,并将HttpSession对象传递到该方法中。触发事件的方法HttpSession.setAttribute("key","value")。

void attributeRemoved(HttpSessionBindingEvent se)

当从HttpSession对象中删除属性时会触发该监听方法,并将HttpSession对象传递到该方法中。触发事件方法HttpSession.removeAttribute("key")。

void attributeReplaced(HttpSessionBindingEvent se)

当从HttpSession对象中属性的值发生替换时会触发该监听方法,并将HttpSession对象传递到该方法中。触发事件的方法HttpSession.setAttribute("key","value")。

    <!--配置HttpSessionAttrListener-->
    <listener>
        <listener-class>cn.it.listener.HttpSessionAttrListener</listener-class>
    </listener>
package cn.it.listener;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

@WebServlet("/sessionAttr.do")
public class HttpSessionAttrServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        session.setAttribute("sessionKey","123");
        session.setAttribute("sessionKey",1123);
        session.removeAttribute("sessionKey");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}
package cn.it.listener;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;

public class HttpSessionAttrListener implements HttpSessionAttributeListener {

    @Override
    public void attributeAdded(HttpSessionBindingEvent se) {
        System.out.println("-------------addStart-------------------");
        System.out.println("监听添加属性:"+se.getName()+"="+se.getValue());
        System.out.println(se.getSession());
        System.out.println("--------------addend------------------");
    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent se) {
        System.out.println("-------------replaceStart-------------------");
        System.out.println("监听替换属性:"+se.getName()+"="+se.getValue());
        System.out.println(se.getSession());
        System.out.println("--------------replaceEnd------------------");
    }

    @Override
    public void attributeRemoved(HttpSessionBindingEvent se) {
        System.out.println("-------------removeStart-------------------");
        System.out.println("监听删除属性:"+se.getName()+"="+se.getValue());
        System.out.println(se.getSession());
        System.out.println("--------------removeEnd------------------");
    }
}

 4.35.5 HttpServletRequest对象的生命周期监听器

ServletRequestListener接口定义了ServletRequest(是HttpServletRequest接口的父接口类型)对象生命周期的监听行为。

void requestInitialized(ServletRequestEvent sre)

HttpServletRequest对象创建后会触发该监听方法,并将已创建HttpServletRequest对象传递到该方法中。

void requestDestroyed(ServletRequestEvent sre)

HttpServletRequest对象在销毁之前会触发该监听方法,并将要销毁HttpServletRequest对象传递到该方法中。

    <!--配置HttpServletRequestCircleListener-->
    <listener>
        <listener-class>cn.it.listener.HttpServletRequestCircleListener</listener-class>
    </listener>
package cn.it.listener;

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;

public class HttpServletRequestCircleListener implements ServletRequestListener {
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println((HttpServletRequest)sre.getServletRequest()+"创建-----------");
    }

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println((HttpServletRequest)sre.getServletRequest()+"销毁-----------");
    }
}

 4.35.6 HttpServletRequest对象的属性操作监听器

<!--配置HttpServletRequestAttrListener-->
<listener>
    <listener-class>cn.it.listener.HttpServletRequestAttrListener</listener-class>
</listener>
package cn.it.listener;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/reqAttr.do")
public class HttpServletRequestAttrServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setAttribute("requestKey","123");
        req.setAttribute("requestKey","123123123");
        req.removeAttribute("requestKey");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}
package cn.it.listener;

import javax.servlet.ServletContext;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;

public class HttpServletRequestAttrListener implements ServletRequestAttributeListener {
    @Override
    public void attributeAdded(ServletRequestAttributeEvent srae) {
        System.out.println("-------------addStart-------------------");
        System.out.println("监听添加属性:"+srae.getName()+"="+srae.getValue());
        System.out.println(srae.getServletRequest());
        System.out.println("--------------addend------------------");
    }

    @Override
    public void attributeReplaced(ServletRequestAttributeEvent srae) {
        System.out.println("-------------replaceStart-------------------");
        System.out.println("监听替换属性:"+srae.getName()+"="+srae.getValue());
        System.out.println(srae.getServletRequest());
        System.out.println("--------------replaceEnd------------------");
    }

    @Override
    public void attributeRemoved(ServletRequestAttributeEvent srae) {
        System.out.println("-------------removeStart-------------------");
        System.out.println("监听删除属性:"+srae.getName()+"="+srae.getValue());
        System.out.println(srae.getServletRequest());
        System.out.println("--------------removeEnd------------------");
    }
}

 4.35.7 基于注解式开发监听器

Listener支持注解式开发,通过@WebListener注解替代web.xml中Listener的配置。

@WebListener
public class HttpServletContextAttrListener implements ServletContextAttributeListener {}
@WebListener
public class HttpServletContextLifeCircleListener implements ServletContextListener {}

4.36 Filter与Listener设计模式

“知其然,知其所以然”。面试会问。

4.36.1 Filter的设计模式

在Servlet的Filter中使用的责任链设计模式。

责任链模式特点

责任链(Chain of Responsibility):责任链模式也叫职责链模式,是一种对象行为模式。在责任链模式里,很多对象由每一个对象对其下一个对象的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不需要知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。

责任链的优缺点

优点:

  • 降低了对象之间的耦合度。
  • 增强了系统的可扩展性。
  • 增强了给对象指派职责的灵活性。
  • 责任链简化了对象之间的连接。
  • 责任分担。每个类只需要处理自己该处理的工作。

缺点:

  • 不能保证请求一定被接收。
  • 对比较长的责任链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
  • 可能会由于责任链的错误设置而导致系统出错,如可能会造成循环调用。

4.36.2 Listener的设计模式

在Servlet的Listener中使用的观察者设计模式。

观察者模式的特点

观察者模式(Observer Pattern):观察者模式是一种对象行为模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

观察者模式的优缺点

优点:

  • 观察者和被观察者是抽象耦合的。
  • 建立一套触发机制。

缺点:

  • 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  • 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_56349691/article/details/130734423

智能推荐

5个超厉害的资源搜索网站,每一款都可以让你的资源满满!_最全资源搜索引擎-程序员宅基地

文章浏览阅读1.6w次,点赞8次,收藏41次。生活中我们无时不刻不都要在网站搜索资源,但就是缺少一个趁手的资源搜索网站,如果有一个比较好的资源搜索网站可以帮助我们节省一大半时间!今天小编在这里为大家分享5款超厉害的资源搜索网站,每一款都可以让你的资源丰富精彩!网盘传奇一款最有效的网盘资源搜索网站你还在为找网站里面的资源而烦恼找不到什么合适的工具而烦恼吗?这款网站传奇网站汇聚了4853w个资源,并且它每一天都会持续更新资源;..._最全资源搜索引擎

Book类的设计(Java)_6-1 book类的设计java-程序员宅基地

文章浏览阅读4.5k次,点赞5次,收藏18次。阅读测试程序,设计一个Book类。函数接口定义:class Book{}该类有 四个私有属性 分别是 书籍名称、 价格、 作者、 出版年份,以及相应的set 与get方法;该类有一个含有四个参数的构造方法,这四个参数依次是 书籍名称、 价格、 作者、 出版年份 。裁判测试程序样例:import java.util.*;public class Main { public static void main(String[] args) { List <Book>_6-1 book类的设计java

基于微信小程序的校园导航小程序设计与实现_校园导航微信小程序系统的设计与实现-程序员宅基地

文章浏览阅读613次,点赞28次,收藏27次。相比于以前的传统手工管理方式,智能化的管理方式可以大幅降低学校的运营人员成本,实现了校园导航的标准化、制度化、程序化的管理,有效地防止了校园导航的随意管理,提高了信息的处理速度和精确度,能够及时、准确地查询和修正建筑速看等信息。课题主要采用微信小程序、SpringBoot架构技术,前端以小程序页面呈现给学生,结合后台java语言使页面更加完善,后台使用MySQL数据库进行数据存储。微信小程序主要包括学生信息、校园简介、建筑速看、系统信息等功能,从而实现智能化的管理方式,提高工作效率。

有状态和无状态登录

传统上用户登陆状态会以 Session 的形式保存在服务器上,而 Session ID 则保存在前端的 Cookie 中;而使用 JWT 以后,用户的认证信息将会以 Token 的形式保存在前端,服务器不需要保存任何的用户状态,这也就是为什么 JWT 被称为无状态登陆的原因,无状态登陆最大的优势就是完美支持分布式部署,可以使用一个 Token 发送给不同的服务器,而所有的服务器都会返回同样的结果。有状态和无状态最大的区别就是服务端会不会保存客户端的信息。

九大角度全方位对比Android、iOS开发_ios 开发角度-程序员宅基地

文章浏览阅读784次。发表于10小时前| 2674次阅读| 来源TechCrunch| 19 条评论| 作者Jon EvansiOSAndroid应用开发产品编程语言JavaObjective-C摘要:即便Android市场份额已经超过80%,对于开发者来说,使用哪一个平台做开发仍然很难选择。本文从开发环境、配置、UX设计、语言、API、网络、分享、碎片化、发布等九个方面把Android和iOS_ios 开发角度

搜索引擎的发展历史

搜索引擎的发展历史可以追溯到20世纪90年代初,随着互联网的快速发展和信息量的急剧增加,人们开始感受到了获取和管理信息的挑战。这些阶段展示了搜索引擎在技术和商业模式上的不断演进,以满足用户对信息获取的不断增长的需求。

随便推点

控制对象的特性_控制对象特性-程序员宅基地

文章浏览阅读990次。对象特性是指控制对象的输出参数和输入参数之间的相互作用规律。放大系数K描述控制对象特性的静态特性参数。它的意义是:输出量的变化量和输入量的变化量之比。时间常数T当输入量发生变化后,所引起输出量变化的快慢。(动态参数) ..._控制对象特性

FRP搭建内网穿透(亲测有效)_locyanfrp-程序员宅基地

文章浏览阅读5.7w次,点赞50次,收藏276次。FRP搭建内网穿透1.概述:frp可以通过有公网IP的的服务器将内网的主机暴露给互联网,从而实现通过外网能直接访问到内网主机;frp有服务端和客户端,服务端需要装在有公网ip的服务器上,客户端装在内网主机上。2.简单的图解:3.准备工作:1.一个域名(www.test.xyz)2.一台有公网IP的服务器(阿里云、腾讯云等都行)3.一台内网主机4.下载frp,选择适合的版本下载解压如下:我这里服务器端和客户端都放在了/usr/local/frp/目录下4.执行命令# 服务器端给执_locyanfrp

UVA 12534 - Binary Matrix 2 (网络流‘最小费用最大流’ZKW)_uva12534-程序员宅基地

文章浏览阅读687次。题目:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=93745#problem/A题意:给出r*c的01矩阵,可以翻转格子使得0表成1,1变成0,求出最小的步数使得每一行中1的个数相等,每一列中1的个数相等。思路:网络流。容量可以保证每一行和每一列的1的个数相等,费用可以算出最小步数。行向列建边,如果该格子是_uva12534

免费SSL证书_csdn alphassl免费申请-程序员宅基地

文章浏览阅读504次。1、Let's Encrypt 90天,支持泛域名2、Buypass:https://www.buypass.com/ssl/resources/go-ssl-technical-specification6个月,单域名3、AlwaysOnSLL:https://alwaysonssl.com/ 1年,单域名 可参考蜗牛(wn789)4、TrustAsia5、Alpha..._csdn alphassl免费申请

测试算法的性能(以选择排序为例)_算法性能测试-程序员宅基地

文章浏览阅读1.6k次。测试算法的性能 很多时候我们需要对算法的性能进行测试,最简单的方式是看算法在特定的数据集上的执行时间,简单的测试算法性能的函数实现见testSort()。【思想】:用clock_t计算某排序算法所需的时间,(endTime - startTime)/ CLOCKS_PER_SEC来表示执行了多少秒。【关于宏CLOCKS_PER_SEC】:以下摘自百度百科,“CLOCKS_PE_算法性能测试

Lane Detection_lanedetectionlite-程序员宅基地

文章浏览阅读1.2k次。fromhttps://towardsdatascience.com/finding-lane-lines-simple-pipeline-for-lane-detection-d02b62e7572bIdentifying lanes of the road is very common task that human driver performs. This is important ..._lanedetectionlite

推荐文章

热门文章

相关标签