Servlet

Servlet和Tomcat的关系

Tomcat 和 Servlet 的关系:Servlet 的运行环境叫做 Web 容器或 Servlet 服务器,Tomcat 是 Web 应用服务器,是一个 Servlet/JSP 容器。Tomcat 作为 Servlet 容器,负责处理客户请求,把请求传送给 Servlet,并将 Servlet 的响应传送回给客户。而 Servlet 是一种运行在支持 Java 语言的服务器上的组件,Servlet 用来扩展 Java Web 服务器功能,提供非常安全的、可移植的、易于使用的 CGI 替代品

执行过程

通过浏览器发送请求,请求首先到达Tomcat服务器,由服务器解析请求URL,然后在部署的应用列表中找到应用。然后找到web.xml配置文件,在web.xml中找到FirstServlet的配置(/),找到后执行service方法,最后由FirstServlet响应客户浏览器。整个过程如下图所示:

实现方式

实现 Servlet 功能时,可以选择以下三种方式:

  • 第一种:实现 Servlet 接口,接口中的方法必须全部实现。
    使用此种方式,表示接口中的所有方法在需求方面都有重写的必要。此种方式支持最大程度的自定义。
  • 第二种:继承 GenericServlet,service 方法必须重写,其他方可根据需求,选择性重写。
    使用此种方式,表示只在接收和响应客户端请求这方面有重写的需求,而其他方法可根据实际需求选择性重写,使我们的开发Servlet变得简单。但是,此种方式是和 HTTP 协议无关的。
  • 第三种:继承 HttpServlet,它是 javax.servlet.http 包下的一个抽象类,是 GenericServlet 的子类。选择继承 HttpServlet 时,需要重写 doGet 和 doPost 方法,来接收 get 方式和 post 方式的请求,不要覆盖 service 方法。使用此种方式,表示我们的请求和响应需要和 HTTP 协议相关,我们是通过 HTTP 协议来访问。每次请求和响应都符合 HTTP 协议的规范。请求的方式就是 HTTP 协议所支持的方式(GET POST PUT DELETE TRACE OPTIONS HEAD )。

异步处理

Servlet 3.0 中的异步处理指的是允许Servlet重新发起一条新线程去调用 耗时业务方法,这样就可以避免等待

生命周期

servlet从创建到销毁的过程:

  • 出生:(初始化)请求第一次到达 Servlet 时,创建对象,并且初始化成功。Only one time

  • 活着:(服务)服务器提供服务的整个过程中,该对象一直存在,每次只是执行 service 方法

  • 死亡:(销毁)当服务停止时,或者服务器宕机时,对象删除,

serrvlet生命周期方法:
init(ServletConfig config)service(ServletRequest req, ServletResponse res)destroy()

默认情况下, 有了第一次请求, 会调用 init() 方法进行初始化【调用一次】,任何一次请求,都会调用 service() 方法处理这个请求,服务器正常关闭或者项目从服务器移除, 调用 destory() 方法进行销毁【调用一次】

扩展:servlet 是单例多线程的,尽量不要在 servlet 里面使用全局(成员)变量,可能会导致线程不安全

  • 单例:Servlet 对象只会创建一次,销毁一次,Servlet 对象只有一个实例。
  • 多线程:服务器会针对每次请求, 开启一个线程调用 service() 方法处理这个请求

线程安全

Servlet运用了单例模式,整个应用中只有一个实例对象,所以需要分析这个唯一的实例中的类成员是否线程安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ServletDemo extends HttpServlet{
//1.定义用户名成员变量
//private String username = null;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = null;
//synchronized (this) {
//2.获取用户名
username = req.getParameter("username");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//3.获取输出流对象
PrintWriter pw = resp.getWriter();
//4.响应给客户端浏览器
pw.print("Welcome:" + username);
//5.关流
pw.close();
//}
}

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

