Bootstrap

Tomcat与Servlet基础教程

文章目录


一、Tomcat

(一)概述

①什么是Tomcat

Tomcat官网Tomcat官网
  Tomcat是一个符合JavaEE WEB标准的最小的WEB容器,所有的JSP程序-定要有 WEB容器的支持才能运行,而且在给定的WEB容器里面都会支持事务处理操作。
  Tomcat 简单的说就是一个运行Java的网络服务器,底层是Socket的一个程序,它也是JSPServlet的一个容器,最新的ServletJSP规范总是能在Tomcat中得到体现。因为Tomcat技术先进、性能稳定,而且免费,因而深受Java爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web应用服务器。
  Tomcat服务器是个免费的开放源代码的Web应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP程序的首选。|对于个初学者来说, 可以这样认为,当在一台机器上配置好Apache服务器,可利用它响应HTML (标准通用标记语言下的一个应用)页面的访问请求。实际上Tomcat部分是Apache服务器的扩展,但它是独立运行的,所以当你运行tomcat时,它实际上作为一个与Apache独立的进程单独运行的。
  当配置正确时,ApacheHTML页面服务,而Tomcat实际上是在运行JSP页面和Servlet。另外,TomcatIISWeb服务器-样,具有处理HTML页面的功能,另外它还是-个ServletJSP容器,独立的Servlet容器是Tomcat的默认模式。不过,Tomcat处理静态HTML的能力不如Apache服务器。

常见概念

  • 请求:客户端发送数据至服务端的过程。
  • 响应:服务端返回数据给客户端的过程。
  • 客户端:浏览器。
  • 服务端(服务器):一种高性能的计算机,用于管理资源并为用户提供服务,即,接收请求,发送资源。相对于普通PC来说,有更高的稳定性、安全性、性能。

②Tomcat核心组件

  Tomcat有一系列组件构成,其中核心的组件有三个:

名称功能
WEB容器负责 WEB 服务的 TCP/IP、HTTP/HTTPS 等协议响应、处理 (nginx 处理静态页面的应用交互)。
JSP(Java Scripts Page)容器一种动态网页开发技术,其使用JSP标签在HTML网页中插入Java代码,通常以"<%“开始,以”%>"结束,是一种Java Servlet,主要用于实现Java web应用程序的用户界面部分,通过网页表单获取 用户输入数据、访问数据库及其他数据源,然后动态地创建网页。常见的有,index.html index.php index.jsp。
Servlet容器Servlet 容器调用 API 接口,找到对接的项目,对接的项目从 mysql 数据库中获得相应信息,比如:数据库交互、加密、支付宝、人脸识别等,处理完后会将这些数据返回给 jsp,通过 jsp 中的 index.jsp 展示出来,再把相应信息返回给客户。

③Tomcat目录结构

以下内容部分参考:https://blog.csdn.net/weixin_51367845/article/details/123429050
这里,我安装的是10.0.27版本:
在这里插入图片描述

  • bin:存放各种启动、关闭和其它程序的脚本(*.sh 文件是针对Unix系统使用的,*.bat文件是针对Windows系统使用的)
  • conf:配置文件及相关数据文件存放的目录,存放server.xmltomcat-users.xmlweb.xml等。
  • lib:Tomcat库函数存放目录(都是jar包)。
  • logs:默认日志文件存放目录(可通过servlet.xml进行修改)。
  • temp:临时文件目录,如上传大文件时的缓存数据会存储在这里。
  • webapps: 存放web应用、用来程序部署的目录(可以通过server.xml文件配置),一个具有独立完整功能的网站,可以称为一个web应用。一个Tomcat的服务器上可以同时部署多个这样的web应用。这些web应用以目录的形式被存放到webapps目录中。
  • work:Tomcat工作目录,如,存放JSP编译后的类文件。

④WEB应用部署目录结构

  部署应用程序时,一般会将其打包成一个 war包,然后放到 Tomcat 的应用程序部署目录 webapps 中。而 web 应用程序有特定的组织格式,是一种层次型目录结构,通常包含了 servlet 代码文件、HTMLJSP 页面文件、类文件、部署描述符文件等等,相关说明如下:

  • /:表示 web 应用程序的根目录,可以存放 HTMLJSP 页面以及其他客户端浏览器必须可见的其他文件(如 JSCSS、图像文件)。在较大的应用程序中,还可以选择将这些文件划分为子目录层次结构。
  • /WEB-INF: 表示 web 应用程序的所有私有资源目录,用户浏览器不可能访问到的,通常 web.xmlcontext.xml 均放置于此目录。
  • /WEB-INF/web.xml: 表示 web 应用程序的私有的部署描述符,描述组成应用程序的 servlet 和其他组件(如 filter),以及相关初始化参数和容器管理的安全性约束。
  • /WEB-INF/classes: 表示 web 应用程序自有的 Java 程序类文件及相关资源存放目录。
  • /WEB-INF/lib: 表示 web 应用程序自有的 JAR 文件,其中包含应用程序所需的 Java 类文件及相关资源(如第三方类库或 JDBC 驱动程序)。

⑤WEB应用的部署

  静态页面就是内容始终固定的页面,即使用户不同、时间不同、输入的参数不同,页面内容也不会发生变化。除非网站的开发人员修改源代码,否则页面的内容始终不变。以下,就是一个在Tomcat中部署静态页面的例子:
hello.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>hello</title>
</head>
<body>
    <div>Hello World</div>
</body>
</html>

hello.html放到webapps/Root目录下:
在这里插入图片描述
使用命令在CMD中启动Tomcat:

startup.bat

在浏览器网址中输入:

http://127.0.0.1:8080/hello.html

在这里插入图片描述
关闭Tomcat服务:

shutdown.bat

(二)Tomcat配置文件

在这里插入图片描述

  • catalina.policy: 当基于-securty选项启动 Tomcat 实例时会读取此配置文件。此文件是 Java 的安全策略配置文件,用于配置访问codebase(代码库)或某些Java类的权限。
  • catalina.propertiesJava属性定义文件,设定类加载器路径、安全包列表和一些调整性能的参数信息。
  • context.xml:为部署与此Tomcat实例上的web应用程序提供的默认配置文件,每个webapp都可以使用独有的context.xml,通常放置于webapp目录的META-INF子目录中,常用于定义会话管理器,RealmJDBC
  • logging.properties:定义日志相关的配置信息,如日志级别、文件路径等。
  • server.xmlTomcat核心配置文件,包含ServiceConnectorEngineRealmValveHosts主组件的相关配置信息。
  • tomcat-users.xml: 包含Realm认证时用到的相关角色、用户和密码等信息;Tomcat自带的 manager默认情况下会用到此文件;在Tomcat中添加火删除用户,为用户指定角色等将通过编辑此文件实现。
  • web.xmlweb.xmlweb应用的描述文件, 它支持的元素及属性来自于Servlet规范定义 。在Tomcat中,Web应用的描述信息包括Tomcat/conf/web.xml中默认配置以及Web应用WEB-INF/web.xml下的定制配置。

配置文件加载方式
1.启动web项目的时候,web容器会去读取web.xml,包括Tomcat和应用程序中的两个web.xml
2.紧接着web容器创建一个servletContext,这个web项目的所有部分都将共享这个上下文对象。
3.容器将转换为键值对,并交给servletContext
4.容器创建中的类实例,创建监听。

①context.xml

  在Tomcat 5.5之前Context体现在/conf/server.xml中的Host里的<Context>元素,它由Context接口定义。每个<Context>元素代表了运行在虚拟主机上的单个Web应用。
  在Tomcat 5.5之后不推荐在server.xml中进行配置,而是在/conf/context.xml中进行独立的配置。因为server.xml是不可动态重加载的资源,服务器一旦启动了以后,要修改这个文件,就得重启服务器才能重新加载。而context.xml文件则不然,Tomcat服务器会定时去扫描这个文件。一旦发现文件被修改(时间戳改变了),就会自动重新加载这个文件,而不需要重启服务器。

<?xml version='1.0' encoding='utf-8'?>
<Context>
<!-- 监控资源文件,如果web.xml改变了,则自动重新加载应用 -->
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<!--本地测试项目-->
<!-- name,指定JNDI名称 -->
<!-- auth,表示认证方式,一般为Container -->
<!-- maxActive,连接池支持的最大连接数 -->
<!-- maxIdle,连接池中最多可空闲连接数 -->
<!-- maxWait,连接池中连接用完时,新的请求等待时间,单位毫秒 -->
<!-- username,password,数据库用户名/密码 -->
<!-- driverClassName,jdbc驱动 -->
<!-- url,数据库url地址 -->
<Resource name="jdbc/opslocal" 
auth="Container" 
type="javax.sql.DataSource" 
username="scott" 
password="admin" 
driverClassName="oracle.jdbc.driver.OracleDriver" 
url="jdbc:oracle:thin:@localhost:1521:orcl" 
maxActive="100000" 
maxIdle="4"/>
<Resource name="jdbc/inslocal" 
auth="Container" 
type="javax.sql.DataSource" 
username="ins" 
password="ayw_ins1" 
driverClassName="oracle.jdbc.driver.OracleDriver" 
url="jdbc:oracle:thin:@132.228.213.137:1521:ossmob" 
maxActive="100000" 
maxIdle="4"/>
<!--生产项目-->
<Resource name="jdbc/cpflocal" 
auth="Container" 
type="javax.sql.DataSource" 
username="cpf_dispatch" 
password="cpf_dispatch" 
driverClassName="oracle.jdbc.driver.OracleDriver" 
url="jdbc:oracle:thin:@132.232.7.51:15211:orcl" 
maxActive="100000" 
maxIdle="4"/>
</Context>

作用范围
1.tomcat server
  在$CATALINA_BASE/conf/context.xml里配置,如果你在这个地方配置、那么这个配置文件将会被所有的webApp共享 。
2.Host级别
  在$CATALINA_BASE/conf/Catalina/${hostName}里添加context.xml,继而进行配置,这个配置将会被这个主机上的所有webapp共享。
web app级别
  在$CATALINA_BASE/conf/Catalina/${hostName}里添加${webAppName}.xml,继而进行配置。

②server.xml

以下内容参考自文章:http://www.cnblogs.com/andy-alone/p/9199736.html
  server.xml的每一个元素都对应了Tomcat中的一个组件;通过对xml文件中元素的配置,可以实现对Tomcat中各个组件的控制。因此,学习server.xml文件的配置,对于了解和使用Tomcat至关重要。
这里,我使用的是apache-tomcat-10.0.27,以下是最原始的server.xml
server.xml

<?xml version="1.0" encoding="UTF-8"?>

<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <GlobalNamingResources>

    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <Service name="Catalina">

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

    <Engine name="Catalina" defaultHost="localhost">

      <Realm className="org.apache.catalina.realm.LockOutRealm">

        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      <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>
    </Engine>
  </Service>
</Server>

元素分类

  • <Server>:整个配置文件的根元素,代表整个Tomcat容器,用于创建一个Server实例,一个Server元素中可以有一个或多个Service元素,一个<Server>对应一个<Engine>和一组<Connector>
  • <Service>ServiceConnectorEngine外面包了一层,把它们组装在一起,对外提供服务。一个Service可以包含多个Connector,但是只能包含一个EngineConnector接收请求,Engine处理请求。
  • <Connector>:连接器标签,代表了外部客户端发送请求到特定Service的接口,同时也是外部客户端从特定Service接收响应的接口。
  • <Engine><Host><Context>:三个容器标签,用于处理Connector接收进来的请求,并产生相应的响应。这些标签是父子关系,<Engine>包含<Host><Host>包含<Context>
    • <Engine>:表示指定service中的请求处理机,接收和处理来自Connector的请求,可以接收用户的http请求,并构建响应报文。
    • <Host>:一个<Host>组件可以处理发向一个特定虚拟主机的所有请求,一个<Host>可以包含多个<Context>元素.每个web应用有唯一的一个相对应的Context代表web应用自身,servlet容器为第一个web应用创建一个ServletContext对象. -->
    • <Context>Context表示运行在虚拟主机上的一个web应用程序,通常为WAR文件。
  • <Listener>:在特定事件发生时执行特定的操作;被监听的事件通常是Tomcat的启动和停止。
  • <GlobalNamingResources>GlobalNamingResources中定义了全局命名服务。
1.核心组件标签
1.1 Server标签
<Server port="8005" shutdown="SHUTDOWN">
	...
</Server>

作用:提供一个接口让客户端能够访问到这个Service集合,同时维护它所包含的所有的Service的声明周期,包括如何初始化、如何结束服务、如何找到客户端要访问的Service

  Serverserver.xml的根元素,代表整个Tomcat容器,可以有一个或多个Service元素。用于创建Server实例,默认使用的实现类是 org.apache.catalina.core.StandardServer

  • port:Tomcat 监听的关闭服务器的端口,使用-1可禁用该端口。
  • shutdown:关闭服务器的指令字符串。
1.2 Service标签
<Service name="Catalina">
	<Connector port="8080" protocol="HTTP/1.1"
			connectionTimeout="20000"
			redirectPort="8443" />
				...
	<Engine name="Catalina" defaultHost="localhost">
			...
		<Host name="localhost"  appBase="webapps"
			unpackWARs="true" autoDeploy="true">
				...
		</Host>
	</Engine>
</Service>

作用:将<Connector><Engine>组合在一起,对外提供服务。实际上,Tomcat可以提供多个Service,不同的Service监听不同的端口。
  一个<service>标签可包含多个<Connector>标签,但只能包含一个<Engine>标签,其中,<Conector>用于从客户端接收请求,而<Engine>用于处理接收的请求。

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

作用:接收连接请求,创建RequestResponse对象用于和请求端交换数据;然后分配线程让Engine来处理这个请求,并把产生的RequestResponse对象传给Engine
  配置Connector,可以控制请求Service的协议及端口号。

  • port:指定端口号。本例中,客户端可以通过8080端口号使用http协议访问Tomcat
  • protocol:规定请求依据的协议。
  • redirectPort:当前Connector不支持SSL请求, 接收到了一个请求, 并且也符合 security-constraint约束, 需要SSL传输,Catalina自动将请求重定向到指定的端口。
  • connectionTimeout:表示连接的超时时间。
  • executor:指定共享线程池的名称, 也可以通过maxThreadsminSpareThreads 等属性配置内部线程池。
  • URIEncoding:用于指定编码URI的字符编码,Tomcat8.x版本默认的编码为UTF-8,Tomcat7.x版本默认为ISO-8859-1
  • maxThreads:池中最大线程数。
  • minSpareThreads:活跃线程数,也就是核心池线程数,这些线程不会被销毁,会一直存在。
  • acceptCount:接收的连接数。
  • maxConnections:接收的最大连接数。
  • compression:是否压缩。
  • compressionMinSize:压缩的大小。
  • disableUploadTimeout:禁用上传超时。
1.4 Engine标签
<Engine name="Catalina" defaultHost="localhost">
	...
	<Host name="localhost"  appBase="webapps"
		unpackWARs="true" autoDeploy="true">
		...
	</Host>
</Engine>

作用EngineService组件中的请求处理组件,其从一个或多个Connector中接收请求并处理,并将完成的响应返回给Connector,最终传递给客户端。
  Engine组件在Service组件中有且只有一个,其包含<Host>标签。

  • name:用于日志和错误信息,在整个Server中应该唯一。
  • defaultHostdefaultHost属性指定了默认的host名称,当发往本机的请求指定的host名称不存在时,一律使用defaultHost指定的host进行处理;因此,defaultHost的值,必须与Engine中的一个Host组件的name属性值匹配。
1.5 Host标签
<Host name="localhost"  appBase="webapps"
	unpackWARs="true" autoDeploy="true">
	...
</Host>

作用Host组件代表的虚拟主机,对应了服务器中一个网络名实体(如”www.test.com”,或IP地址”116.25.25.25”);为了使用户可以通过网络名连接Tomcat服务器,这个名字应该在DNS服务器上注册。Host虚拟主机的作用,是运行多个Web应用(一个Context代表一个Web应用),并负责安装、展开、启动和结束每个Web应用。

  • name:指定虚拟主机的主机名,一个Engine中有且仅有一个Host组件的name属性与Engine组件的defaultHost属性相匹配;一般情况下,主机名需要是在DNS服务器中注册的网络名,但是Engine指定的defaultHost不需要。
  • unpackWARs:是否将代表Web应用的WAR文件解压;如果为true,通过解压后的文件结构运行该Web应用,如果为false,直接使用WAR文件运行Web应用。
  • appBase:指定Web应用所在的目录,默认值是webapps,这是一个相对路径,代表Tomcat根目录下webapps文件夹。
  • autoDeployTomcat在运行时定期检查新的Web应用或Web应用的更新,发现更新则自动部署。
  • xmlBase:指定Web应用的XML配置文件所在的目录,默认值为conf/<engine_name>/<host_name>
  • deployOnStartupTomcat在启动时检查Web应用,且检测到的所有Web应用视作新应用,并进行部署。

  HostEngine的子容器。Engine组件中可以内嵌1个或多个Host组件,每个Host组件代表Engine中的一个虚拟主机。Host组件至少有一个,且其中一个的name必须与Engine组件的defaultHost属性相匹配。
  客户端通常使用主机名来标识它们希望连接的服务器;该主机名也会包含在HTTP请求头中。TomcatHTTP头中提取出主机名,寻找名称匹配的主机。如果没有匹配,请求将发送至默认主机。因此默认主机不需要是在DNS服务器中注册的网络名,因为任何与所有Host名称不匹配的请求,都会路由至默认主机。

1.6 Context标签
<!--<Context docBase="D:\IntelliJ IDEA\JavawebDemo\web" path=""/>-->

作用Context元素代表在特定虚拟主机上运行的一个Web应用,ContextHost的子容器,每个Host中可以定义任意多的Context元素。

  • docBase:指定了该Web应用使用的WAR包路径,或应用目录。需要注意的是,在自动部署场景下(配置文件位于xmlBase中),docBase不在appBase目录中,才需要指定;如果docBase指定的WAR包或应用目录就在docBase中,则不需要指定,因为Tomcat会自动扫描appBase中的WAR包和应用目录,指定了反而会造成问题。
  • path:指定了访问该Web应用的上下文路径,当请求到来时,Tomcat根据Web应用的path属性与URI的匹配程度来选择Web应用处理相应请求。例如,Web应用app1path属性是"/app1",Web应用app2path属性是"/app2",那么请求/app1/index.html会交由app1来处理;而请求/app2/index.html会交由app2来处理。如果一个Context元素的path属性为””,那么这个Context是虚拟主机的默认Web应用;当请求的uri与所有的path都不匹配时,使用该默认Web应用来处理。
  • reloadable:指示Tomcat是否在运行时监控在WEB-INF/classesWEB-INF/lib目录下class文件的改动。如果值为true,那么当class文件改动时,会触发Web应用的重新加载。在开发环境下,reloadable设置为true便于调试;但是在生产环境中设置为true会给服务器带来性能压力,因此reloadable参数的默认值为false

注意:在自动部署场景下(配置文件位于xmlBase中),不能指定path属性,path属性由配置文件的文件名、WAR文件的文件名或应用目录的名称自动推导出来。如扫描Web应用时,发现了xmlBase目录下的app1.xml,或appBase目录下的app1.WARapp1应用目录,则该Web应用的path属性是"app1"。如果名称不是app1而是ROOT,则该Web应用是虚拟主机默认的Web应用,此时path属性推导为""。
  可以看到server.xml配置文件中并没有出现Context元素的配置。这是因为,Tomcat开启了自动部署,Web应用没有在server.xml中配置静态部署,而是由Tomcat通过特定的规则自动部署。下面介绍一下Tomcat自动部署Web应用的机制。

1.7 WEB应用自动部署机制

Host的配置
  要开启Web应用的自动部署,需要配置所在的虚拟主机;配置的方式就是前面提到的Host元素的deployOnStartupautoDeploy属性。如果deployOnStartupautoDeploy设置为true,则tomcat启动自动部署:当检测到新的Web应用或Web应用的更新时,会触发应用的部署(或重新部署)。二者的主要区别在于,deployOnStartuptrue时,Tomcat在启动时检查Web应用,且检测到的所有Web应用视作新应用;autoDeploytrue时,Tomcat在运行时定期检查新的Web应用或Web应用的更新。除此之外,二者的处理相似。
  要开启Web应用的自动部署,需要配置所在的虚拟主机;配置的方式就是前面提到的Host元素的deployOnStartupautoDeploy属性。如果deployOnStartupautoDeploy设置为true,则Tomcat启动自动部署:当检测到新的Web应用或Web应用的更新时,会触发应用的部署(或重新部署)。
  二者的主要区别在于,deployOnStartuptrue时,Tomcat在启动时检查Web应用,且检测到的所有Web应用视作新应用;autoDeploytrue时,Tomcat在运行时定期检查新的Web应用或Web应用的更新。除此之外,二者的处理相似。
  appBase属性指定Web应用所在的目录,默认值是webapps,这是一个相对路径,代表Tomcat根目录下webapps文件夹。
  xmlBase指定Web应用的XML配置文件所在的目录。

