Servlet简介

什么是Servlet

在Java Web中Servlet、请求和响应是最基本的三个对象,在Web容器的管理下,这三者能够完成基本的HTTP请求处理。

Servlet的作用是为客户提供服务。servlet的角色是接受一个客户的请求,再返回一个响应。请求可能非常简单,例如:给我提供一个欢迎页面;也可能非常复杂,例如:为当前的购物车结账,这个请求会带一些客户端传来的参数,servlet需要知道自己如何使用请求中的参数,还需要知道该返回什么样的响应。

Java Web服务器处理用户请求的基本过程:用户在客户端点击一个链接,浏览器会向Web应用服务器发送一个URL请求,该URL会指向一个servlet;Web容器看出这个请求指向某个servlet A,就会创建两个对象(HttpServletRequest和HttpServletResponse),并分配或创建一个线程,调用servlet A对应的service方法(上述请求和响应对象作为参数);service根据HTTP请求区分出客户端发来的是GET还是POST请求,并调用对应的doGet()或doPost()方法;在doGet()或doPost()方法中进行业务逻辑的处理,处理完成后的结果通过响应对象返回写回给客户端。

Servlet体系结构

Servlet

Servlet的框架是由两个Java包组成的:javax.servlet与javax.servlet.http。在javax.servlet包中定义了所有的Servlet类都必须实现或者扩展的通用接口和类。在javax.servlet.http包中定义了采用Http协议通信的HttpServlet类。Servlet的框架的核心是javax.servlet.Servlet接口,所有的Servlet都必须实现这个接口。

在Servlet接口中定义了5个方法:

init(ServletConfig)方法:负责初始化Servlet对象,在Servlet的生命周期中,该方法执行一次;该方法执行在单线程的环境下,因此开发者不用考虑线程安全的问题;
service(ServletRequest req,ServletResponse res)方法:负责响应客户的请求;为了提高效率,Servlet规范要求一个Servlet实例必须能够同时服务于多个客户端请求,即service()方法运行在多线程的环境下,Servlet开发者必须保证该方法的线程安全性;
destroy()方法:当Servlet对象退出生命周期时,负责释放占用的资源;
getServletInfo:就是字面意思,返回Servlet的描述;
getServletConfig:这个方法返回由Servlet容器传给init方法的ServletConfig。

写一个简单的servlet来验证一下这些方法的执行:

①创建一个web项目

②编写一个MyServlet实现servlet接口,重写方法

package com.yj.servlet;

import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class MyServlet implements Servlet {

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

	@Override
	public ServletConfig getServletConfig() {
		System.out.println("getServletConfig");
		return null;
	}

	@Override
	public String getServletInfo() {
		System.out.println("getServletInfo");
		return null;
	}

	@Override
	public void init(ServletConfig arg0) throws ServletException {
		System.out.println("init");
	}

	@Override
	public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException {
		System.out.println("service");
	}

	public MyServlet() {
		super();
		System.out.println("MyServlet 构造器");
	}
}

③在 web.xml 文件中配置和映射这个servlet

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>Servlet</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>

	<!-- 配置和映射 Servlet -->
	<servlet>
		<!-- Servlet 注册的名字 -->
		<servlet-name>MyServlet</servlet-name>
		<!-- Servlet 的全类名 -->
		<servlet-class>com.yj.servlet.MyServlet</servlet-class>
		<!-- 配置启动时加载 -->
		<!-- <load-on-startup>1</load-on-startup> -->
	</servlet>
	<servlet-mapping>
		<!-- 需要和某一个 servlet 节点的 serlvet-name 子节点的文本节点一致 -->
		<servlet-name>MyServlet</servlet-name>
		<!-- 映射具体的访问路径: / 代表当前 WEB 应用的根目录 -->
		<url-pattern>/hello</url-pattern>
	</servlet-mapping>
</web-app>

 浏览器发起请求后,如何找到对应的servlet来执行呢?

url-pattern-》servlet-name-》servlet-name-》servlet-class-》执行service方法

为了解耦,http服务器不直接调用servlet,而是把请求交给servlet容器(比如tomcat)来处理:https://blog.csdn.net/qq_24313635/article/details/105714724

ServletRequest & ServletResponse

对于每一个HTTP请求,servlet容器会创建一个封装了HTTP请求的ServletRequest实例传递给servlet的service方法,ServletResponse则表示一个Servlet响应,其隐藏了将响应发给浏览器的复杂性。通过ServletRequest的方法你可以获取一些请求相关的参数,而ServletResponse则可以将设置一些返回参数信息,并且设置返回内容。

ServletConfig

ServletConfig封装可以通过@WebServlet或者web.xml传给一个Servlet的配置信息,以这种方式传递的每一条信息都称做初始化信息,初始化信息就是一个个K-V键值对。为了从一个Servlet内部获取某个初始参数的值,init方法中调用ServletConfig的getinitParameter方法或getinitParameterNames方法获取,除此之外,还可以通过getServletConfig获取ServletConfig对象。

