[CSDN] 【JavaWeb-5】Servlet的原理、周期、创建方法、转发、ServletConfig以及重要的ServletContext

发表于:3个月前  阅读量:17626

摘要

1、HTTP。我们在控制台查看浏览器发送的请求和接受的响应时,有时候会发现响应的状态码是304,这个是表示浏览器使用了缓存。2、servlet是一个Java类,是用来专门处理请求和响应的,是部署在服务...

1、HTTP。我们在控制台查看浏览器发送的请求和接受的响应时,有时候会发现响应的状态码是304,这个是表示浏览器使用了缓存

2、servlet是一个Java类,是用来专门处理请求和响应的,是部署在服务器上工作的小程序,之所以叫小程序,是因为它只要部署上去就自动运行工作了可以处理事情了。

3、操作步骤:先创建一个实现servlet接口的Java类,实现接口所定义的方法,然后在web.xml中配置servlet的信息。

——我们的目的就是让用户通过url来访问我们的刚刚实现了servlet接口的类,然后在这个类里面做一些处理。但是我们的类是写在src中,然后编译成字节码.class文件是存放在WEB-INF中的,我们部署应用的时候就把WEB-INF的东西部署到服务器上,而WEB-INF文件中的东西是不允许外部访问的,所以如果想要访问的这个类(严格意义上说是这个类生成的字节码文件),我们就需要通过一些配置来告诉服务器某个URL地址是用来访问某个类的。这就是我们为什么要配置web.xml的原因。

——我们新增了一个实现servlet接口的类,叫ServletDemo1.java。只在service函数里写了个输出,只要访问到这个servlet,它就会通过执行这个service函数。其次,我们可以看到现在导入的包都是javax.xxx的,这个javax是java的扩展包,几乎都是JavaEE的包

package com.hello.servlet;

import java.io.IOException;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class ServletDemo1 implements Servlet {

    public void destroy() {
        // TODO Auto-generated method stub

    }

    public ServletConfig getServletConfig() {
        // TODO Auto-generated method stub
        return null;
    }

    public String getServletInfo() {
        // TODO Auto-generated method stub
        return null;
    }

    public void init(ServletConfig arg0) throws ServletException {
        // TODO Auto-generated method stub

    }

    public void service(ServletRequest arg0, ServletResponse arg1)
            throws ServletException, IOException {
        // TODO Auto-generated method stub
        System.out.println("Hello,servlet!");
    }

}

——我们接着在web.xml中做了配置。新增了如下内容。这里的访问逻辑是,我们输入localhost:8080/应用名/demo1后,它就会找到<servlet-mapping>中相应的url(在<url-pattern>中),然后根据这个url找到对应的<servlet-name>叫servletDemo1,然后就在<servlet>这个节点里再找到该<servlet-name>对应的<servlet-class>。至此,就自动找到这个类,去执行相应的任务了。

<servlet>
    <servlet-name>servletDemo1</servlet-name>
    <servlet-class>com.hello.servlet.ServletDemo1</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>servletDemo1</servlet-name>
    <url-pattern>/demo1</url-pattern>
</servlet-mapping>

——我们在service函数中输出了“Hello,servlet!”这个字符串,我们访问url后,浏览器是什么都没有输出,但是我们在MyEclipse的控制台Console中有发现这句话的输出。至此,一个简单的servlet就实现了。

4、Servlet的生命周期。

——当我们访问servlet的时候,先执行的是构造方法(这个构造方法在类中没有明写,我们也可以自己写一个重载构造方法)、然后是init初始化方法、然后是service方法。其中构造方法和init方法只在第一次访问servlet的时候调用,后面再访问的话就不调用了。

——而service方法是访问一次调用一次。

——最后是destroy方法,这个方法在应用被卸载的时候会调用。我们可以很暴力地直接停止服务器来达到卸载应用的目的,通过下图箭头的stop server来停止,而不是下面的那个停止按钮,下面那个停止按钮相当于“断电”,而上面那个相当于“关闭”它会执行关闭的一些处理。

这里写图片描述

——部署是deployment,卸载应用其实就是解除部署,是undeploy。如果服务器还在running的话,我们直接在服务器的webapps目录里删除我们的应用文件夹显然是不行的。我们可以通过”localhost:8080”打开我们Tomcat的首页,在首页上有一个“Manager App”的按钮,点击进去找到我们的应用,点击后面的undeploy即可。当然如果你没配置过的话,是进不去app管理界面的,第一次的话需要我们在服务器的conf/tomcat-users.xml文件中做一些配置,其实就是配置用户名和密码这些,它会有提示的,复制提示里的代码改成自己的用户名和密码即可。需要提醒的是,配置之后需要重启服务器

