(三)详解Servlet-读书笔记

来源:互联网 发布:店家开通淘宝客条件 编辑:程序博客网 时间:2024/05/17 12:53

Servlet是Server+Applet的缩写,表示一个服务器应用。Servlet其实就是一套规范,我们按照规范写的代码可以直接在Java的服务器上面运行。

Servlet3.1中的Servlet结构图:

这里写图片描述

Servlet接口

Servlet是一个interface,全局限定名:javax.servlet.Servlet,其子接口为:HttpJspPage,JspPage,其实现类为:FacesServlet(fianl),GenericServlet(abstract),HttpServlet(abstract)。抽象类GenericServlet直接 implements Servlet,抽象类HttpServlet直接 extends GenericServlet,它们具有统一的方法(继承于Servlet):

//javax.servlet.Servletpublic 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();}

init方法在容器启动时被容器调用(当loan-on-startup设置为负数或者不设置时会在Servlet第一次用到时才被调用),只会调用一次;getServletConfig方法用于获取ServletConfig;service方法用于具体处理一个请求;getServletInfo方法可以获取一些Servlet相关信息,如作者、版权等,这个方法需要自己实现,默认返回空字符串;destroy主要用于在Servlet销毁(一般指关闭服务器)时释放一些资源,也只会调用一次。

init方法被调用时会接收到一个ServletConfig类型的参数,是容器传进去的。ServletConfig指的是Servlet配置,我们在web.xml中定义的Servlet时通过init-param标签配置的参数就是通过ServletConfig来保存的,比如,定义Spring MVC的Servlet时指定配置文件位置的contextConfigLocation参数就保存在ServletConfig中,例如:

<servlet>    <servlet-name>demoDispatcher</servlet-name>    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>    <init-param>        <param-name>contenxtConfigLocation</param-name>        <param-value>demo-servlet.xml</param-value>    </init-param>    <load-on-startup>1</load-on-startup></servlet>

Tomcat中Servlet的init方法是在org.apache.catalina.core.StandardWrapper的initServlet方法中调用的,ServletConfig传入的是StandardWrapper(里面封装着Servlet)自身的门面类StandardWrapperFacade。Servlet是通过xml文件配置的,在解析xml时就会把配置参数给设置进去,这样StandardWrapper本身就包含配置项了,当然,并不是StandardWrapper的所有内容都是Config相关的,所以就用其门面Facade类。下面是ServletConfig接口的定义:

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

getServletName用于获取Servlet的名字,也就是我们在web.xml中定义的servlet-name:getInitParameter方法用于获取init-param配置的参数;getInitParameterNames用于获取配置的所有init-param的名字集合;getServletContext的返回值ServletContext代表是我们这个应用本身,ServletContext其实就是Tomact中Context的门面类AppliactionContextFacade。ServletContext代表应用本身,那么ServletContext里边设置的参数就可以被当前应用的所有Servlet共享。Application的共享数据,很多时候就是保存在ServletContent中。

我们可以理解,ServletConfig是Servlet级的,而ServletContext是Context(Application)级的。

Servlet级和Context级都可以操作,那么有没有更高一层的站点级就是Tomcat中的Host级的相应操作呢?在Servlet标准里有一个方法:public ServletContext getContext(String uripath),它可以根据路径获取到同一个站点下的别的应用的ServletContext!由于安全原因,一般会返回Null,如果想使用需要进行一些配置。

ServletConfig和ServletContext最常见使用之一是传递初始化参数。我们以spring配置中使用最多的contextConfigLocation参数为例:

<display-name>initParam Demo</display-name><context-param>    <param-name>contextConfigLocation</param-name>    <param-name>application-context.xml</param-name></context-param><servlet>    <servlet-name>DemoServlet</servlet-name>    <servlet-class>com.excelib.DemoServlet</servlet-class>    <init-param>        <param-name>contextConfigLocation</param-name>        <param-value>demo-servlet.xml</param-value>    </init-param></servlet>

上面通过context-param配置的contextConfigLocation配置到了ServletContext中,而通过servlet下的init-param配置的contextConfigLocation配置到了ServletConfig中。 在Servlet中可以分别通过它们的getInitParameter方法进行获取:

String contextLocation = getServletConfig().getServletContext().getInitParameter("contextConfigLocation");String servletLocation = getServletConfig().getInitParameter("contextConfigLocation");

为了方便操作,GenericServlet定义了getInitParameter方法,内部返回getServletConfig().getInitParameter的返回值,因此,我们如果需要获取ServletConfig中的参数,可以不再调用getServletConfig(),而直接调用getInitParameter。

另外ServletContext中常用的用法就是保存Application级的属性,这个可以使用setAttruibute来完成:

getServletContext().setAttribute("contextConfigLocation","new path");

这里设置同名Atttibute并不会覆盖initParameter中的参数值,它们是两套数据,互不干扰。ServletConfig不可以设置属性。

ServletContext与ServletConfig

ServletContext是servlet与servlet容器之间的直接通信的接口。Servlet容器在启动一个Webapp时,会为它创建一个ServletContext对象,即servlet上下文环境。每个webapp都有唯一的ServletContext对象。同一个webapp的所有servlet对象共享一个ServeltContext,servlet对象可以通过ServletContext来访问容器中的各种资源。