启动两个浏览器,输入不同的参数(http://localhost:8080/ServletDemo/username=aaa 或者bbb),访问之后发现输出的结果都是一样,所以出现线程安全问题。

在Servlet中定义了类成员之后,多个浏览器都会共享类成员的数据,其中任何一个线程修改了数据,都会影响其他线程。因此,我们可以认为Servlet它不是线程安全的。因为Servlet是单例,单例对象的类成员只会随类实例化时初始化一次,之后的操作都是改变,而不会重新初始化。

解决办法:如果类成员是共用的,只在初始化时赋值,其余时间都是获取。或者加锁synchronized

创建方式

  • 第一种:应用加载时创建Servlet,它的优势是在服务器启动时,就把需要的对象都创建完成了,从而在使用的时候减少了创建对象的时间,提高了首次执行的效率。它的弊端是在应用加载时就创建了Servlet对象,因此,导致内存中充斥着大量用不上的Servlet对象,造成了内存的浪费。
  • 第二种:请求第一次访问是创建Servlet,它的优势就是减少了对服务器内存的浪费,因为一直没有被访问过的Servlet对象都没有创建,因此也提高了服务器的启动时间。而它的弊端就是要在应用加载时就做的初始化操作,它都没法完成,从而要考虑其他技术实现。

在web.xml中是支持对Servlet的创建时机进行配置的,配置的方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
<!--配置ServletDemo3-->
<servlet>
<servlet-name>servletDemo</servlet-name>
<servlet-class>com.itheima.web.servlet.ServletDemo</servlet-class>
<!--配置Servlet的创建顺序,当配置此标签时,Servlet就会改为应用加载时创建
配置项的取值只能是正整数(包括0),数值越小,表明创建的优先级越高-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>servletDemo</servlet-name>
<url-pattern>/servletDemo</url-pattern>
</servlet-mapping>

ServletContext

ServletContext 对象是应用上下文对象。服务器为每一个应用都创建了一个 ServletContext 对象,ServletContext 属于整个应用,不局限于某个 Servlet,可以实现让应用中所有 Servlet 间的数据共享。

上下文代表了程序当下所运行的环境,联系整个应用的生命周期与资源调用,是程序可以访问到的所有资源的总和,资源可以是一个变量,也可以是一个对象的引用

生命周期:

  • 出生:应用一加载,该对象就被创建出来。一个应用只有一个实例对象(Servlet 和 ServletContext 都是单例的)
  • 活着:只要应用一直提供服务,该对象就一直存在。
  • 死亡:应用被卸载(或者服务器停止),该对象消亡。

域对象:指的是对象有作用域,即有作用范围,可以实现数据共享,不同作用范围的域对象,共享数据的能力不一样。

Servlet 规范中,共有4个域对象,ServletContext 是其中一个,web 应用中最大的作用域,叫 application 域,可以实现整个应用间的数据共享功能。

数据共享:

获取ServletContext:

  • Java 项目继承 HttpServlet,HttpServlet 继承 GenericServlet,GenericServlet 中有一个方法可以直接使用

    1
    2
    3
    public ServletContext getServletContext() {
    return this.getServletConfig().getServletContext();
    }
  • ServletRequest 类方法:

    1
    ServletContext getServletContext()//获取ServletContext对象

常用API:

  • String getInitParameter(String name) : 根据名称获取全局配置的参数
  • String getContextPath : 获取当前应用访问的虚拟目录
  • String getRealPath(String path) : 根据虚拟目录获取应用部署的磁盘绝对路径
  • void setAttribute(String name, Object object) : 向应用域对象中存储数据
  • Object getAttribute(String name) : 根据名称获取域对象中的数据,没有则返回null
  • void removeAttribute(String name) : 根据名称移除应用域对象中的数据

代码实现:

  • web.xml配置:
    配置的方式,需要在<web-app>标签中使用<context-param>来配置初始化参数,它的配置是针对整个应用的配置,被称为应用的初始化参数配置。

Servlet和Spring的Service层接口的关系

一个Service接口(方法)就是一个Servlet(实例)

示例:设定具体 Controller,控制层 java / controller / UserController

1
2
3
4
5
6
7
8
9
10
11
@Controller  //@Component衍生注解
public class UserController {
//设定当前方法的访问映射地址,等同于Servlet在web.xml中的配置
@RequestMapping("/save")
//设置当前方法返回值类型为String,用于指定请求完成后跳转的页面
public String save(){
System.out.println("user mvc controller is running ...");
//设定具体跳转的页面
return "success.jsp";
}
}

所以 一个方法就是一个Servlet实例