详解Servlet

Servlet其实就是一套规范,我们按照这套规范写的代码就可以直接在java的服务器上运行。Servlet的结构如下:
这里写图片描述

1.1 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方法在容器启动时被容器调用,只会被调用一次。
  • getServletConfig方法用于获取ServletConfig。
  • service方法用于处理一个请求。
  • getServletInfo方法可以获取一些Servlet相关的信息,如作者、版权等,这个方法需要自己实现,默认返回空字符串;
  • destroy 主要用于在Serviet 销毁(一般指关闭服务器) 时释放一些资源,也只会调用一次。
      init 方法被调用时会接收到一个ServletConfig 类型的参数,是容器传进去的。ServietConfig 顾名思义指的是Serviet 的配置,我们在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>contextConfigLocation</param-name>
        <param-value>demo-servlet.xml</param- value>
        </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

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


import java.util.Enumeration;
public interface ServletConfig {
    public String getServletName ();
    public ServletContext getServletContext();
    public String getlnitParameter (String name );
    publiC Enumeration<String> getInitParameterNames ();
}

  getServletName 用于获取Servlet 的名字,也就是我们在web.xml 中定义的servlet-name ; getlnitParameter 方法用于获取init-param 配置的参数; getlnitParameterNames 用于获取配置的所有init-param 的名字集合; getServletContext 非常重要,它的返回值Serv ]letContext 代表的是我们这个应用本身,如果你看了前面Tomcat 的分析就会想到, ServletContext 其实就是Tomcat 中Context 的门面类ApplicationContextFacade (具体代码参考StandardContext 的getServ letContext 方法) 。既然ServletContext 代表应用本身,那么ServletContext 里边设置的参数就可以被当前应用的所有Serviet 共享了。我们做项目的时候都知道参数可以保存在Session 中,也可以保存在Application 中,而后者很多时候就是保存在了ServletContext 中。
  我们可以这么理解, ServletConfig 是Servlet 级的p 而ServletContext 是Context (也就是Application ) 级的。当然, ServletContext 的功能要强大很多,并不只是保存一下配置参数,否则就叫ServletContextConfig 了。
另外,在Serv etContext 接口中有这么一个方法: public ServletContext getContext(String uripath) ,它可以根据路径获取到同一个站点下的别的应用的ServletContext ! 当然由于安全的原因, 一般会返回null ,如果想使用需要进行一些设置。
  ServletConfig 和ServletContext 最常见的使用之一是传递初始化参数。我们就以spring 配置中使用得最多的contextConfigLocation 参数为例来看一下:

    <servlet>  
        <servlet-name>springMVC</servlet-name>  
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
        <init-param>  
            <param-name>contextConfigLocation</param-name>  
            <param-value>classpath*:/applicationContext/application_spring_mvc.xml</param-value>  
        </init-param>  
        <load-on-startup>1</load-on-startup>  
    </servlet>  
    <servlet-mapping>  
        <servlet-name>springMVC</servlet-name>  
        <url-pattern>/</url-pattern>  
    </servlet-mapping>  

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

String contextLocation = getServletConfig().getServletContext().getinitParameter("context ConfigLocation");
String servletLocation = getServletConfig().getinitParameter("contextConfigLocation") ;

  为了操作方便, GenericServlet 定义了getinitParameter 方法,内部返回getServIetConfig().getlnitParameter 的返回值。因此,我们如果需要获取ServletConfig 中的参数,以不再调用getServletConfig(),而直接调用getlnitParameter。
  另外ServletContext 中非常常用的用法就是保存Application 级的属性,这个可以使用setAttribute 来完成,比如:

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

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

1.2 GenericServlet接口

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

  • 实现了民rvletConfig 接口,我们可以直接调用ServJetConfig 里面的方法;
  • 提供了元参的init 方法;
  • 提供了log 方法。

  GenericServlet 实现了ServletConfig 接口,我们在需要调用ServletConfig 中方法的时候可以直接调用,而不再需要先获取ServletConfig 了,比如,获取ServletContext 的时候可以直接调用getServletContext,而无须调用getServletConfig().getServletContext()了,不过其底层实现其实是在内部调用了。getServletContext 的代码如下:

@Override
    public ServletContext getServletContext() {
        return getServletConfig().getServletContext();
    }

  GenericServlet 实现了Serviet 的init(ServletConfigconfig)方法,在里面将config设置给了内部变量config ,然后调用了无参的init()方法,这个方法是个模板方法,在子类中可以通过覆盖它来完成自己的初始化工作,代码如下:

@Override
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }
    public void init() throws ServletException {
        // NOOP by default
    }

  这种做法有三个作用:首先,将参数config 设置给了内部属性config ,这样就可以在ServletConfig 的接口方法中直接调用config 的相应方法来执行;其次,这么做之后我们在写Servlet 的时候就可以只处理自己的初始化逻辑,而不需要再关心config 了;还有一个作用就是在重写init 方法时也不需要再调用super.init(con fig)了。如果在自己的Servlet 中重写了带参数的init 方法,那么一定要记着调用super.init(config),否则这里的config 属性就接收不到值,相应的ServletConfig 接口方法也就不能执行了。

1.3 HttpServlet

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

@Override
    public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException {

        HttpServletRequest  request;
        HttpServletResponse response;

        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException("non-HTTP request or response");
        }
        service(request, response);
    }
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;
                try {
                    ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                } catch (IllegalArgumentException iae) {
                    // Invalid date header - proceed as if none was set
                    ifModifiedSince = -1;
                }
                if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                    // 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);
        }
    }

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

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_BAD_REQUEST, msg);
        }
    }

这就是HttpServlet ,它主要将不同的请求方式路由到了不同的处理方法。不过SpringMVC中由于处理思路不一样,又将所有请求合并到了统一的一个方法进行处理,在后续的文章中SpringMVC中再详细讲解。

猜你喜欢

转载自blog.csdn.net/gchd19921992/article/details/78773724