Jsp/Servlet容器初始化一个Servlet类型的对象时,会为这个Servlet对象创建一个ServletConfig对象。在ServletConfig对象中包含了Servlet的初始化参数信息。此外,ServletConfig对象还与ServletContext对象关联。Jsp/Servlet容器在调用Servlet对象的init(ServletConfig config)方法时,会把ServletConfig类型的对象当做参数传递给servlet对象。Init(ServletConfig config)方法会使得当前servlet对象与ServletConfig类型的对象建立关联关系。

ServletContext就是一个全局的概念,它属于应用,但我们有时候不想让某些参数被其他Servlet应用,仅仅想在自己的Servlet中被共享,这时候就需要把它保存在ServletConfig中,换句话说,从【一个Servlet】来看,ServletConfig是它的全局,而从一个【Servlet集合(Web应用)】来看,ServletContext是它的全局。

ServletContext与Application
两者的区别就是Application用在jsp中,ServletContext用在servlet中。application和page request session 都是JSP中的内置对象,在后台用ServletContext存储的属性数据可以用application对象获得。

GenericServlet

GenericServlet是Servlet的默认实现,主要做了三件事:

  • 实现了ServletConfig接口;
    比如可以直接通过getServletContext()获取,无需再调用getServletConfig().getServletContext()

  • 提供了无参的init方法;
    GenericServlet实现了Servlet的init(ServletConfig config)方法,在里面将config设置给了内部变量config,然后调用无参init()方法,这个方法是个模板方法,在子类中可以通过覆盖台来完成自己的初始化工作。

//javax.servlet.GenericServletpublic void init(ServletConfig config) throws ServletException{    this.config = config;    this.init();}public void init() throws ServletException{}
  • 提供了log方法(一个记录日志,一个记录异常)。
    具体实现是通过传给ServletContext的日志实现的。

GenericServlet是与具体协议无关的。

HttpServlet

HttpServlet是用HTTP协议实现的Servlet的基类,写Servlet时直接继承他就可以了,不需要再从头实现Servlet接口。Spring MVC中的DispatcherServlet就是继承的HttpServlet。既然HttpServlet是跟协议相关的,当然主要关心的是如何处理请求了,所以HttpServlet主要重写了service方法,在service方法中首先将ServletRequest和ServletResponse转换为了HttpServletRequest和HttpServletResponse,然后根据Http请求的类型不同将请求路由到不同的处理方法。

//javax.servlet.http.HttpServletpublic void service(ServletRequest req,ServletResponse res) throws ServletException,IOException{    HttpServletRequest request;    HttpServletResponse response;    //如果请求类型不相符,则抛出异常    if(!(req instanceif HttpServletRequest &&         res instanceof HttpServletResponse)){        throw new ServletException("non-HTTP request or response");    }    //转换request和response的类型    request = (HttpServletRequest)req;    response = (HttpServletResponse)req;    //调用http的处理方法    service(request,response);}protected void service(HttpServletRequest req,HttpServletResponse resp) throws ServletExcption,IOException{    //获取请求类型    String method = req.getMethod();    //将不同的请求类型路由到不同处理方法    if(method.equals(METHOD_GET)){        long lastModified = getLastModified(req);        if(lastModified == -1){            doGet(req,resp);        }else {            long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);            if(ifModifiedSince < lastModified){                maybeSetLastModified(resp,lastModified);                doGet(req,resp);            }else{                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);            }        }    }else if(method.equals(METOHD_HEAD)){        long lastModified = getLastModified(req);        maybeSetLastModified(resp,lastModified);        doHead(req,resp);    }else if(method.equals(METHOD_POST)){        doPost(req,resp);    }    ...}

具体处理方法是doXXX的结构,如最常用的doGet,doPost就是在这里定义的。doGet、doPost、doPut、doDelete方法都是模板方法,而且如果子类没有实现将抛出异常,在调用doGet方法前还对是否过期做了检查,如果没有过期则直接返回304状态码使用缓存;doHead调用了doGet请求,然后返回空body的Response;doOptions和doTrace正常不需要使用,主要用来做一些调试工作,doOptions返回所有支持的处理类型的集合,正常情况下可以禁用,doTrace是用来远程诊断服务器的,它会将接收到的header原封不动地返回,这种罪罚很可能会被黑客利用,存在安全漏洞,所以如果不是必须使用,最好禁用。由于doOptions和doTrace功能非常固定,所以HttpServlet做了默认的实现。

doGet代码如下(doPost、doPut、doDelete类似):

//javax.servlet.http.HttpServletprotected void doGet(HttpServletRequest req,HttpServletResponse resp) throws ServletException,IOException{    String protocol = req.getProtocol();    String msg = lStrings.getString("http.method_get_not_supported");    if(protocol.endsWith("1.1")){        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED,msg);    }else{        resp.sendError(HttpServletResponse.SC_RAD_REQUEST,msg);    }}

这就是HttpServlet,它主要将不同的请求方式路由到了不同处理方法。不过Spring MVC中由于处理思路不一样,又将所有请求合并到了统一的一个方法进行处理。