Servlet总结(一)

一、前言

Servlet是Java这种编程语言为如何生成动态网页提供的解决办法。我在之前的一篇文章中分析过,所谓网页其实就是一堆HTML标记,由浏览器负责解析这些标记并展现成我们所看到的样子。这些HTML标记浏览器本身是没有的,它要跟服务器去要,因为只有服务器才有。

那么服务器上的HTML是怎么来的呢?两种方式,一种是静态的,也就是我们提前把HTML标记保存到一个文本文件中,然后以.html为后缀名。每次访问这个文件,得到的都是相同的HTML标记,除非修改了这个文件,这也就是称之为“静态”的原因。另外一种是所谓的动态,HTML标记并不是人工手写的,而由程序根据输入条件自动生成的,浏览器每次访问这个程序可能会获取到不同的HTML标记,这就是所谓的“动态”。

至于这个生成HTML标记的程序长啥样,浏览器其实并不关心,你只要给我一段完整的HTML标记就好了。目前有很多语言可以干这件事,除了Java还有PHP、Python、Ruby等等甚至C语言。因为说真的,不就是生成一对HTML格式的字符串吗,太简单了。

恩,是简单,所以我选择了java。其实Java很强大,可以做很多事,一个Servlet技术只是其中之一而已。Servlet技术由容器加Servlet接口两部分组成,我们平时开发用的其实只是一个接口,但是光有接口是无法完成生成HTML标签的伟业的,还需要容器的支持。我想,在Servlet技术刚刚诞生的时候,应该还没有这么细分。只是随着需求的变更,生成HTML标签这件事越来越复杂,需要分工和专业化,所以将原来的整体拆分成容器和接口两部分,容器提供底层支持,而接口则专注于具体业务。可以简单的理解为,容器是接口的实现,而接口则是Servlet暴露给我们的API,我们只需要关注如何利用这些API实现自己的业务逻辑就好了。不过既然说到了接口的实现,那必然不只是一种实现,我们熟悉的Tomcat只是其中之一,其他的还有Jetty、GlassFish、JBoss等等。

我写这一系列的文章,第一步是分析这些接口,然后再分析其中一种实现,也就是Tomcat。

二、Servlet API

说我们平时用的Servlet只是一个API是有证据的,首先就是我们用maven做一个工程时所引入的jar名称,如下图:

然后看一下jar包的内容架构:

东西还不少,看一下Servlet这个最重要的“类”:

哇,就是一个interface嘛!这个包里的大部分类其实都是接口。

Servlet给我们的API主要分为三大块:Servlet、Filter和Listener,这个可以从包内类名上得到印证。然后,主要到有一个叫http的子包,这个包的东西专门用来处理和http协议有关的事情。这意味着,Servlet一开始设计的时候,其实是更加可扩展的,不仅仅是为了http,还可以处理其他类型的协议。然而由于http协议在web中的统治地位,所以Servlet还是专门为http增加了一个子包。

三、Servlet

先看下这个接口中的方法:

public interface Servlet {
    public void init(ServletConfig config) throws ServletException;
    public ServletConfig getServletConfig();
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
    public String getServletInfo();
    public void destroy();
}

实现这个接口是业务开发要做的事情,他们要把所有的业务逻辑实现在这几个方法中。观察可以发现,接口中并没有main方法,那么实现类要怎么去执行呢?这就是容器的职责了。容器负责管理Servlet接口的实现类,在不同的阶段调用不同的方法。main方法在容器中。

讲Servlet的书或文章都会讲到生命周期,这个事情其实也是由容器控制的,容器只会调用一次init和destroy方法,但是每次请求到来都会反复调用service方法。所以在实现这个接口时,初始化工作放在init中,清理工作放在destroy中,而主要的业务逻辑放在service中。