检查WEB应用更新
  一个Web应用可能包括以下文件:XML配置文件,WAR包,以及一个应用目录(该目录包含Web应用的文件结构)。Tomcat按照如下的顺序进行扫描,来检查应用更新:

  1. 扫描虚拟主机指定的xmlBase下的XML配置文件。
  2. 扫描虚拟主机指定的appBase下的WAR文件。
  3. 扫描虚拟主机指定的appBase下的应用目录。
1.8 WEB应用静态部署机制

  我们也可以在server.xml中通过<context>元素静态部署Web应用。静态部署与自动部署是可以共存的。在实际应用中,并不推荐使用静态部署,因为server.xml是不可动态重加载的资源,服务器一旦启动了以后,要修改这个文件,就得重启服务器才能重新加载。而自动部署可以在Tomcat运行时通过定期的扫描来实现,不需要重启服务器。

<Context path="/" docBase="D:\Program Files \app1.war" reloadable="true"/>
  • docBase:静态部署时,docBase可以在appBase目录下,也可以不在;本例中,docBase不在appBase目录下。
  • path:静态部署时,可以显式指定path属性,但是仍然受到了严格的限制:只有当自动部署完全关闭(deployOnStartupautoDeploy都为false)或docBase不在appBase中时,才可以设置path属性。在本例中,docBase不在appBase中,因此path属性可以设置。
  • reloadable:用法与自动部署时相同。
2. 其他组件标签
2.1 Listener标签
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <!-- Security listener. Documentation at /docs/config/listeners.html
  <Listener className="org.apache.catalina.security.SecurityListener" />
  -->
  <!-- APR library loader. Documentation at /docs/apr.html -->
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <!-- Prevent memory leaks due to use of particular java/javax APIs-->
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

作用:在特定事件发生时执行特定的操作;被监听的事件通常是Tomcat的启动和停止。
  监听器可以在ServerEngineHostContext中,本例中的监听器都是在Server中。

  • className:规定了监听器的具体实现类,该类必须实现了org.apache.catalina.LifecycleListener接口。
    • VersionLoggerListener:当Tomcat启动时,该监听器记录TomcatJava和操作系统的信息。该监听器必须是配置的第一个监听器。
    • AprLifecycleListener:Tomcat启动时,检查APR库,如果存在则加载。APR,即Apache Portable Runtime,是Apache可移植运行库,可以实现高可扩展性、高性能,以及与本地服务器技术更好的集成。
    • JasperListener:在Web应用启动之前初始化JasperJasperJSP引擎,把JVM不认识的JSP文件解析成java文件,然后编译成class文件供JVM使用。
    • JreMemoryLeakPreventionListener:与类加载器导致的内存泄露有关。
    • GlobalResourcesLifecycleListener:通过该监听器,初始化<GlobalNamingResources>标签中定义的全局JNDI资源;如果没有该监听器,任何全局资源都不能使用。
    • ThreadLocalLeakPreventionListener:当Web应用因thread-local导致的内存泄露而要停止时,该监听器会触发线程池中线程的更新。当线程执行完任务被收回线程池时,活跃线程会一个一个的更新。只有当Web应用(即Context元素)的renewThreadsWhenStoppingContext属性设置为true时,该监听器才有效。
2.2 GlobalNamingResources标签

  GlobalNamingResources中定义了全局命名服务。

<GlobalNamingResources>
	<!‐‐ 可编辑的用户数据库,UserDatabaseRealm也可以使用该数据库对用户进行身份验证 ‐‐>
	<Resource 	name="UserDatabase" 
				auth="Container" 
				type="org.apache.catalina.UserDatabase" 
				description="User database that can be updated and saved" 
				factory="org.apache.catalina.users.MemoryUserDatabaseFactory" 
				pathname="conf/tomcat‐users.xml" />
</GlobalNamingResources>
2.3 Executor标签

  默认情况下,Service并未添加共享线程池配置。 如果我们想添加一个线程池, 可以在 下添加如下配置:

<Executor   name="tomcatThreadPool" 
			namePrefix="catalina‐exec‐" 
			maxThreads="200" 
			minSpareThreads="100" 
			maxIdleTime="60000" 
			maxQueueSize="Integer.MAX_VALUE" 
			prestartminSpareThreads="false" 
			threadPriority="5"
			className="org.apache.catalina.core.StandardThreadExecutor" />
  • name:线程池名称,用于Connector中指定。
  • namePrefix:所创建的每个线程的名称前缀,一个单独的线程名称为 namePrefix+threadNumber
  • maxThreads:池中最大线程数。
  • minSpareThreads:活跃线程数,也就是核心池线程数,这些线程不会被销毁,会一直存在。
  • maxIdleTime:线程空闲时间,超过该时间后,空闲线程会被销毁,默认值为6000(1分钟),单位毫秒。
  • maxQueueSize:在被执行前最大线程排队数目,默认为Int的最大值,也就是广义的无限。除非特殊情况,这个值不需要更改, 否则会有请求不会被处理的情况发生。
  • prestartminSpareThreads:启动线程池时是否启动 minSpareThreads部分线程。 默认值为false,即不启动。
  • threadPriority:线程池中线程优先级,默认值为5,值从1到10。
  • className:线程池实现类,未指定情况下,默认实现类为 org.apache.catalina.core.StandardThreadExecutor。 如果想使用自定义线程池首先需要实现 org.apache.catalina.Executor接口。
3.Server.xml请求处理流程

  以请求http://localhost:8080/app1/index.html为例,首先通过协议和端口号(http8080)选定Service;然后通过主机名(localhost)选定Host;然后通过uri/app1/index.html)选定Web应用。
1.根据协议和端口号选定Service和Engine
  Service中的Connector组件可以接收特定端口的请求,因此,当Tomcat启动时,Service组件就会监听特定的端口。在例子中,Catalina这个Service监听了8080端口(基于HTTP协议)和8009端口(基于AJP协议)。当请求进来时,Tomcat便可以根据协议和端口号选定处理请求的ServiceService一旦选定,Engine也就确定。
  通过在Server中配置多个Service,可以实现通过不同的端口号来访问同一台机器上部署的不同应用。
2.根据域名或IP地址选定Host
  Service确定后,TomcatService中寻找名称与域名/IP地址匹配的Host处理该请求。如果没有找到,则使用Engine中指定的defaultHost来处理该请求。在第一部分的例子中,由于只有一个Hostname属性为localhost),因此该Service/Engine的所有请求都交给该Host处理。
3.根据URI选定Context/Web应用
  Tomcat根据应用的path属性与URI的匹配程度来选择Web应用处理相应请求。

③tomcat-users.xml

以下内容参考文章:https://blog.csdn.net/qq_38490457/article/details/108440922
  当我们在服务器上部署好Tomcat并运行之后,此时我们去访问Tomcat的默认页面,其提供了一些服务用于查看服务器信息:
在这里插入图片描述

服务作用
Server Status查看 Tomcat 服务的状态信息
Manager App查看Tomcat Web应用管理器
Host Manager查看Tomcat虚拟机管理器

  但当部署之后直接访问时,会报403异常,这是因为还未在Tomcat上注册用户,而Tomcat默认是关闭管理界面的访问权限的,这时,就需要在tomcat-users.xml中增加用户user和用户角色role
  事实上,从早期的Tomcat版本开始,就提供了Web版的管理控制台,他们是两个独立的Web应用程序,位于webapps目录下,分别是用于管理Hosthost-manager和用于管理Web应用的manager
  而tomcat-users.xml配置文件,包含了所有Tomcat服务器的注册用户,其中主要有用户,角色两种信息,用来控制Tomcathost-managermanager的访问权限。

<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
              version="1.0">
<!--
  <user username="admin" password="<must-be-changed>" roles="manager-gui"/>
  <user username="robot" password="<must-be-changed>" roles="manager-script"/>
-->

<!--
  <role rolename="tomcat"/>
  <role rolename="role1"/>
  <user username="tomcat" password="<must-be-changed>" roles="tomcat"/>
  <user username="both" password="<must-be-changed>" roles="tomcat,role1"/>
  <user username="role1" password="<must-be-changed>" roles="role1"/>
-->
</tomcat-users>

  对于role,其用于配置用户的权限,对于user,其用于配置单个用户信息。其中,user节点的roles属性值与role节点的rolename属性值相对应,表示当前用户具备该role节点所表示的角色权限。当然,一个用户可以具备多种权限,因此属性roles的值可以是多个rolename,多个rolename之间以英文逗号隔开即可。
例如:

<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
              version="1.0">
    <role rolename="admin-gui" />
    <role rolename="manager-gui" />
    <user username="root" password="123456" roles="admin-gui,manager-gui" />
</tomcat-users>
1.host-manager应用配置

  host-manager应用程序,主要用于虚拟主机管理,它的配置会影响到Server StatusManager App
  事实上,Tomcat启动之后,可以通过 http://localhost:8080/host-manager/html 访问该Web应用。host-manager默认添加了访问权限控制,当打开网址时,需要输入用户名和密码(conf/tomcat-users.xml中配置) 。所以要想访问该页面,需要在conf/tomcat-users.xml中配置,并分配对应的角色。
常用角色

role作用
manager-gui访问HTML页面
manager-status仅访问 “服务器状态” 页面,会使Server Status生效
manager-script访问脚本页面,以及 “服务器状态” 页面
manager-jmx访问 JMX 代理接口和 “服务器状态” 页面
2.manager应用配置

  manager应用程序,主要用于服务器管理和项目部署。
  manager的访问地址为 http://localhost:8080/manager,同样,manager也添加了页面访问控制,所以要想访问该页面,需要在conf/tomcat-users.xml中配置,并分配对应的角色。
常用角色

role作用
admin-gui访问HTML页面
admin-script访问脚本页面

④web.xml

  这里,将web.xml文件配置放在了下文的Servlet当中。

(三)请求处理流程

在这里插入图片描述

  1. 用户点击网页内容,请求被发送到本机端口8080Service作为一个进程支持TomcatConnector作为一个连接器(连接nginx或者外部请求),等着去监听HTTP1.1版本中的8080端口(Coyote:可以看做运行Connector连接器运行的环境)。
  2. 交给后端container容器中的Engine(支持容器正常运行的引擎)。
  3. 在引擎所支持的container容器内会有一个项目host(比如支付宝、淘宝等),进行交互,借助于 context做连接的服务,连接的是java的前端和后端。
  4. 交给servlet处理java后端数据与数据库交互。
  5. servlet 处理完会返回给 context(连接器)。
  6. context 返回给 Engine 引擎。
  7. Engine引擎返回给端口,最终通过映射端口的方式将页面展现给客户。

二、Servlet

(一)概述

①什么是WEB服务器

在这里插入图片描述
  Web服务器使用 HTTP 协议传输数据。在一般情况下,用户在浏览器(客户端)中键入 URL(例如www.baidu.com/static.html),并获取要读取的网页。所以服务器所做的就是向客户机发送一个网页。信息的交换采用指定请求和响应消息的格式的 HTTP 协议。
  简单来说,WEB服务器所做的工作本质上是:将某个主机上的资源映射为一个URL供外界访问。
  在早期的WEB技术当中,web应用主要用于浏览新闻等静态页面,用户通过HTTP协议请求服务器上的静态页面,服务器上的web服务器软件接收到请求后,读取URI标示的资源,再加上消息报头发送给客户端浏览器,浏览器负责解析HTML,将结果呈现出来。

②什么是Servlet

  随着时间发展,用户已经不满足于仅浏览静态页面。用户需要一些交互操作,获取一些动态结果。
  Java ServletJava服务器小程序)是一个基于Java技术的Web组件,运行在服务器端,它由Servlet容器所管理,用于生成动态的内容。 Servlet是平台独立的Java类,编写一个Servlet,实际上就是按照Servlet规范编写一个Java类。Servlet被编译为平台独立的字节码,可以被动态地加载到支持Java技术的Web服务器中运行。

1.Servlet工作流程

在这里插入图片描述

  Servlet就是运行在服务器上的一个小程序,用来处理服务器接收到的请求。比如一般的网页程序,是由我们通过浏览器来访问实现的,在这个过程中,我们的浏览器发送访问请求,服务器接收到请求,并对浏览器的请求做出相应的处理,这就是我们熟悉的B/S模型(浏览器—服务器模型)。而Servlet就是对请求做出处理的组件,运行于支持java的应用服务器中。
主要任务

  • 读取Web服务器(如tomcat)发送过来的数据(表单数据、HTTP请求数据-cookie等)。
  • 处理接收到的数据,可能调用其他业务服务或者直接访问数据库,生成需要的响应结果。
  • 将指定格式的响应结果返回给Web服务器(文本文件、二进制文件、Excel、cookie参数等)。
2.Servlet的编写方式
2.1 Servlet生命周期

  上文中提到,“编写一个Servlet,实际上就是按照Servlet规范编写一个Java类”,事实上,Servletjavax.servlet 包中定义的接口。它声明了 Servlet 生命周期的三个基本方法:init()service()destroy()。它们由每个 Servlet Class(在 SDK 中定义或自定义)实现,并由服务器在特定时机调用。
Servlet接口方法

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;
 
    ServletConfig getServletConfig();
 
    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
 
    String getServletInfo();
 
    void destroy();
}
  • init():在 Servlet生命周期的初始化阶段调用。它被传递一个实现 javax.servlet.ServletConfig接口的对象,该接口允许 ServletWeb 应用程序访问初始化参数。
  • service():负责用户的请求,当容器接收到客户端访问Servlet对象的请求时,就会调用此方法,容器会构造一个表示客户端请求信息的ServletRequsest对象和一个响应客户端的ServletResponse对象作为参数传递给service()方法,在service()方法中可以通过ServletRequset对象得到客户端的相关请求,对请求进行处理后,调用ServletResponse对象的方法设置响应信息。
  • destroy():当要销毁Servlet时,Servlet容器就会调用这个方法,用来释放所持有的资源。
  • getServletInfo():返回一个字符串,其中包含关于Servlet的信息,如:作者、版本、版权等。
  • getServletConfig():这个方法会返回由Servlet容器传给init()的ServletConfig对象。

生命周期

  以下生命周期方法并不需要用户手动调用,而是在特定的时机由容器自动调用。

  1. 实例初始化时机:客户端向Servlet容器发送HTTP请求访问Servlet时,Servlet容器首先会解析请求,检查内存中是否有该Servlet对象,如果有直接访问该Servlet对象,如果没有就要创建Servlet实例对象,然后通过init()方法实现Servlet初始化工作,且,init()方法只被调用一次
  2. 就绪/调用/服务阶段Servlet容器会为这个请求创建代表HTTP请求的ServletRequest对象和代表HTTP响应的ServletResponse对象然后将他们作为参数传递给Servletservice()方法。service()方法从ServletRequest对象中获取客户端发来的请求信息并处理该请求,通过ServletResponse对象生成响应结果。在Servlet整个生命周期内,对于Servlet的每一次请求访问,Servlet容器都会调用一次Servletservice()方法,并且创建新的ServletRequest对象和ServletResponse对象
  3. 销毁时机:当容器关闭时(应用程序停止时),会将程序中的Servlet实例进行销毁,且,在Servlet的整个生命周期中,destroy()方法也只会被调用一次。

代码演示
MyServlet01.java

package com.example.servletdemo;

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

@WebServlet("/ser01")
public class MyServlet01 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet被调用了");
    }
    /*
     * 服务器关闭或应用程序停止时会调用,只会执行一次.
     * */
    @Override
    public void destroy() {
        System.out.println("Servlet已被销毁");
    }


    @Override
    public void init() throws ServletException {
        System.out.println("Servlet被创建");
    }
}

运行结果:

06-Feb-2023 11:38:12.994 信息 [Catalina-utility-1] org.apache.catalina.startup.HostConfig.deployDirectory 把web 应用程序部署到目录 [D:\Tomcat\apache-tomcat-10.0.27\webapps\manager]
06-Feb-2023 11:38:13.045 信息 [Catalina-utility-1] org.apache.catalina.startup.HostConfig.deployDirectory Web应用程序目录[D:\Tomcat\apache-tomcat-10.0.27\webapps\manager]的部署已在[51]毫秒内完成
Servlet被创建
Servlet被调用了
D:\Tomcat\apache-tomcat-10.0.27\bin\catalina.bat stop
与目标 VM 断开连接, 地址为: ''127.0.0.1:50518',传输: '套接字''
Using CATALINA_BASE:   "C:\Users\lenovo\AppData\Local\JetBrains\IntelliJIdea2022.3\tomcat\5309f170-c52e-4938-9ade-a78de861f33d"
Using CATALINA_HOME:   "D:\Tomcat\apache-tomcat-10.0.27"
Using CATALINA_TMPDIR: "D:\Tomcat\apache-tomcat-10.0.27\temp"
Using JRE_HOME:        "C:\Users\lenovo\.jdks\openjdk-19.0.2"
Using CLASSPATH:       "D:\Tomcat\apache-tomcat-10.0.27\bin\bootstrap.jar;D:\Tomcat\apache-tomcat-10.0.27\bin\tomcat-juli.jar"
Using CATALINA_OPTS:   ""
NOTE: Picked up JDK_JAVA_OPTIONS:  --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
06-Feb-2023 11:38:36.558 信息 [main] org.apache.catalina.core.StandardServer.await 通过关闭端口接收到有效的关闭命令。正在停止服务器实例。
06-Feb-2023 11:38:36.558 信息 [main] org.apache.coyote.AbstractProtocol.pause 暂停ProtocolHandler["http-nio-8080"]
06-Feb-2023 11:38:36.572 信息 [main] org.apache.catalina.core.StandardService.stopInternal 正在停止服务[Catalina]
06-Feb-2023 11:38:36.582 信息 [main] org.apache.coyote.AbstractProtocol.stop 正在停止ProtocolHandler ["http-nio-8080"]
Servlet已被销毁
06-Feb-2023 11:38:36.584 信息 [main] org.apache.coyote.AbstractProtocol.destroy 正在摧毁协议处理器 ["http-nio-8080"]
已与服务器断开连接
2.2 Servlet编写过程
  1. 写一个Java类,且,需要实现Servlet接口或继承。
  2. 编译。
  3. 打包(将这个Java类变成Servlet组件),目录架构为(classes中放置之前写好的java类的字节码文件,lib中放置需要用到的 jar包,web.xml用来描述Servlet):
    在这里插入图片描述
  4. 部署:将第三部创建好的文件夹拷贝到servlet容器指定的文件夹下面。(注:可以将第三步创建好的文件夹先使用jar命令压缩成以.war结尾的文件,然后再拷贝)
  5. 启动Servlet容器,访问Servlet

③什么是Servlet容器

  Servlet没有main方法,不能独立运行,它必须被部署到Servlet容器中,由容器来实例化和调用Servlet的方法(如doGet()doPost()),Servlet容器在Servlet的生命周期内包容和管理Servlet。在JSP技术 推出后,管理和运行Servlet/JSP的容器也称为Web容器。
  事实上,Servlet本身是“非常被动”的一个角色,处理的事情也很简单。网络请求与响应,不是它的主要职责,它其实更偏向于业务代码。所谓的requestrespondServlet容器传给它,用来处理请求和响应的工具,但它本身不处理这些。

1.Servlet容器工作流程

  从 Servlet 对象的生命周期中,我们可以看到 Servlet 类是由类加载器动态加载到容器中的。每个请求都在自己的线程中,Servlet 对象可以同时服务多个线程(线程不安全的)。当它不再被使用时,会被 JVM 垃圾收集。
  简单来说,Servlet容器是一个装载一堆Servlet对象的容器,并且具备管理这些对象的功能。
