Servlet生命周期以及工作原理

  最近感觉到用久了SpringMVC、Struts2等框架,反而对它们的底层实现,即Servlet,的相关知识有了许多遗忘。现在参考了网上的一些博客,来进行一次知识点总结。
  

Servlet响应客户端请求的过程

image_1b205lk8c1f881cu01s401jn01b6n9.png-54kB

Servlet生命周期

  1. init方法:当Servlet容器第一次加载并创建Servlet实例时,在调用该Servlet的构造函数后立即调用init方法对该Servlet对象进行初始化。构造器和init方法都只会被调用一次,这说明Servlet是单实例的(需要考虑线程安全的问题,不推荐在其中写全局变量)。
  2. service方法:每次请求都会调用service方法,它是实际用于响应请求的方法,可以被多次调用。
  3. destroy方法:在服务器端停止且卸载Servlet时执行该方法,用于释放当前Servlet所占用的资源,只会被调用一次。

以上方法都由Servlet容器(例如Tomcat)负责调用。
 
 

ServletConfig

  Servlet接口的init方法会接收一个ServletConfig对象作为参数,即init(ServletConfig servletConfig),ServletConfig对象封装了该Serlvet的配置信息,并且可以获取ServletContext对象。例如:
  
在web.xml中配置Servlet的初始化参数:

<servlet>
        <!-- Servlet注册的名字 -->
        <servlet-name>helloServlet</servlet-name>
        <!-- Servlet全类名 -->
        <servlet-class>servlet.HelloServlet</servlet-class>
        <!-- 配置该Servlet的初始化参数 -->
        <init-param>
            <param-name>username</param-name>
            <param-value>root</param-value>
        </init-param>
        <init-param>
            <param-name>password</param-name>
            <param-value>123456</param-value>
        </init-param>
    </servlet>

可以在HelloServlet的init方法中获取这些参数的信息(在实际开发中,通常使用的Servlet都继承了HttpServlet类,可以通过getServletConfig()在该Servlet对象中获取到ServletConfig对象,不一定只能在init方法中使用,下面的getServletContext()也是一样):

