• 05.Servlet


    一、Servlet入门

    1、什么是Servlet

    Servlet是JavaWeb的三大组件(Servlet、Filter、Listener)之一,它属于动态资源。Servlet的作用是处理请求,服务器(Tomcat)会把接收到的请求交给Servlet来处理,Servlet的任务有:

    • 接收请求数据;
    • 处理请求;
    • 完成响应。

    补充:

    Filter(过滤器)的用法与Servlet极其相似,但是Servlet主要负责处理请求,而Filter主要负责拦截请求和放行。

    Listener(监听器),我们在JavaSE开发或者Android开发时,经常会给按钮加监听器,当点击这个按钮就会触发监听事件,调用onClick方法,本质是方法回调。在JavaWeb的Listener也是这么个原理,但是它监听的内容不同,它可以监听Application、Session、Request域对象,当这些对象发生变化就会调用对应的监听方法。

    Tomcat是Web应用服务器,是一个Servlet/JSP容器。Tomcat作为Servlet容器,负责处理客户请求,把请求传送给Servlet,并将Servlet的响应传送回给客户。而Servlet是一种运行在支持Java语言的服务器上的组件。

    2、实现Servlet的方式(由我们自己来写!

    Servlet技术的核心是Servlet接口,它是所有Servlet类必须直接或者间接实现的一个接口,它规定了每个Servlet所必须实现的方法。

    实现Servlet有三种方式:

    • 实现javax.servlet.Servlet接口;
    • 继承javax.servlet.GenericServlet抽象类;
    • 继承javax.servlet.http.HttpServlet抽象类;

    我们看一下Servlet结构体系的UML图。

    其中,Servlet、ServletConfig和Serializable是三个接口,Servlet和ServletConfig是javax.servlet包中的接口,Serializable是java.io包中的序列化接口;GenericServlet是部分实现上面三个接口的抽象类,位于javax.servlet包中;HttpServlet是继承了GenericServlet的抽象类,位于javax.servlet.http包中。实际应用时我们需要从GenericServlet和HttpServlet两个抽象类继承出自己的Servlet类,并实现所需的功能。

    通常我们会去继承HttpServlet类来完成我们的Servlet,但学习Servlet还要从javax.servlet.Servlet接口开始学习。

    另外,Servlet依赖于ServletRequest和ServletResponse接口,这两个接口负责为Servlet接收和发送信息。 HttpServlet也类似,需要依赖于HttpServletRequest和HttpServletResponse接口。

    3、创建HelloServlet应用

    完成Servlet需要分为两步:

    编写Servlet类;

    web.xml文件中配置Servlet或者使用注解的形式@WebServlet()

    下面我们按照这两步来创建Servlet。

    开始我们的第一个Servlet应用,项目名称为HelloServlet,选择Eclipse工具栏的File > New > Other选项,进入新建工程向导界面,选择Web分类下的Dynamic Web Project(动态网页项目)。

     

    在创建项目时,我们要勾选“Generate web.xml deployment descriptor”,才会创建web.xml文件。如果创建项目时没有勾选自动创建web.xml文件,我们后期也可以在创建好的项目上点击右键,选择Java EE Tools > Generate Deployment Descriptor Stup选中后,项目名下即出现web.xml。(如果已经存在web.xml文件,Generate Deployment Descriptor Stup变为灰色)。

    在src目录下创建包com.sdbi.servlet,在包下创建AServlet类,去实现javax.servlet.Servlet接口,并实现其抽象方法。

     

    我们暂时忽略Servlet中其他四个方法,只关心service()方法,因为它是用来处理请求的方法。我们在该方法内给出一条输出语句。

    public class AServlet implements Servlet {
        public void init(ServletConfig config) throws ServletException {}
        public ServletConfig getServletConfig() { return null; }
        public void destroy() {}
        public String getServletInfo() { return null; }
        public void service(ServletRequest req, ServletResponse res)
                throws ServletException, IOException {
            System.out.println("Hello AServlet!");
        }
    }

    将项目编译生成的class文件指定到/WebContent/WEB-INF/classes文件夹下,项目上右键 > Build path  > Configure Build Path > Source,Default output folder目录为“项目名/WebContent/WEB-INF/classes”。

    在web.xml文件中增加如下代码配置(下面内容需要背下来)。

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://java.sun.com/xml/ns/javaee"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
        <display-name>HelloServlet</display-name>
        <servlet>
            <servlet-name>aaa</servlet-name>
            <servlet-class>com.sdbi.servlet.AServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>aaa</servlet-name>
            <url-pattern>/AServlet</url-pattern>
        </servlet-mapping>
    </web-app>

    在web.xml中配置Servlet的目的其实只有一个,就是把访问路径与一个Servlet绑定到一起,上面配置是把访问路径:“/AServlet”与“com.sdbi.servlet.AServlet”绑定到一起。

    • <servlet>:指定AServlet这个Servlet的名称为aaa;
    • <servlet-mapping>:指定/AServlet访问路径所访问的Servlet名为aaa。

    <servlet>和<servlet-mapping>通过<servlet-name>这个元素关联在一起了!

    部署发布Web应用,项目上右键 > Run As > Run on Server,打开浏览器,地址栏里输入http://localhost:8080/HelloServlet/AServlet(http://主机名:端口号/项目名称/Servlet路径名

    查看控制台,发现输出一条语句,说明Servlet运行成功。

    二、Servlet接口

    1、Servlet接口中的方法

    javax.servlet.Servlet接口包含以下5个方法:

    (1)init() 方法

    用于初始化,在Servlet启动时调用。该方法接收一个ServletConfig类型的参数,Servlet容器通过这个参数向Servlet传递初始化配置信息。

    public void init(ServletConfig config) throws ServletException;

    (2)service() 方法

    Servlet通过这个方法,从req获得客户端请求,处理并生成结果,再通过res发送给客户端。

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

    (3)destroy()方法 

    Servlet销毁时执行的方法,释放Servlet对象占用的资源。

    public void destroy();

    (4)getServletConfig() 方法

    获取包含Servlet各种信息的ServletConfig对象,返回值是Servlet容器调用init(ServletConfig config)方法时传递的ServletConfig对象。

    public ServletConfig getServletConfig();

    (5)getServletInfo()方法 

    将Servlet的信息作为字符串返回。

    public String getServletInfo();

    我们在这5个方法中分别添加输出语句。

    public class AServlet implements Servlet {
        @Override
        public void init(ServletConfig config) throws ServletException {
            System.out.println("init()...");
        }
        @Override
        public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
            System.out.println("service()...");
        }
        @Override
        public void destroy() {
            System.out.println("destroy()...");
        }
        @Override
        public ServletConfig getServletConfig() {
            System.out.println("getServletConfig()...");
            return null;
        }
        @Override
        public String getServletInfo() {
            System.out.println("getServletInfo()...");
            return null;
        }
    }

    我们不用重启Tomcat,在浏览器地址栏里重新请求之前的网址即可,http://localhost:8080/HelloServlet/AServlet,你会在命令行窗口看到。

    当某个Servlet第一次被请求时,服务器(Servlet容器)会生成该Servlet对象并调用它的init()方法,再调用service()方法处理请求。处理结束后,该Servlet会常驻于容器中,下一个请求则不再重新生成Servlet对象,而是直接调用常驻的Servlet对象的service()方法。服务器停止时,会调用该Servlet对象的destroy()方法。(关闭浏览器,Servlet对象不会被销毁!)

    因此,Servlet的一个生命周期中,init()destroy()仅会被调用一次,而service()则会被调用多次。

    2、Servlet的生命周期

    所谓生命周期,就是从出生到死亡的过程。Servlet生命周期也是这样,一共分为三个阶段,分别对应三个生命周期方法。

    (1)初始化阶段

    void init(ServletConfig config)

    服务器会在Servlet第一次被访问时创建Servlet,或者是在服务器启动时创建Servlet。如果服务器启动时就创建Servlet,那么还需要在web.xml文件中配置。也就是说默认情况下,Servlet是在第一次被访问时服务器创建的。

    而且一个Servlet类型,服务器只创建一个实例对象,例如在我们首次访问http://localhost:8080/HelloServlet/AServlet时,服务器通过“/AServlet”找到了绑定的Servlet名称为com.sdbi.servlet.AServlet,然后服务器查看这个类型的Servlet是否已经创建过,如果没有创建过,那么服务器才会通过反射来创建AServlet的实例。当我们再次访问http://localhost:8080/HelloServlet/AServlet时,服务器就不会再次创建AServlet实例了,而是直接使用上次创建的实例。

    在Servlet被创建后,服务器会马上调用Servlet的void init(ServletConfig)方法。请记住,Servlet创建后马上就会调用init()方法,而且在一个Servlet的生命周期内,这个方法只会被调用一次。

    因此,我们可以把一些对Servlet的初始化工作放到init方法中!

    (2)运行阶段

    void service(ServletRequest req, ServletResponse res)

    当服务器每次接收到请求时,都会去调用Servlet的service()方法来处理请求。服务器接收到一次请求,就会调用service() 方法一次,所以service()方法是会被调用多次的。正因为如此,所以我们才需要把处理请求的代码写到service()方法中!

    (3)销毁阶段

    void destroy()

    Servlet是不会轻易销毁的,通常都是在服务器关闭时Servlet才会销毁。在服务器被关闭时,服务器会去销毁Servlet,在销毁Servlet之前服务器会先去调用Servlet的destroy()方法,我们可以把对某些资源的释放等代码放到destroy()方法中。

    以上三个方法为Servlet的生命周期方法,Servlet接口中另外两个抽象方法不是生命周期方法,那他们是干什么用的呢?

    • ServletConfig getServletConfig():获取包含Servlet各种信息的ServletConfig对象,返回值是Servlet容器调用init(ServletConfig config)方法时传递的ServletConfig对象,我们可以在init()方法中通过全局的成员变量将它保存起来,然后作为getServletConfig()方法的返回值返回。
    • String getServletInfo():将Servlet的信息作为字符串返回。我们可以自定义一个Servlet的描述信息作为返回值,也可以ServletConfig对象的某些属性作为返回值,例如,ServletConfig对象.getServletName()。

    3、Servlet接口相关类型

    在Servlet接口中还存在三个我们不熟悉的类型:

    • ServletRequest:service()方法的参数,它表示请求对象,它封装了所有与请求相关的数据,它是由服务器创建的;
    • ServletResponse:service()方法的参数,它表示响应对象,在service()方法中完成对客户端的响应需要使用这个对象;
    • ServletConfig:init()方法的参数,它表示Servlet配置对象,它对应Servlet的配置信息,它对应web.xml文件中的<servlet>元素

    (1)ServletRequest和ServletResponse(我们在之后的请求和响应章节中会详细讲解这两个)

    ServletRequest和ServletResponse是Servlet#service() 方法的两个参数,一个是请求对象,一个是响应对象,可以从ServletRequest对象中获取请求数据,可以使用ServletResponse对象完成响应。ServletRequest和ServletResponse的实例由服务器创建,然后传递给service()方法。你以后会发现,这两个对象总是成对出现。

    (2)ServletConfig

    ServletConfig对象对应web.xml文件中的<servlet>元素。例如你想获取当前Servlet在web.xml文件中的配置名,那么可以使用ServletConfig.getServletName()方法获取。

    三、GenericServlet抽象类

    由于javax.servlet.Servlet接口中的5个方法都是抽象方法,我们在使用时非常麻烦,需要把这5个方法都实现。

    所以,这里又提供了javax.servlet.GenericServlet抽象类,它与任何网络应用层协议无关。

    GenericServlet抽象类为我们实现了Servlet接口的大部分方法,除了service()方法之外。

    因此,我们在创建自己的Servlet时,只需要继承GenericServlet并重写service()方法即可。

    1、init()方法

    在GenericServlet中,有两个init()方法,一个是无参的,一个是有参数的。GenericServlet中定义了一个ServletConfig config成员变量(全局变量),并在有参数的init(ServletConfig)方法中把参数ServletConfig赋给了成员变量config。然后在该类的很多方法中使用了成员变量config。

    注意,我们创建Servlet继承GenericServlet时,如果子类覆盖(重写)了GenericServlet的有参的init(ServletConfig)方法,那么this.config=config这一条语句就会被覆盖了,也就是说GenericServlet的成员变量config的值为null,那么所有依赖config的方法都不能使用了。如果真的希望完成一些初始化操作,我们要去覆盖GenericServlet提供的无参的init()方法,它会在init(ServletConfig)方法中被调用,也能够得到执行,也能够完成初始化。

    2、实现了ServletConfig接口

    GenericServlet还实现了ServletConfig接口,所以可以直接调用getInitParameter()、getInitParameterNames()、getServletContext()、getServletName()等ServletConfig的方法。

    我们在HelloServlet项目中,创建一个名为BServlet的类,让它去继承GenericServlet类。

    这时我们只需重写service()方法即可,并且在GenericServlet类定义了一个log()方法可以方便的用于日志的输出,我们来调用试一下。

    BServlet.java代码如下:

    BServlet.java代码如下:
    public class BServlet extends GenericServlet {
        @Override
        public void service(ServletRequest req, ServletResponse res)
                throws ServletException, IOException {
            log("service()...");
        }
    }

    web.xml文件配置如下:

    <servlet>
        <servlet-name>bbb</servlet-name>
        <servlet-class>com.sdbi.servlet.BServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>bbb</servlet-name>
        <url-pattern>/BServlet</url-pattern>
    </servlet-mapping>

    部署运行Tomcat,浏览器打开http://localhost:8080/HelloServlet/BServlet地址。

    详细参见GenericServlet源代码:

    public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable {
        private static final long serialVersionUID = 1L;
        private transient ServletConfig config; // transient不需要序列化的属性
        public GenericServlet() {   }    
        @Override
        public void init(ServletConfig config) throws ServletException {
            this.config = config;
            this.init();
        }
        // 子类继承时,如果需要初始化,要覆盖这个自定义的无参init()方法
        // 如果覆盖那个有参init()方法,就会导致全局变量config为null,其他方法使用它时报错
        public void init() throws ServletException {    } 
        @Override
        public abstract void service(ServletRequest req, ServletResponse res)
                throws ServletException, IOException;
        @Override
        public void destroy() {   }
        @Override
        public ServletConfig getServletConfig() { return config; }
        @Override
        public String getServletInfo() { return ""; }
        @Override
        public String getInitParameter(String name) {
            return getServletConfig().getInitParameter(name);
        }
        @Override
        public Enumeration<String> getInitParameterNames() {
            return getServletConfig().getInitParameterNames();
        }
        @Override
        public ServletContext getServletContext() {
            return getServletConfig().getServletContext();
        }
        @Override
        public String getServletName() { return config.getServletName(); }
        public void log(String msg) {
            getServletContext().log(getServletName() + ": " + msg);
        }
        public void log(String message, Throwable t) {
            getServletContext().log(getServletName() + ": " + message, t);
        }
    }

    四、HttpServlet抽象类

    HttpServlet抽象类是GenericServlet抽象类的子类,HttpServlet类为Servlet接口提供了与HTTP协议相关的通用实现,对GenericServlet类进行了泛化,实现了service()方法。其实HttpServlet中没有什么抽象方法,但是它还是一个抽象类,因为要求我们不能直接使用HttpServlet,而是要自己创建Servlet使用,另外HttpServlet还提供了对HTTP请求的特殊支持,所以通常我们都会通过继承HttpServlet来完成自定义的Servlet。

    1、HttpServlet中的两个service()方法

    我们查看HttpServlet的源代码,发现这里面定义了两个service()方法。大家注意,这两个service()方法的参数列表是不一样的。其中,service(ServletRequest req, ServletResponse res)是重写了父类GenericServlet中的抽象方法service()方法,是Servlet的生命周期方法。service(HttpServletRequest, HttpServletResponse)方法,是HttpServlet自己的方法,不是覆盖GenericServlet继承来的。

    • void service(HttpServletRequest req, HttpServletResponse resp)
    • void service(ServletRequest req, ServletResponse res)

    HttpServlet.java的部分源码如下:

    public abstract class HttpServlet extends GenericServlet {
    ……
    protected void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
            String method = req.getMethod();
            …...
        }
        @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");
            }
         // 调用HttpServlet中的自定义service(HttpServletRequest, HttpServletResponse)
            service(request, response); 
        }
    }

    两个service()方法,其中void service(ServletRequest req, ServletResponse res)方法是由Tomcat自动调用,它将接收的客户端请求和响应强制类型转换为HttpServletRequest和HttpServletResponse类型,然后转交给HttpServlet中的另一个protected void service(HttpServletRequest req,HttpServletResponse resp)方法,此保护类型的service方法再把将请求和响应分发给doPost()、doGet()方法进行下一步处理。

    2、doGet()和doPost()方法

    在HttpServlet的service(HttpServletRequest req,HttpServletResponse res)方法会去判断当前请求是GET还是POST,如果是GET请求,那么会去调用本类的doGet()方法,如果是POST请求会去调用doPost()方法,这说明我们在子类中去覆盖doGet()或doPost()方法即可。

    我们在HelloServlet项目中,创建一个名为CServlet的类,让它去继承HttpServlet类。重写其中的init()、doGet(HttpServletRequest req, HttpServletResponse resp)、doPost(HttpServletRequest req, HttpServletResponse resp)三个方法。

    public class CServlet extends HttpServlet {    
        @Override
        public void init() throws ServletException {
            System.out.println("init()...");
        }    
        @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()...");
        }
    }

    在这三个方法中去掉父类方法的调用super.xxx(),使用输出语句输出一段文字,表示方法被执行。

    我们在web.xml文件中配置好CServlet,代码如下:

    <servlet>
        <servlet-name>ccc</servlet-name>
        <servlet-class>com.sdbi.servlet.CServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>ccc</servlet-name>
        <url-pattern>/CServlet</url-pattern>
    </servlet-mapping>

    我们重新部署应用程序。浏览器地址栏里输入http://localhost:8080/HelloServlet/CServlet,回车。

    发现出现空白页面,但是控制台输出了“init()…”和“doGet()...”。说明CServlet正确执行了。 

    我们再来测试一下POST请求,这时需要有一个表单,我们在WebContent目录下创建一个HTML页面,名称为login.html,在<body>里面定义一个<form>标签,其中定义一个提交按钮,表单的提交方式为"POST"。运行这个HTML页面,点击“提交”按钮。

    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
        This is my HTML page.
        <br>
        <form action="/HelloServlet/CServlet" method="POST">
            <input type="submit" value="提交" />
        </form>
    </body>
    </html>

    控制台输出“init()…”和“doPost()...”,说明doPost()方法被执行了。

    五、使用向导创建Servlet

    我们之前是创建一个Java类,让它继承HttpServlet抽象类。另外,Eclipse提供了专门创建Servlet的向导,我们下面来操作一下看看。

    1、使用向导创建Servlet

    在src文件夹下的包名上点击右键,选择New > Servlet,进入Servlet创建向导。

    指定Servlet所在包名称为“com.sdbi.servlet”,Servlet类名为“DServlet”,父类为“javax.servlet.http.HttpServlet”。

     

    选择需要向导帮我们自动创建的方法。

     如果出现HttpServlet抽象类找不到的情况,我们可以选择Eclipse给我们提供的修复方法。

    我们不用配置web.xml文件,部署运行DServlet程序,你会发现我们的DServlet可以运行,而web.xml文件中没有关于DServlet的配置信息。这是因为,从Servlet 3.0之后(含3.0),Servlet的配置直接在Java代码中通过注解进行了配置。我们来看一下Java代码中的注解。

    这样可以省去我们配置web.xml的麻烦,但是需要服务器应该是Tomcat 7以上才支持。

    六、Servlet高级

    1、自动加载Servlet

    默认情况下,服务器会在某个Servlet第一次收到请求时创建它。也可以在web.xml中对Servlet进行配置,使服务器启动时就创建Servlet。

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://java.sun.com/xml/ns/javaee"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
        <display-name>HelloServlet</display-name>
        <servlet>
            <servlet-name>aaa</servlet-name>
            <servlet-class>com.sdbi.servlet.AServlet</servlet-class>
            <load-on-startup>2</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>aaa</servlet-name>
            <url-pattern>/AServlet</url-pattern>
        </servlet-mapping>
        <servlet>
            <servlet-name>bbb</servlet-name>
            <servlet-class>com.sdbi.servlet.BServlet</servlet-class>
            <load-on-startup>0</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>bbb</servlet-name>
            <url-pattern>/BServlet</url-pattern>
        </servlet-mapping>
        <servlet>
            <servlet-name>ccc</servlet-name>
            <servlet-class>com.sdbi.servlet.CServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>ccc</servlet-name>
            <url-pattern>/CServlet</url-pattern>
        </servlet-mapping>
    </web-app>

     在<servlet>元素中配置<load-on-startup>元素可以让服务器在启动时就创建该Servlet,其中<load-on-startup>元素的值必须是大于等于0的整数,代表服务器启动时创建Servlet的顺序,值越小优先级越高。上例中,根据<load-on-startup>的值可以得知服务器创建Servlet的顺序为BServlet、CServlet、AServlet。

    如果该Servlet类使用@WebServlet注解的形式配置,我们可以在注解中增加属性。

    @WebServlet(urlPatterns = { "/DServlet" }, loadOnStartup = 0)

    参见博客园:《Servlet3.0中@WebServlet注解

    2、多重映射

    (1)配置多个<servlet-mapping>元素

    在web.xml文件中对于AServlet配置多个<servlet-mapping>元素,代码如下:

    <servlet>
        <servlet-name>aaa</servlet-name>
        <servlet-class>com.sdbi.servlet.AServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>aaa</servlet-name>
        <url-pattern>/AServlet</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>aaa</servlet-name>
        <url-pattern>/EServlet</url-pattern>
    </servlet-mapping>

    重新部署运行程序,我们通过下面两个地址http://localhost:8080/HelloServlet/AServlet和http://localhost:8080/HelloServlet/EServlet都可以运行AServlet。

    (2)在一个<servlet-mapping>中配置多个<url-pattern>元素

    在web.xml文件中对于AServlet的<servlet-mapping>元素中配置多个<url-pattern>,代码如下:

    <servlet>
        <servlet-name>aaa</servlet-name>
        <servlet-class>com.sdbi.servlet.AServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>aaa</servlet-name>
        <url-pattern>/AServlet</url-pattern>
        <url-pattern>/EServlet</url-pattern>
    </servlet-mapping>

    在以上代码中,说明<url-pattern>是<servlet-mapping>的子元素,用来指定Servlet的访问路径,即URL。它必须是以“/”开头!

    如果该Servlet类使用@WebServlet注解的形式配置,我们可以在注解中增加属性。

    @WebServlet(urlPatterns = { "/DServlet", "/FServlet" })

    或者  @WebServlet(value = { "/DServlet", "/FServlet" })

    参见博客园:《Servlet3.0中@WebServlet注解

    3、使用通配符

    我们还可以在<url-pattern>中使用通配符,所谓通配符就是星号“*”,星号可以匹配任何URL前缀或后缀,使用通配符可以命名一个Servlet绑定一组URL,例如:

    • <url-pattern>/servlet/*<url-patter>:/servlet/a、/servlet/b,都匹配/servlet/*;
    • <url-pattern>*.do</url-pattern>:/abc/def/ghi.do、/a.do,都匹配*.do;
    • <url-pattern>/*<url-pattern>:匹配所有URL;

    请注意,通配符要么为前缀,要么为后缀,不能出现在URL中间位置,也不能只有通配符。例如:/*.do就是错误的,因为星号出现在URL的中间位置上了。

    通配符是一种模糊匹配URL的方式,如果存在更具体的<url-pattern>,那么访问路径会优先去匹配具体的<url-pattern>

    4、默认Servlet

    说到上面的<url-pattern>/*<url-pattern>(匹配所有URL),这就是一种默认Servlet的匹配方式。其实,对于默认Servlet,我们可以直接给他匹配<url-pattern>/<url-pattern>即可,也就是我们通过这个路径http://localhost:8080/HelloServlet/访问,就可以方法到默认的Servlet。

    七、ServletConfig接口

    ServletConfig是一个接口(Interface),这个接口对象在之前的Servlet接口和GenericServlet抽象类中都作为了init()方法的参数和getServletConfig()方法的返回值。当Servlet配置了初始化参数之后,Web容器(Tomcat)在创建Servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用Servlet的init方法时,将ServletConfig对象传递给Servlet。进而,我们可以通过ServletConfig对象得到当前Servlet的初始化参数信息。简而言之,ServletConfig对象对应web.xml文件中的<servlet>元素。

    例如,你想获取当前Servlet在web.xml文件中的配置名(别名),那么可以使用ServletConfig.getServletName()方法获取。

     

    ServletConfig接口中定义的抽象方法有4个。这个接口的实现类是由Tomcat给我们实现提供的,不用我们自己去实现,我们只需要知道这4个方法的作用和用法就可以。

    1、String getServletName()

    获取当前Servlet在web.xml中配置的名字。对应的<servlet-name>元素的数据。

    2、String getInitParameter(String name)

    获取当前Servlet指定名称的初始化参数的值。对应的<init-param>元素中的数据。

    3、Enumeration<String> getInitParameterNames()

    获取当前Servlet所有初始化参数的名字组成的枚举。对应的<param-name>元素的数据。

    4、ServletContext getServletContext()

    获取代表当前web应用的ServletContext对象(ServletContext上下文,接口)。

    一个Servlet对应一个ServletConfig,一个Web应用对应一个ServletContext

    同一个Web应用中的多个ServletConfig对应同一个ServletContext !!!

    我们以之前的HelloServlet应用为例,学习一下ServletConfig的使用。找到之前的web.xml文件,在原来名的AServlet的<servlet>元素中添加初始化参数,代码如下:

    <servlet>
        <servlet-name>aaa</servlet-name>
        <servlet-class>com.sdbi.servlet.AServlet</servlet-class>
        <init-param>
            <param-name>p1</param-name>
            <param-value>v1</param-value>
        </init-param>
        <init-param>
            <param-name>p2</param-name>
            <param-value>v2</param-value>
        </init-param>
    </servlet>

    在AServlet.java代码的init()方法中,添加如下代码:

    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("AServlet:init()...");
        // 获取初始化参数
        System.out.println("p1 = " + config.getInitParameter("p1"));
        System.out.println("p2 = " + config.getInitParameter("p2"));
        // 获取所有初始化参数名称
        Enumeration e = config.getInitParameterNames();
        while (e.hasMoreElements()) {
            System.out.println(e.nextElement());
        }
    }

    运行程序,访问http://localhost:8080/HelloServlet/AServlet,输出如下:

    如果这个Servlet是用注解的形式配置的,那么初始化参数应该怎么添加呢?

    其实我们之前见到的注解@WebServlet("/DServlet")是省略了urlPatterns属性名的,完整的写法是:@WebServlet(urlPatterns = { "/DServlet" })。

    博客园:https://www.cnblogs.com/lihuawei/p/14776188.html

    如果添加参数,我们可以在注解中增加属性:

    @WebServlet(urlPatterns = { "/DServlet" }, initParams = {
            @WebInitParam(name = "p1", value = "v1"),
            @WebInitParam(name = "p2", value = "v2") })

    如下图所示:

     

    另外,我们在init()方法中可以获取初始化参数,如果我们想在其他方法中获得初始化参数呢?

    我们在CServlet(继承于HttpServlet)中测试一下,先在web.xml中添加如下代码:

    <servlet>
        <servlet-name>ccc</servlet-name>
        <servlet-class>com.sdbi.servlet.CServlet</servlet-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>ccc</servlet-name>
        <url-pattern>/CServlet</url-pattern>
    </servlet-mapping>

    然后在CServlet.java的doGet()方法中添加如下代码:

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        System.out.println("doGet()...");
        // 获取ServletConfig对象
        ServletConfig config = getServletConfig();
        // 获取初始化参数encoding的值
        String param = config.getInitParameter("encoding");
        System.out.println("encoding = " + param);
    }

    运行程序,访问http://localhost:8080/HelloServlet/CServlet,输出如下:

    由此可以看出,通过ServletConfig对象,我们可以获得web.xml文件中的参数信息。

    这个接口中的4个方法,其中最常用的方法是getServletContext(),用于获得一个ServletContext对象。

    八、ServletContext接口

    当Tomcat(Servlet容器)启动时,会为每个Web应用创建一个唯一的ServletContext对象,代表当前Web应用,这个对象不仅封装了当前Web应用的所有信息,而且实现了多个Servlet之间的数据共享(域对象,对应Application,该Web应用程序)。

    • 一个Web应用只有一个ServletContext对象;
    • 可以在多个Servlet中获取这个唯一的ServletContext对象,可以实现Servlet间数据传递;
    • 生存时间长,Tomcat启动时,就创建该对象,Tomcat关闭时才销毁。

    1、ServletContext作用

    在整个Web应用的动态资源之间共享数据!例如在AServlet中向ServletContext对象中保存一个值,然后在BServlet中就可以获取这个值,这就是共享数据了。

    ServletContext是JavaWeb四大域对象之一:(范围由小到大)

    • PageContext
    • ServletRequest
    • HttpSession
    • ServletContext

    所有域对象都有存取数据的功能,因为域对象内部都有一个Map,用来存储数据。

    2、获取ServletContext的方法

    • ServletConfig#getServletContext();
    • GenericServlet#getServletContext();
    • HttpSession#getServletContext()
    • ServletContextEvent#getServletContext()

    (1)在Servlet中获取ServletContext对象:

    在Servlet接口中没有定义ServletContext对象的获取,所以我们需要在自己的实现Servlet接口的子类中,从ServletConfig对象中获取ServletContext对象。在init()方法中会传入ServletConfig对象,所以我们可以在void init(ServletConfig config)中通过ServletConfig类的getServletContext()方法可以用来获取ServletContext对象:

    ServletContext context = config.getServletContext();

    (2)在GenericeServlet(或HttpServlet)中获取ServletContext对象:

    在GenericeServlet抽象类中定义了一个getServletContext()方法,具体实现代码如下:

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

    另外由于HttpServlet继承于GenericeServlet,所以HttpServlet中也有getServletContext()方法。因此,我们可以在GenericeServlet或HttpServlet中,直接使用this.getServletContext()来获取ServletContext对象。

    ServletContext context = this.getServletContext();

    (3)使用HttpSession和ServletContextEvent获取ServletContext对象:以后用到再讲。

    3、获取应用初始化参数

    我们之前使用ServletConfig也可以获取初始化参数,但它是局部的参数,一个Servlet只能获取自己的初始化参数,不能获取别人的,我们要是想为所有Servlet配置初始化参数,这时就需要使用ServletContext了。修改web.xml文件,如下所示:

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://java.sun.com/xml/ns/javaee"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
        ……
        <context-param>
            <param-name>school</param-name>
            <param-value>SDBI</param-value>
        </context-param>
        <context-param>
            <param-name>author</param-name>
            <param-value>lihuawei</param-value>
        </context-param>
        ……
    </web-app>

    <context-param>元素位于<web-app>根元素中。要想获取这些信息,我们需要使用ServletContext对象的getInitParameterNames()和getInitParameter(String name)方法分别获取参数名列表和参数值。

    在CServlet.java的doGet()方法中添加如下代码:

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        System.out.println("doGet()...");
        // 获取ServletContext对象
        ServletContext context = getServletContext();
        Enumeration<String> paramNames = context.getInitParameterNames();
        // 遍历所有的参数名列表,得到相应的参数值
        while (paramNames.hasMoreElements()) {
            String name = (String) paramNames.nextElement();
            System.out.println(name + " = " + context.getInitParameter(name));
        }
    }

    运行程序,访问http://localhost:8080/HelloServlet/CServlet,输出如下:

    4、实现多个Servlet对象共享数据

    ServletContext中用来操作数据的方法有:

    • void setAttribute(String name, Object value):用来存储一个对象,也可以称之为存储一个域属性,例如:servletContext.setAttribute(“xxx”, “XXX”),在ServletContext中保存了一个域属性,域属性名称为xxx,域属性的值为XXX。请注意,如果多次调用该方法,并且使用相同的name,那么会覆盖上一次的值,这一特性与Map相同;
    • Object getAttribute(String name):用来获取ServletContext中的数据,当前在获取之前需要先去存储才行,例如:String value = (String)servletContext.getAttribute(“xxx”);,获取名为xxx的域属性;
    • void removeAttribute(String name):用来移除ServletContext中的域属性,如果参数name指定的域属性不存在,那么本方法什么都不做;
    • Enumeration<String> getAttributeNames():获取所有域属性的名称枚举;

    我们新建两个Servlet:EServlet和FServlet,让它们分别去继承HttpServlet。注意,使用@WebServlet()注解的方式,或者使用<servlet>和<servlet-mapping>元素在web.xml中注册的方式都可以。

    <servlet>
        <servlet-name>EServlet</servlet-name>
        <servlet-class>com.sdbi.servlet.EServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>EServlet</servlet-name>
        <url-pattern>/EServlet</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>FServlet</servlet-name>
        <servlet-class>com.sdbi.servlet.FServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>FServlet</servlet-name>
        <url-pattern>/FServlet</url-pattern>
    </servlet-mapping>

    我们在EServlet的doGet()方法中保存数据,代码如下:

    public class EServlet extends HttpServlet {
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            ServletContext context = getServletContext();
            context.setAttribute("book", "JavaWeb");
            context.setAttribute("price", "56");
        }
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            doGet(request, response);
        }
    }

    我们在FServlet的doGet()方法中获取数据,代码如下:

    public class FServlet extends HttpServlet {
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            ServletContext context = getServletContext();
            String book = (String) context.getAttribute("book");
            System.out.println("book = " + book);
            System.out.println("price = " + context.getAttribute("price"));
        }
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            doGet(request, response);
        }
    }

    在浏览器地址栏里面先输入http://localhost:8080/HelloServlet/EServlet,访问EServlet,存入数据,再输入http://localhost:8080/HelloServlet/FServlet,访问FServlet,获取数据,结果显示如下:

    5、读取Web应用下的资源文件

    (1)获取真实路径:getRealPath()

    可以使用ServletContext对象的getRealPath()方法来获取Web应用下的资源的真实路径,参数必须以“/”开头,表示Web应用程序根目录。例如,在HelloServlet应用的根目录下有个login.html文件。

    • 获取login.html的真实路径:
    ServletContext context = getServletContext();
    String realPath = context.getRealPath("/login.html");

    realPath的值为login.html文件的绝对路径:

    D:\eclipse-jee-kepler-SR2-win32-x86_64\apache-tomcat-7.0.93\webapps\HelloServlet\login.html

    • 获取/WEB-INF/web.xml的真实路径:
    String realPath = context.getRealPath("/WEB-INF/web.xml");

    (2)获取资源数据流:getResourceAsStream()

    不只可以获取资源的路径,还可以通过ServletContext的getResourceAsStream()方法获取资源数据流,即把资源以输入流的方式获取,参数必须以“/”开头,表示Web应用程序根目录。

    ServletContext context = getServletContext();
    InputStream input = context.getResourceAsStream("/login.html");
    System.out.println("length = " + input.available()); //获取数据流里有多少个字节
    int len = 0;
    byte[] bys = new byte[1024];
    while ((len = input.read(bys)) != -1) {
        System.out.println(new String(bys, 0, len, "utf-8"));
    }

     

    如果在Web应用的/WEB-INF/files目录下,创建一个资源文件sdbi.properties,资源文件的内容为:

    School = SDBI

    Author = lihuawei

    我们如果想读取这个文件的内容,可以结合使用Properties类来读取。属性映射(Properties):是一种存储键/值对的数据结构,属性映射经常被用来存放配置信息。

    ServletContext context = getServletContext();
    InputStream input = context.getResourceAsStream("/WEB-INF/files/sdbi.properties");
    Properties properties = new Properties();
    properties.load(input);
    System.out.println(properties.size());
    Enumeration<String> names = (Enumeration<String>) properties.propertyNames();
    while (names.hasMoreElements()) {
        String name = (String) names.nextElement();
        System.out.println(name + " = " + properties.getProperty(name));
    }

    (3)获取指定目录下所有资源路径:getResourcePaths()

    使用ServletContext的getResourcePaths()方法获取指定目录下所有资源路径,参数必须以“/”开头,例如,获取/WEB-INF下所有资源的路径:

    ServletContext context = getServletContext();
    Set set = context.getResourcePaths("/WEB-INF");
    System.out.println(set);

    (4)获取资源的URL:getResource()

    我们可以通过ServletContext的getResource()方法获取某个资源文件的URL对象,参数必须以“/”开头,例如,获取/login.html的URL对象:

    ServletContext context = getServletContext();
    URL url = context.getResource("/login.html");
    System.out.println(url.toString());

    6、应用案例:访问量统计

    大家一定见过很多访问量统计的网站,即“本页面被访问过XXX次”。

    因为无论是哪个用户访问指定页面,都会累计访问量,所以这个访问量统计应该是整个项目共享的!

    很明显,这需要使用ServletContext来保存访问量。

    思路:创建一个Integer类型的变量,用来保存访问量,然后把它保存到ServletContext的域中,这样可以保证所有的Servlet都可以访问到!

    步骤:

    最初时,ServletContext中没有保存访问量相关的属性;

    当本站第一次被访问时,创建一个变量,设置其值为1,保存到ServletContext中;

    当以后的访问(第2~N次)时,就可以从ServletContext中获取这个变量,然后在其基础之上加1后,再保存到ServletContext中。

    ServletContext context = getServletContext();
    Integer count = (Integer) context.getAttribute("count"); // 获取当前数值
    if (count == null) { 
        count = 1; // 第一次访问
    } else {
        count++; // 加1
    }
    response.setContentType("text/html;charset=utf-8");
    response.getWriter().print("<h1>本项目一共被访问" + count + "次!</h1>");
    context.setAttribute("count", count); // 保存最新数值

  • 相关阅读:
    github上的每日学习 13
    github上的每日学习 12
    github上的每日学习 11
    github上的每日学习 10
    github上的每日学习 9
    github上的每日学习 8
    github上的每日学习 7
    面向对象程序设计寒假作业2
    MySQL安装和配置
    Fast Packet Processing with eBPF and XDP部分
  • 原文地址:https://www.cnblogs.com/lihuawei/p/16652108.html
Copyright © 2020-2023  润新知