Servlet容器和WEB服务器处理请求流程
在这里插入图片描述

  1. 用户请求访问指定URL,浏览器将此URL生成HTTP请求,并发送到Web服务器。 Web 服务器接收 HTTP 请求,并将请求转发到Servlet容器。
  2. Servlett容器创建一个线程处理该HTTP请求,并将此HTTP请求解析并映射到相应的Servlet(即为HttpServlet对象)中(这里将请求封装成了HttpRequest对象并传给HttpServlet对象),Servlet会被动态检索并加载到容器的地址空间中,开始创建servlet实例。
  3. Servlet容器调用 init() 方法进行初始化(仅在第一次加载 Servlet 时调用一次),可以将自定义的初始化参数传递给该方法,以便可以自定义servlet配置。(在Tomcat或项目的web.xml文件中进行配置)
  4. 容器调用Servletservice()方法来处理 HTTP 请求,读取请求中的数据并构建响应(这里创建了 HttpResponse对象)。Servlet 将暂时保留在容器的地址空间中,可以继续处理其它 HTTP 请求(后面的每次处理HTTP请求都是直接调用service()方法,而不需要再次调用init()方法进行初始化)。
  5. Web 服务器将动态生成的结果返回到浏览器/客户端。

:实际开发中,不同请求的逻处理辑是不同的这时,就需要抽取出来做成servlet,交给程序员自己编写,同时,这也是三层架构中service层的由来。但是servlet并不擅长往浏览器输出HTML页面,所以就出现了JSP。等Spring家族出现后,Servlet开始退居幕后,取而代之的是SpringMVCSpringMVC的核心组件DispacterServlet其实本质是一个Servlet,但是它已经自立门户,在原来HttpServlet的基础上,有封装了一层逻辑。

2.Tomcat与Servlet容器的关系

  在前文中提到过,Tomcat的核心组件包含WEB容器、JSP容器、Servlet容器三大部分,这就使得TomcatIISApacheWeb服务器一样,具有处理HTML页面的功能,且,由于它还是一个ServletJSP容器,独立的Servlet容器是Tomcat的默认模式。不过,Tomcat处理静态HTML的能力不如Apache,我们可以将ApacheTomcat集成在 一起使用,Apache作为HTTP Web服务器,Tomcat作为Web容器。
  常见的Servlet容器有很多,如,TomcatJettyWebLogic ServerWebSphereJBoss等。

④Servlet开发流程

  事实上,我们的Web应用完全是基于http协议的。而http有请求报文(request)和响应报文(response),请求报文就是浏览器向服务器发送的数据形成的数据对象,同理,响应报文就是服务器向浏览器发送的数据形成的数据对象。当登录某网站并提交信息时,就会被http协议封装成请求报文的形式发送到服务器的,这样,servlet就能够读取请求报文的类容,并对报文进行处理。

1.新建项目

引入依赖

<dependency>
	<groupId>javax.servlet</groupId>
	<artifactId>javax.servlet-api</artifactId>
	<version>4.0.1</version>
</dependency>

在这里插入图片描述

2.创建目录结构

  在Tomcat中提到过,web 应用程序有特定的组织格式,是一种层次型目录结构,通常包含了 servlet 代码文件、HTMLJSP 页面文件、类文件、部署描述符文件等等。
  在main目录下创建webapp目录,并在其内部创建WEB-INF目录,和一个web.xml文件,此时,IDEA会自动检测到有Web目录结构:
在这里插入图片描述
点击确定,此时,项目结构当中就会自动添加Web目录结构:
在这里插入图片描述
web.xml当中填写代码:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">
</web-app>
3.编写代码

创建HelloServlet.java

package org.example;

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("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        System.out.println("Hello Servet!");
        response.getWriter().write("Hello!");
    }
}

(关于为什么这么些,请见下文)
  在前文的叙述中可知,servlet方法是由Servlet容器来调用,即,main 方法已经被包含在 Tomcat 里, 我们写的代码会被Tomcat在合适的时机调用起来。此时我们写的代码并不是一个完整的程序, 而是 Tomcat 这个程序的一小部分逻辑。而能够被Tomcat调用需要满足以下条件:

  • 创建的类继承于HttpServlet
  • 这个类需要关联上一个 HTTP 的路径。
  • 这个类需要实现 doXXX 方法。

当这三个条件都满足之后, Tomcat 就可以找到这个类, 并且在合适的时机进行调用。

4.打包程序
  • jar包:普通的java 程序打包的结果. 里面会包含一些 .class 文件。
  • war包java web 的程序,里面除了会包含 .class 文件之外, 还会包含 HTML, CSS, JavaScript, 图 片, 以及其他的 jar 包,打成 war 包格式才能被 Tomcat 识别。

编写pom.xml使之打包方式为war包,引入打包插件,并指定打包后的包名:

<groupId>org.example</groupId>
<artifactId>DemoTwo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<build>
	<finalName>WebDemo</finalName>
    <packaging>war</packaging>
    <plugins>
    	<plugin>
    		<groupId>org.apache.maven.plugins</groupId>
    		<artifactId>maven-war-plugin</artifactId>
    		<version>3.3.1</version>
    	</plugin>
    </plugins>
</build>
5.部署项目

找到打包后得到的war包:
在这里插入图片描述
war包放在Tomcat目录下:
在这里插入图片描述
之后,启动Tomcat,使用以下URL访问即可:

http://127.0.0.1:8080/WebDemo/hello

事实上,可在编辑配置当中简化:
在这里插入图片描述

(二)核心对象

  Servlet API主要包含以下四个Java包(这里是Tomcat 10.0.27javaxjakarta是一样的,只是改了名字):

  • jakarta.servlet:其中包含定义servletservlet容器之间契约的类和接口。
  • jakarta.servlet:其中包含定义HTTP ServletServlet之间的关系。
  • jakarta.servlet.annotation:包含标注servletFilterListener的标注。
  • jakarta.servlet.descriptor:其中包含提供程序化登录Web应用程序的配置信息的类型。

在平时开发当中,Servlet继承体系如下:
在这里插入图片描述

①Servlet

  Servlet,它是所有Servlet类必须直接或者间接实现的一个接口。在编写实现ServletServlet类时,直接实现它。在扩展实现这个这个接口的类时,间接实现它,它的方法也反映了Servlet的所有生命周期:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package jakarta.servlet;

import java.io.IOException;

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}
  • init():在 Servlet生命周期的初始化阶段调用。它被传递一个实现 javax.servlet.ServletConfig接口的对象,该接口允许 ServletWeb 应用程序访问初始化参数。
  • service():负责用户的请求,当容器接收到客户端访问Servlet对象的请求时,就会调用此方法,容器会构造一个表示客户端请求信息的ServletRequsest对象和一个响应客户端的ServletResponse对象作为参数传递给service()方法,在service()方法中可以通过ServletRequset对象得到客户端的相关请求,对请求进行处理后,调用ServletResponse对象的方法设置响应信息。
  • destroy():当要销毁Servlet时,Servlet容器就会调用这个方法,用来释放所持有的资源。
  • getServletInfo():返回一个字符串,其中包含关于Servlet的信息,如:作者、版本、版权等。
  • getServletConfig():这个方法会返回由Servlet容器传给init()的ServletConfig对象。

②ServletConfig

  Tomcat解析web.xml文件时,会将web.xml文件中<servlet></servlet>标签中的配置信息自动包装到ServletConfig对象中,即,ServletConfig接口代表当前Servletweb.xml中的配置信息。事实上,Servlet对象和ServletConfig对象都是Tomcat服务器创建。并且默认情况下,他们都是在用户发送第一次请求的时候创建,且,Tomcat服务器调用Servlet对象的init方法的时候需要传一个ServletConfig对象的参数给init方法。

1.方法说明
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package jakarta.servlet;

import java.util.Enumeration;

public interface ServletConfig {
    String getServletName();

    ServletContext getServletContext();

    String getInitParameter(String var1);

    Enumeration<String> getInitParameterNames();
}
  • String getServletName():获取web.xml配置文件当中<servlet-name></servlet-name>中的值。
  • ServletContext getServletContext():获取ServletContext对象。
  • String getInitParameter(String var1):通过初始化参数的name来获取value
  • Enumeration<String>:获取所有初始化参数的名称。
2.两种常用获取方法
  1. 直接调用getServletConfig()方法可获取到ServletConfig对象,并进行方法的调用。这是因为GenericServlet实现了ServletServletConfigSerializable,类中包含了ServletConfig对象属性,并提供了获取方法,而HttpServlet等,都继承了GenericServlet
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package jakarta.servlet;

import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.ResourceBundle;

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
	...
    private transient ServletConfig config;
    ...
    public ServletConfig getServletConfig() {
        return this.config;
    }
}

2.在自己编写的Servlet类当中直接使用this去调用ServletConfig的四种方法。因为自己编写的Servlet类会继承实现GenericServlet类,所以直接用this调用其方法也行。

3.使用案例

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">
    <servlet>
        <servlet-name>MyServlet</servlet-name>
        <servlet-class>com.example.servletdemo.MyServlet01</servlet-class>
        <init-param>
            <param-name>username</param-name>
            <param-value>admin</param-value>
        </init-param>
        <init-param>
            <param-name>password</param-name>
            <param-value>123456</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>MyServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
</web-app>

MyServlet01.java

package com.example.servletdemo;

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServlet;

import java.util.Enumeration;

public class MyServlet01 extends HttpServlet {
    @Override
    public void init(){
        //获取ServletConfig对象
        ServletConfig config=this.getServletConfig();
        //1.通过getServletName获取<servlet-name></servlet-name>
        String ServletName=config.getServletName();
        //2.通过getInitParameter指定<param-name>获取<param-value>
        String ServletUserName=config.getInitParameter("username");
        String ServletPassword=config.getInitParameter("password");
        System.out.println("ServletName为:"+ServletName+",UserName为:"+ServletUserName+",ServletPassword为:"+ServletPassword);
        //3.通过getInitParameterNames获取包含所有<param-name>的Enumeration<String>对象
        Enumeration<String>initParameterNames=config.getInitParameterNames();
        while(initParameterNames.hasMoreElements()){
            String parameterName=initParameterNames.nextElement();
            String parameterValue=config.getInitParameter(parameterName);
            System.out.println("Name:"+parameterName+",Value:"+parameterValue);
        }
        //4.通过getServletContext获取ServletContext对象
        System.out.println(config.getServletContext());
    }
}

注:不使用ServletConfig对象,直接使用this调用方法亦可。
访问URL

http://localhost:8080/hello

运行结果
在这里插入图片描述

③ServletContext

  ServletContext,应用程序上下文,表示Servlet应用程序。每个Web应用程序都只有一个ServletContext对象,且,全局唯一,该工程内所有Servlet都共享这个对象(域对象),可荣国该对象在不同的Serlvet之间传递和共享数据。在将一个应用程序同时部署到多个容器的分布式环境中,每台Java虚拟机上的Web应用都会有一个ServletContext对象。
  事实上,ServletContext对象在WEB服务器启动的时候由WEB服务器创建,在服务器关闭的时候销毁

这就是ServletContext对象的生命周期
两大作用

  • 作为域对象来共享数据,此时数据在整个应用程序中共享。
  • 保存了当前应用程序相关信息。

在web.xml中配置全局参数

  <!-- 全局配置参数,因为不属于任何一个servlet,但是所有的servlet都可以通过servletContext读取这个数据 -->
  <context-param>
         <param-name>param1</param-name>
         <param-value>value1</param-value>
  </context-param>
   <context-param>
         <param-name>param2</param-name>
         <param-value>value2</param-value>
  </context-param>
1.方法说明
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package jakarta.servlet;

import jakarta.servlet.descriptor.JspConfigDescriptor;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.Map;
import java.util.Set;

public interface ServletContext {
    String TEMPDIR = "jakarta.servlet.context.tempdir";
    String ORDERED_LIBS = "jakarta.servlet.context.orderedLibs";

    String getContextPath();

    ServletContext getContext(String var1);

    int getMajorVersion();

    int getMinorVersion();

    int getEffectiveMajorVersion();

    int getEffectiveMinorVersion();

    String getMimeType(String var1);

    Set<String> getResourcePaths(String var1);

    URL getResource(String var1) throws MalformedURLException;

    InputStream getResourceAsStream(String var1);

    RequestDispatcher getRequestDispatcher(String var1);

    RequestDispatcher getNamedDispatcher(String var1);

    /** @deprecated */
    @Deprecated
    Servlet getServlet(String var1) throws ServletException;

    /** @deprecated */
    @Deprecated
    Enumeration<Servlet> getServlets();

    /** @deprecated */
    @Deprecated
    Enumeration<String> getServletNames();

    void log(String var1);

    /** @deprecated */
    @Deprecated
    void log(Exception var1, String var2);

    void log(String var1, Throwable var2);

    String getRealPath(String var1);

    String getServerInfo();

    String getInitParameter(String var1);

    Enumeration<String> getInitParameterNames();

    boolean setInitParameter(String var1, String var2);

    Object getAttribute(String var1);

    Enumeration<String> getAttributeNames();

    void setAttribute(String var1, Object var2);

    void removeAttribute(String var1);

    String getServletContextName();

    ServletRegistration.Dynamic addServlet(String var1, String var2);

    ServletRegistration.Dynamic addServlet(String var1, Servlet var2);

    ServletRegistration.Dynamic addServlet(String var1, Class<? extends Servlet> var2);

    ServletRegistration.Dynamic addJspFile(String var1, String var2);

    <T extends Servlet> T createServlet(Class<T> var1) throws ServletException;

    ServletRegistration getServletRegistration(String var1);

    Map<String, ? extends ServletRegistration> getServletRegistrations();

    FilterRegistration.Dynamic addFilter(String var1, String var2);

    FilterRegistration.Dynamic addFilter(String var1, Filter var2);

    FilterRegistration.Dynamic addFilter(String var1, Class<? extends Filter> var2);

    <T extends Filter> T createFilter(Class<T> var1) throws ServletException;

    FilterRegistration getFilterRegistration(String var1);

    Map<String, ? extends FilterRegistration> getFilterRegistrations();

    SessionCookieConfig getSessionCookieConfig();

    void setSessionTrackingModes(Set<SessionTrackingMode> var1);

    Set<SessionTrackingMode> getDefaultSessionTrackingModes();

    Set<SessionTrackingMode> getEffectiveSessionTrackingModes();

    void addListener(String var1);

    <T extends EventListener> void addListener(T var1);

    void addListener(Class<? extends EventListener> var1);

    <T extends EventListener> T createListener(Class<T> var1) throws ServletException;

    JspConfigDescriptor getJspConfigDescriptor();

    ClassLoader getClassLoader();

    void declareRoles(String... var1);

    String getVirtualServerName();

    int getSessionTimeout();

    void setSessionTimeout(int var1);

    String getRequestCharacterEncoding();

    void setRequestCharacterEncoding(String var1);

    String getResponseCharacterEncoding();

    void setResponseCharacterEncoding(String var1);
}

方法声明说明
String getInitParameter(String name)通过初始化参数的name获取value,与ServletConfig不同,ServletContext获取的是<context-param></context-param>中的信息。
Enumeration<String> getInitParameterNames()获取所有的初始化参数的name,与ServletConfig不同,ServletContext获取的是<context-param></context-param>中的信息。
public String getContextPath()获取应用的根路径,相当于拿到了项目名。
public String getRealPath(String path)输入文件的相对路径,获取文件的绝对路径
public void setAttribute(String name, Object value)ServletContext域中存数据。
public Object getAttribute(String name)ServletContext域中取数据。
public void removeAttribute(String name)删除ServletContext域中的数据。
2.三种常用获取方法
//1.通过ServletConfig对象来获取
ServletConfig config=this.getServletConfig();
ServletContext context=config.getServletContext();
//2.通过this来获取
ServletContext context=this.getServletContext();
//3.通过session对象获取
ServletContext servletContext= request.getSession().getServletContext();
3.使用案例

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">
    <context-param>
        <param-name>name1</param-name>
        <param-value>value1</param-value>
    </context-param>
    <context-param>
        <param-name>name2</param-name>
        <param-value>value2</param-value>
    </context-param>
    <servlet>
        <servlet-name>MyServlet</servlet-name>
        <servlet-class>com.example.servletdemo.MyServlet01</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>MyServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
</web-app>

MyServlet01.java

package com.example.servletdemo;

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServlet;
import java.util.Enumeration;

public class MyServlet01 extends HttpServlet {
    @Override
    public void init(){
        ServletContext context=this.getServletContext();
        //1.遍历设置的所有全局配置参数
        Enumeration<String>enumeration=context.getInitParameterNames();
        while(enumeration.hasMoreElements()){
            String paramName=enumeration.nextElement();
            String paramValue=context.getInitParameter(paramName);
            System.out.println("paramName:"+paramName+",paramValue:"+paramValue);
        }
        //根据web.xml相对路径获取其当前绝对路径
        System.out.println(context.getRealPath("/WEB-INF/web.xml"));
    }
}

在这里插入图片描述

④GenericServlet

  前面我们编写Servlet一直是通过实现Servlet接口来编写的,但是,使用这种方法,则必须要实现Servlet接口中定义的所有的方法,即使有一些方法中没有任何东西也要去实现,并且还需要自己手动的维护ServletConfig这个对象的引用,并传递给init()作为初始化参数,这样去实现Servlet是比较麻烦的。
  而GenericServlet抽象类为ServletServletConfig接口中的方法提供了默认的实现,而不需要手动编写。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package jakarta.servlet;

import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.ResourceBundle;

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    private static final long serialVersionUID = -8592279577370996712L;
    private static final String LSTRING_FILE = "jakarta.servlet.LocalStrings";
    private static ResourceBundle lStrings = ResourceBundle.getBundle("jakarta.servlet.LocalStrings");
    private transient ServletConfig config;

    public GenericServlet() {
    }

    public void destroy() {
    }

    public String getInitParameter(String name) {
        ServletConfig sc = this.getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
        } else {
            return sc.getInitParameter(name);
        }
    }

    public Enumeration<String> getInitParameterNames() {
        ServletConfig sc = this.getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
        } else {
            return sc.getInitParameterNames();
        }
    }

    public ServletConfig getServletConfig() {
        return this.config;
    }

    public ServletContext getServletContext() {
        ServletConfig sc = this.getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
        } else {
            return sc.getServletContext();
        }
    }

    public String getServletInfo() {
        return "";
    }

    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }

    public void init() throws ServletException {
    }

    public void log(String msg) {
        this.getServletContext().log(this.getServletName() + ": " + msg);
    }

    public void log(String message, Throwable t) {
        this.getServletContext().log(this.getServletName() + ": " + message, t);
    }

    public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    public String getServletName() {
        ServletConfig sc = this.getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
        } else {
            return sc.getServletName();
        }
    }
}
1. init()方法

  在GenericServlet抽象类当中有两个init()方法:

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
	...
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }

    public void init() throws ServletException {
    }
    ...
}

  对于第一个init()方法,它会将ServletConfig参数赋给了一个内部的ServletConfig引用从而来保存ServletConfig对象,不需要程序员自己去维护ServletConfig
  由于抽象类无法产生实例,需要另一个类去继承这个抽象类,那么就会发生方法覆盖的问题,如果在类中覆盖了GenericServlet抽象类的init()方法,那么程序员就必须手动的去维护ServletConfig对象了,还得调用super.init(servletConfig)方法去调用父类GenericServlet的初始化方法来保存ServletConfig对象,这样会给程序员带来很大的麻烦。对于第二个不带参数的init()方法,它由第一个带参数的init()的方法进行调用,使得,程序员如果需要覆盖这个GenericServlet的初始化方法,则只需要覆盖那个不带参数的init( )方法就好了,此时,servletConfig对象仍然有GenericServlet保存着。

2. service()方法
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

  service()方法,是抽象类GenericServlet中唯一的一个抽象方法,它把处理请求的任务交给了子类。子类必须实现该方法。

⑤HttpServlet

   HttpServlet抽象类是继承于GenericServlet抽象类而来的,本身也是个抽象类,不能直接进行实例化,必须给出子类才能实例化(即不能直接使用,只能继承它)。HttpServlet是采用Http协议进行通信的,所以它也实现Http协议中的多种方法,每种方法可以处理相应类型的请求
  使用HttpServlet抽象类时,还需要借助分别代表Servlet请求和Servlet响应的HttpServletRequestHttpServletRequest接口扩展于javax.servlet.ServletRequest接口)和HttpServletResponseHttpServletResponse接口扩展于javax.servlet.servletResponse接口)对象。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package jakarta.servlet.http;

import jakarta.servlet.GenericServlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.ResourceBundle;