另外两个方法是辅助用的,其中getServletInfo很少用到,它的任务是返回关于实现类的描述信息;另一个方法getServletConfig比较重要,它的任务是从配置文件中获取到servlet实现类的初始化参数。这意味着servlet的初始化参数并没有放在实现类本身内部,而是放到配置文件中。有没有觉得不对劲?难道我就是要在实现类中放几个private变量不行吗?当然可以,但是从getServletConfig这个方法的存在上就可以看出,其实servlet的发明者不建议这么做,尤其是这个参数跟业务紧密关联而时常需要改变时。实际上,容器只会为每个servlet的实现类创建一个对象,只用这一个对象来应付所有访问它的请求。当不止一个请求时,为了效率必然要开启多线程。也就是说,到时会有多个线程在访问这个对象的service方法,如果在service方法中修改了private变量,那必然会造成数据不一致的问题;而如果把private变量设成同步的,又会造成效率的下降。所以就把初始化参数放到配置文件中,但是servlet接口中并没有修改他们的方法;这就是说在程序运行过程中初始化参数是只读的,要改变参数值,只能在程序运行前修改配置文件。

然而,要做到这一点也可以通过把private变量设置成final的做到啊?但是要改变参数值时,是修改类代码方便还是修改配置文件方便呢?

然后来看一下第一个方法init,容器调用它时需要传入一个ServletConfig参数,这说明容器已经根据配置文件生成了一个ServletConfig对象。那么在init方法里面要做些什么?对应第二个方法getServletConfig,它要返回一个ServletConfig对象,这个对象从哪儿来呢?这个对象必然是容器调用init时传入的那个ServletConfig对象,所以代码应该是这样的:

public class MyServlet implements Servlet {
    private ServletConfig servletConfig;

    public init(ServletConfig servletConfig) throw ServletException {
        this.servletConfig = servletConfig;
    }

    public ServletConfig getServletConfig() {
        return this.servletConfig;
    }
    ...
}

也就是说,实现类中还是要有一个private的ServletConfig变量,而且是必须的。幸好的是,这个变量跟业务无关,并且通常你只是用它来获取信息。而init的第一件任务必然初始化这个ServletConfig变量。

接着分析一下service方法,他有两个参数ServletRequest和ServletResponse,这两个参数显然也是容器传入的。当请求到来时,容器会将请求封装成一个ServletRequest对象,同时生成一个ServletResponse对象,都传递给servlet。后者从ServletRequest对象中获取必要信息,然后把处理结果写入到ServletResponse对象。需要注意的是,ServletRequest和ServletResponse都只是接口,它们的实现由容器完成。

四、ServletConfig接口

这个接口也是由容提负责具体实现,可以看出,凡是涉及底层支持的接口,都是容器负责实现;而涉及业务的就是自己实现,我觉得这个设计很棒。先看接口的架构:

public interface ServletConfig {
    public String getServletName();
    public ServletContext getServletContext();
    public String getInitParameter(String name);
    public Enumeration<String> getInitParameterNames();
}

只有四个方法,最重要的当然是后两个,用于获取配置文件中的参数。但是分析它们之前,先要知道配置文件到底是啥,在哪儿?这就要从servlet规范规定的Web应用目录结构说起。一个动态的Web应用包含servlet编译后的class文件,html静态文件,jsp文件,js脚本,各种配置文件和图像文件等其他多媒体资源文件。这些文件显然不能混杂的扔到一个目录中。不管使用Tomcat还是Jetty做容器,首先要有自己的工程目录存放上述文件。比如工程目录起名叫MyWeb,那么servlet规范规定了MyWeb下必须要有一个WEB-INF目录。WEB-INF目录下面要有两个子目录classes和lib,前者存放编译后的class文件,后者存放用到的jar包。WEB-INF下还要有一个最重要的配置文件,这就是我们熟悉的web.xml:

<web-app>
  <servlet>
  	<servlet-name>CometServlet</servlet-name>
  	<servlet-class>comet.CometServlet</servlet-class>
  </servlet>

  <servlet-mapping>
  	<servlet-name>CometServlet</servlet-name>
  	<url-pattern>/test/comet</url-pattern>
  </servlet-mapping>
