Servlet

Servlet

什么是Servlet

Servlet实在服务器上运行的小程序,一个Servlet就是一个Java类,并且可以通过“请求-响应”编程模型来访问的这个驻留再服务器内存里的Servlet程序

Tomcat容器等级

Tomcat - Container - HOST - Servlet容器 - Context

容器等级

Servlet的容器管理Context容器,一个Context对应一个Web工程。

Servlet执行流程

  • 浏览器请求

    1. 浏览器向服务器发出请求,服务器从web.xml里面寻找路径名(URI)
    2. 根据URI找到已注册的servlet名称
      1. 根据映射表找到servlet名
      2. 根据servlet名找到全限定类名
  • 服务器创建对象

    1. 服务器找到全限定类名后,通过反射创建servlet对象,同时也创建了servletConifg,里面存放了一些初始化信息(只创建一次)
  • 调用init方法

    1. 对象创建好之后,首先要执行init方法,但是我们发现我们自定义类下没有init方法,所以程序会到其父类HttpServlet里找

    2. 发现HttpServlet里也没有init方法,所以继续向上找,既向其父类GenericServlet中继续寻找,在GenericServlet中我们发现了init方法,则执行init方法(对接口Servlet中的init方法进行了重写)

      在GenericServlet中执行public void init(ServletConfig config)方法的时候,又调用了自己无参无方法体的init()方法,其目的是为了方便开发者,如果开发者在初始化的过程中需要实现一些功能,可以重写此方法

  • 调用service方法

    服务器会先创建两个对象:ServletRequest请求对象和ServletResponse响应对象,用来封装浏览器的请求数据和封装向浏览器的响应数据

    1. 接着服务器会默认在我们写的类里寻找service(ServletRequest req, ServletResponse res)方法,但是DemoServlet中不存在,那么会到其父类(HttpServlet)中寻找,直接调用此方法,并将之前创建好的两个对象传入
    2. 然后将传入的两个参数强转,并调用HttpServlet下的另外个service方法
    3. 接着执行service(HttpServletRequest req, HttpServletResponse resp)方法,在此方法内部进行了判断请求方式,并执行doGet和doPost,但是doGet和doPost方法已经被我们自己重写了,所以会执行我们重写的方法
  • 向浏览器响应

    1. 处理完数据后,将数据响应到浏览器

Servlet生命周期(重要)

生命周期

ServletConfig getServletConfig() :获取当前servlet的配置对象

  • void init(ServletConfig config):初始化
    • 初始化方法
    • 执行者:服务器
    • 执行次数:一次
    • 执行时机:默认第一次访问的时候
  • void service(ServletRequest request,ServletResponse response):服务 处理业务逻辑
    • 服务
      • 执行者:服务器
      • 执行次数:请求一次执行一次
      • 执行时机:请求来的时候
    • void destroy():销毁
      • 销毁
      • 执行者:服务器
      • 执行次数:只执行一次
      • 执行时机:当servlet被移除的时候或者服务器正常关闭的时候

serlvet是单实例多线程(重要)

  • 默认第一次访问的时候,服务器创建servlet,并调用init实现初始化操作.并调用一次service方法
  • 每当请求来的时候,服务器创建一个线程,调用service方法执行自己的业务逻辑
  • 当serlvet被移除的时候服务器正常关闭的时候,服务器调用servlet的destroy方法实现销毁操作.

在下列时刻Servlet容器装载Servlet:

  • Servlet容器启动时自动装载某些Servlet,需要在web.xml文件中的\\之间添加如下代码:
    • \1\ (数字越小表示优先级别越高)。
  • 在Servlet容器启动后,客户端首次向Servlet发送请求。
  • Servlet类文件被更新/修改后,重新装载Servlet。

Servlet被装载后,Servlet容器创建一个Servlet实例并且调用Servlet的init()方法进行初始化。在Servlet的整个生命周期内,init()方法只被调用一次。

Servlet线程安全吗?

Servlet不是线程安全的。

要解释为什么Servlet为什么不是线程安全的,需要了解Servlet容器(即Tomcat)使如何响应HTTP请求的。

当Tomcat接收到Client的HTTP请求时,Tomcat从线程池中取出一个线程,之后找到该请求对应的Servlet对象并进行初始化,之后调用service()方法。要注意的是每一个Servlet对象再Tomcat容器中只有一个实例对象,即是单例模式。如果多个HTTP请求请求的是同一个Servlet,那么着两个HTTP请求对应的线程将并发调用Servlet的service()方法。