5、我们之前说,实例化和初始化一个servlet都是在访问它的时候才开始,但这是默认情况。实际中,我们可以在服务器启动的时候就执行,只需要在我们需要的servlet类节点下面增加一个<load-on-startup>节点。

<servlet>
    <servlet-name>servletDemo1</servlet-name>
    <servlet-class>com.hello.servlet.ServletDemo1</servlet-class>
    <load-on-startup>10</load-on-startup>
</servlet>

6、创建一个servlet的另一种方法是通过适配器模式,也就是不实现Servlet接口,而是继承GenericServlet抽象类,这个GenericServlet抽象类不仅实现了Servlet接口,而且还实现了ServletConfig接口,也就是说这个GenericServlet抽象类里面有更多的方法可供调用,但是这里面只有一个service方法是抽象方法(也就是必须要实现的),其他的都不是强制实现,所以方便很多,相比而言,我们直接实现Servlet接口创建的servlet是一定要强制实现5个方法的。

——可见我们创建的这个servlet是继承(extends)自 GenericServlet的。并且只强制实现了一个service方法。

package com.hello.servlet;

import java.io.IOException;

import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class ServletDemo2 extends GenericServlet {

    @Override
    public void service(ServletRequest arg0, ServletResponse arg1)
            throws ServletException, IOException {
        System.out.println("Hello,Demo2!");

    }

}

——同样的,我们在web.xml中配置以下,就可以通过“http://localhost:8080/Day04_Servlet/demo2”访问了。

<servlet>
    <servlet-name>servletDemo2</servlet-name>
    <servlet-class>com.hello.servlet.ServletDemo2</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>servletDemo2</servlet-name>
    <url-pattern>/demo2</url-pattern>
</servlet-mapping>

——我们可以查看这个GenericServlet类的源码。按住ctrl+点击后,第一次如果没有加载过源码的话,会提示源码没有找到,我们点击“Attach Source”选择External File找到源码加载进来即可。
这里写图片描述

——我们可以在右边outline里面看到这个GenericServlet抽象类有哪些方法。凡是前面有倒三角符号的都是继承过来的方法,我们Servlet接口只有5个方法,那么其他的方法就是继承自ServletConfig接口的。还有一些没有到倒三角符号的就是它自己定义的,其中init方法就是覆写(override)Servlet接口中的init方法。
这里写图片描述

7、创建servlet的最常见的方式是下面这一种,是直接继承HttpServlet抽象类,这个HttpServlet抽象类是继承了GenericServlet抽象类,所以我们在HttpServlet类中常见的方法中没看到我们之前的init、destroy等方法,只是看到doGet、doPost和service方法,就是因为它是继承了GenericServlet类,需要init等的时候用了父类的。

——这是我们创建的servlet,当然需要在web.xml中做个配置才能访问,该配置这里就省略了,和之前配置的类似。

package com.hello.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ServletDemo3 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        System.out.println("*****doGet*****");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        System.out.println("*****doPOST*****");
    }

}

——可以看到我们重写了doGet和doPost方法。这里的逻辑是这样的。比如当我们访问“http://localhost:8080/Day04_Servlet/demo3”的时候,这个ServletDemo3就会被访问,它里面的构造方法、init等方法当然也会执行,执行后就会调用service方法,但是我们这里没有写service,所以它调用的是它父类(HttpServlet类)的service方法。这里需要注意的是HttpServlet有两个service方法,首先调用的是HttpServlet继承自GenericServlet的service方法,在这个方法里面,做的事情就是把ServletRequest 和ServletResponse 转换成HttpServlet特有的HttpServletRequest和HttpServletResponse,转换之后,调用了自己的service方法。在自己的service方法里面,判断用户提交的method方法,如果是get就调用doGet,是post就调用doPost。当然,这个doGet和doPost它自己也写了一个,所以如果我们不覆写的话,我们通过url访问就会出现“405 method get not supported”之类的提示,这个提示就是在它自己写的doGet或者doPost里面的语句。

    // 源码
    @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);
    }

——所以,记住的是:我们利用继承HttpServlet写的servlet只需要覆写doGet和doPost这两个最常用的方法用来分别处理两种请求即可,service方法不需要覆写,因为覆写的话我们得把它的那一套转化和选择的逻辑都要重写一遍

