Servlet总结(一)

一、前言
Servlet是Java这样的编程语言为怎样生成动态网页提供的解决的方法。

我在之前的一篇文章中分析过。所谓网页事实上就是一堆HTML标记,由浏览器负责解析这些标记并展现成我们所示样子。

这些HTML标记浏览器本身是没有的,它要跟server去要,由于仅仅有server才有。
那么server上的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做一个project时所引入的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做容器,首先要有自己的project文件夹存放上述文件。比方project文件夹起名叫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,port号。web应用名。当中IP和port号之间用冒号切割,可是web应用名是能够为空的。当在浏览器中直接訪问IP:port号。比方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对象。我们稍后会分析它。

本篇先写这么多吧。

原文地址:https://www.cnblogs.com/yjbjingcha/p/7373875.html