public abstract class HttpServlet extends GenericServlet {
    private static final long serialVersionUID = 8466325577512134784L;
    private static final String METHOD_DELETE = "DELETE";
    private static final String METHOD_HEAD = "HEAD";
    private static final String METHOD_GET = "GET";
    private static final String METHOD_OPTIONS = "OPTIONS";
    private static final String METHOD_POST = "POST";
    private static final String METHOD_PUT = "PUT";
    private static final String METHOD_TRACE = "TRACE";
    private static final String HEADER_IFMODSINCE = "If-Modified-Since";
    private static final String HEADER_LASTMOD = "Last-Modified";
    private static final String LSTRING_FILE = "jakarta.servlet.http.LocalStrings";
    private static ResourceBundle lStrings = ResourceBundle.getBundle("jakarta.servlet.http.LocalStrings");

    public HttpServlet() {
    }

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_get_not_supported");
        resp.sendError(this.getMethodNotSupportedCode(protocol), msg);
    }

    protected long getLastModified(HttpServletRequest req) {
        return -1L;
    }

    protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        NoBodyResponse response = new NoBodyResponse(resp);
        this.doGet(req, response);
        response.setContentLength();
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_post_not_supported");
        resp.sendError(this.getMethodNotSupportedCode(protocol), msg);
    }

    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_put_not_supported");
        resp.sendError(this.getMethodNotSupportedCode(protocol), msg);
    }

    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_delete_not_supported");
        resp.sendError(this.getMethodNotSupportedCode(protocol), msg);
    }

    private int getMethodNotSupportedCode(String protocol) {
        switch (protocol) {
            case "HTTP/0.9":
            case "HTTP/1.0":
                return 400;
            default:
                return 405;
        }
    }

    private Method[] getAllDeclaredMethods(Class<? extends HttpServlet> c) {
        Class<?> clazz = c;

        Method[] allMethods;
        for(allMethods = null; !clazz.equals(HttpServlet.class); clazz = clazz.getSuperclass()) {
            Method[] thisMethods = clazz.getDeclaredMethods();
            if (allMethods != null && allMethods.length > 0) {
                Method[] subClassMethods = allMethods;
                allMethods = new Method[thisMethods.length + allMethods.length];
                System.arraycopy(thisMethods, 0, allMethods, 0, thisMethods.length);
                System.arraycopy(subClassMethods, 0, allMethods, thisMethods.length, subClassMethods.length);
            } else {
                allMethods = thisMethods;
            }
        }

        return allMethods != null ? allMethods : new Method[0];
    }

    protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Method[] methods = this.getAllDeclaredMethods(this.getClass());
        boolean ALLOW_GET = false;
        boolean ALLOW_HEAD = false;
        boolean ALLOW_POST = false;
        boolean ALLOW_PUT = false;
        boolean ALLOW_DELETE = false;
        boolean ALLOW_TRACE = true;
        boolean ALLOW_OPTIONS = true;

        for(int i = 0; i < methods.length; ++i) {
            String methodName = methods[i].getName();
            if (methodName.equals("doGet")) {
                ALLOW_GET = true;
                ALLOW_HEAD = true;
            } else if (methodName.equals("doPost")) {
                ALLOW_POST = true;
            } else if (methodName.equals("doPut")) {
                ALLOW_PUT = true;
            } else if (methodName.equals("doDelete")) {
                ALLOW_DELETE = true;
            }
        }

        StringBuilder allow = new StringBuilder();
        if (ALLOW_GET) {
            allow.append("GET");
        }

        if (ALLOW_HEAD) {
            if (allow.length() > 0) {
                allow.append(", ");
            }

            allow.append("HEAD");
        }

        if (ALLOW_POST) {
            if (allow.length() > 0) {
                allow.append(", ");
            }

            allow.append("POST");
        }

        if (ALLOW_PUT) {
            if (allow.length() > 0) {
                allow.append(", ");
            }

            allow.append("PUT");
        }

        if (ALLOW_DELETE) {
            if (allow.length() > 0) {
                allow.append(", ");
            }

            allow.append("DELETE");
        }

        if (ALLOW_TRACE) {
            if (allow.length() > 0) {
                allow.append(", ");
            }

            allow.append("TRACE");
        }

        if (ALLOW_OPTIONS) {
            if (allow.length() > 0) {
                allow.append(", ");
            }

            allow.append("OPTIONS");
        }

        resp.setHeader("Allow", allow.toString());
    }

    protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String CRLF = "\r\n";
        StringBuilder buffer = (new StringBuilder("TRACE ")).append(req.getRequestURI()).append(" ").append(req.getProtocol());
        Enumeration<String> reqHeaderEnum = req.getHeaderNames();

        while(reqHeaderEnum.hasMoreElements()) {
            String headerName = (String)reqHeaderEnum.nextElement();
            buffer.append(CRLF).append(headerName).append(": ").append(req.getHeader(headerName));
        }

        buffer.append(CRLF);
        int responseLength = buffer.length();
        resp.setContentType("message/http");
        resp.setContentLength(responseLength);
        ServletOutputStream out = resp.getOutputStream();
        out.print(buffer.toString());
    }

    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader("If-Modified-Since");
                if (ifModifiedSince < lastModified) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }

    }

    private void maybeSetLastModified(HttpServletResponse resp, long lastModified) {
        if (!resp.containsHeader("Last-Modified")) {
            if (lastModified >= 0L) {
                resp.setDateHeader("Last-Modified", lastModified);
            }

        }
    }

    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
            HttpServletRequest request = (HttpServletRequest)req;
            HttpServletResponse response = (HttpServletResponse)res;
            this.service(request, response);
        } else {
            throw new ServletException("non-HTTP request or response");
        }
    }
}
1.service方法

  HttpServlet抽象类覆盖了GenericServlet抽象类中的Service( )方法,并且添加了一个自己独有的Service(HttpServletRequest request,HttpServletResponse)方法。
GenericServlet.java

public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

HttpServlet.java

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
    HttpServletRequest request;
    HttpServletResponse response;
    try {
        request = (HttpServletRequest)req;
        response = (HttpServletResponse)res;
    } catch (ClassCastException var6) {
        throw new ServletException("non-HTTP request or response");
    }
 
    this.service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String method = req.getMethod();
    long lastModified;
    if (method.equals("GET")) {
        lastModified = this.getLastModified(req);
        if (lastModified == -1L) {
            this.doGet(req, resp);
        } else {
            long ifModifiedSince = req.getDateHeader("If-Modified-Since");
            if (ifModifiedSince < lastModified) {
                this.maybeSetLastModified(resp, lastModified);
                this.doGet(req, resp);
            } else {
                resp.setStatus(304);
            }
        }
    } else if (method.equals("HEAD")) {
        lastModified = this.getLastModified(req);
        this.maybeSetLastModified(resp, lastModified);
        this.doHead(req, resp);
    } else if (method.equals("POST")) {
        this.doPost(req, resp);
    } else if (method.equals("PUT")) {
        this.doPut(req, resp);
    } else if (method.equals("DELETE")) {
        this.doDelete(req, resp);
    } else if (method.equals("OPTIONS")) {
        this.doOptions(req, resp);
    } else if (method.equals("TRACE")) {
        this.doTrace(req, resp);
    } else {
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[]{method};
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(501, errMsg);
    }
 
}

  HttpServletservice()方法继承自父类,它只接收HTTP请求,这里把相应的requestresponse转换为了基于HTTP协议的相应对象,最终将请求转到带protected关键字的service()方法中。
  protected service()方法根据请求的类型将请求转发到相应的doDelete()doGet()doOptions()doPost()doPut()等方法中。所以开发自己的Servlet时,不需要覆盖HttpServletservice()方法,因为该方法最终将请求转发相相应的doXXX方法中,只需要覆盖相应的doXXX方法进行请求处理即可。如果重写了该方法,那么就不会根据方法名调用其他具体的方法了。

2.doXxx方法

  在HTTP/1.1协议中共定义了八种请求方式(GETPOSTHEADOPTIONSPUTDELETETRACECONNECT方式),来表明Request-URL指定的资源不同的操作方式。
  而在HttpServlet当中,包含了doGetdoPostdoHeaddoPutdoTracedoOptionsdoDelete七种HTTP协议方法,用于实现收到这些请求后进行的处理操作,目前,浏览器只支持POSTGET请求,故而实现具体业务逻辑时,只需要实现这两种方法即可。

⑥ServletRequest

  ServletRequest封装了请求信息,可从中获取到任何请求的信息。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package jakarta.servlet;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;

public interface ServletRequest {
    Object getAttribute(String var1);

    Enumeration<String> getAttributeNames();

    String getCharacterEncoding();

    void setCharacterEncoding(String var1) throws UnsupportedEncodingException;

    int getContentLength();

    long getContentLengthLong();

    String getContentType();

    ServletInputStream getInputStream() throws IOException;

    String getParameter(String var1);

    Enumeration<String> getParameterNames();

    String[] getParameterValues(String var1);

    Map<String, String[]> getParameterMap();

    String getProtocol();

    String getScheme();

    String getServerName();

    int getServerPort();

    BufferedReader getReader() throws IOException;

    String getRemoteAddr();

    String getRemoteHost();

    void setAttribute(String var1, Object var2);

    void removeAttribute(String var1);

    Locale getLocale();

    Enumeration<Locale> getLocales();

    boolean isSecure();

    RequestDispatcher getRequestDispatcher(String var1);

    /** @deprecated */
    @Deprecated
    String getRealPath(String var1);

    int getRemotePort();

    String getLocalName();

    String getLocalAddr();

    int getLocalPort();

    ServletContext getServletContext();

    AsyncContext startAsync() throws IllegalStateException;

    AsyncContext startAsync(ServletRequest var1, ServletResponse var2) throws IllegalStateException;

    boolean isAsyncStarted();

    boolean isAsyncSupported();

    AsyncContext getAsyncContext();

    DispatcherType getDispatcherType();
}

方法声明说明
String getParameter(string name)根据请求参数的名字来获取参数值,若参数值有多个,则返回第一个。
String[] getParameterValues(string name)根据请求参数的名字来获取参数值数组。
Enumeration getParameterNames()返回所有请求参数名的Enumeration对象
Map<String, String[]> getParameterMap()返回请求参数的键值对Map对象。
int getContentLength()获取请求正文的长度。
String getContentType()获取请求正文的MIME类型。
ServletInputStream getInputStream() throws IOException;返回用于读取请求正文的输入流。
String getLocalAddr()返回服务器的IP地址。
String getLocalName()返回服务器端的主机名
int getLocalPort()返回服务器端的端口号
String getProtocol()返回客户端和服务器端通信所用的协议的名称及版本号。
String getRemoteAddr()返回客户端的IP地址。
String getRemoteHost()返回客户端的主机名。
int getLocalPort()返回客户端的端口号。

以下三个方法在请求范围共享数据:

方法声明说明
void setAttribute(String var1, Object var2);在请求范围内保存一个属性,参数name标识属性名,参数object标识属性值。
Object getAttribute(String var1)根据name参数给定的属性名,返回请求范围内的匹配的属性值。
void removeAttribute(String var1)从请求范围内删除一个属性。

⑦HttpServletRequest

  HttpServletRequest对象:主要是用来接收客户端发送过来的请求信息,如,请求的参数、发送的头信息等都属于客户端发来的信息,service()方法中形参接收的是HttpServletRequest接口的实例化对象,表示该对象只要用在HTTP协议上,该对象是由Tomcat封装好传递过来的。
  HttpServletRequestServletRequest的唯一一个子接口,之所以不将二者合并为一个,是因为现在主要用的协议是HTTP协议,但以后可能会出现更多新的协议,若想要支持新协议,只需要直接继承ServletRequest接口即可。

1.HTTP请求

  HTTP,即为超文本传输协议,是一种实现客户端和服务器之间通信的响应协议,基于客户端/服务端(C/S)的架构模型,通过一个可靠的链接来交换信息,是一个无状态的请求/响应协议。
  HTTP用作客户端和服务器之间的请求,客户端(浏览器)会向服务器提交HTTP请求;然后服务器向客户端返回响应;其中响应包含有关请求的状态信息,还可能包含请求的内容。其中,HTTP使用统一资源标识符(Uniform Resource Identifiers, URI)来传输数据和建立连接。

1.1 八种请求方法

  在HTTP/1.1协议中共定义了八种请求方式(GETPOSTHEADOPTIONSPUTDELETETRACECONNECT方式),来表明Request-URL指定的资源不同的操作方式。

  1. OPTIONS:返回服务器针对特定资源所支持的HTTP请求方法,也可以利用向web服务器发送‘*’的请求来测试服务器的功能性。
  2. HEADHEAD方法与GET方法相同,但没有响应体,仅传输状态行和标题部分。这对于恢复相应头部编写的元数据非常有用,而无需传输整个内容。
  3. GET:用来获取数据的,只是用来查询数据,不对服务器的数据做任何的修改,新增,删除等操作。get请求会把请求的参数附加在URL后面,这样是不安全的,在处理敏感数据时不用,或者参数做加密处理。
  4. POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
  5. PUT:数据发送到服务器以创建或更新资源。
  6. TRACETRACE方法用于沿着目标资源的路径执行消息环回测试;它回应收到的请求,以便客户可以看到中间服务器进行了哪些(假设任何)进度或增量。
  7. DELETE:用来删除指定的资源,它会删除URI给出的目标资源的所有当前内容。
  8. CONNECTCONNECT方法用来建立到给定URI标识的服务器的隧道;它通过简单的TCP / IP隧道更改请求连接,通常实使用解码的HTTP代理来进行SSL编码的通信(HTTPS)。

  • 对于GET请求:要求服务器将URL定位的资源放在响应报文的数据部分,回送给客户端。
    使用GET方法时,请求参数和对应的值附加在URL后面,利用一个问号 ? 代表URL的结尾与请求参数的开始,传递参数长度受限制。例如,/index.jsp?id=100&op=bind,这样通过GET方式传递的数据直接表示在地址中,显然,这种方式不适合传送私密数据。另外,由于不同的浏览器对地址的字符限制也有所不同,一般最多只能识别1024个字符,所以如果需要传送大量数据的时候,也不适合使用GET方式
  • 对于POST请求:将请求参数封装在HTTP请求数据中,以名称/值的形式出现,可以传输大量数据,这样POST方式对传送的数据大小没有限制,而且也不会显示在URL中。
  • 对于HEAD请求:类似于GET请求,但是服务端接受到HEAD请求后只返回响应头,而不会发送响应内容。当我们只需要查看某个页面的状态的时候,使用HEAD是非常高效的,因为在传输的过程中省去了页面内容
1.2请求信息

请求报文的一般格式
在这里插入图片描述

以下内容部来自:https://blog.csdn.net/qq_34666857/article/details/104677407
  客户端HTTP请求的信息主要为三部分信息:请求行、请求头、请求正文。
在这里插入图片描述

1.2.1 请求行

  请求行由请求方法字段、URL字段和HTTP协议版本字段3个字段组成,包含请求方法、请求资源名、请求路径等信息,它们用空格分隔。例如,GET /index.html HTTP/1.1

1.2.2 请求头

  请求头部由关键字/值对组成,每行一对,关键字和值用英文冒号:分隔,用于通知服务器有关于客户端请求的基本信息。
常见请求头

请求头名说明
HostURL中主机名称和端口号,允许多个域名同处一个IP地址,即虚拟主机。
Connection表示是否需要持久连接。若为keep-alive,则 当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接;若为close,则一个Request完成后,客户端和服务器之间用于传输HTTP数据的TCP连接会关闭, 当客户端再次发送Request,需要重新建立TCP连接。
Content-Length表示消息正文的长度。
Cache-Control指定请求和响应的缓存机制。默认为private,则响应只能够作为私有的缓存,不能再用户间共享;若为public,则响应会被缓存,并且在多用户间共享;若为must-revalidate,则响应在特定条件下会被重用,以满足接下来的请求,但是它必须到服务器端去验证它是不是仍然是最新的;若为no-cache,则响应不会被缓存,而是实时向服务器端请求资源;若为max-age=响应时间,可设置缓存最大有效时间,单位为秒;若为no-store,则在任何条件下,响应都不会被缓存。
Accept浏览器可以接受的MIME类型。
Origin用来说明最初请求是从哪发起的,只适用于POST
User-Agent浏览器相关信息,如,浏览器类型、浏览器版本号、浏览器语言、客户的操作系统和版本。
Accept-Encoding浏览器可以进行解码的数据编码方式。
Accept-Charset浏览器可接受的字符集。
Content-Type请求内容的MIME内容。
Referer包括一个URL,表示从哪个地址出发访问到当前请求地址。
Accept-Language浏览器所希望的语言种类。
Cookie客户端的Cookie信息。
1.2.3 请求数据

  如果Http请求中携带参数(url中携带的参数),可以在Query String Parameters中查看到(本次为了显示此部分内容,手动的在url中添加了?username=liaizhu&password=123456):
在这里插入图片描述
如果Http请求方式为post,装在请求体中的数据可以在Form Data中看到:
在这里插入图片描述
Query String ParametersForm Data是可以共存的,即Http协议既允许我们通过url传参,也可以通过请求体传参,GetPost方式更多的是对请求进行规范化,开发中还是尽量只使用一种方式传参。

2.方法说明

  在Servlet API中,ServletRequest接口被定义为用于封装请求的信息,其对象由Servlet容器在用户请求Servlet时创建并传入Servletservice()的方法中。
  而HttpServletRequest接口继承了ServletRequest接口,是专用于HTTP协议的子接口,用于封装HTTP请求信息。在HttpServlet类的service()方法中,传入的ServletRequest对象被强制转换为HttpServletRequest对象来进行HTTP请求信息的处理。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package jakarta.servlet.http;

import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import java.io.IOException;
import java.security.Principal;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;

public interface HttpServletRequest extends ServletRequest {
    String BASIC_AUTH = "BASIC";
    String FORM_AUTH = "FORM";
    String CLIENT_CERT_AUTH = "CLIENT_CERT";
    String DIGEST_AUTH = "DIGEST";

    String getAuthType();

    Cookie[] getCookies();

    long getDateHeader(String var1);

    String getHeader(String var1);

    Enumeration<String> getHeaders(String var1);

    Enumeration<String> getHeaderNames();

    int getIntHeader(String var1);

    default HttpServletMapping getHttpServletMapping() {
        return new HttpServletMapping() {
            public String getMatchValue() {
                return "";
            }

            public String getPattern() {
                return "";
            }

            public String getServletName() {
                return "";
            }

            public MappingMatch getMappingMatch() {
                return null;
            }

            public String toString() {
                return "MappingImpl{matchValue=" + this.getMatchValue() + ", pattern=" + this.getPattern() + ", servletName=" + this.getServletName() + ", mappingMatch=" + this.getMappingMatch() + "} HttpServletRequest {" + HttpServletRequest.this.toString() + '}';
            }
        };
    }

    String getMethod();

    String getPathInfo();

    String getPathTranslated();

    default PushBuilder newPushBuilder() {
        return null;
    }

    String getContextPath();

    String getQueryString();

    String getRemoteUser();

    boolean isUserInRole(String var1);

    Principal getUserPrincipal();

    String getRequestedSessionId();

    String getRequestURI();

    StringBuffer getRequestURL();

    String getServletPath();

    HttpSession getSession(boolean var1);

    HttpSession getSession();

    String changeSessionId();

    boolean isRequestedSessionIdValid();

    boolean isRequestedSessionIdFromCookie();

    boolean isRequestedSessionIdFromURL();

    /** @deprecated */
    @Deprecated
    boolean isRequestedSessionIdFromUrl();

    boolean authenticate(HttpServletResponse var1) throws IOException, ServletException;

    void login(String var1, String var2) throws ServletException;

    void logout() throws ServletException;

    Collection<Part> getParts() throws IOException, ServletException;

    Part getPart(String var1) throws IOException, ServletException;

    <T extends HttpUpgradeHandler> T upgrade(Class<T> var1) throws IOException, ServletException;

    default Map<String, String> getTrailerFields() {
        return Collections.emptyMap();
    }

    default boolean isTrailerFieldsReady() {
        return true;
    }
}

2.1 获取请求行信息

  Http的请求行中,会包含请求方法、URL(包括协议、服务器名、端口号、资源路径等)、Http版本等信息。

