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); // 保存最新数值

 

posted @ 2022-09-03 14:16  熊猫Panda先生  阅读(867)  评论(0编辑  收藏  举报