(三)详解Servlet-读书笔记

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.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();
}

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.GenericServlet
public 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.HttpServlet
public 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.HttpServlet
protected 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中由于处理思路不一样,又将所有请求合并到了统一的一个方法进行处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值