简介
Java Servlet 是运行在 Web 服务器或应用服务器上的程序
在Java web b/s架构中,servlet扮演了重要的角色,作为一个中转处理的容器,他连接了客户端和服务器端的信息交互和处理。
简单来说,客户端和服务器端的信息交互通常可以分为三个步骤:
- servlet容器随时监听服务端口,当客户端发送请求到服务端,servlet容器将http请求报文包装成为request对象和一个空的response对象后,根据请求的url等信息再转发到具体的servlet
- 对应的servlet在调用自身的service()方法来对请求进行处理,并且将响应的数据存放到response对象中
- response传递到servlet容器,servlet容器负责将response对象转换成为响应报文之后再发送回客户端
大体流程如下图:
也可以参看此动画来理解:
生命周期
先了解一下web应用大概启动流程:
- web应用启动,初始化一系列环境
- xml解析,创建ServletContext实例
然后等待请求,如果所请求的Servlet如果还没有创建,就执行创建过程
- xml解析,反射创建Servlet实例
- xml解析,创建servletConfig实例
- servletConfig作为形参来初始化Servlet
其中servlet从创建到毁灭所经历的过程如图所示
可以看到生命周期分为以下三个阶段:
- 初始化阶段:会调用init()来进行初始化
- 默认情况下,Servlet是在第一次被访问时由服务器创建的
- 如果在web.xml中配置了的话,就会在服务器启动的时候创建Servlet
- 服务阶段:请求到来时使用service()对外服务
- 单实例多线程服务模式,针对每个请求都会从线程池中获得一个线程来处理请求
- 销毁阶段:服务终止也就是销毁阶段,调用destroy()释放资源,标记为可回收等待回收
接口介绍
Servlet接口
- Tomcat之所以放心地交给我们实现,是因为Servlet里主要写的代码都是业务逻辑代码。和原始的、底层的解析、连接等没有丝毫关系。最难的几个操作,人家已经给你封装成形参传进来了。
- Servlet虽然是个接口,但实现类只是个空壳,我们写点业务逻辑就好了。
GenericServlet接口
- 提升了init方法中原本是形参的servletConfig对象的作用域(成员变量),方便其他方法使用
- init方法中还调用了一个init空参方法,如果我们希望在servlet创建时做一些什么初始化操作,可以继承GenericServlet后,覆盖init空参方法
- 由于其他方法内也可以使用servletConfig,于是写了一个getServletContext方法
- service未实现,留给用户自己实现
HttpServlet接口
继承自GenericServlet
GenericServlet本身是一个抽象类,目的是是为了不让new
实现了抽象方法service方法替我们完成了复杂的请求方法判断,我们后续只需要实现对应的方法即可
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);
}
}
参数介绍
也就是说,servletConfig对象封装了servlet的一些独有参数信息,servletContext对象封装了整个应用中所有servlet共享的参数信息。如果需要,我们可以从它们获取相应参数值。
ServletConfig
特点
- 某个servlet独享
- 只能被拥有改servletConfig的servlet获取,其他的servlet无法获取
- 与Servlet是1对1关系
- 当 Tomcat 初始化一个 Servlet 时,会将该 Servlet 的配置信息封装到 ServletConfig 对象中,此时可以通过调用 init(ServletConfig config)方法将 ServletConfig 对象传递给 Servlet。
方法说明
- String getInitParameter(String name)
- 根据初始化参数名返回对应的初始化参数值
- Enumeration getInitParameterNames()
- 返回一个 Enumeration 对象,其中包含了所有的初始化参数名
- ServletContext getServletContext()
- 返回一个代表当前 Web 应用的 ServletContext 对象
- String getServletName()
- 返回 Servlet 的名字,即 web.xml 中 <servlet-name>元素的值
ServletContext
ServletContext对象表示Servlet应用程序,每个web程序都只有一个ServletContext对象。因为有了ServletContext对象,就可以共享从应用程序中的所有资料处访问到的信息,并且可以动态注册Web对象,使得servlet之间传递和共享信息。前者将对象保存在ServletContext中的一个内部Map中。保存在ServletContext中的对象被称作属性。
特点
- 全局共享
- 服务器启动的时候创建,服务器关闭的时候销毁
- 与Servlet是1对n关系
域对象方法
ServletContext作为域对象,同样有这三个域对象方法,用于对域属性进行获取或修改
- setAttribute(String name,Object value)
- 往域对象里面添加数据,添加时以key-value形式添加
- getAttribute(String name)
- 根据指定的key读取域对象里面的数据
- removeAttribute(String name)
- 根据指定的key从域对象里面删除数据使用方法
使用方法
配置初始化
在web.xml中配置全局参数
<!-- 全局配置参数 -->
<context-param>
<param-name>param1</param-name>
<param-value>value1</param-value>
</context-param>
使用
获取参数值
- getServletContext().getInitParameter(name)
- 根据指定的参数名获取参数值
- getServletContext().getInitParameterNames()
- //获取所有参数名称列表
搜索当前工程目录下的资源文件
- getServletContext().getRealPath(path)
- 根据相对路径获取服务器上资源的绝对路径
- getServletContext().getResourceAsStream(path)
- 根据相对路径获取服务器上资源的输入字节流
获取工程名字
- getServletContext().getContextPath();
ServletRequest & ServletResponse
ServletRequest是对一次HTTP请求的封装
ServletResponse是对一次HTTP响应的封装
此处就不展开叙述,具体可以参看之前的博文。
使用方法
使用一个servlet我们需要做两个方面的工作
- 实现servlet类
- 注册servlet
实现servlet类
任选一种方式实现Servlet类
- 继承javax.servlet.Servlet类;
- 继承javax.servlet.GenericServlet类;
- 继承javax.servlet.HTTPServlet类;
然后实现对应的方法即可
注册servlet
注册servlet有两种方式
web.xml文件配置
在web.xml中将Servlet注册到容器中
<servlet>
<!-- servlet的内部名称,自定义 -->
<servlet-name>DemoAction</servlet-name>
<!-- servlet的类全名:包名+类名 -->
<servlet-class>com.uplooking.controller.DemoAction</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- servlet的映射配置 -->
<servlet-mapping>
<!-- servlet的内部名称,一定要和上面的内部名称保持一致 -->
<servlet-name>DemoAction</servlet-name>
<!-- servlet的映射路径(访问serclet的名称 -->
<url-pattern>/DemoAction</url-pattern>
</servlet-mapping>
- 首先,从浏览器中发送请求,是从当前工程中的路径与servlet-mapping标签中的url-pattern的标签值进行匹配。
- 根据这个映射值,找到servlet-mapping标签中的servlet-name的值与servlet标签中的servlet-name进行匹
- 匹配到以后,找到servlet标签中的servlet-class标签中对应servlet类的src文件夹下的全路径。
- 从而调用并执行相应的servlet类。
注意:servlet-mapping标签中的servlet-name的值与servlet标签中的servlet-name必须相同。
注解配置
只需在对应的servlet类中添加servlet注解即可,从浏览器发送请求时,是用当前“工程”下的路径,会去对应servlet类的上面寻找是否存在对应url名称的@webServlet注解,存在的话,调用并执行对应的servlet类。
@WebServlet("/DemoAction")
public class DemoAction extends HttpServlet{
}
是否单例
大部分情况下来说,一个servlet同一时间只有一个实例,但是,如果有多个Url映射到同一个servlet时,就会出现多个实例。所以虽然 Servlet 在多数情况下只有一个实例。但它并不是单例设计模式,即不是真正的单例。
线程安全问题
当多个请求发送到同一个servlet,服务器会为每个请求创建一个新进程来处理
因为会多线程处理,所以就可能访问到公共资源造成线程不安全的情况,所以我们在写代码的时候使用到公共变量的时候需要慎重考虑线程安全问题
参考文章
servlet/tomcat等容器/springMVC之间的关系
JavaWeb——Servlet(全网最详细教程包括Servlet源码分析)
ServletConfig对象与ServletContext区别
本文由博客群发一文多发等运营工具平台 OpenWrite 发布