<servlet>
	<servlet-name>MyServlet</servlet-name>
	<servlet-class>com.yj.servlet.MyServlet</servlet-class>
	<!-- 配置servlet的初始化参数,且节点必须要load-on-startup节点前面 -->
	<init-param>
		<param-name>user</param-name>
		<param-value>root</param-value>
	</init-param>
	<load-on-startup>1</load-on-startup>
</servlet>

ServletContext

servlet引擎为每个web应用程序都创建一个对应的servletContext对象,servletContext对象被包含在servletConfig对象中,调用servletConfig.getServletContext方法可以返回servletContext对象的引用。由于一个web应用程序中所有的servlet都共享同一个servletContext对象,所以,servletContext对象被称为application对象(web应用程序对象)。

<!-- 配置当前web应用的初始化参数,可以被所有的servlet获取-->
<context-param>
  	<param-name>driver</param-name>
	<param-value>com.mysql.jdbc.Drvice</param-value>
</context-param>

ServletContext方法:

  • 获取初始化的参数

getInitParameter(),这个和ServletConfig的getInitParameter方法类似

  • 获取当前web应用的根路径下的某一个文件在服务器上面的绝对路径(是发布到tomcat之后的路径):

servletContext.getRealPath(String path)

  • 获取当前web应用的根路径:

servletContext.getContextPath()

  •  获取当前web应用的某一个文件对应的输入流

servletContext.getResourceAsStream(String path)

GenericServlet

前面编写的Servlet应用中通过实现Servlet接口来编写Servlet,但是我们每次都必须为Servlet中的所有方法都提供实现,还需要将ServletConfig对象保存到一个类级别的变量中,GenericServlet抽象类就是为了为我们省略一些模板代码,实现了Servlet和ServletConfig,完成了以下几个工作:

将init方法中的ServletConfig赋给一个类级变量,使的可以通过getServletConfig来获取。

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

同时为避免覆盖init方法后在子类中必须调用super.init(servletConfig),GenericServlet还提供了一个不带参数的init方法,当ServletConfig赋值完成就会被带参数的init方法调用。这样就可以通过覆盖不带参数的init方法编写初始化代码,而ServletConfig实例依然得以保存为Servlet接口中的所有方法提供默认实现。

HttpServlet

HttpServlet是基于Http协议实现的Servlet的基类,写Servlet时直接继承HttpServlet即可,不需要再重头实现Servlet接口,SpringMvc中的dispatcherServlet就是HttpServlet的子类。 HttpServlet是与Http协议相关的,HttpServlet处理请求主要是通过重写父类的service方法来完成具体的请求处理的。在service方法中首先是将ServletRequest和ServletResponse转换成HttpServletRequest和HttpServletResponse,然后根据请求的不同路由到不同的处理过程中去【处理方法就是我们常见的doXXX的方法。最常见的就是doGet和doPost】

Servlet的注册和运行

  • servlet程序必须通过servlet容器来启动运行,并且储存目录有特殊要求,通需要存储在<WEB应用程序目录>\WEB-INF\classes目录中。
  • servlet程序必须在WEB应用程序的web.xml文件中进行注册和映射其访问路径,才可以被servlet引擎加载和被外界访问。
  • 一个<servlet>元素用于注册一个 servlet,它包含有两个主要的子元素<servlet-name>和<servlet-class>,分别用于设置 servlet的注册名称和 Servlet的完整类名。
  • 一个<servlet-mapping>元素用于映射一个已注册的servlet的一个对外访问路径,它包含有两个子元素:<servlet-name>和<url-pattern>,分别用于指定servlet的注册名称和 servlet的对外访问路径。