——以上的这种创建模式叫做模板方法设计模式,也是较为常见的创建方法。其实本质上是服务器实例化的时候不认识我们自己创建的servlet,它只认识Servlet类,所以服务器给我们自动执行的实例化语句是(假设):

Servlet s = new ServletDemo3();

——然后拿着这个s只能调用5个方法,比如调用service方法。因为Servlet是接口,所以s只能找它的实现类(也就是ServletDemo3)去调用service方法,而我们的ServletDemo3中没有,所以又去找ServletDemo3的父类,也就是我们集成的HttpServlet,HttpServlet里有一个覆写了它父类(也就是GenericServlet)的service,在这个service里面做了request和response的转换,然后再调用自己的service方法,再根据判断选择具体的doGet和doPost方法,但因为我们在ServletDemo3中覆写了doGet等方法,所以它调用的时候就优先调用我们自己写的doGet等方法。其实这段话把上面的那段话又重复说了一遍。

8、上面说了最常用的是继承自HttpServlet的,其实这个还是有点复杂,因为需要我们创建之后手动覆写doGet等方法并且配置web.xml文件。实战情况是,我们直接在src中new一个Servlet文件即可。在弹出的框框中填填信息即可。
这里写图片描述
这里写图片描述

——看到上面两张图了麽,第一张图自动就继承HttpServlet了,我们在下面只勾选了doGet和doPost两个方法,其他的都没有勾选,当然这个框框里你还需要给这个Servlet类起个名字。

——第二张图显示的作用,就是帮我们自动配置web.xml文件,这里面Serlet/JSP Name被我自己修改过,它默认是大小写和这个类一样的名字,我们改成首字母小写了。Servlet/JSP Mapping URL里写的就是<url-pattern>里的东西,截图中我们忘了在demo4前面加一个“/”了,这可以在web.xml中手动添加一下。当然,下面的Display Name和Description都被我们清空了,为了保持清洁。

——但是我们发现自动创建的doGet等方法里面是一大串的内容,外面还有一大串描述文档,里面其实就是输出了一个HTML文档。这个我们也可以通过安装插件修改成很简洁的样子。下面是默认的样子:

/**
     * The doGet method of the servlet. <br>
     *
     * This method is called when a form has its tag value method equals to get.
     * 
     * @param request the request send by the client to the server
     * @param response the response send by the server to the client
     * @throws ServletException if an error occurred
     * @throws IOException if an error occurred
     */
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
        out.println("<HTML>");
        out.println("  <HEAD><TITLE>A Servlet</TITLE></HEAD>");
        out.println("  <BODY>");
        out.print("    This is ");
        out.print(this.getClass());
        out.println(", using the GET method");
        out.println("  </BODY>");
        out.println("</HTML>");
        out.flush();
        out.close();
    }

这么长,最终在浏览器输出的结果类似于:

This is class com.hello.servlet.ServletDemo4, using the GET method

——我们可以找到C:\Users\自己用户名\AppData\Local\MyEclipse\Common\plugins目录,这里面有一个com.genuitec.eclipse.wizards_9.0.0.me201108091322.jar文件,网上找一个修改过的覆盖这个就行,做之前最好先备份好原版的那个文件,以防万一出错了还可以恢复。以下就是简洁版本:

public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

    }

9、我们可以给同一个Servlet配置多个<servlet-mapping>,也就是同一个名字,不同的路径,那随之而来的问题就是,如果有多个符合匹配条件的话,那到底执行哪一个?

——优先是绝对匹配,比如<url-pattern>/demo2</url-pattern>,那么如果有路径是“http://localhost:8080/Day04_Servlet/demo2”的话,就会直接匹配这个。