比如下面的Servlet中的 namei变量就会引发线程安全问题。

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
29
30
31
32
33
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ThreadSafeServlet extends HttpServlet {

public static String name = "Hello"; //静态变量,可能发生线程安全问题
int i; //实例变量,可能发生线程安全问题
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

@Override
public void init() throws ServletException {
super.init();
System.out.println("Servlet初始化");
}

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.printf("%s:%s[%s]\n", Thread.currentThread().getName(), i, format.format(new Date()));
i++;
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("%s:%s[%s]\n", Thread.currentThread().getName(), i, format.format(new Date()));
resp.getWriter().println("<html><body><h1>" + i + "</h1></body></html>");
}
}

在Tomcat中启动这个Servlet并在浏览器发起多个HTTP访问,最后会发现变量 i 是多线程共享的。

Single ThreadMode接口。Tomcat将保证在一个时刻仅有一个线程可以在给定的Serlvet实例的service方法中执行。其他所有请求进行排队。

变量的线程安全

这里的变量变量指的是字段和共享数据,主要是表单的参数值。基于多线程不共享局部变量的特点我们可以将这类变量参数本地化。

Servlet与JSP九大内置对象

对应关系

获取初始化参数

在web.xml中配置Servlet时,可以配置一些初始化参数,在Servlet中可以通过ServletConifg接口提供的方法来取得这些参数。

1
2
3
4
5
6
7
8
9
<!-- web.xml中 -->
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.servlet.LoginServlet</servlet-class>
<init-param>
<param-name>username</param-name>
<param-value>123</param-value>
</init-param>
</servlet>
1
2
3
4
5
6
7
8
9
10
/**
* Servlet中的init()方法中将会通过ServletConfig接口提供的方法来获取参数
* Initialization of the servlet. <br>
*
* @throws ServletException if an error occurs
*/
public void init() throws ServletException {
// Put your code here
String username = this.getInitParameter("username");
}

MVC与java web的关系

Controller - Servlet

View - JSP

Model - Java Bean

域对象

  • servletcontext(一个JavaWeb应用只创建一个ServletContext对象,该应用内部共享)
  • session(当前会话所有Servlet共享)
  • request(仅在当前请求中有效)
  • pageContext(页面执行期)

servletcontext创建和销毁:

  • 当项目启动的时候,服务器为每一个web项目创建一个servletcontext对象.
  • 当项目被移除的时候或者服务器关闭的时候servletcontext销毁
  • 存放:共享的数据

request:

  • 创建:一次请求来的时候
  • 销毁:响应生成的时候
  • 作用:一次请求里面的数据

请求转发(请求链,请求串):request.getRequestDispatcher(“内部路径”).forward(request,response);

Session和Cookie

Session

服务器创建session出来后,会把session的id号,以cookie的形式回写给客户机,这样,只要客户机的浏览器不关,再去访问服务器时,都会带着session的id号去,服务器发现客户机浏览器带session id过来了,就会使用内存中与之对应的session为之服务。

getSession(boolean create)意思是返回当前reqeust中的HttpSession ,如果当前reqeust中的HttpSession 为null,当create为true,就创建一个新的Session,否则返回null;
简而言之:

1
2
HttpServletRequest.getSession(ture) // 等同于 HttpServletRequest.getSession() 
HttpServletRequest.getSession(false) // 等同于 如果当前Session没有就为null;

如果客户端禁用cookie,如何使session依然起作用

URL重写:

  • response.encodeRedirectURL(java.lang.String url) // 用于对sendRedirect方法后的url地址进行重写。
  • response.encodeURL(java.lang.String url) // 用于对表单action和超链接的url地址进行重写

集群服务器Session同步

  • session复制
    • 只适合小型集群,规模变大的时候,就需要大量复制
  • Session绑定:利用负载均衡的源地址Hash算法实现,负载均衡服务器总是将来自同一个IP地址的访问分发到同一台服务器上。这样整个会话期间,用户所有的请求都来自一台服务器,保证了Session总是从这台服务器获取。
    • 如果其中一台服务器宕机,则Session会话会丢失,一部分用户无法访问
  • 利用Cookie记录Session:将Session记录在客户端,每次请求服务器的时候,将Session放在请求中发送给服务器,服务器处理完成后再将修改后的Session响应给客户端。
    • Cookie大小限制,能记录的信息也有限,因为很多时候我们在Session中储存的也并非String类型的记录。每次请求都需要传输Cookie,影响性能;另外如果用户关闭Cookie功能就不能用了。
    • 但是这种方式因此高可用性、支持服务器的线性伸缩,许多网站都在使用这种方式。
  • Session服务器:利用独立部署的Session服务器统一管理Session,应用服务器每次读写Session时,都访问Session服务器。
    • 这种方式实际上是将应用服务器的状态分离,分为无状态的应用服务器和有状态的Session服务器,然后针对这两种服务器的不同特性分别设计其架构。对于有状态的Session服务器,一种比较简单的方式是利用分布式缓存、数据库等。
Ty.Wings wechat
欢迎您订阅我的公众号,并在GitHub上为我Star!