方法声明说明
String getMethod()该方法用于获取HTTP请求消息中的请求方式(GET、POST等)
String getRequestURI()该方法用于获取请求行中资源名称,即,位于URL的主机和端口之后、参数部分之前的部分。
String getQueryString()该方法用于获取请求行中的参数部分,也就是URL中?后的所有内容。
String getContextPath()用于获取请求URL中属于WEB应用程序的路径。
String getServletPath()获取Servlet名称或Servlet所映射的路径。
String getServerName()用于获取当前请求所指的主机名,即HTTP请求信息中Host头字段所对应的主机名部分。
int getServerPort()用于获取当前请求所连接的服务器端口号。
String getScheme()获取请求的协议名,如,http、https、ftp等。
StringBuffer getRequestURL()用于获取客户端发出请求时的完整URL,包括协议、服务器名、端口号、资源路径等,但不包括后面的参数。
String getLocalAddr()返回服务器的IP地址。
String getLocalName()返回服务器端的主机名
int getLocalPort()返回服务器端的端口号
int getLocalPort()返回客户端的端口号。
String getProtocol()返回客户端和服务器端通信所用的协议的名称及版本号。
String getRemoteAddr()返回客户端的IP地址。
String getRemoteHost()返回客户端的主机名。
int getRemotePort()用于获取请求客户端网络连接的端口号。

案例

package com.example.servletdemo;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet("/MyServlet")
public class MyServlet01 extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取请求行的相关信息
        System.out.println("getMethod:" + request.getMethod());
        System.out.println("getQueryString:" + request.getQueryString());
        System.out.println("getProtocol:" + request.getProtocol());
        System.out.println("getContextPath" + request.getContextPath());
        System.out.println("getPathInfo:" + request.getPathInfo());
        System.out.println("getPathTranslated:" + request.getPathTranslated());
        System.out.println("getServletPath:" + request.getServletPath());
        System.out.println("getRemoteAddr:" + request.getRemoteAddr());
        System.out.println("getRemoteHost:" + request.getRemoteHost());
        System.out.println("getRemotePort:" + request.getRemotePort());
        System.out.println("getLocalAddr:" + request.getLocalAddr());
        System.out.println("getLocalName:" + request.getLocalName());
        System.out.println("getLocalPort:" + request.getLocalPort());
        System.out.println("getServerName:" + request.getServerName());
        System.out.println("getServerPort:" + request.getServerPort());
        System.out.println("getScheme:" + request.getScheme());
        System.out.println("getRequestURL:" + request.getRequestURL());
    }
}

输出结果:

已连接到服务器
[2023-02-07 03:25:48,531] 工件 ServletDemo:war exploded: 正在部署工件,请稍候…
[2023-02-07 03:25:48,939] 工件 ServletDemo:war exploded: 工件已成功部署
[2023-02-07 03:25:48,939] 工件 ServletDemo:war exploded: 部署已花费 408 毫秒
07-Feb-2023 15:25:58.136 信息 [Catalina-utility-2] org.apache.catalina.startup.HostConfig.deployDirectory 把web 应用程序部署到目录 [D:\Tomcat\apache-tomcat-10.0.27\webapps\manager]
07-Feb-2023 15:25:58.193 信息 [Catalina-utility-2] org.apache.catalina.startup.HostConfig.deployDirectory Web应用程序目录[D:\Tomcat\apache-tomcat-10.0.27\webapps\manager]的部署已在[57]毫秒内完成
getMethod:GET
getQueryString:null
getProtocol:HTTP/1.1
getContextPath
getPathInfo:null
getPathTranslated:null
getServletPath:/MyServlet
getRemoteAddr:0:0:0:0:0:0:0:1
getRemoteHost:0:0:0:0:0:0:0:1
getRemotePort:53073
getLocalAddr:0:0:0:0:0:0:0:1
getLocalName:0:0:0:0:0:0:0:1
getLocalPort:8080
getServerName:localhost
getServerPort:8080
getScheme:http
getRequestURL:http://localhost:8080/MyServlet
2.2 获取请求头信息

  当请求Servlet时,需要通过请求头向服务器传递附加信息,为此,在HttpServletRequest接口中,定义了一系列用于获取HTTP请求头字段的方法。

方法声明说明
String getHeader(String name)该方法用于获取一个指定头字段的值,如果请求消息中没有包含指定的头字段,getHeader()方法返回null;如果请求消息中包含有多个指定名称的头字段,getHeader()方法返回其中第一个头字段的值。
Enumeration getHeaders(String name)该方法返回一个Enumeration集合对象,该集合对象由请求消息中出现的某个指定名称的所有头字段值组成。在多数情况下,一个头字段名在请求消息中只出现一次,但有时候可能会出现多次。
Enumeration getHeaderNames()该方法用于获取一个包含所有请求头字段的Enumeration对象。
int getIntHeader(String name)该方法用于获取指定名称的头字段,并且将其值转换为int类型。需要注意的是,如果指定名称的头字段不存在,返回值为-1;如果获取到的头字段的值不能转为int类型,将发生NumberFormatException异常。
Long getDateHeader(String name)该方法用于获取指定头字段的值,并将其按GMT时间格式转换成一个代表日期/时间的长整数,这个长整数是自1970年1月1日0点0分0秒算起的以毫秒为单位的时间值。
String getContentType()该方法用于获取Content-Type头字段的值,结果为String类型。
int getContentLength()该方法用于获取Content-Length头字段的值,结果为int类型。
String getCharacterEncoding()该方法用于返回请求消息的实体部分的字符集编码,通常是从Content-Type头字段中进行提取,结果为String类型。

案例

package com.example.servletdemo;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.Enumeration;

@WebServlet("/MyServlet")
public class MyServlet01 extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取请求头信息
        Enumeration headerNames = request.getHeaderNames();
        //使用循环遍历请求头,并通过getHeader()方法获取一个指定名称的头字段
        while (headerNames.hasMoreElements()) {
            String headerName = (String) headerNames.nextElement();
            System.out.println(headerName + " : " + request.getHeader(headerName));
        }
    }
}

输出结果:

host : localhost:8080
connection : keep-alive
cache-control : max-age=0
sec-ch-ua : "Not_A Brand";v="99", "Microsoft Edge";v="109", "Chromium";v="109"
sec-ch-ua-mobile : ?0
sec-ch-ua-platform : "Windows"
upgrade-insecure-requests : 1
user-agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.78
accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
sec-fetch-site : none
sec-fetch-mode : navigate
sec-fetch-user : ?1
sec-fetch-dest : document
accept-encoding : gzip, deflate, br
accept-language : zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
cookie : Webstorm-d0809e2=2857bb9e-a4b6-49d9-83f9-b55b437b30fc; Idea-1c60499b=e0a9565d-f97e-4830-b651-a377499d2d2f
2.3 获取请求体(请求参数)

  HttpServletRequest为获取请求参数提供了一些获取请求参数的方法:

方法声明说明
String getParameter(String name)该方法用于获取某个指定名称的参数值,如果请求消息中没有包含指定名称的参数,getParameter()方法返回null;如果指定名称的参数存在但没有设置值,则返回一个空串;如果请求消息中包含有多个该指定名称的参数,getParameter()方法返回第一个出现的参数值。
String[] getParameterValues(String name)HTTP请求消息中可以有多个相同名称的参数(通常由一个包含有多个同名的字段元素的FORM表单生成),如果要获得HTTP请求消息中的同一个参数名所对应的所有参数值,那么就应该使用getParameterValues()方法,该方法用于返回一个String类型的数组。
Enumeration getParameterNames()该方法用于返回一个包含请求消息中所有参数名的Enumeration对象,在此基础上,可以对请求消息中的所有参数进行遍历处理。
Map getParameterMap()用于将请求消息中的所有参数名和值装入进一个Map对象中返回。
2.3.1 POST请求

  POST请求的数据是在请求体中发送到后台的,在实际开发中,经常需要获取用户提交的表单数据,例如,用户名,密码、电子邮件等,为了方便获取表单中的请求参数,在HttpServletRequest接口中,定义了一些列获取请求参数的方法。
案例
Login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录页面</title>
</head>
<body>
<form method="post" action="MyServlet">
    <!--web和src都是根目录,二者是同级的-->
    姓名:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    爱好:
    <input type="checkbox" name="hobby" value="sing">唱歌
    <input type="checkbox" name="hobby" value="dance">跳舞
    <input type="checkbox" name="hobby" value="football">足球
    <input type="submit" value="提交">
</form>
</body>
</html>

MyServlet01.java

package com.example.servletdemo;

import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@WebServlet("/MyServlet")
public class MyServlet01 extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request,HttpServletResponse response){
        String username=request.getParameter("username");
        String password=request.getParameter("password");
        System.out.println("用户名:"+username);
        System.out.println("密码:"+password);
        //获取hobby参数值
        String[] hobby = request.getParameterValues("hobby");
        if(hobby!=null){
            System.out.println("爱好:");
            for (String s : hobby) {
                System.out.printf(s + " ");
            };
        }
    }
}

使用URL进行访问:

http://localhost:8080/Login.jsp

在这里插入图片描述
在这里插入图片描述

2.3.2 GET请求

  GET请求提交的数据是拼接在URL之后的,获取方式与POST请求相同。
案例
MyServlet01.java

package com.example.servletdemo;

import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@WebServlet("/MyServlet")
public class MyServlet01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request,HttpServletResponse response){
        String username=request.getParameter("username");
        String password=request.getParameter("password");
        System.out.println("用户名:"+username);
        System.out.println("密码"+password);
        String[] hobby= request.getParameterValues("hobby");
        if(hobby!=null){
            System.out.println("爱好:");
            for(String s:hobby){
                System.out.printf(s+" ");
            }
        }
    }
}

使用URL进行访问:

http://localhost:8080/MyServlet?username=张三&password=123456&hobby=dance&hobby=sing

在这里插入图片描述

2.3.3 GET和POST对比
  • GET请求
    • 该方法将表单数据以名称/值对的形式附加到URL中。
    • URL中放置的数据量是有限制的(不同的浏览器有差别),所以无法确保所有表单数据得到正确地传输。
    • 密码或其他敏感信息在浏览器地址栏中是可见的,故不能使用GET方式传输敏感信息。
  • POST请求
    • 该方法以HTTP POST事务的方式来传递表单数据。
    • GET相比,POST方法更健壮更安全,而且 POST没有容量限制

⑧ServletResponse

  Servlet通过ServletResponse对象来生成响应结果。当Servlet容器接收到客户端要求访问特定Servlet的请求时,容器会创建一个ServletResponse对象,并把它作为参数传给Servletservice()方法。
  在ServletResponse接口中定义了一系列与生成响应相关的方法。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package jakarta.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Locale;

public interface ServletResponse {
    String getCharacterEncoding();

    String getContentType();

    ServletOutputStream getOutputStream() throws IOException;

    PrintWriter getWriter() throws IOException;

    void setCharacterEncoding(String var1);

    void setContentLength(int var1);

    void setContentLengthLong(long var1);

    void setContentType(String var1);

    void setBufferSize(int var1);

    int getBufferSize();

    void flushBuffer() throws IOException;

    void resetBuffer();

    boolean isCommitted();

    void reset();

    void setLocale(Locale var1);

    Locale getLocale();
}
方法声明说明
void setCharacterEncoding(String charset)设置响应正文的字符编码。响应正文的默认字符编码为ISO-8859-1。
void setContentLength(int len)设置响应正文的长度。
void setContentType(String type)设置响应正文的MIME类型。
String getCharacterEncoding()返回响应正文的字符编码。
String getContentType()获取响应正文的MIME类型。
void setBufferSize(int size)设置用于存放响应正文数据的缓存区的大小。
int getBufferSize()获得用于存放正文数据的缓存区的大小。
void reset()清空缓存区内的正文数据,并且清空响应状态代码及响应头。
void resetBuffer()清空缓存区内的正文数据,不清空响应状态代码及响应头。
void flushBuffer()强制性地把缓存区内的响应正文数据发送到客户端。
boolean isCommitted()返回一个boolean类型的值。如果为true,表示缓存区内的数据已经提交给客户,即数据已经发送到客户端。
ServletOutputStream getOutputStream()返回一个ServletOutputStream对象,Servlet用它来输出二进制的正文数据。
PrintWriter getWriter()返回一个PrintWriter对象,Servlet用它来输出字符串形式的正文数据。

⑨HttpServletResponse

  HttpServletResponse接口,是ServletResponse的子接口,处理HTTP协议响应信息。该对象封装了服务器要返回客户端的所有HTTP响应信息的响应行、响应头部和响应体。

1.HTTP响应

  服务器收到了客户端发来的HTTP请求后,根据HTTP请求中的动作要求,服务端做出具体的动作,将结果回应给客户端,称为HTTP响应。
  HTTP响应包含响应行、响应头、响应正文三部分。

1.1 响应行

  响应行由协议版本、状态码及其回应短语组成组成,如,HTTP/1.1 200 Hello,其中,协议版本为HTTP/1.1,状态码为200,回应短语Hello
常见状态码

  • 1xx:表示成功接收请求,要求客户端继续提交下一次请求才能完成整个处理过程。
  • 2xx:表示成功接收请求并已完成整个处理过程。
  • 3xx:重定向,需要进一步的操作以完成请求。
  • 4xx:客户端错误,请求包含语法错误或无法完成请求。
  • 5xx:服务器错误,服务器在处理请求的过程中发生了错误。
状态码状态码英文名称中文描述
100Continue客户端应继续其请求
101Switching Protocols切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议
200OK请求成功。一般用于GET与POST请求
201Created已创建。成功请求并创建了新的资源
202Accepted已接受。已经接受请求,但未处理完成
203Non-Authoritative Information非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本
204No Content无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档
205Reset Content重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域
206Partial Content部分内容。服务器成功处理了部分GET请求
300Multiple Choices多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择
301Moved Permanently永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
302Found临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
303See Other查看其它地址。与301类似。使用GET和POST请求查看
304Not Modified未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
305Use Proxy使用代理。所请求的资源必须通过代理访问
306Unused已经被废弃的HTTP状态码
307Temporary Redirect临时重定向。与302类似。使用GET请求重定向
400Bad Request客户端请求的语法错误,服务器无法理解
401Unauthorized请求要求用户的身份认证
402Payment Required保留,将来使用
403Forbidden服务器理解请求客户端的请求,但是拒绝执行此请求
404Not Found服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
405Method Not Allowed客户端请求中的方法被禁止
406Not Acceptable服务器无法根据客户端请求的内容特性完成请求
407Proxy Authentication Required请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权
408Request Time-out服务器等待客户端发送的请求时间过长,超时
409Conflict服务器完成客户端的PUT请求是可能返回此代码,服务器处理请求时发生了冲突
410Gone客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置
411Length Required服务器无法处理客户端发送的不带Content-Length的请求信息
412Precondition Failed客户端请求信息的先决条件错误
413Request Entity Too Large由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息
414Request-URI Too Large请求的URI过长(URI通常为网址),服务器无法处理
415Unsupported Media Type服务器无法处理请求附带的媒体格式
416Requested range not satisfiable客户端请求的范围无效
417Expectation Failed服务器无法满足Expect的请求头信息
500Internal Server Error服务器内部错误,无法完成请求
501Not Implemented服务器不支持请求的功能,无法完成请求
502Bad Gateway作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应
503Service Unavailable由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中
504Gateway Time-out充当网关或代理的服务器,未及时从远端服务器获取请求
505HTTP Version not supported服务器不支持请求的HTTP协议的版本,无法完成处理
1.2 响应头

  响应头用于描述服务器的基本信息,以及数据的描述,服务器通过这些数据的描述信息,可以通知客户端如何处理等一会儿它回送的数据。
常见响应头

响应头名称说明
Allow显示服务器支持的请求方法,如,GET、POST。
Cache-Control与请求头中的Cache-Control相对应。默认为private,表示响应只能够作为私有的缓存,不能在用户间共享;public,表示浏览器和缓存服务器都可以缓存页面信息;must-revalidate,表示对于客户机的每次请求,代理服务器必须想服务器验证缓存是否过时;no-cache,表示浏览器和缓存服务器都不应该缓存页面信息;max-age,可用于设置浏览器自动刷新缓冲区,单位为秒;no-store,表示请求和响应的信息都不应该被存储在对方的磁盘系统中。
Content-Type服务器通过这个头,回送数据的类型,如,Content-Type:text/html;charset=UTF-8 告诉客户端,资源文件的类型,还有字符编码,客户端通过utf-8对资源进行解码,然后对资源进行html解析。通常我们会看到有些网站是乱码的,往往就是服务器端没有返回正确的编码。
Content-Encoding服务器通过这个头,告诉浏览器数据采用的压缩格式,如,Content-Encoding:gzip,告诉客户端,服务端发送的资源是采用gzip编码的,客户端看到这个信息后,应该采用gzip对资源进行解码。
Date告诉客户机,返回响应的时间。
Server告诉客户端服务器的类型信息。
Transfer-Encoding告诉浏览器数据的传送格式。
vary用于列出一个响应字段列表,告诉缓存服务器遇到同一个 URL 对应着不同版本文档的情况时,如何缓存和筛选合适的版本。
Location配合302状态码使用,告诉用户端找谁。
Content-Length告诉服务器返回数据的长度。
Content-Language返回服务器的语言环境。
Last-Modified返回当前资源的缓存时间。
Refresh返回服务器的刷新间隔,单位为秒。
1.3 响应体

  响应体就是响应的消息体,如果是纯数据就是返回纯数据,如果请求的是HTML页面,那么返回的就是HTML代码,如果是JS就是JS代码,如此之类。

2.方法说明

  Servlet中的doXXX方法的目的就是根据请求计算得到响应, 然后把响应的数据设置到HttpServletResponse 对象中,然后 Tomcat就会把这个 HttpServletResponse 对象按照 HTTP 协议的格式, 转成一个字符串, 并通过Socket写回给浏览器。
  HttpServletResponse提供了一些方法用于设置返回给浏览器的响应对象。

方发声明说明
void setStatus(int sc)设置响应状态码。
void setHeader(String name,String value)设置一个带有给定的名称和值的Header,如果name已经存在,则覆盖旧的值。
void addHeader(int sc)设置一个带有给定的名称和值的Header,如果name存在,不会覆盖旧的值,并列添加新的值。
void setContentType(String type)设置被发送到客户端的响应的内容类型。
void setCharacterEncoding(String charset)设置被发送到客户端的响应的字符编码(MIME 字符集)。
void sendRedirect(String location)使用指定的重定向位置 URL 发送临时重定向响应到客户端。
PrintWriter getWriter()用于往 body 中写入文本格式数据。
OutputStream getOutStream()用于往 body 中写入二进制格式数据。
2.1 设置响应状态码

response.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
<h3>设置状态码</h3>
<input type="text" id="status">
<br>
<button onclick="setStatus()">提交</button>
</body>
<script>
    function setStatus(){
        //js中发请求:(1)ajax (2)直接修改地址栏URL
        let status = document.querySelector("#status");
        //后端将文本框输入的值作为响应状态码
        window.location.href= "response?status="+status.value;
    }
</script>
</html>

MyServlet01.java

package com.example.servletdemo;

import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@WebServlet("/response")
public class MyServlet01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request,HttpServletResponse response){
        //获取请求发送的queryString数据:status=xxx
        String status = request.getParameter("status");
        response.setStatus(Integer.parseInt(status));
        System.out.println("响应状态码设置成功");
    }
}

在这里插入图片描述
此时,输入不同的状态码会出现相应的响应页面:
在这里插入图片描述

2.2 设置响应头

response.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
<h3>设置响应头</h3>
<a href="response">响应头</a>
</body>
</html>

MyServlet01.java

package com.example.servletdemo;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet("/response")
public class MyServlet01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setStatus(301);
        System.out.println("响应状态码设置成功");
        //设置响应头的键值对,键可以是标准的HTTP响应头的键,也可以是自定义的
        //响应状态码是301、302、307时,响应头有location字段,才是重定向
        resp.setHeader("location","http://www.baidu.com" );
        resp.setHeader("username","张三");
    }
}

注:只有3xx的状态码才会重定向。
在这里插入图片描述
点击之后即可完成跳转:
在这里插入图片描述

2.3 设置响应内容:字节流与字符流

  在早期JSP还没有诞生的时代,许多动态页面是通过在Serlvet中使用。接收到客户端请求后,可通过HttpServletResponse对象直接进行响应,响应时需要获取输出流HttpServletResponse输出到页面上的。