——其次是匹配有“/”开头的,比如<url-pattern>/*</url-pattern>,那么如果有路径是“http://localhost:8080/Day04_Servlet/demo2”的话,就会匹配这个。

——再其次,就是没有“/”开头的带有通配符的,比如<url-pattern>*.do</url-pattern>,那么如果有路径是“http://localhost:8080/Day04_Servlet/login.do”的话,就会匹配这个。

——如果我们直接访问“http://localhost:8080/Day04_Servlet/”的话,得到的是index.jsp页面,此处的原理是:服务器默认帮我们配置了这个servlet,它的<url-pattern>就是“/”对应的<servlet-name>就是default,然后对应的servlet类是“org.apache.catalina.servlets.DefaultServlet”。这个配置是在服务器conf目录里web.xml文件里。

    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

——这里的另一个潜在意义是证明了,所有的访问,其实本质上都是经过servlet了,也就是说我们访问的所有资源都是servlet,虽然如上所示我们表面上访问的是index.jsp文件

——这里的再一个意义就是我们前面说的优先匹配的规则,因为我们系统配置的是“/”,所以,如果我们自己匹配了比“/”更具体的路径比如“/demo4”,那么就会优先匹配我们的”/demo4”,如果没有的话,就匹配我们的“/”,如果没有“/”开头的,那么久匹配我们的如“*.do”路径。

10、Servlet线程的问题。Servlet是一个单实例,也即是只创建一个实例,每次访问的时候都是同一个实例,但是新开了线程,所以就出现了多线程。多线程的安全问题,实用的建议是不要使用全局变量,要使用局部变量

11、开始探索ServletConfig的行程。我们获取ServletConfig信息的方式有很多种,其中我们自己创建的Servlet和直接继承的HttpServlet类是没有init(ServletConfig)方法,而再上一层的GenericServlet类里有这个方法。我们可以在自己创建的Servlet类中实现这个方法,这个方法是初始化的方法,也就是说每次启动的时候会自动执行。

——我们实现了这个方法,并且把init里面的config赋值给我们自己定义的变量,所以这个时候我们自己定义的变量其实已经有了值。

    private ServletConfig config;

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

——当然,我们说config里面的值其实是在web.xml里面配置的,如下进行配置。

  <servlet>
    <servlet-name>ServletConfigDemo1</servlet-name>
    <servlet-class>com.hello.servlet.ServletConfigDemo1</servlet-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>GBK</param-value>
    </init-param>
  </servlet>

——我们在doGet中拿到config这个变量,并取得里面的值,类似于key-value取值的方法。

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String encoding=config.getInitParameter("encoding");
        System.out.println(encoding);
    }

12、我们还有2种得到ServletConfig配置信息的方法。

——我们直接在Servlet类中使用this.getInitParameter()方法。这个方法是哪里来的?我们的Servlet类继承了HttpServlet类,HttpServlet类继承了GenericServlet类,这个GenericServlet类实现了Servlet和ServletConfig接口。其中ServletConfig接口中就有getInitParameter()方法,所以我们这个一路继承下来的Servlet类当然可以理所当然的调用这个方法。

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String encoding=this.getInitParameter("encoding");
        System.out.println(encoding);
    }

——另外一个方法,本质上和上面类似。只是这一次是先调用了Servlet接口的getServletConfig()方法,然后在这个返回的config对象上再使用了getInitParameter()方法。

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String encoding=this.getServletConfig().getInitParameter("encoding");
        System.out.println(encoding);
    }

13、重要的并且经常会使用到的ServletContext。一个ServletContext相当于一个应用,它也是一个单实例。所以ServletContext的第一个作用就是充当域对象,可以获得不同Servlet类中配置的信息。

——这么一说,也就是表明我们上面在某个Servlet类中使用的this.getInitParameter()方法只能获得这个Servlet类中的配置信息,不能获得其他Servlet类中的配置信息,而我们这里学习的ServletContext可以做到跨Servlet类来获得配置信息。

——比如,我们在一个Servlet类中设置了一个属性,当然这个属性是统一维护在ServletContext中的一个Mapping表。我们之所以能够直接用this.来获得ServletContext,是因为我们往上找发现是在ServletConfig里有getServletContext()这么一个方法,所以我们可以直接调用。

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        ServletContext application=this.getServletContext();
        application.setAttribute("name", "Andy");
    }

——然后,我们再另一个Servlet类中获取这个属性。设置属性的时候,key是一个String,value是一个Object,所以我们取出来的时候值也是一个Object,需要强制转换。

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String name=(String) this.getServletContext().getAttribute("name");
        System.out.println(name);
    }

——当然,这里面有一个问题。我们在第一个Servlet类中设置属性的时候是在doGet方法中的,如果我们直接访问第二个Servlet类的话,发现打印出来的是null,也就是我们的name没有值。所以我们应该先访问第一个Servlet类,这样相当于执行了一下doGet方法,也就执行了里面的设置属性的代码。然后我们再访问第二个Servlet类,就能够有值了。这里面的属性的设置最明显的区别就是不需要再web.xml文件里配置了。

14、ServletContext的另一个作用是获取全局配置变量。这什么意思呢?我们之前在web.xml中配置变量都是在某一个servlet中配置的,当然这个配置就是局部配置变量。但是有一种是可以在任何servlet之外的配置,就叫做全局配置变量。比如:

    <context-param>
        <param-name>age</param-name>
        <param-value>30</param-value>
    </context-param>
    <servlet>
    ……
    </servlet>

然后,通过如下就可以直接访问了:

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    System.out.println(this.getServletContext().getInitParameter("age"));
}

15、ServletContext的另一个作用是获取资源路径。注意:这个获取是可以获取任何位置的任何资源。我们之前用过ResourceBundle,但ResourceBundle只能获取classes里面的资源,不能获取WEB-INF里的资源。但是我们通过ServletContext可以风雨无阻地获取。

——我们在3个位置新建了3个文件,并设置了name=a_Eric等3个不同值。如下图:
这里写图片描述

——我们先通过this.getServletContext().getRealPath(“/WEB-INF/a.properties”)获得绝对路径,解释下面代码里有,这里要说明的是getRealPath里的参数需要以“/”开头,这个“/”就相当于应用根目录。然后取得目录后,就是利用了Properties的一些属性和方法处理值。

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 这个是获得在服务器上的绝对路径,也就是说结果类似于E:\apache-tomcat-7.0.52\webapps\Day04_Servlet\WEB-INF\a.properties
    String path=this.getServletContext().getRealPath("/WEB-INF/a.properties");
    // 取得路径后,因为是properties文件,可以创建个对象然后取得其中的值
    Properties pro=new Properties();
    pro.load(new FileInputStream(path));
    System.out.println(pro.getProperty("name"));
}

——从上面例子可见,this.getServletContext().getRealPath()可以访问WEB-INF里的资源。当然,如果文件是在src里的话,那么我们的参数应该是WEB-INF下classes目录里,比如b.properties和c.properties文件。

// b.properties要传递的参数
String path=this.getServletContext().getRealPath("/WEB-INF/classes/b.properties");
// c.properties要传递的参数
String path=this.getServletContext().getRealPath("/WEB-INF/classes/com/hello/servlet/c.properties");

16、Servlet的转发。也就是说接收到一个请求之后,把这个请求转发给另一个Servlet去处理,然后得到响应后继续执行。

——我们创建2个Servlet。一个是ServletDispatcherDemo1,在里面写:

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    System.out.println("我是ServletDispatcherDemo1,你的事儿我办不了,我给你转发出去!");
    // RequestDispatcher rd=this.getServletContext().getRequestDispatcher("/servlet/ServletDispatcherDemo2");
    // rd.forward(request, response);
    // 常用以下写法代替上面2行代码
        this.getServletContext().getRequestDispatcher("/servlet/ServletDispatcherDemo2").forward(request, response);
    System.out.println("我是ServletDispatcherDemo1,你的事有回应了,办好了!");
}

——另一个是ServletDispatcherDemo2,在里面只写个输出:

public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    System.out.println("我是ServletDispatcherDemo2,你的事儿我办好了,我返回响应给ServletDispatcherDemo1!");
}

——我们访问的是第一个Servlet,也就是路径为“http://localhost:8080/Day04_Servlet//servlet/ServletDispatcherDemo1”,在Console中得到以下输出信息,可见,ServletDispatcherDemo1接受到请求后,转发给ServletDispatcherDemo2处理了,ServletDispatcherDemo2处理好后ServletDispatcherDemo1继续执行下面的代码。

我是ServletDispatcherDemo1,你的事儿我办不了,我给你转发出去!
我是ServletDispatcherDemo2,你的事儿我办好了,我返回响应给ServletDispatcherDemo1!
我是ServletDispatcherDemo1,你的事有回应了,办好了!

17、总结以下Servlet相关的接口、类之间的关系。

——总的来说,根上是Servlet和ServletConfig接口。然后实现它俩接口的是GenericServlet抽象类,而HttpServlet抽象类又进一步继承了GenericServlet,并且对ServletRequest和ServletResponse做了包装转换成自己的HttpServletRequest和HttpServletResponse。而HttpServlet这一套东西全都在javax.servlet.http.*的jar中,上面被继承的那些接口和类都在javax.servlet.*的jar包中。
这里写图片描述

关键词:
渝ICP备16002246号 Copyright © 2017. Singee77.com