03-Servlet
Servlet 简介
Sun 提供的一种动态web资源开发技术。本质上一段 Java 程序。将 Servlet 加入到 Servlet 容器中运行。
区分以下几个名词:
- Servlet:属于 JavaEE 重要技术规范,构建了 "接收请求 → 调用 Servlet 程序处理 → 返回响应" 基本模型
- Servlet 程序: Java 提供了开发 Servlet 程序的 API,该 API 可以说是 Servlet 容器的一部分,它对接应用程序和 Servlet 容器
- Servlet 容器:实现了 Servlet 技术规范的部署环境,它可以部署运行 Servlet 程序
- JavaWEB 容器
- Web容器:能够部署多个 WEB 应用程序的环境
- JavaWEB 容器实现了 JavaEE 规定的 WEB 应用技术规范(Servlet、JSP、Java Web Socket)的部署环境
- 完整的 JavaWeb 容器包含 Servlet 容器
手写一个 Servlet
- 写一个类实现
Servlet<I>
- 将写好的类配置到 tomcat 下 web 应用的 web.xml 中,配置对外访问路径(虚拟路径)
一次请求响应过程
步骤
- 分析出当前请求的是哪台虚拟主机
- 查看 "Host" 请求头,分析出访问的是哪台虚拟主机
- 如果没有 "Host" 请求头(访问时直接敲的 IP 地址),则会去找缺省虚拟主机
- 查看 "Host" 请求头,分析出访问的是哪台虚拟主机
- 分析当前请求访问的是当前虚拟主机中的哪个 Web 资源
- 从请求行中请求的资源部分中分析出当前访问的是哪个 web 应用
- 分析当前请求要访问的是这个 web 应用中的哪个资源
- Server 查找该 web 应用的 web.xml 文件,查看有没有对应的虚拟路径
- 如果有,则用这个虚拟路径对应的资源作响应
- 没有,404
- 如果有,则用这个虚拟路径对应的资源作响应
Servlet相关工作
- Server 首先检查是否已经装载并创建了该 Servlet 的实例
- Servlet 在第一次被访问到的时候,Server 创建出该 Servlet 的一个实例对象
- 创建出对象后,立即调用
init()
做初始化操作 - 创建出来的对象会一直驻留在内存中,为后续对这个 Servlet 的访问服务
- Servlet 容器创建本次 HTTP 请求相关的对象,即:
- 封装HTTP请求消息的 HttpServletRequest 对象
- 一个代表HTTP响应消息的 HttpServletResponse 对象
- 调用 Servlet 的
service()
并将请求和响应对象作为参数传递进去,每次对这个 Servlet 的访问都会导致 Servlet 中service()
执行 - 当 web 应用被移除出容器或 Server 被关闭,随着 web 应用的销毁,Servlet 会被销毁,但在销毁之前,Server 会调用 Servlet 的
destory()
做一些善后工作 - Server 从 response 对象中获取出之前写入的数据,组织成 HTTP 响应消息打给 browser
流程图
Servlet 继承结构
Servlet<I>
:定义了 Servlet 应该具有的基本方法GenericServlet <abstract C>
:通用的基本Servlet实现- 对于不常用的方法,在这个实现类中进行了基本的实现
- 对于
service()
则保留为抽象方法,需要让子类去实现
HttpServlet<C>
- 在 GenericServlet 的基础上基于 HTTP 协议进行了进一步强化,即实现了
service()
:根据当前请求方式,调用对应的 doXXX()
- 在实际开发 Servlet 中,只需继承 HttpServlet,覆盖具体要处理的 doXXX 方法,就可以实现"根据不同的请求方式实现不同的处理"
- 在 GenericServlet 的基础上基于 HTTP 协议进行了进一步强化,即实现了
Tips
Servlet 映射
- 一个
<servlet>
可以对应多个<servlet-mapping>
,从而使一个 Servlet 可以有多个路径来访问 <url-pattern>
中的路径可以使用*
匹配符进行匹配- 规范:① 得以
/
开头 ,/*
"结尾 ②*.后缀
- 规范:① 得以
- 由于
*
的引入,有可能一个路径被多个<url-pattern>
匹配,这时优先级判断条件- 哪个
<url-pattern>
最长匹配 选哪个 *.后缀
优先级最低,比/*
还低- 举例
- 哪个
- 注意,[路径] 和 [扩展名] 匹配无法同时设置,比如下面的几个
<url-pattern>
都是非法的,如果设置,tomcat 将报错<url-pattern>/kata/*.jsp</url-pattern> <url-pattern>/*.jsp</url-pattern> <url-pattern>he*.jsp</url-pattern>
- 关于
/*
the /* on a servlet overrides all other servlets, including all servlets provided by the servletcontainer such as the default servlet and the JSP servlet. Whatever request you fire, it will end up in that servlet. This is thus a bad URL pattern for servlets. <servlet>
配置<load-on-startup>
标签- 概述
- 配置该元素可以使得在 web 应用启动时,一并装载和创建该 Servlet 的实例对象,以及调用 Servlet 实例对象的
init()
- 该标签的标签体是一个 int 类型的数值,如果有多个配置该元素的 Servlet,可根据数值大小,来确定启动顺序
- 配置该元素可以使得在 web 应用启动时,一并装载和创建该 Servlet 的实例对象,以及调用 Servlet 实例对象的
- 用途:为 web 应用写一个 InitServlet,这个 Servlet 配置为启动时装载,为整个web应用创建必要的数据库表和数据
- 概述
缺省 Servlet
- 如果有一个 Servlet 的
<url-pattern>
被配置为/
,这个 Servlet 就变成了缺省 Servlet - 其他 Servlet 都不处理的请求,由缺省Servlet来处理,也就是 "请求资源部份的名称" 对不上任一
<url-pattern>
,比如 html、css、js… - 对静态资源的访问、404、500 页面… ,都是由缺省Servlet来处理的
- 通常不会自己去配置缺省 Servlet,tomcat/config/web.xml 中给配过了
线程安全问题
- 起源:当多个客户端并发访问同一个 Servlet 时,web 服务器会为每一个客户端的访问请求创建一个线程,并在这个线程上调用 Servlet 的
service()
,因此service()
内如果访问了同一个资源的话,就有可能引发线程安全问题 - 解决方案1:实现
SingleThreadModel<I>
- 如果某个 Servlet 实现了
SingleThreadModel<I>
,那么Servlet引擎将以单线程模式来调用service()
。SingleThreadModel <I>
没有任何方法,只是一个"标记接口"。在 Server 内部维护一个"池",产生多个 Servlet 实例对象,并发的每个线程分别调用一个独立的 Servlet 实例对象。 - 实现
SingleThreadModel <I>
并不能真正解决 Servlet 的线程安全问题,因为 Servlet 引擎会创建多个 Servlet 实例对象,而真正意义上解决多线程安全问题是指一个 Servlet 实例对象被多个线程同时调用的问题。事实上,在 Servlet API 2.4 中,已经将SingleThreadModel <I>
标记为Deprecated(过时的)。
- 如果某个 Servlet 实现了
- 解决方法2:加锁
- 效率低下。故尽量少使用类变量,如果必须使用类变量,就要加锁,代码中尽量只包住有线程问题的代码,让锁的时间尽量的短,减少等待时间,提高响应速度
ServletConfig
- 代表当前 Servlet 在 web.xml 中的配置信息。
- 如何获得 ServletConfig
- ServletConfig 常用方法
- 代码演示
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletConfig config = this.getServletConfig(); // 获取当前Servlet在web.xml中配置的名称 String sName = config.getServletName(); System.out.println(sName); // 获取当前Servlet在web.xml中配置的初始化参数name1 String value1 = config.getInitParameter("name1"); System.out.println(value1); // 获取所有初始化参数 Enumeration<String> enums = config.getInitParameterNames(); while(enums.hasMoreElements()) { String name = enums.nextElement(); String value = config.getInitParameter(name); System.out.println(name + ":" + value); } }
ServletContext
概述
- WEB 容器在启动时,它会为每个WEB应用程序都创建一个对应的 ServletContext 对象,它代表当前web应用。
- ServletConfig 对象中维护了 ServletContext 对象的引用,开发人员在编写 servlet 时,可以通过 ServletConfig 的
getServletContext()
获得 ServletContext 对象。
- 作用范围:整个 web 应用范围内共享数据
功能
- 作为"域对象"可以在整个 web 应用范围内共享数据
- 域对象
- 作用范围:整个 web 应用范围内共享数据
- 生命周期:server 启动,web 加载后创建出 ServletContext 对象后,域产生;当 web 应用被移除出容器或 server 关闭,随着 web 应用的销毁,域销毁
- 常用方法
public void setAttribute(String name, Object object) public Object getAttribute(String name) public void removeAttribute(String name)
- 域对象
- 获得 web 应用的初始化参数
- web 应用的初始化参数
- 常用方法
String getInitParameter(String name) Enumeration<String> getInitParameterNames()
- 附:搞清楚"请求参数"、"初始化参数"、"域属性"的区别
- 请求参数(parameter):browser 发送过来的请求中的参数信息
- 初始化参数(InitParameter):在 web.xml 中为 Servlet 或 ServletContext 配置的初始化时带有的基本参数
- 域属性(attribute):四大作用域中存取的键值对
- web 应用的初始化参数
- 实现 Servlet 的转发
- 请求转发和请求重定向
- 请求重定向(302+Location):两次请求,两次响应
- 请求转发(server 内部进行资源流转):一次请求,一次响应
- 代码演示
// 虚拟路径 String path = "/servlet/SContextServlet3"; RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(path); dispatcher.forward(request, response);
- 请求转发和请求重定向
- 加载资源文件
- 在 Servlet 中读取资源文件时,遇到的问题
- 如果写相对路径,路径会相对于程序启动目录:JavaProject → workspace/project_name;Web Project → tomcat/bin
- 如果写硬盘路径,可以找到资源。但是可移植性差
- 为了解决这样的问题,ServletContext 提供了方法:
public String getRealPath(String)
- 代码演示
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // File file = new File("config.properties"); // 当前程序启动的目录下找文件 => D:\Java\tomcat\bin\config.properties // System.out.println(file.getAbsolutePath()); // 方法底层会拼接 [当前 web 应用在虚拟主机管理的文件夹的 // 硬盘路径] 在方法形参前面从而获得当前资源的硬盘路径 String path = getServletContext().getRealPath("config.properties"); System.out.println(path); }
- 补充:在 !Servlet 环境下,可以采用 [类加载器] 的方式读取资源。类加载器从哪里加载类,就从哪里加载资源文件,即 webapps/[project_name]/WEB-INF/classes,web 项目的类加载目录
public void method() { // 类加载器从哪里加载类, 就从哪里加载资源文件, 对应到 web 应用, 也就是 classes 文件夹 File file = new File(PathDemo.class.getClassLoader() .getResource("config2.properties").getPath()); System.out.println(file.getAbsolutePath()); }
- 在 Servlet 中读取资源文件时,遇到的问题