方法声明说明
ServletOutputStream getOutputStream()返回一个适合写入二级制数据的ServletOutputStream对象,并通过flush()来提交此次响应。
PrintWriter getWriter()返回一个PrintWriter ,可将字符文本发送到客户端。

  从中我们可以看到,getOutputStream()方法返回ServletOutputStream对象,更适合向客户端写入二进制数据,并且Servlet容器不会对这些二进制数据进行编码,因此我们常用ServletOutputStream来向客户端发送如图片、文件等内容;对于getWriter()方法返回的PrintWriter对象,里面封装了更多的写入字符文本的函数,并且我们上文提到的setContentType()方法设置的MIME类型对其输出内容有效,因此也可以很好地解决中文乱码问题。
注意:这两个方法在一个response对象中不可以同时调用,否则会抛出一个IllegalStateException,因为输出流只能有一个。

  关于这两种对象的使用,请见本专栏的java-IO流一文,此处不再赘述。

response.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h3>响应正文为简单的html网页</h3>
    <a href="html?type=1">查看</a>
</body>
</html>

MyServlet01.java

package com.example.servletdemo;

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

@WebServlet("/response")
public class MyServlet01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //响应html:设置响应的content-type
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter pw = resp.getWriter();

        //获取queryString中,type的值
        String type = req.getParameter("type");
        if("1".equals(type)){
            //返回简单的html
            pw.println("<h3>获取网页成功</h3>");
        }
    }
}
3.响应乱码问题

  在响应中,若响应的内容有中文,则有可能出现乱码,这是因为服务器响应的数据也会经过网络传输,服务器端有一种编码方式,在客户端也存在一种编码方式,当两端使用的编码方式不同时则出现乱码。
  对于getWriter获取到的字符流,响应中文后必定出现乱码,这是因为服务器端在进行编码时默认会使用ISO-8859-1格式的编码,该编码并不支持中文。要解决该种乱码只能在服务器端告知服务器使用一种能够支持中文的编码格式,比如常用的UTF-8

response.setCharacterEncoding("UTF-8);

要保证数据正确显示,还要指定客户端的解码方式:

response.setHeader("content-type","text/html;charset=UTF-8")

例:

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	resp.setCharacterEncoding("UTF-8");
	resp.setHeader("content-type","text/html;charset=UTF-8");
	//获取字符输出流
	PrintWriter writer=resp.getWriter();
	//输出数据
	writer.write("张三");
}

(三)web.xml

以下内容参考:https://blog.csdn.net/weixin_44458879/article/details/102742817
  web.xmlJava Web项目中的一个配置文件,主要用于配置首页、FilterListenerServlet等。Tomcat在部署启动web应用时,会解析加载${CATALINA_HOME}/conf目录下所有web应用通用的web.xml,然后解析加载web应用目录中的WEB-INF/web.xml。如果没有WEB-INF/web.xml文件,Tomcat会输出找不到的消息,但仍然会部署并使用web应用程序,因此,这个web.xml并不是必要的,不过通常最好还是让每一个上线的web应用程序都有一个自己的WEB-INF/web.xml
  conf/web.xml文件中的设定会应用于所有的web应用程序,而web应用程序的WEB-INF/web.xml中的设定只应用于该应用程序本身。

<web-app> 
    <!--定义了WEB应用的名字 -->
    <display-name></display-name>
    <!--声明WEB应用的描述信息 -->
    <description></description> 
    <!--context-param元素声明应用范围内的初始化参数。-->
    <context-param></context-param>  
    <!--过滤器元素将一个名字与一个实现javax.servlet.Filter接口的类相关联。-->
    <filter></filter>  
    <!--一旦命名了一个过滤器,就要利用filter-mapping元素把它与一个或多个servlet或JSP页面相关联。 -->
    <filter-mapping></filter-mapping> 
    <!--servlet API的版本2.3增加了对事件监听程序的支持,事件监听程序在建立、修改和删除会话或servlet环境时得到通知。Listener元素指出事件监听程序类。-->
    <listener></listener> 
    <!--在向servlet或JSP页面制定初始化参数或定制URL时,必须首先命名servlet或JSP页面。Servlet元素就是用来完成此项任务的。-->
    <servlet></servlet> 
    <!--服务器一般为servlet提供一个缺省的URL:http://host/webAppPrefix/servlet/ServletName.但是,常常会更改这个URL,以便servlet可以访问初始化参数或更容易地处理相对URL。在更改缺省URL时,使用servlet-mapping元素。 -->
    <servlet-mapping></servlet-mapping> 
    <!--如果某个会话在一定时间内未被访问,服务器可以抛弃它以节省内存。 可通过使用HttpSession的setMaxInactiveInterval方法明确设置单个会话对象的超时值,或者可利用session-config元素制定缺省超时值。 -->
    <session-config></session-config> 
    <!--如果Web应用具有想到特殊的文件,希望能保证给他们分配特定的MIME类型,则mime-mapping元素提供这种保证。 -->
    <mime-mapping></mime-mapping>
    <!--指示服务器在收到引用一个目录名而不是文件名的URL时,使用哪个文件。 -->
    <welcome-file-list></welcome-file-list> 
    <!--在返回特定HTTP状态代码时,或者特定类型的异常被抛出时,能够制定将要显示的页面。 -->
    <error-page></error-page> 
    <!--对标记库描述符文件(Tag Libraryu Descriptor file)指定别名。此功能使你能够更改TLD文件的位置,而不用编辑使用这些文件的JSP页面。 -->
    <taglib></taglib> 
    <!--声明与资源相关的一个管理对象。 -->
    <resource-env-ref></resource-env-ref>
    <!--声明一个资源工厂使用的外部资源。 -->
    <resource-ref></resource-ref> 
    <!--制定应该保护的URL。它与login-config元素联合使用 -->
    <security-constraint></security-constraint> 
    <!--指定服务器应该怎样给试图访问受保护页面的用户授权。它与sercurity-constraint元素联合使用。 -->
    <login-config></login-config> 
    <!--给出安全角色的一个列表,这些角色将出现在servlet元素内的security-role-ref元素的role-name子元素中。分别地声明角色可使高级IDE处理安全信息更为容易。 -->
    <security-role></security-role>
    <!--声明Web应用的环境项。 -->
    <env-entry></env-entry>
    <!--声明一个EJB的主目录的引用。 -->
    <ejb-ref></ejb-ref>
    <!--声明一个EJB的本地主目录的应用。 -->
    <ejb-local-ref></ejb-local-ref>
</web-app>

①加载过程

  1. 启动Web应用时,容器(Tomcat)会去读取web.xml文件的</listener></context-param>两个节点。
  2. 对于</listener>,容器会创建类实例,并根据配置的class类路径<listener-class>来创建监听,在监听中会有contextInitialized(ServletContextEvent args)初始化方法,启动Web应用时,系统调用Listener的该方法。
  3. 对于</context-param>,容器会以name作为键,value作为值,将其转化为键值对,存入ServletContext
  4. 按照</filter>配置节出现的顺序初始化</filter>,当请求资源匹配多个filter-mapping时,filter拦截资源是按照filter-mapping配置节出现的顺序来依次调用doFilter()方法的。
  5. 按照</servlet>配置节出现的顺序初始化</servlet>

②元素定义

1.命名空间
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">
</web-app>
2.应用图标
<icon> 
	<!--用于指定小图标-->
    <small-icon>路径</small-icon> 
    <!--用于指定大图标-->
    <large-icon>路径</large-icon> 
</icon> 
3.应用名称
<display-name>App Name</display-name>
4.Web应用描述
<discription>描述内容</discription>
5.ServletContext初始化参数
<context-param> 
    <param-name>ContextParameter</para-name> 
    <param-value>test</param-value> 
    <description>It is a test parameter.</description> 
</context-param> 

  配置之后,在web应用的整个生命周期中上下文初始化参数都存在,任意的Servletjsp都可以随时随地访问它。

6.Filter过滤器配置
<!--****************************过滤器配置*********************************-->
<!-- 字符集过滤器 -->
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<!-- 单点登出过滤器 -->
<filter>
    <filter-name>CAS Single Sign Out Filter</filter-name>
    <filter-class>com.yonyou.mcloud.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<!-- 认证过滤器 -->
<filter>
    <filter-name>CAS Authentication Filter</filter-name>
    <filter-class>com.yonyou.mcloud.cas.client.authentication.ExpandAuthenticationFilter</filter-class>
    <init-param>
        <param-name>casServerLoginUrl</param-name>
        <param-value>https://dev.yonyou.com:443/sso-server/login</param-value>
    </init-param>
    <init-param>
        <!--这里的server是服务端的IP -->
        <param-name>serverName</param-name>
        <param-value>http://********</param-value>
    </init-param>
</filter>
<!-- 验证ST/PT过滤器 -->
<filter>
    <filter-name>CAS Validation Filter</filter-name>
    <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
    <init-param>
        <param-name>casServerUrlPrefix</param-name>
        <param-value>https://dev.yonyou.com:443/sso-server</param-value>
    </init-param>
    <init-param>
        <param-name>serverName</param-name>
        <param-value>http://10.1.215.40:80</param-value>
    </init-param>
    <init-param>
        <param-name>proxyCallbackUrl</param-name>
        <param-value>https://dev.yonyou.com:443/business/proxyCallback</param-value>
    </init-param>
    <init-param>
        <param-name>proxyReceptorUrl</param-name>
        <param-value>/proxyCallback</param-value>
    </init-param>
    <init-param>
        <param-name>proxyGrantingTicketStorageClass</param-name>
        <param-value>com.yonyou.mcloud.cas.client.proxy.MemcachedBackedProxyGrantingTicketStorageImpl</param-value>
    </init-param>
    <!-- 解决中文问题 -->
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter>
    <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
    <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<filter>
    <filter-name>CAS Assertion Thread Local Filter</filter-name>
    <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
</filter>
<filter>
    <filter-name>NoCache Filter</filter-name>
    <filter-class>com.yonyou.mcloud.cas.client.authentication.NoCacheFilter</filter-class>
</filter>
<!--****************************映射关系配置********************************-->
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>NoCache Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>CAS Single Sign Out Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>CAS Validation Filter</filter-name>
    <url-pattern>/proxyCallback</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>CAS Authentication Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>CAS Validation Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>CAS Assertion Thread Local Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
元素名作用
<filter-name>用来定义过滤器的名称,该名称在整个程序中都必须唯一。
<filter-class>指定过滤器类的完全限定的名称,即Filter的实现类。
<init-param>配置参数。
<filter-mapping>用来声明Web应用中的过滤器映射,过滤器被映射到一个servlet或一个URL模式。这个过滤器的和必须具有相同的,指定该Filter所拦截的URL。过滤是按照部署描述符的出现的顺序执行的。

Filter使用流程
  使用Filter的完整流程是:Filter对用户请求进行预处理,接着将请求HttpServletRequest交给Servlet进行处理并生成响应,最后Filter再对服务器响应HttpServletResponse进行后处理。FilterServlet具有完全相同的生命周期,且Filter也可以通过来配置初始化参数,获取Filter的初始化参数则使用FilterConfiggetInitParameter()

7.Listener配置

  此处与Tomcat中的server.xml相同,监听器用于监听各种事件,所有的监听器按照相同的方式定义,功能取决去它们各自实现的接口,常见的Web监听器接口:

接口作用
ServletContextListener用于监听Web Application的启动和关闭。
ServletContextAttributeListener用于监听ServletContext范围(Application)内属性的改变。
ServletRequestListener用于监听用户的请求。
ServletRequestAttributeListener用于监听ServletRequest范围(Request)内属性的改变。
HttpSessionListener用于监听用户session的开始和结束。
HttpSessionAttributeListener用于监听HttpSession范围(Session)内属性的改变。
<listener> 
    <listerner-class>listener.SessionListener</listener-class> 
</listener>
8.servlet配置

  为了使Servlet能响应用户请求,还必须将Servlet配置在web应用中,配置Servlet需要修改web.xml文件(或使用注解)。可以使用子元素将初始化参数名和参数值传递给Servlet,访问Servlet配置参数通过ServletConfig对象来完成,ServletConfig提供如下方法:getInitParameter(String name),用于获取初始化参数。
加载过程
  容器的Context对象对请求路径(URL)做出处理,去掉请求URL的上下文路径后,按路径映射规则和Servlet映射路径做匹配,如果匹配成功,则调用这个Servlet处理请求。

<!--用户登出Servlet-->
<servlet>
    <servlet-name>LogOutServlet</servlet-name>
    <servlet-class>com.yonyou.mcloud.cas.web.servlet.LogOutServlet</servlet-class>
    <init-param>
      <param-name>serverLogoutUrl</param-name>
      <param-value>https://dev.yonyou.com:443/sso-server/logout</param-value>
    </init-param>
    <init-param>
      <param-name>serverName</param-name>
      <param-value>http://10.1.215.40:80/business/</param-value>
    </init-param>
</servlet>
<!--servlet映射关系配置-->
<servlet-mapping>
    <servlet-name>LogOutServlet</servlet-name>
    <url-pattern>/logout</url-pattern>
</servlet-mapping>
标签名作用
<servlet-name>用来定义servlet的名称,该名称在整个应用中必须是惟一的。
<servlet-class>用来指定servlet的完全限定的名称。
<load-on-startup>决定Servlet的加载顺序,内容可以为空,或者是一个整数。
<servlet-mapping>Servlet的名字,唯一性和一致性,与元素中声明的名字一致。
<url-pattern>指定相对于Servlet的URL的路径。该路径相对于web应用程序上下文的根路径。将URL模式映射到某个Servlet。
9.欢迎页面配置
<welcome-file-list> 
    <welcome-file>index.jsp</welcome-file> 
    <welcome-file>index.html</welcome-file> 
</welcome-file-list> 

  显示时按照配置顺序显示,如果能找到index.jsp文件就显示该文件,如果找不到就找第二个,依次类推。对于 Tomcat 来说,会默认先查找index.html文件,如果找到了,就将其返回给浏览器;如果没有找到,就继续查找index.jsp文件,如果都没有找到,那么Tomcat就会显示The requested resource is not available的页面。

10.错误页面配置

法一:通过错误码进行配置

<error-page> 
    <error-code>404</error-code> 
    <location>/404.jsp</location> 
</error-page> 

上面的配置当系统发生404错误时,页面将跳转到错误处理页面404.jsp。
法二:通过异常类型配置

<error-page> 
    <exception-type>java.lang.NullException</exception-type> 
    <location>/error.jsp</location> 
</error-page> 

当系统发生java.lang.NullException异常时,页面将跳转到错误处理页面error.jsp。

(四)常用功能

①Servlet映射配置

1.XML文件配置

  在上文的web.xml中就提到了Servlet映射配置,此处有三种配置方法。

1.1 全类名配置
<servlet>
    <servlet-name>servletDemo</servlet-name>
    <servlet-class>com.Servlet.ServletDemo</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>servletDemo</servlet-name>
    <url-pattern>/servletDemo</url-pattern> 