关于servlet-mapping:

  • 同一个servlet可以被映射到多个url上,即多个<servlet-mapping>元素的<servlet-name>子元素的设置值可以是同一个servlet的注册名
  • 在servlet映射到的url中可以使用*通配符,但是只能有两种固定的格式,一种格式是“*.拓展名”,另一种是以正斜杆/开头,并以/*结尾

既带/又带拓展名的不合法:

<servlet-mapping>
	<servlet-name>MyServlet</servlet-name>
	<url-pattern>/*.html</url-pattern>
</servlet-mapping>

Servlet工作原理

当Web服务器接收到一个HTTP请求时,它会先判断请求内容——如果是静态网页数据,Web服务器将会自行处理,然后产生响应信息;如果牵涉到动态数据,Web服务器会将请求转交给Servlet容器。此时Servlet容器会找到对应的处理该请求的Servlet实例来处理,结果会送回Web服务器,再由Web服务器传回用户端。

针对同一个Servlet,Servlet容器会在第一次收到http请求时建立一个Servlet实例,然后启动一个线程。第二次收到http请求时,Servlet容器无须建立相同的Servlet实例,而是启动第二个线程来服务客户端请求。所以多线程方式不但可以提高Web应用程序的执行效率,也可以降低Web服务器的系统负担。

接着我们描述一下Servlet 容器Tomcat与Servlet是如何工作的,工作原理时序:

  1. Web Client 向Servlet容器(Tomcat)发出Http请求;

  2. Servlet容器接收Web Client的请求;

  3. Servlet容器创建一个HttpServletRequest对象,将Web Client请求的信息封装到这个对象中,然后调用 Servlet 容器的 service 方法;

  4. Servlet容器创建一个HttpServletResponse对象;

  5. Servlet 容器拿到请求后,根据请求的 URL 和 Servlet 的映射关系,找到相应的 Servlet,如果 Servlet 还没有被加载,就用反射机制创建这个 Servlet,并调用 Servlet 的 init 方法来完成初始化,接着调用HttpServlet对象的service方法,把HttpServletRequest对象与HttpServletResponse对象作为参数传给 HttpServlet对象;

  6. HttpServlet调用HttpServletRequest对象的有关方法,获取Http请求信息;

  7. HttpServlet调用HttpServletResponse对象的有关方法,生成响应数据;

  8. Servlet容器把HttpServlet的响应结果传给Web Client;

HttpServlet的service方法,根据请求的类型,分别调用不同的do方法执行:

 protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                if (ifModifiedSince < lastModified) {
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);
            
        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);
            
        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);
            
        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);
            
        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);
            
        } else {
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //

            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);
            
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

Servlet生命周期

以前我们写的java类,都是由我们自己手动来进行调用。而servlet的生命周期则是由servlet容器控制,servlet容器负责创建servlet,并调用servlet的相关生命周期的方法。这里的说的servlet容器指的是运行servlet、jsp,filter等的软件环境(tomcat)。

Servlet生命周期管理主要有几个方法:

  • 构造方法
  • init()初始化阶段
  • service()处理客户端请求阶段
  • destroy()终止阶段

构造器方法:

只被调用一次(单例,共享的全局变量的话,会有线程安全的问题),只有第一次请求servlet时,创建servlet对象。

在容器启动时,XXXServlet在JVM的管理下被实例化(调用构造方法)为一个对象,这时候它还不是servlet。需要在容器的管理下调用init()方法进行初始化,获得ServletConfig和ServletContext对象的引用后,才称为一个真正的servlet。

init初始化阶段

Servlet容器加载Servlet,加载完成后,Servlet容器会创建一个Servlet实例并调用init()方法,init()方法只会调用一次,
Servlet容器会在以下几种情况装载Servlet:

  1. Servlet容器启动时自动装载某些servlet,实现这个需要在web.xml文件中添加<load-on-startup>1</load-on-startup>
  2. 在Servlet容器启动后,客户首次向Servlet发送请求
  3. Servlet类文件被更新后,重新装载

service处理客户端请求阶段:

每次收到请求,调用一次,每收到一个客户端请求,服务器就会产生一个新的线程去处理。
对于用户的Servlet请求,Servlet容器会创建一个特定于请求的ServletRequest和ServletResponse。
对于tomcat来说,它会将传递来的参数放入一个HashTable中,这是一个String-->String[]的键值映射

终止阶段:

只被调用一次,当web应用被终止,或者Servlet容器终止运行,或者Servlet重新装载Servlet新实例时,Servlet容器会调用Servlet的destroy()方法,用于释放当前servlet锁占用的资源。

load-on-startup参数可以指定servlet被创建的时机(也就是调用构造方法和init方法):

  • 若为负数,则在第一次请求时被创建
  • 若为0或者正数,则在当前web应用被servlet容器加载时创建实例,且数字越小,越先被创建。

Servlet中的Listener

Listener 使用的非常广泛,它是基于观察者模式设计的,Listener 的设计对开发 Servlet 应用程序提供了一种快捷的手段,能够方便的从另一个纵向维度控制程序和数据。目前 Servlet 中提供了 5 种两类事件的观察者接口,它们分别是:4 个 EventListeners 类型的,ServletContextAttributeListener、ServletRequestAttributeListener、ServletRequestListener、HttpSessionAttributeListener 和 2 个 LifecycleListeners 类型的,ServletContextListener、HttpSessionListener。如下图所示:

它们基本上涵盖了整个 Servlet 生命周期中,你感兴趣的每种事件。这些 Listener 的实现类可以配置在 web.xml 中的 <listener> 标签中。当然也可以在应用程序中动态添加 Listener,需要注意的是 ServletContextListener 在容器启动之后就不能再添加新的,因为它所监听的事件已经不会再出现。掌握这些 Listener 的使用,能够让我们的程序设计的更加灵活。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值