@Override
    public void init(ServletConfig servletConfig) throws ServletException {
        //获取Servlet初始化参数相关的信息
        String username = servletConfig.getInitParameter("username");
        System.out.println("username is "+username);
        Enumeration<String> names = servletConfig.getInitParameterNames();
        while(names.hasMoreElements()){
            String name = names.nextElement();
            System.out.println("name is "+name);
            System.out.println("value is "+servletConfig.getInitParameter(name));
        }

当有请求发送到该Servlet后可以在控制台看到输出:

image_1b207k93m1f0knmd1vej8pcqlsm.png-17.4kB
 
 

ServletContext

  ServletContext对象代表着当前的WEB应用。可以认为SerlvetContext是当前 WEB应用的一个大管家,可以从中获取到当前WEB应用的各个方面的信息。
  ServletContext对象可以由SerlvetConfig获取:

ServletContext servletContext = servletConfig.getServletContext();

  ServletContext主要的作用有:
  
① 获取当前WEB应用的初始化参数:
  
在web.xml中配置当前WEB应用的初始化参数:(这些参数可以被所有的 Servlet通过ServletContext获取,而上面配置在Servlet中的参数只能由对应的Servlet获取)

<context-param>
        <param-name>driver</param-name>
        <param-value>com.mysql.jdbc.Driver</param-value>
    </context-param>
    <context-param>
        <param-name>jdbcUrl</param-name>
        <param-value>jdbc:mysql:///xiangwanpeng</param-value>
    </context-param>

在init方法中获取到这些参数:

@Override
    public void init(ServletConfig servletConfig) throws ServletException {
        //获取ServletContext对象,它包含了当前WEB应用的各种信息
        ServletContext servletContext = servletConfig.getServletContext();
        //获取WEB应用的初始化信息,方法和servletConfig类似
        String driver = servletContext.getInitParameter("driver");
        System.out.println("driver is "+driver);
        Enumeration<String> names2 = servletContext.getInitParameterNames();
        while(names2.hasMoreElements()){
            String name2 = names2.nextElement();
            System.out.println("name2 is "+name2);
            System.out.println("value2 is "+servletContext.getInitParameter(name2));
        }
}

当有请求发送到该Servlet后可以在控制台看到输出:

image_1b207q5r788mo0hf6m1gj1omo13.png-9.8kB

② 获取当前WEB应用的某一个文件在服务器上的绝对路径,而不是部署前的路径。

例如现在在根目录下新建一个note.txt:

image_1b209obula7s169r1501pn71a7k1t.png-5kB

在Servlet中获取:

String realPath = servletContext.getRealPath("/note.txt");
        System.out.println("realPath is "+realPath);

获取请求后在控制台输出:

realPath is G:\Eclipse\J2EE\workspace.metadata.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\JavaWeb\note.txt

③ 获取当前 WEB应用的名称:

String contextPath = servletContext.getContextPath();
System.out.println(contextPath);

④ 获取当前 WEB应用的某一个文件对应的输入流:

在src目录下新建一个jdbc.properties:

image_1b20aapc615gg2qd4iv1ofebah2a.png-6.3kB

在Servlet中获取该文件的输入流:

InputStream is = servletContext.getResourceAsStream("/WEB-INF/classes/jdbc.properties");
        System.out.println("is is "+is);

输出:

image_1b20acpqjehe1b8lsrk1if4dtr2n.png-7.1kB

注意区别另一种方法,即通过getClassLoader获取,这种方法的路径是没有/WEB-INF/classes/前缀的:

InputStream is2 = this.getClass().getClassLoader().getResourceAsStream("jdbc.properties");
        System.out.println("is2 is "+is2);

⑤ 和attribute相关的几个方法。
 
 

GenericServlet

  GenericServlet是Servlet接口和ServletConfig接口的实现类,是一个抽象类,因为其中的service()方法为抽象方法。它的作用是:如果新建的 Servlet程序直接继承GenericSerlvet会使开发更简洁。
  
具体实现:
① 在GenericServlet中声明了一个SerlvetConfig类型的成员变量,在init(ServletConfig)方法中对其进行了初始化。
② 利用servletConfig成员变量的方法实现了 ServletConfig接口的方法。
③ 还定义了一个 init()方法,在init(SerlvetConfig)方法中对其进行调用,子类可以直接覆盖init()在其中实现对Servlet的初始化。
④ 不建议直接覆盖 init(ServletConfig),因为如果忘记编写super.init(config),而还是用了SerlvetConfig接口的方法,则会出现空指针异常。
⑤ 新建的 init(){}并非Serlvet的生命周期方法,而init(ServletConfig)是生命周期相关的方法。

以下是源码:

public abstract class GenericServlet 
    implements Servlet, ServletConfig, java.io.Serializable
{
    private transient ServletConfig config;

    public GenericServlet() { }

    public void destroy() {
    }

    public String getInitParameter(String name) {
    return getServletConfig().getInitParameter(name);
    }

    public Enumeration getInitParameterNames() {
    return getServletConfig().getInitParameterNames();
    }   

    public ServletConfig getServletConfig() {
    return config;
    }

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


    public String getServletInfo() {
    return "";
    }

    public void init(ServletConfig config) throws ServletException {
    this.config = config;
    this.init();
    }

    public void init() throws ServletException {

    }

    public void log(String msg) {
    getServletContext().log(getServletName() + ": "+ msg);
    }

    public void log(String message, Throwable t) {
    getServletContext().log(getServletName() + ": " + message, t);
    }

    public abstract void service(ServletRequest req, ServletResponse res)
    throws ServletException, IOException;

    public String getServletName() {
        return config.getServletName();
    }
}

HttpServlet

  HttpServlet是一个继承自 GenericServlet的Servlet,而且它是针对于 HTTP协议所定制。
  HttpServlet在service(ServletRequest,ServletResponse)方法中直接把ServletReuqest和 ServletResponse强制转换为HttpServletRequest和HttpServletResponse。并调用了重载的service(HttpServletRequest, HttpServletResponse)方法。
  在service(HttpServletRequest, HttpServletResponse)中,通过request.getMethod()获取请求方式,并根据请求方式创建了doXxx()方法(xxx为具体的请求方式,比如 doGet,doPost)。
  实际开发中, 我们通常直接继承HttpServlet,这样做的好处是可以直接有针对性的覆盖doXxx()方法;直接使用HttpServletRequest和HttpServletResponse,而不再需要进行类型的强制转换。
  
主要源码如下:

 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 = req.getDateHeader(HEADER_IFMODSINCE);
        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);
    }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值