</servlet-mapping>
1.2 /+通配符配置
<servlet>
    <servlet-name>servletContextDemo</servlet-name>
    <servlet-class>com.itheima.Servlet.ServletContextDemo</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>servletContextDemo</servlet-name>
    <url-pattern>/servlet/*</url-pattern>
</servlet-mapping>

  此时,访问路径URL只要符合目录结构即可。

1.3 通配符+固定格式结尾配置
<servlet>
    <servlet-name>servletContextDemo</servlet-name>
    <servlet-class>com.itheima.Servlet.ServletContextDemo</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>servletContextDemo</servlet-name>
    <url-pattern>*.do</url-pattern>
</servlet-mapping>

  此时,访问路径URL只要带有.do结尾即可。

2.@WebServlet

  Servlet3.0提供了注解包(annotation),我们可以不用再web.xml里面配置servlet,只需要加上@WebServlet注解就可以修改该servlet的属性了。web.xml可以配置的servlet属性,在@WebServlet中都可以配置。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package jakarta.servlet.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
    String name() default "";

    String[] value() default {};

    String[] urlPatterns() default {};

    int loadOnStartup() default -1;

    WebInitParam[] initParams() default {};

    boolean asyncSupported() default false;

    String smallIcon() default "";

    String largeIcon() default "";

    String description() default "";

    String displayName() default "";
}

  要想使注解生效,需要指定metadata-complete

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0"
         metadata-complete="false">
    <!--metadata-complete="false"	//false是指使用注解式-->
    <!--metadata-complete="true"	//true是指使用配置式-->
</web-app>
属性名类型功能
nameString相当于<servlet-name>,未指定时默认为类的全限定类名。
valueString[]等价于<url-pattern>,可用于指定一组URL(要记得使用"/")。
urlPatternsString[]等价于value,二者不能同时使用。
loadOnStartupint等价于<load-on-startup>,用于指定Servlet的加载顺序。
initParamsWebInitParam[]等价于<init-param>,用于指定一组Servlet初始化参数。
asyncSupportedboolean等价于<async-supported>,声明Servlet是否支持异步操作模式。
descriptionString等价于<description>,用于指定Servlet的描述信息。
displayNameString等价于<display-name>,用于指定Servlet的显示名。

②请求转发与重定向

1.请求转发

  请求转发,是一种服务器的行为(请求重定向则是一种客户端行为),指一个web资源收到客户端请求后,通知服务器去调用另外一个web资源进行处理。
注,客户端请求到达后,服务器进行转发,此时会将请求对象进行保存,地址栏中的URL地址不会改变,在得到响应后,服务器端再将响应发送给客户端,从始至终只有一个请求发出,从而达到多个资源协同响应的效果。

1.1实现方式
request.getRequestDispatcher(url).forward(request,response);

  javax.serlvet包中定义了一个RequestDispatcher接口,其对象由Servlet容器创建,用于封装由路径所标识的Web资源,利用RequestDispatcher对象,可将请求转发给其他的Web资源。
RequestDispatcher对象获取方式

  1. 调用ServletContextgetRequestDispatcher(String path)方法,参数path指定目标资源的路径,必须为绝对路径。
  2. 调用ServletRequestgetRequestDispatcher(String path)方法,参数path指定目标资源的路径,可以为绝对路径,也可以为相对路径。

RequestDispatcher接口方法

方法声明说明
void forward(ServletRequest request, ServletResponse response)用于将请求转发给另一个 Web 资源。该方法必须在响应提交给客户端之前被调用,否则将抛出 IllegalStateException 异常。
void include(ServletRequest request,ServletResponse response)用于将其他的资源作为当前响应内容包含进来。
1.2请求转发工作原理

在这里插入图片描述
请求转发的特点

  1. 请求转发不支持跨域访问,只能跳转到当前应用中的资源。
  2. 请求转发之后,浏览器地址栏中的URL不会发生变化,因此浏览器不知道在服务器内部发生了转发行为,更无法得知转发的次数。
  3. 参与请求转发的Web资源之间共享同一request对象和response对象。
  4. 由于forward()方法会先清空response缓冲区,因此只有转发到最后一个Web资源时,生成的响应才会被发送到客户端。
1.3 案例实现

MyServlet01.java

package com.example.servletdemo;

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

@WebServlet("/MyServlet01")
public class MyServlet01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //请求转发给MyServlet02进行处理
        request.getRequestDispatcher("/MyServlet02").forward(request,response);
    }
    @Override
    protected void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }
}

MyServlet02.java

package com.example.servletdemo;

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

@WebServlet("/MyServlet02")
public class MyServlet02 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PrintWriter writer=response.getWriter();
        writer.write("MyServlet02");
    }

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

使用URL进行访问:

http://localhost:8080/MyServlet01

在这里插入图片描述

2.重定向

  重定向是一种由服务器指导的客户端行为。客户端发出第一个请求,被服务器接收处理后,服务器会进行响应,响应的同时,服务器会给客户端一个新的地址(response.sendRedirect(url);),当客户端接收到响应后,会立刻根据服务器发给的新地址发起第二个请求,服务器接收请求并作出响应,重定向完成。且,前后页不公用一个request,不能读取转向前通过request域设置的属性值。

1.1实现方式
//重定向跳转到index.jsp
response.sendRedirect("index.jsp");
1.2实现工作原理

在这里插入图片描述
特点

  1. 客户端一共向服务端发送两次请求。
  2. 在前后两次执行后,地址栏发送改变,是目标文件的地址。
  3. 可转向本web应用之外的网页和网站,如,百度等,而请求转发不可。
  4. URL中所包含的/代表根路径的目录。
1.3案例

MyServlet01.java

package com.example.servletdemo;

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

@WebServlet("/MyServlet01")
public class MyServlet01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PrintWriter writer=response.getWriter();
        writer.write("MyServlet01");
        response.sendRedirect("MyServlet02");
    }
    @Override
    protected void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }
}

MyServlet02.java

package com.example.servletdemo;

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

@WebServlet("/MyServlet02")
public class MyServlet02 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PrintWriter writer=response.getWriter();
        writer.write("MyServlet02");
    }

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

使用URL

http://localhost:8080/MyServlet01

之后地址栏会发生改变:

http://localhost:8080/MyServlet02

在这里插入图片描述

3.对比
区别请求转发重定向
浏览器地址栏URL是否发生改变
是否支持跨域跳转
请求与响应次数一次请求和一次响应两次请求和两次响应
是否共享request和response对象
是否能通过request对象传递数据
速度相对要快相对要慢
行为服务器行为客户端行为

③三大域对象

  在JavaWeb中,Servlet中三大作用域分别是requestsessionapplication,其作用是用来共享数据,本质是根据作用域的范围,生命周期决定其使用的方式。
作用域对比

作用域名功能生命周期作用范围
request每一次请求都是一个新的request对象,如果在Web组件之间需要共享同一个请求中的数据,只能使用请求转发。只作用于当前一次请求。一次请求中有效,请求转发有效,重定向失效。
session每一次会话都是一个新的session对象,如果需要在一次会话中的多个请求之间需要共享数据,只能使用session。一次会话(多次请求)。一次会话中有效,请求转发和重定向都有效,session销毁后失效。
application应用对象,Tomcat 启动到关闭,表示一个应用,在一个应用中有且只有一个application对象,作用于整个Web应用,可以实现多次会话之间的数据共享。项目的启动到结束在整个应用程序中有效,服务器关闭后失效。

三大作用域对应的域对象

作用域域对象
requestHttpServletRequest
sessionHttpSession
applicationServletContext
1.request

  request表示一个请求,只要发出一个请求就会创建一个request,当做一个容器来存储数据,存储数据的目的是为了在多个servlet之间传递数据(请求转发),它的作用域仅在当前请求中有效,如果Web组件之间需要共享同一个请求中的数据,只能使用请求转发。
作用:用于服务器间同一请求不同页面之间的参数传递、用于表单的控件值传递等。
常用方法

方法声明说明
void setAttribute(String name, Object o)往request域中设置值。
Object getAttribute(String name)从request域中取值。
void removeAttribute(String name)从request域中移除值

注:getParameter方法和getAttribute方法并不一致,前者获取的是浏览器提交的数据(即,表单提交、URL中的参数),而后者获取的是request域中的数据。

2.session

  对于服务器而言,每一个连接到它的客户端都是一个sessionservlet容器使用此接口创建HTTP客户端和HTTP服务器之间的会话,会话将保留指定的时间段,跨多个连接或来自用户的页面请求。一个会话通常对应于一个用户,该用户可能多次访问一个站点,可以通过此接口查看和操作有关某个会话的信息,比如,会话标识符、创建时间、最后一次访问时间。在整个session中,最重要的就是属性的操作。
  session无论客户端还是服务器端都可感知到,若重新打开一个新的浏览器,则无法获取之前设置的session,因为每一个session只会保存在当前的浏览器中,并在相关的页面取得,浏览器关闭后,再也访问不到和该浏览器对应的session,或是在超时之后被自动销毁。
  session的作用就是为了标识一次会话,或者说确认一个用户,并且在一次会话(一个用户的多次请求)期间共享数据,我们可通过request.getSession()方法,来获取当前会话的session对象。
获取方式

//若session对象存在,则获取;若不存在,则创建
HttpSession session=request.getSession();

作用:用于web开发中的登陆验证界面、用于电商网站购物车功能等。
常用方法

方法声明说明
void setAttribute(String var1, Object var2);设置session域对象。
Enumeration<String> getAttributeNames();获取session域对象所有键的Enumeration对象。
void removeAttribute(String var1);移除指定名称的session域对象。
Object getAttribute(String var1);根据键获取session对象的值。
void invalidate();销毁session域对象。
void setMaxInactiveInterval(int var1);设置最大不活动时间,单位为秒。
int getMaxInactiveInterval();获取最大不活动时间。

可在Tomcatconf目录下的web.xml文件中进行修改,此时对全局有效:

<!--session默认的最大不活动时间,单位:分钟-->
<session-config>
    <session-timeout>30<session-timeout>
<session-config>
3.application

  ServletContext在服务器启动时创建,服务器关闭时销毁。一个JavaWeb应用只创建一个ServletContext对象,所有的浏览器在访问服务器时都共享同一个ServletContext对象,ServletContext对象一般用于在多个浏览器间共享数据时使用。
获取application域对象:在上文的ServletContext中已写明,故不再赘述。
常用方法

方法声明说明
Object getAttribute(String var1);根据键获取application域对象的值。
Enumeration <String> getAttributeNames();获取application域对象键的Enumeration对象。
void setAttribute(String var1, Object var2);设置application对象的键值对。
void removeAttribute(String var1);移除application对象。

④Cookie

  Cookie是浏览器提供的一种技术,通过服务器的程序能将一些保存在客户端进行处理的数据放在本地的计算机上的一个文件中,该文件可以理解为是一个Cookie域(键值对集合),往后每次访问该网站时都会在请求头中带着这些数据,而不需要通过网络传输,因而提高了网页处理的效率,并且能够减少服务器的负载,但是由于Cookie是服务器端保存在客户端的信息,所以其安全性也是很差的。
  一般情况下,cookie是以键值对进行表示的(key-value),例如name=jack,这个就表示cookie的名字是namecookie携带的值是jack
常见应用:是否要记住密码、相似内容推荐等。

1.概述

基本原理:当一个浏览器访问某web服务器时,web服务器会调用HttpServletResponseaddCookie()方法,在响应头中添加一个名叫Set-Cookie的响应字段用于将Cookie返回给浏览器,当浏览器第二次访问该web服务器时会自动的将该cookie回传给服务器,来实现用户状态跟踪。
分类:cookie有2种存储方式,一种是会话性,一种是持久性。

级别说明
会话级别默认情况下是一个会话级别的cookie,存储在浏览器的内存中,用户退出浏览器之后被删除。
持久化级别若希望浏览器将该cookie存储在磁盘上,则需要设置该cookie的生命周期setMaxAge,并给出一个以秒为单位的时间,cookie会保存在用户的硬盘中,直至生存期结束或者用户主动将其销毁。将最大时效设为0,则是命令浏览器删除该cookie。若为负数,表示不存储该cookie,事实上,cookie的maxAge属性的默认值就是-1,意为只在浏览器内存中存活,一旦关闭浏览器窗口,则cookie将会消失。

例:
在这里插入图片描述
常见属性

属性名作用
Name/Value设置Cookie的名称及相对应的值,对于认证Cookie,Value值包括Web服务器所提供的访问令牌。
Expires设置Cookie的生存期。有两种存储类型的Cookie:会话性与持久性。Expires属性缺省时,为会话性Cookie,仅保存在客户端内存中,并在用户关闭浏览器时失效;持久性Cookie会保存在用户的硬盘中,直至生存期到或用户直接在网页中单击“注销”等按钮结束会话时才会失效。
Path定义了Web站点上可以访问该Cookie的目录。
Domain指定了可以访问该 Cookie 的 Web 站点或域。Cookie 机制并未遵循严格的同源策略,允许一个子域可以设置或获取其父域的 Cookie。当需要实现单点登录方案时,Cookie 的上述特性非常有用,然而也增加了 Cookie受攻击的危险,比如攻击者可以借此发动会话定置攻击。因而,浏览器禁止在 Domain 属性中设置.org、.com 等通用顶级域名、以及在国家及地区顶级域下注册的二级域名,以减小攻击发生的范围。
Secure指定是否使用HTTPS安全协议发送Cookie。使用HTTPS安全协议,可以保护Cookie在浏览器和Web服务器间的传输过程中不被窃取和篡改。该方法也可用于Web站点的身份鉴别,即在HTTPS的连接建立阶段,浏览器会检查Web网站的SSL证书的有效性。但是基于兼容性的原因(比如有些网站使用自签署的证书)在检测到SSL证书无效时,浏览器并不会立即终止用户的连接请求,而是显示安全风险信息,用户仍可以选择继续访问该站点。由于许多用户缺乏安全意识,因而仍可能连接到Pharming攻击所伪造的网站。
HTTPOnly用于防止客户端脚本通过document.cookie属性访问Cookie,有助于保护Cookie不被跨站脚本攻击窃取或篡改。但是,HTTPOnly的应用仍存在局限性,一些浏览器可以阻止客户端脚本对Cookie的读操作,但允许写操作;此外大多数浏览器仍允许通过XMLHTTP对象读取HTTP响应中的Set-Cookie头。
2.常用方法

  jakarta.servlet.http包提供了一个专门操作Cookie的类javax.servlet.http.Cookie

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package jakarta.servlet.http;

import java.io.Serializable;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.ResourceBundle;

public class Cookie implements Cloneable, Serializable {
    private static final long serialVersionUID = -6454587001725327448L;
    private static final String TSPECIALS;
    private static final String LSTRING_FILE = "jakarta.servlet.http.LocalStrings";
    private static ResourceBundle lStrings = ResourceBundle.getBundle("jakarta.servlet.http.LocalStrings");
    private String name;
    private String value;
    private String comment;
    private String domain;
    private int maxAge = -1;
    private String path;
    private boolean secure;
    private int version = 0;
    private boolean isHttpOnly = false;

    public Cookie(String name, String value) {
        if (name != null && name.length() != 0) {
            if (this.isToken(name) && !name.equalsIgnoreCase("Comment") && !name.equalsIgnoreCase("Discard") && !name.equalsIgnoreCase("Domain") && !name.equalsIgnoreCase("Expires") && !name.equalsIgnoreCase("Max-Age") && !name.equalsIgnoreCase("Path") && !name.equalsIgnoreCase("Secure") && !name.equalsIgnoreCase("Version") && !name.startsWith("$")) {
                this.name = name;
                this.value = value;
            } else {
                String errMsg = lStrings.getString("err.cookie_name_is_token");
                Object[] errArgs = new Object[]{name};
                errMsg = MessageFormat.format(errMsg, errArgs);
                throw new IllegalArgumentException(errMsg);
            }
        } else {
            throw new IllegalArgumentException(lStrings.getString("err.cookie_name_blank"));
        }
    }

    public void setComment(String purpose) {
        this.comment = purpose;
    }

    public String getComment() {
        return this.comment;
    }

    public void setDomain(String domain) {
        this.domain = domain.toLowerCase(Locale.ENGLISH);
    }

    public String getDomain() {
        return this.domain;
    }

    public void setMaxAge(int expiry) {
        this.maxAge = expiry;
    }

    public int getMaxAge() {
        return this.maxAge;
    }

    public void setPath(String uri) {
        this.path = uri;
    }

    public String getPath() {
        return this.path;
    }

    public void setSecure(boolean flag) {
        this.secure = flag;
    }

    public boolean getSecure() {
        return this.secure;
    }

    public String getName() {
        return this.name;
    }

    public void setValue(String newValue) {
        this.value = newValue;
    }

    public String getValue() {
        return this.value;
    }

    public int getVersion() {
        return this.version;
    }

    public void setVersion(int v) {
        this.version = v;
    }

    private boolean isToken(String value) {
        int len = value.length();

        for(int i = 0; i < len; ++i) {
            char c = value.charAt(i);
            if (c < ' ' || c >= 127 || TSPECIALS.indexOf(c) != -1) {
                return false;
            }
        }

        return true;
    }

    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException var2) {
            throw new RuntimeException(var2.getMessage());
        }
    }

    public void setHttpOnly(boolean isHttpOnly) {
        this.isHttpOnly = isHttpOnly;
    }

    public boolean isHttpOnly() {
        return this.isHttpOnly;
    }

    static {
        if (Boolean.valueOf(System.getProperty("org.glassfish.web.rfc2109_cookie_names_enforced", "true"))) {
            TSPECIALS = "/()<>@,;:\\\"[]?={} \t";
        } else {
            TSPECIALS = ",; ";
        }

    }
}

常用方法

方法声明说明
new Cookie(String name, String value)创建一个Cookie对象。
String getName()获取cookie的键。
String getValue()获取cookie的值。
void setValue(String newValue)用于设置Cookie的值。
void setMaxAge(int expiry)设置cookie的有效期。
int getMaxAge()获取cookie的有效期。
void setPath(String uri)设置cookie的作用域。
void setDomain(String pattern)用于设置该Cookie项的有效域。
void setVersion(int v)用于设置cookie采用的版本协议。
int getVersion()用于返回cookie采用的版本协议。
void setComment(String purpose)用于设置该Cookie项的注解部分。
void setSecuire(boolean flag)用于设置该Cookie项是否只能使用安全协议传送。
boolean getSecure()用于返回该Cookie项是否只能使用安全协议传送。
String getPath()获取cookie的作用域。
response.addCookie(Cookie cookie)将cookie讲给客户端保存。
resquest.getCookies()得到客服端传过来的所有cookie对象。
4.中文问题

  Cookie中不能出现中文,若有中文,则通过URLEnCoder.encode()来进行编码,获取时通过URLDecoder.decode()来进行解码。
编码

String name="姓名";
String value="张三";
//通过URLEncoder.encode()来进行编码
name=URLEncoder.encode(name);
value=URLEncode.encode(value);
//创建Cookie对象
Cookie cookie=new Cookie(name,value);
//发送Cookie对象
response.addCookie(cookie);

解码

//获取时通过URLDecoder.decode()来进行解码
URLEncoder.decode(cookie.getName());
URLEncoder.decode(cookie.getValue());
5.路径问题

  CookiesetPath设置cookie的路径,这个路径直接决定服务器的请求是否会从浏览器中加载某些cookie
  cookie的路径指的是可访问该cookie的顶层目录,该路径的子路径也可访问该cookie
情景一:当前服务器下任何项目的任意资源都可获取Cookie对象。

/*当前项目路径为:s01*/
Cookie cookie=new Cookie("xxx","XXX");
//设置路径为"/",表示在当前服务器下任何项目都可访问到Cookie对象
cookie.setPath("/");
response.addCookie(cookie);

情景二:当前项目下的资源可获取Cookie对象(默认不设置Cookiepath)。

/*当前项目路径为:s01*/
Cookie cookie=new Cookie("xxx","XXX");
//设置路径为"/s01",表示在当前项目下任何项目都可访问到Cookie对象
cookie.setPath("/s01");//默认情况,可不设置path的值
response.addCookie(cookie);

情景三:指定项目下的资源可获取Cookie对象。

/*当前项目路径为:s01*/
Cookie cookie=new Cookie("xxx","XXX");
//设置路径为"/s02",表示在s02项目下才可访问到Cookie对象
cookie.setPath("/s02");//只能在s02项目下获取Cookie,就算cookie由s01产生,s01也不能获取它
response.addCookie(cookie);

  若我们设置path,且,当前访问的路径包含了cookie路径(当前访问路径比cookie路径基础上要比cookie的范围小),cookie就会加载到request对象中。

6.案例

  实现自动保存用户名、密码功能。
MyServlet01.java:用于请求转发到Login.jsp页面。

package com.example.servletdemo;

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

@WebServlet("/MyServlet01")
public class MyServlet01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
        //在request域中设置参数
        request.setAttribute("uname","");
        request.setAttribute("password","");
        //得到所有cookie
        Cookie[] cookies=request.getCookies();
        //遍历cookie数组,将用户名和密码存入request域当中
        for (Cookie cookie:cookies) {
            if("uname".equals(cookie.getName())){
                request.setAttribute("uname",cookie.getValue());
            }
            if("password".equals(cookie.getName())){
                request.setAttribute("password",cookie.getValue());
            }
        }
        //转发到login.jsp页面
        request.getRequestDispatcher("/Login.jsp").forward(request,response);
    }
    @Override
    protected void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }
}

Login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>

<html>
<head>
    <title>登录</title>
</head>
<body>
<form action="LoginServlet" method="post">
    用户名:<input type="text" name="uname" value="<%=request.getAttribute("username")%>"><br/>
    密 码:<input type="password" name="password" value="<%=request.getAttribute("password")%>"><br/>
    <input type="submit" value="登录">
</form>
</body>
</html>

LoginServlet.java

package com.example.servletdemo;

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

@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
    @Override
    protected void doGet(HttpServletRequest request,HttpServletResponse response) throws IOException {
        //得到用户名和密码
        String uname=request.getParameter("uname");
        String password=request.getParameter("password");
        //判断是否登录成功
        if("root".equals(uname)&&"123456".equals(password)){
            //编写cookie,并将其存入客户端
            Cookie unameCookie=new Cookie("uname",uname);
            Cookie passwordCookie=new Cookie("password",password);
            response.addCookie(unameCookie);
            response.addCookie(passwordCookie);
            //返回提示
            response.getWriter().write("<h1>登录成功</h1>");
        }else {
            response.getWriter().write("<h1>登录失败</h1>");
        }
    }
}

在这里插入图片描述
下一次登录会自动填写:
在这里插入图片描述

⑤Session

1.概述

  session在网络应用中称为“会话控制”,是服务器为了保存用户状态而创建的一个特殊的对象。简而言之,session就是一个存储于服务器端的特殊对象,服务器会为每一个游览器(客户端)创建一个唯一的session,这个session是服务器端共享,每个客户端独享,用于存储、共享信息。 但session由服务器进行控制,session的创建和销毁都是服务器进行管理的。
在这里插入图片描述

2.基本原理

  session机制采用的是在服务器端保持HTTP状态信息的方案。为了加速session的读取和存储,web服务器中会开辟一块内存用来保存服务器端所有的session,每个session都会有一个唯一标识sessionid,根据客户端传过来的jsessionid(cookie中),找到对应的服务器端的session
  事实上,在访问一个网站时,在HTTP请求中往往会携带一个cookie,这个cookie的名字是JSESSIONID,这个JSESSIONID表示的就是sessionid,这个是由服务器创建的,并且是唯一的。服务器在使用session时,会根据JSESSIONID来进行不同操作。

在这里插入图片描述
流程:当用户发送一个请求到服务器端时,服务器会先检查请求中是否含有sessionid(存在cookie中或者在url中),有以下两种情况:

  • 如果不存在sessionid(说明是第一次请求),就会为该请求用户创建一个session对象,并将该session对象的sessionid(放到响应头的set-cookie中,格式set-cookie:sessionid,下次再请求时cookie中就会有一个namejsessionidcookievalue就是sessionid)响应给客户端。
  • 如果存在sessionid,就会在服务器端查找是否有该sessionid对应的session,如果有就使用,没有就创建一个。
3.常用方法
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package jakarta.servlet.http;

import jakarta.servlet.ServletContext;
import java.util.Enumeration;

public interface HttpSession {
    long getCreationTime();

    String getId();

    long getLastAccessedTime();

    ServletContext getServletContext();

    void setMaxInactiveInterval(int var1);

    int getMaxInactiveInterval();

    /** @deprecated */
    @Deprecated
    HttpSessionContext getSessionContext();

    Object getAttribute(String var1);

    /** @deprecated */
    @Deprecated
    Object getValue(String var1);

    Enumeration<String> getAttributeNames();

    /** @deprecated */
    @Deprecated
    String[] getValueNames();

    void setAttribute(String var1, Object var2);

    /** @deprecated */
    @Deprecated
    void putValue(String var1, Object var2);

    void removeAttribute(String var1);

    /** @deprecated */
    @Deprecated
    void removeValue(String var1);

    void invalidate();

    boolean isNew();
}

方法声明说明
String getId();获取sessionid。
void invalidate();让session失效。
Object getAttribute(String var1);根据key获取session的value。
void setAttribute(String var1, Object var2);向session中存放键值对。
void removeAttribute(String var1);从session中根据key移除键值对。
long getCreationTime();获取session的创建时间。
void setMaxInactiveInterval(int var1);设置session的最大有效时间。
int getMaxInactiveInterval();获取session的最大有效时间。
long getLastAccessedTime();获取session最后一次访问时间。
HttpServletRequest.getSession()获取session对象。
4.案例

  实现登录验证,如果用户登录成功了,那么我们在1天内访问主页面就不需要登录了。我们利用session进行实现。
Login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>

<html>
<head>
    <title>登录</title>
</head>
<body>
    <form action="LoginServlet" method="post">
        用户名:<input type="text" name="uname" /><br/>
        密 码:<input type="password" name="password" /><br/>
        <input type="submit" value="登录">
    </form>
</body>
</html>

MyServlet01.java

package com.example.servletdemo;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.*;
import java.io.IOException;