</web-app>

最顶层的标签是web-app,它下面有很多子标签,但是现在我们先关注<servlet>和<servlet-mapping>,其他标签用到的时候再慢慢加进来。

我们看到<servlet>标签中有两个元素:<servlet-name>和<servlet-class>,后者是servlet实现类的全限定类名,容器会自动从WEB-INF/classes目录下去查找;而前者是我们给它起的名字——可以任意起名。getServletName方法一般也就是返回的这个名字。这个名字是为<servlet-mapping>服务的,这个标签的的主要作用将一个url映射到一个servlet上,当浏览器访问这个url时,容器就会调用这个servlet的service方法,将其产生的html标签返回给浏览器。

在此有必要提一下url:配置文件中的url显然不是一个完整的url,它是有前缀的,前缀包括三部分,IP,端口号,web应用名。其中IP和端口号之间用冒号分割,但是web应用名是可以为空的。当在浏览器中直接访问IP:端口号,比如localhost:8080时,浏览器实际访问的路径是localhost:8080/,也就是会默认加上一个斜线。这个斜线代表根目录,至于根目录到底指向哪个web应用,不同的容器有不同的方案,比如Tomcat会访问ROOT目录。为了区别根目录的默认值,我们最好还是给自己的web应用起一个有意义的名字。对于Tomcat而言,应用的名字是在server.xml文件中配置的,具体细节先参考这里,等我解读Tomcat源码时会仔细分析。

应用名后面一般不再有斜线,所以<url-pattern>标签的值往往以斜线开头,斜线后面的部分可以是一个正则表达式,比如/*代表任何路径,也就是所有访问这个web应用的请求都会由对应的servlet处理。当然也可以把值写的非常具体,那么对应的servlet就只负责这一个url的请求。但是这里有一点需要非常注意:具体url的优先级要高于泛用的url。比如servlet1对应的url是/*,而servlet2对应的url是/test,那么浏览器访问/test时,容器将优先调用servlet2。

好了,扯了半天都没有说到ServletConfig。servlet初始化参数是<servlet>标签内定义的,如下:

<servlet>
  	<servlet-name>CometServlet</servlet-name>
  	<servlet-class>comet.CometServlet</servlet-class>
	<init-param>
          <param-name>color</param-name>
          <param-value>red</param-value>
      </init-param>
</servlet>

<param-name>标签定义参数名,<param-value>给出了参数值,它们两个要用<init-param>包围起来。当定义多个初始化参数时,就需要多个<init-param>标签。配置文件中定义的参数取出来都是String类型的,唯一的类型。而getInitParameterNames方法返回的Enumeration中,包含了所有的<init-param>。

容器是在什么时候读取配置文件中这些初始化参数呢?首先要明白的一点是,容器启动时不会马上把所有的Servlet都加载到内存中,而是等到有浏览器访问对应的url时才会去加载这个servlet,也就是new这个实现类的对象。读取初始化参数应该是在new完对象、调用init方法之前。

ServletConfig还有一个方法是getServletContext,它返回一个ServletContext对象,我们稍后会分析它。

本篇先写这么多吧。

时间: 2024-11-09 23:59:25

Servlet总结(一)的相关文章

Description Resource Path Location Type The superclass &quot;javax.servlet.http.HttpServlet&quot; was not foun

一段时间没亲自建新项目玩乐,今天建立了一Maven project的时候发现了以下异常,Description Resource Path Location Type The superclass "javax.servlet.http.HttpServlet" was not found on the Java Build Path index.jsp /easyBuy/src/main/webapp line 1 JSP Problem 经过查找原因,原来是因为忘记设置server

Spring Cloud ZooKeeper集成Feign的坑2,服务调用了一次后第二次调用就变成了500,错误:Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.n

错误如下: 2017-09-19 15:05:24.659 INFO 9986 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.spring[email protected]56528192: startup date [Tue Sep 19 15:05:24 CST 2017]; root of context hierarchy 2017-09-19 15:05:24.858 INFO 9986 --

Java web之servlet

入坑必备之servlet(O(∩_∩)O哈!) 两个问题:是什么?怎么用? the first question:what?       Servlet是sun公司提供的一门用于开发动态web资源的技术,sun公司在其API中提供了一个servlet接口.由此可以理解为原生的servlet是一个接口,提到接口,我们应该想道我们必须去实现它才能被我们使用,servlet这个接口当然也不例外,从概念上讲,servlet是指sun公司提供的这个API接口,约定俗称,现在我们说的servlet是指实现这

JavaWeb之Java Servlet完全教程(转)

Servlet 是一些遵从Java Servlet API的Java类,这些Java类可以响应请求.尽管Servlet可以响应任意类型的请求,但是它们使用最广泛的是响应web方面的请求. Servlet必须部署在Java servlet容器才能使用.虽然很多开发者都使用Java Server Pages(JSP)和Java Server Faces(JSF)等Servlet框架,但是这些技术都要在幕后通过Servlet容器把页面编译为Java Servlet.也就是说,了解Java Servle

servlet理解

一.Servlet简介 Servlet是sun公司提供的一门用于开发动态web资源的技术. Sun公司在其API中提供了一个servlet接口,用户若想用发一个动态web资源(即开发一个Java程序向浏览器输出数据),需要完成以下2个步骤: 1.编写一个Java类,实现servlet接口. 2.把开发好的Java类部署到web服务器中. 按照一种约定俗成的称呼习惯,通常我们也把实现了servlet接口的java程序,称之为Servlet 二.Servlet的运行过程 Servlet程序是由WEB

web.xml 中的listener、filter、servlet加载及一些配置

在项目中总会遇到一些关于加载的优先级问题,近期也同样遇到过类似的,所以自己查找资料总结了下,下面有些是转载其他人的,毕竟人家写的不错,自己也就不重复造轮子了,只是略加点了自己的修饰. 首先可以肯定的是,加载顺序与它们在 web.xml 文件中的先后顺序无关.即不会因为 filter 写在 listener 的前面而会先加载 filter.最终得出的结论是:listener -> filter -> servlet 同时还存在着这样一种配置节:context-param,它用于向 Servlet

Servlet简介与生命周期

转载请注明原文地址: 一:Servlet是什么 Servlet是运行在Web服务器上的Java程序,作为处理来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层.JSP在web服务器上要先转换成servlet,然后才能在JVM运行,并把结果拼接成浏览器可识别的文件(如html)传回浏览器显示. 二:Servlet的应用场景 单纯地对客户端的请求做处理时,如果我们用纯JSP文件(即:只有Java语句)来处理的话,还需要先转换为servlet才能运行

Java—Servlet开发

掌握Servlet API是Java Web的基础. 首先新建一个Web类型的工程,然后再创建Servlet类.而且Servlet类名,要以Servlet作为后缀.--Servlet技术,就在Servlet类中. 然后要运行Servlet,就需要一个容器(JSP也需要),如开源的Tomcat. Tomcat的配置: 下载:

Servlet的生命周期

Servlet的生命周期是由Servlet的容器来控制的,它可以分为三个阶段:初始化.运行.销毁1.初始化阶段:(1)Servlet容器加载Servlet类,把Servlet类的.class文件中数据读到内存中:(2)然后Servlet容器创建一个ServletConfig对象.ServletConfig对象包含了Servlet的初始化配置信息:(3)Servlet容器创建一个Servlet对象:(4)Servlet容器调用Servlet对象的init方法进行初始化.2.运行阶段当Servlet

Servlet入门

1.在tomcat中新建一个day01web应用,然后在web应用中新建一个web-inf/classes目录:2.在classes目录中新建一个FirstServlet.java文件:package cn.itcastimport java.io.*;import javax.servlet.*;public class Firstservlet extends GenericServlet{ public void service(ServletRequest req, ServletRes