@WebServlet("/MyServlet01")
public class MyServlet01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
        //得到session
        HttpSession session= request.getSession();
        //取出用户名
        Object uname=session.getAttribute("uname");
        if(uname!=null){
            //在一天内登陆过,无需再次登陆
            response.getWriter().write("<h1>登录成功</h1>");
        }else {
            //session失效,重定向到登陆界面
            response.sendRedirect(request.getContextPath()+"/LoginServlet");
        }
    }
    @Override
    protected void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }
}

LoginServlet.java

package com.example.servletdemo;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.*;

import java.io.IOException;

@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
    @Override
    protected void doGet(HttpServletRequest request,HttpServletResponse response) throws IOException {
        //得到用户名和密码
        String uname=request.getParameter("uname");
        String password=request.getParameter("password");
        //判断是否登录成功
        if("root".equals(uname)&&"123456".equals(password)){
            //编写session
            HttpSession session=request.getSession();
            //设置最长访问时间间隔
            session.setMaxInactiveInterval(60*60*24);
            //存入用户名进入session
            session.setAttribute("uname",uname);
            //重定向到主页面
            response.sendRedirect(request.getContextPath()+"/MyServlet01");
        }else {
            response.getWriter().write("<h1>登录失败</h1>");
        }
    }
}
5.Cookie与Session对比
  1. cookie保存在客户端,session保存在服务端,故而,cookie相对于session并不安全。
  2. cookie作用于他所表示的path中(url中要包含path),范围较小。session代表客户端和服务器的一次会话过程,web页面跳转时也可以共享数据,范围是本次会话,客户端关闭也不会消失。会持续到我们设置的session生命周期结束(默认30min)。
  3. 我们使用session需要cookie的配合。cookie用来携带JSESSIONID,且,cookie存放的数据量较小,session可以存储更多的信息。
  4. 由于session是存放于服务器的,当有很多客户端访问时,肯定会产生大量的session,这些session会对服务端的性能造成影响。

⑥文件上传与下载

1.文件上传

  文件上传涉及前台页面的编写和后台服务器端代码的编写,前台发送文件、数据等,后台接收并保存。
前端页面
  在做文件上传时,会有一个上传的页面,首先需要一个表单,且表单的请求方式为POST,其次。form表单的encytpe必须设置为multipart/form=data,即:

enctype="multipart/form-data";

意为,设置表单的类型为文件上传表单,默认情况下表单的类型为:

enctype="application/x-www-form-unrlencoded";

不能用于文件的上传。
upload.jsp

<%--
  Created by IntelliJ IDEA.
  User: lenovo
  Date: 2022/12/30
  Time: 11:12
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>文件上传</title>
</head>
<body>
    <!--
        文件上传
            1.准备表单
            2.设置表单的提交类型为POST请求
            3.设置表单类型为文件上传表单
            4.设置文件提交的地址
            5.准备表单元素
                1):普通表单项 type="text"
                2):文件项 type="file"
            6.设置表单元素的name属性值,用于给后台接收数据
                -->
    <form method="post" enctype="multipart/form-data" action="uploadServlet">
        姓名:<input type="text" name="uname">

        文件:<input type="file" name="myfile">

        <!--button默认的类型是提交类型 type="submit"-->
        <button>提交</button>
    </form>
</body>
</html>

后端实现
  使用注解@MultipartConfig将一个Servlet标识为支持文件上传。Servletmultipart/form=dataPOST请求封装为Part,通过Part对上传的文件进行操作。
upload.java

package com.xxxx.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.MultipartConfig;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.Part;
import java.io.IOException;

@WebServlet("/uploadServlet")
@MultipartConfig//文件上传类型表单必须加的注解
public class uploadServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("文件上传");
        //设置请求的编码格式
        req.setCharacterEncoding("UTF-8");
        //获取普通表单项(文本框)
        String uname=req.getParameter("uname");
        System.out.println("uname:"+uname);
        //通过getPart(name)方法获取Part对象(name代表的是页面中file文件域的name属性值)
        Part part=req.getPart("myfile");
        //获取上传的文件名
        String fileName=part.getSubmittedFileName();
        System.out.println("上传的文件名为:"+fileName);
        //获取上传文件需要存放的路径(先得到项目存放的真实路径)
        String filePath=req.getServletContext().getRealPath("/");
        System.out.println("文件存放的路径为:"+filePath);
        //将文件上传到指定位置
        part.write(filePath+fileName);
    }
}

创建一个txt文件,并执行代码:
在这里插入图片描述
点击提交,URL会自动跳转到:

localhost:8080/uploadServlet

控制台输出:

文件上传
uname:zhangsan
上传的文件名为:test.txt
文件存放的路径为:D:\IntelliJ IDEA\demo1\target\demo1-1.0-SNAPSHOT\

target目录中查找:
在这里插入图片描述

2.文件下载

  文件下载,即,将服务器上的资源下载(拷贝)到本地,可通过两种方式下载:

  1. 通过超链接本身的特性来进行下载。
  2. 通过代码下载
1.超链接下载

  在HTMLJSP页面中使用a标签时,原意是希望能够进行跳转,但当超链接遇到浏览器不识别的资源时就会自动下载;当遇见浏览器能够显示的资源时,浏览器就会默认显示出来,比如,txtpngjpg等。当然,也可通过download属性规定浏览器进行下载,但有些浏览器并不支持。
默认下载

<!--当超链接遇到浏览器不识别的资源时,会自动下载-->
<a href="test.zip">超链接下载</a>

指定download属性下载

<!--当超链接遇到浏览器识别的资源时,默认不会下载,通过download属性可进行下载-->
<a href="test.txt" download>超链接下载</a>

  download属性可不写任何信息,会自动使用默认文件名,若设置了download属性值,则使用设置的值作为文件名,当用户打开浏览器点击链接的时候就会下载文件。

准备资源:
在这里插入图片描述
新建目录进行存放:
在这里插入图片描述
注意,IDEA在启动Tomcat时会将这些资源放在target输出目录中,即,启动后download目录及其文件并不在该项目下,故而需要单独设置:
在这里插入图片描述
点击"外部源"进行添加,且,要修改"应用程序上下文",指出是在当前项目demo1下的download,完成后浏览器即可获取该资源。
download.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文件下载</title>
</head>
<body>
    <a href="download/test.txt">文本文件</a>
    <a href="download/b0514f5013364ac6bf67d33e9b90929e.png">图片文件</a>
    <a href="download/test.zip">压缩文件</a>
</body>
</html>

输入URL

http://localhost:8080/demo1/download/download.html

在这里插入图片描述
点击文本文件:
在这里插入图片描述
点击图片文件:
在这里插入图片描述
(可见这两者浏览器均可直接识别)
点击压缩文件:
在这里插入图片描述
浏览器会进行下载。
  若想要前两者也进行下载,则可通过设置download属性的方式:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文件下载</title>
</head>
<body>
    <a href="download/test.txt" download>文本文件</a>
    <a href="download/b0514f5013364ac6bf67d33e9b90929e.png" download>图片文件</a>
    <a href="download/test.zip">压缩文件</a>
</body>
</html>

且,download属性可指定其值作为下载时的文件名。

2.后台实现下载
  1. 需要通过response.setContentType方法设置Content-type头字段的值,为浏览器无法使用某种方式或激活某个程序来处理的MIME类型,例如,application/octet-streamapplication/x-msdownload等。
  2. 需要通过response.setHeader方法设置Content-Disposition头的值为attachment;filename=文件名
  3. 读取下载文件,调用response.getOutputStream方法向客户端写入附件内容。

download.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文件下载</title>
</head>
<body>
    <!--浏览器能识别的资源-->
    <a href="download/test.txt" download>文本文件</a>
    <a href="download/b0514f5013364ac6bf67d33e9b90929e.png" download>图片文件</a>
    <!--浏览器不能识别的资源-->
    <a href="download/test.zip">压缩文件</a>
    <hr>
    <form action="downloadServlet">
        文件名:<input type="text" name="fileName" placeholder="请输入要下载的文件名">
        <button>下载</button>
    </form>
</body>
</html>

downloadServlet.java

package com.xxxx.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

@WebServlet("/downloadServlet")
public class downloadServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("文件下载...");
        //设置请求的编码格式
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=UTF-8");
        //获取参数(要下载的文件名)
        String fileName=req.getParameter("fileName");
        //参数的非空判断(trim:去除字符串的前后空格)
        if(fileName==null||"".equals(fileName.trim())){
            resp.getWriter().write("请输入要下载的文件名!");
            resp.getWriter().close();
            return ;
        }
        //得到图片存放的路径
        String path=req.getServletContext().getRealPath("/download/");
        //通过路径得到file对象
        File file=new File(path+fileName);
        //判断文件对象是否存在且是一个标准文件
        if(file.exists()&&file.isFile()){
            //设置响应类型(浏览器无法使用某种方式或激活某个程序来处理的MIME类型)
            resp.setContentType("application/x-msdownload");
            //设置响应头
            resp.setHeader("Content-Disposition","attachment;filename="+fileName);
            //得到file文件输入流
            InputStream in=new FileInputStream(file);
            //得到字节输出流
            ServletOutputStream out=resp.getOutputStream();
            //定义byte数组
            byte[] bytes=new byte[1024];
            //定义长度
            int len=0;
            //循环输出
            while((len=in.read(bytes))!=-1){
                //输出
                out.write(bytes,0,len);
            }
            //关闭资源
            out.close();
            in.close();
        }else {
            resp.getWriter().write("文件不存在,请重试");
            resp.getWriter().close();
        }
    }
}

在这里插入图片描述
输入test.zip
在这里插入图片描述

⑦过滤器与监听器

1.过滤器
1.1 概述

  Filter即为过滤,用于在Servlet之外对Request进行修改,它主要用于对 用户请求进行预处理,也可对HttpServletResponse进行后处理。
  Filter,过滤器,是javaweb的三大组件之一,javaweb的三大 组件分别是Servlet程序、Filter过滤器、Listener监听器。
  使用Filter的完整流程:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。在一个web应用中,可开发编写多个Filter,这些Filter组合起来称之为一个Filter链。
在这里插入图片描述
在这里插入图片描述
对于一个过滤器链,请求时先配置先执行,而响应时则以相反的顺序执行。
  在HttpServletRequest到达Servlet之前,拦截客户的HttpServletRequest,并根据需要检查HttpServletRequest,也可修改其头和数据。
Filter的生命周期

生命周期过程
创建Filter的创建和销毁由web服务器负责。 web应用程序启动时,web服务器将创建Filter的实例对象,并调用其init方法,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作,filter对象只会创建一次,init方法也只会执行一次。通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。
过滤拦截请求,执行doFilter()方法。
销毁web容器调用destroy方法销毁Filter。destroy方法在Filter的生命周期中仅执行一次。在destroy方法中,可以释放过滤器使用的资源。
1.2 FilterConfig

  FilterConfigFilter过滤器的配置文件类,Tomcat每次创建Filter的时候,也会同时创建一个FilterConfig类,这里包含了Filter配置文件的配置息。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package jakarta.servlet;

import java.util.Enumeration;

public interface FilterConfig {
    String getFilterName();

    ServletContext getServletContext();

    String getInitParameter(String var1);

    Enumeration<String> getInitParameterNames();
}

FilterConfig类的作用是获取filter过滤器的配置内容:

方法声明说明
String getFilterName();获取Filter的名称。
ServletContext getServletContext();获取ServletContext对象。
String getInitParameter(String var1);根据键获取初始化参数。
Enumeration<String> getInitParameterNames();获取初始化参数的所有键。
1.3 编写过滤器

  Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package jakarta.servlet;

import java.io.IOException;

public interface Filter {
    default void init(FilterConfig filterConfig) throws ServletException {
    }

    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

    default void destroy() {
    }
}

  过滤器的实现需要两步:

  1. 编写Java实现类实现Filter接口,并实现其doFilter方法。
  2. 通过@WebFilter注解设置它所能拦截的资源。

例:
Servlet14.java

package com.xxxx.servlet;

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

@WebServlet("/ser14")
public class Servlet14 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet14");
    }
}

Filter01.java

package com.xxxx.filter;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter("/ser14")//通过拦截路径来指定拦截的资源
public class Filter01 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("Filter01 init...");
    }

    @Override
    public void destroy() {
        System.out.println("Filter01 destroy...");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //doFilter放行方法前去做请求拦截
        System.out.println("Filter01 正在拦截...");
        //拦截并进行处理后放行资源
        filterChain.doFilter(servletRequest, servletResponse);
        //doFilter放行方法后去做响应拦截
        System.out.println("Filter01 处理响应...");
    }
}

Filter02.java

package com.xxxx.filter;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter("/*")//拦截所有资源
public class Filter02 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("Filter02 init...");
    }

    @Override
    public void destroy() {
        System.out.println("Filter02 destroy...");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("Filter02 正在拦截...");
        //拦截并进行处理后放行资源
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("Filter02 处理响应...");
    }
}

输出:

已连接到服务器
[2022-12-31 10:05:03,758] 工件 demo1:war exploded: 正在部署工件,请稍候…
[2022-12-31 10:05:03,758] 工件 download: 正在部署工件,请稍候…
Filter01 init...
[2022-12-31 10:05:04,190] 工件 demo1:war exploded: 工件已成功部署
[2022-12-31 10:05:04,190] 工件 demo1:war exploded: 部署已花费 432 毫秒
[2022-12-31 10:05:04,233] 工件 download: 工件已成功部署
[2022-12-31 10:05:04,233] 工件 download: 部署已花费 475 毫秒
31-Dec-2022 22:05:13.247 信息 [Catalina-utility-2] org.apache.catalina.startup.HostConfig.deployDirectory 把web 应用程序部署到目录 [D:\Tomcat\apache-tomcat-10.0.27\webapps\manager]
31-Dec-2022 22:05:13.294 信息 [Catalina-utility-2] org.apache.catalina.startup.HostConfig.deployDirectory Web应用程序目录[D:\Tomcat\apache-tomcat-10.0.27\webapps\manager]的部署已在[47]毫秒内完成
Filter01 正在拦截...
Servlet14

输出:

Filter01 正在拦截...
Filter02 正在拦截...
Servlet14
Filter02 处理响应...
Filter01 处理响应...
1.4 部署Filter

  部署Filter分为两步:

  1. 注册Filter

  开发好Filter之后,需要在web.xml文件中进行注册,这样才能够被web服务器调用。在web.xml文件中注册Filter范例:

<filter>
    <description>过滤器名称</description>
    <filter-name>自定义的名字</filter-name>
    <filter-class>com.yangcq.filter.FilterTest</filter-class>
    <!--配置FilterTest过滤器的初始化参数-->
    <init-param>
        <description>配置过滤器的初始化参数</description>
        <param-name>name</param-name>
        <param-value>gacl</param-value>
    </init-param>
    <init-param>
        <description>配置FilterTest过滤器的初始化参数</description>
        <param-name>like</param-name>
        <param-value>java</param-value>
    </init-param>
</filter>
元素说明
<description>用于添加描述信息,该元素的内容可为空,<description>可以不配置。
<filter-name>用于为过滤器指定一个名字,该元素的内容不能为空。
<init-param>元素用于为过滤器指定初始化参数,它的子元素<param-name>指定参数的名字,<param-value>指定参数的值。
<param-name>指定参数的名字。
<param-value>指定参数的值。
  • 映射Filter
      在web.xml文件中注册了Filter之后,还要在web.xml文件中映射Filter
<!--映射过滤器-->
  <filter-mapping>
      <filter-name>FilterTest</filter-name>
      <!--"/*"表示拦截所有的请求 -->
      <url-pattern>/*</url-pattern>
      <dispatcher>REQUEST</dispatcher>
  </filter-mapping>
元素说明
<filter-mapping>用于设置一个Filter所负责拦截的资源。
<filter-name>用于设置filter的注册名称,该值必须是在<filter>元素中声明过的过滤器的名字。
<url-pattern>设置 filter 所拦截的请求路径。
<servlet-name>指定过滤器所拦截的Servlet名称。
<dispatcher>指定过滤器所拦截的资源被 Servlet 容器调用的方式,可以是REQUEST,INCLUDE,FORWARD和ERROR之一,默认REQUEST。
  • REQUEST:当用户直接访问页面时,Web容器将会调用过滤器。如果目标资源是通过RequestDispatcherinclude()forward()方法访问时,那么该过滤器就不会被调用。
  • INCLUDE:如果目标资源是通过RequestDispatcherinclude()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。
  • FORWARD:如果目标资源是通过RequestDispatcherforward()方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。
  • ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。
2.监听器
2.1 概述

  Listener,监听器,是javaweb的三大组件之一,javaweb的三大 组件分别是Servlet程序、Filter过滤器、Listener监听器。
  web监听器是Servlet中一种特殊的接口,能帮助开发者监听web中的特定事件,比如,ServletContextHttpSessionServletRequest的创建和销毁;变量的创建、销毁和修改等,可在某些动作前后增加处理,实现监控。
  监听器接口一个有8个,这些接口都继承于EventListener接口(空接口,起标志作用),通过这8个接口,我们基本可以实现对整个web项目的监听,8个接口如下:

监听器接口说明
ServletRequestListener用于监听request对象创建/销毁的。
ServleRequestAttributeListener用于监听request域里面数据变化。
HttpSessionListener用于监听session对象创建/销毁。
HttpSessionAttributeListener用于监听session域里面的数据变化。
HttpSessionBindingListener处理session对象监听器绑定和解绑。
HttpSessionActivationListener处理session对象钝化和活化。
ServletContextListener用于监听ServletContext对象创建/销毁。
ServletContextAttributeListener用于监听ServletContext域里面数据变化。

当按照监听对象划分时,可分为三种:

  1. ServletRequest:对应监控request内置对象的创建和销毁,包括ServletRequestAttributeListener接口、ServletRequestListener接口。
接口方法声明方法说明
ServletRequestListenerdefault void requestDestroyed(ServletRequestEvent sre)监听request对象的销毁。
default void requestInitialized(ServletRequestEvent sre)监听request对象的初始化。
ServletRequestAttributeListenerdefault void attributeAdded(ServletRequestAttributeEvent srae)监听Request属性对象的增加。
default void attributeRemoved(ServletRequestAttributeEvent srae)监听Request属性对象的删除。
default void attributeReplaced(ServletRequestAttributeEvent srae)监听Request属性对象的修改。
  1. HttpSession:对应监控session内置对象的创建和销毁,包括HttpSessionListenerHttpSessionBindingListenerHttpSessionAttributeListenerHttpSessionActivationListener
接口方法声明方法说明
HttpSessionListenerdefault void sessionCreated(HttpSessionEvent se)监听session对象的创建。
default void sessionDestroyed(HttpSessionEvent se)监听session对象的销毁。
HttpSessionBindingListenerdefault void valueBound(HttpSessionBindingEvent event)监听session与对象的绑定。
default void valueUnbound(HttpSessionBindingEvent event)监听session与对象的解绑。
HttpSessionAttributeListenerdefault void attributeAdded(HttpSessionBindingEvent event)监听session对象属性的添加。
default void attributeRemoved(HttpSessionBindingEvent event)监听session对象属性的移除。
default void attributeReplaced(HttpSessionBindingEvent event)监听session对象属性的修改。
HttpSessionActivationListenerdefault void sessionWillPassivate(HttpSessionEvent se)监听session对象的活化。
default void sessionDidActivate(HttpSessionEvent se)监听session对象的钝化。
  1. ServletContext:对应监控application内置对象的创建跟销毁,包括ServletContextAttributeListenerServletContextListener
接口方法声明方法说明
ServletContextListenerdefault void contextInitialized(ServletContextEvent sce)监听context对象的创建。
default void contextDestroyed(ServletContextEvent sce)监听context对象的销毁。
ServletContextAttributeListenerdefault void attributeAdded(ServletContextAttributeEvent event)监听context对象属性的添加。
default void attributeRemoved(ServletContextAttributeEvent event)监听context对象属性的移除。
default void attributeReplaced(ServletContextAttributeEvent event)监听context对象属性的修改。
2.2 配置

  可直接使用IDEA来创建监听器,创建后会默认实现ServletContextListenerHttpSessionListenerHttpSessionAttributeListener这3个接口
在这里插入图片描述

2.2.1 XML配置

  在web.xml文件当中进行配置:

<listener>
    <listener-class>监听器的全路径</listener-class>
</listener>
2.2.2 注解配置

   注解方式配置十分简单,我们在自己写的监听器类上面加上@WebListener就行了。

@WebListener
public class MyServlet implements HttpServlet{
	...
}

(五)注解开发

  留个坑,有空再写了。

;