Servlet


掌握Servlet API 是成为一名技术高超的JAVA Web开发者的基础.必须非常熟悉Servlet API中所定义的70多种类型.
本篇博客将介绍Servlet API,并教你编写第一个Servlet应用程序.

1.Servlet API 概述

Servlet API中有4个Java包,包括:

  • javax.servlet. 包含定义Servlet与Servlet容器之间契约的类和接口.
  • javax.servlet.http. 包含定义HTTP Servlet与Servlet容器之间契约的类和接口.
  • javax.servlet.annotation. 包含对Servlet Filter和Listener进行标注的注解.它还为标注元件指定元数据.
  • javax.servlet.descriptor. 包含为Web应用程序的配置信息提供编程式访问的类型.

Servlet技术的核心是Servlet接口,这是所有Servlet类都必须直接或者间接实现的一个接口.Servlet接口定义了Servlet与Servlet容器之间的一个契约,Servlet容器会把Servlet类加载到内存中,并在Servlet实例中调用特定的方法.每个Servlet类型只能有一个实例.

Servlet容器还为每个应用程序创建一个ServletContext实例.这个对象封装context(应用程序)的环境细节.每个context只有一个ServletContext.每个Servlet实例还有一个封装Servlet配置信息的ServletConfig.

2.Servlet

Servlet接口中定义了以下5个方法:

public void init(ServletConfig servletConfig)throws ServletException 

public void service(ServletRequest request,ServletResponse response) throws ServletException,IOException 

public void destroy()

public ServletConfig getServletConfig() 

public String getServletInfo() 

编写Java方法签名的规则是:与包含该方法的类型不在同一个包中的类型,要使用全类名.
init() service() destroy()方法是Servlet的生命周期方法,调用规则如下:

  • init(): 第一次请求Servlet时,Servlet容器会调用这个方法.在后续的请求中不会在调用.
  • service(): 每次请求Servlet时都会调用此方法,必须在这里编写要Servlet完成的响应代码. 第一次调用Servlet时会调用init()和service()方法,之后请求只调用service()方法.
  • destroy(): Servlet要销毁时调用此方法,通常发生在卸载应用程序,或者关闭Servlet容器的时候.
  • getServletInfo(): 该方法返回Servlet的描述,可以是任意字符串,甚至为null.
  • getServletConfig(): 该方法返回由Servlet容器传给init方法的ServletConfig.为了让getServletConfig返回非null值,应该为传给init方法的ServletConfig赋给一个类级变量.

必须注意线程安全.一个应用程序中的所有用户将共用一个Servlet实例,因此 不建议使用类级变量,除非它们是只读的,或者是java.util.concurrent.atomic包中的成员.

3.编写基础的Servlet应用程序

Servlet应用程序编写起来非常简单,只需要创建一个目录结果,并将Servlet类放在某一个目录下即可.

3.1编写和编译Servlet类

在本节中,将学习如何编写一个简单的Servlet应用程序,将它命名为app01a.最初它只包含一个Servlet:MyServlet,其会给用户发送一条问候信息.

package app01a;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;

@WebServlet(name = "MyServlet", urlPatterns = { "/my" })
public class MyServlet implements Servlet {

    private transient ServletConfig servletConfig;

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

    @Override
    public ServletConfig getServletConfig() {
        return servletConfig;
    }

    @Override
    public String getServletInfo() {
        return "My Servlet";
    }

    @Override
    public void service(ServletRequest request,
            ServletResponse response) throws ServletException,
            IOException {
        String servletName = servletConfig.getServletName();
        response.setContentType("text/html");
        PrintWriter writer = response.getWriter();
        writer.print("<html><head></head>"
                + "<body>Hello from " + servletName 
                + "</body></html>");
    }

    @Override
    public void destroy() {
    }    
}

查看代码首先会注意到下面这个注解:

@WebServlet(name = “MyServlet”, urlPatterns = { “/my” })
注解中:name属性是可选的,一般用来提供Servlet类的名字,关键是urlPatterns它也是可选的,在MyServlet中,urlPatterns告诉容器,/my模式应该调用这个Servlet.

  • URL模式必须一条正斜线开头.

3.2应用程序的目录结构

Servlet应用程序必须以特定目录结果进行部署,应用程序包含WEB-INF目录
它有两个子目录:

  • classes目录:Servlet类和其他的Java类都必须放在这里.类下方的目录反映了类的包结构.
  • lib目录:在这里部署Servlet应用程序所需要的Jar文件.
    所有的图像文件可以放在一个image目录下,所有的JSP页面可以放在一个jsp目录下,以此类推.
    放在应用程序目录下的任何资源,用户可以通过输入该资源的URL而直接进行访问.如果希望某个资源可以被Servlet访问,但是不能被用户访问,那么应该把它放在WEB-INF目录下面.

把应用程序部署到Tomcat中:

  • 一种部署方法是将应用程序目录直接复制到Tomcate的webapps目录下
  • 可以通过在Tomcat的conf目录下编辑server.xml文件来部署应用程序
  • 为了不用编辑server.xml,而单独部署一个XML文件到conf\Catalina\localhost目录下.
  • 将应用程序打包成war文件来进行部署.war文件是指以war作为扩展名的jar文件.

3.3调用Servlet

运行MyServlet应用程序,通过以下地址:

http://localhost:8080/app01a/my

允许结果:

恭喜!你的第一个Servlet应用程序已经运行!

4.ServletRequest

下面是ServletRequest接口中的部分方法:

public int getContentLength()

返回请求主体中的字节数,如果不知道字节长度,则返回-1;

public java.lang.String getContentType()

返回请求主体的MIME类型,如果不知道类型,则返回null;

public java.lang.String getProtocol()

返回这个HTTP请求的协议名称和版本号;

public java.lang.String getParameter(java.lang.String name)

返回指定请求参数的值,返回一个HTML表单域的值,获取查询字符串的值.如果不存在返回null;

利用getParameterNames getParameterMap和getParameterValues来获取表单域的名称和值,以及查询字符串.

5.ServletResponse

  • ServletResponse中定义了getWriter()方法,它返回可以将文本传给客户端的java.io.PrintWriter.默认情况下,PrintWriter对象采用ISO-8859-1编码.

  • 在发送任何HTML标签之前,先通过调用setContentType来设置响应的内容类型,比如:将text/html作为参数传递,告诉浏览器内容类型为HTML.

6.ServletConfig

  • getInitParameter(java.lang.String name)

从一个Servlet内部获取某个初始参数的值,由传入init方法的ServletConfig中调用getInitParameter.

  • java.util.Enumeration getInitParameterNames()

返回所有初始参数名称的一个Enumeration.

  • getServletContext()

从Servlet内部获取ServletContext.

举个ServletConfig的例子.添加一个ServletConfigDemoServlet:

package app01a;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;

@WebServlet(name = "ServletConfigDemoServlet", 
    urlPatterns = { "/servletConfigDemo" },
    initParams = {
        @WebInitParam(name="admin", value="Harry Taciak"),
        @WebInitParam(name="email", value="admin@example.com")
    }
)
public class ServletConfigDemoServlet implements Servlet {
    private transient ServletConfig servletConfig;

    @Override
    public ServletConfig getServletConfig() {
        return servletConfig;
    }

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

    @Override
    public void service(ServletRequest request, 
            ServletResponse response)
            throws ServletException, IOException {
        ServletConfig servletConfig = getServletConfig();
        String admin = servletConfig.getInitParameter("admin");
        String email = servletConfig.getInitParameter("email");
        response.setContentType("text/html");
        PrintWriter writer = response.getWriter();
        writer.print("<html><head></head><body>" + 
                "Admin:" + admin + 
                "<br/>Email:" + email +
                "</body></html>");
    }

    @Override
    public String getServletInfo() {
        return "ServletConfig demo";
    }

    @Override
    public void destroy() {
    }    
}

7.ServletContext

ServletContext表示Servlet应用程序.每个Web应用程序只有一个context.
用ServletConfig中调用getServletContext方法可以获得ServletContext.
保存在ServletContext中的对象称作属性.
ServletContext中包含如下常用属性:
- getAttribute()
- getAttributeNames()
- setAttribute()
- removeAttribute()

8.GenericServlet

通过Servlet接口编写Servlet很麻烦,需要把它的所有方法都实现及时不用的方法,所以,我们有GenericServlet抽象类,它实现了Servlet和ServletConfig,并做了以下工作:

  • 将init方法中的ServletConfig赋给一个类级变量,使它可以通过调用getServletConfig来获取.
  • 为Servlet接口中的所有方法提供默认实现.
  • 提供方法来包装ServletConfig中的方法.

在类中如果覆盖了init()方法,则必须调用super.init(servletConfig)来保存ServletConfig.
可以通过覆盖无参的init方法来编写初始化代码,ServletConfig仍然由GenericServlet实例保存.
接下来举个继承GenericServlet的Servlet栗子:

package app01a;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;

@WebServlet(name = "GenericServletDemoServlet", 
    urlPatterns = { "/generic" },
    initParams = {
        @WebInitParam(name="admin", value="Harry Taciak"),
        @WebInitParam(name="email", value="admin@example.com")
    }
)
public class GenericServletDemoServlet extends GenericServlet {

    private static final long serialVersionUID = 62500890L;

    @Override
    public void service(ServletRequest request, 
            ServletResponse response)
            throws ServletException, IOException {
        ServletConfig servletConfig = getServletConfig();
        String admin = servletConfig.getInitParameter("admin");
        String email = servletConfig.getInitParameter("email");
        response.setContentType("text/html");
        PrintWriter writer = response.getWriter();
        writer.print("<html><head></head><body>" + 
                "Admin:" + admin + 
                "<br/>Email:" + email +
                "</body></html>");
    }
}

9.1 HTTPServlet

javax.servlet.http包是Servlet API中的第二个包,其包含了编写Servlet应用程序的类和接口.
HttpServlet类覆盖javax.servlet.GenericServlet类.
HttpServlet中有两项特性是GenericServlet所没有的:

  • 不覆盖service方法,而是覆盖doGet doPost,或者两者都覆盖掉.在极少数情况下,还要覆盖以下某个方法:doHead() doPut() doTrace() doOptions()或doDelete().
  • 将用HttpServletRequest和HttpServletResponse代替ServletRequest和ServletResponse.

9.2 HttpServletRequest

HttpServletRequest表示HTTP环境中的Servlet请求.它继承java.servlet.ServletRequest接口,并增加了几个方法:

  • String getContextPath()

返回表示请求context的请求URI部分.

  • Cookie[] getCookies()

返回一个Cookie对象数组.

  • String getHeader(String name)

返回指定HTTP标头的值.

  • String getMethod()

返回发出这条请求的HTTP方法的名称.

  • String getQueryString()

返回请求URL中的查询字符串.

  • HttpSession getSession()

返回与这个请求有关的session对象.如果没有找到,则创建新的session对象.

  • HttpSession getSession(boolean create)

返回与这个请求有关的session对象.如果没有找到,并且create参数为true,那么将创建新的session对象.

9.3 HttpServletResponse

HttpServletResponse表示HTTP环境下的Servlet响应.

  • void addCookie(Cookie cookie) 给这个响应对象添加cookie
  • void addHeader(String name,String value) 给这个响应对象添加标头.
  • void sendRedirect(String location) 发送响应代号,将浏览器重定向到指定的位置.

10.处理HTML表单

每个Web应用程序中几乎都会包含一个或者多个HTML表单,用来接收用户输入.可以轻松地将一个HTML表单从Serlet发送到浏览器.
HTML输入域或文本域中的值将被当作字符串发送到服务器.空白的输入域或文本域将发送一条空白的字符串,因此,带有一个输入域名称的ServletRequest.getParameter将永远不会返回null.

  • 接下来举个栗子:FormServlet类示范了处理HTML表单的方法.它的doGet方法发送了一个给浏览器.它的doPost方法获取输入的值,并输出它们.
package app01b;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name = "FormServlet", urlPatterns = { "/form" })
public class FormServlet extends HttpServlet {
    private static final long serialVersionUID = 54L;
    private static final String TITLE = "Order Form";

    @Override
    public void doGet(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter writer = response.getWriter();
        writer.println("<html>");
        writer.println("<head>");
        writer.println("<title>" + TITLE + "</title></head>");
        writer.println("<body><h1>" + TITLE + "</h1>");
        writer.println("<form method='post'>");
        writer.println("<table>");
        writer.println("<tr>");
        writer.println("<td>Name:</td>");
        writer.println("<td><input name='name'/></td>");
        writer.println("</tr>");
        writer.println("<tr>");
        writer.println("<td>Address:</td>");
        writer.println("<td><textarea name='address' "
                + "cols='40' rows='5'></textarea></td>");
        writer.println("</tr>");
        writer.println("<tr>");
        writer.println("<td>Country:</td>");
        writer.println("<td><select name='country'>");
        writer.println("<option>United States</option>");
        writer.println("<option>Canada</option>");
        writer.println("</select></td>");
        writer.println("</tr>");
        writer.println("<tr>");
        writer.println("<td>Delivery Method:</td>");
        writer.println("<td><input type='radio' " +
                "name='deliveryMethod'"
                + " value='First Class'/>First Class");
        writer.println("<input type='radio' " +
                "name='deliveryMethod' "
                + "value='Second Class'/>Second Class</td>");
        writer.println("</tr>");
        writer.println("<tr>");
        writer.println("<td>Shipping Instructions:</td>");
        writer.println("<td><textarea name='instruction' "
                + "cols='40' rows='5'></textarea></td>");
        writer.println("</tr>");
        writer.println("<tr>");
        writer.println("<td>&nbsp;</td>");
        writer.println("<td><textarea name='instruction' "
                + "cols='40' rows='5'></textarea></td>");
        writer.println("</tr>");
        writer.println("<tr>");
        writer.println("<td>Please send me the latest " +
                "product catalog:</td>");
        writer.println("<td><input type='checkbox' " +
                "name='catalogRequest'/></td>");
        writer.println("</tr>");
        writer.println("<tr>");
        writer.println("<td>&nbsp;</td>");
        writer.println("<td><input type='reset'/>" +
                "<input type='submit'/></td>");
        writer.println("</tr>");
        writer.println("</table>");
        writer.println("</form>");
        writer.println("</body>");
        writer.println("</html>");
    }

    @Override
    public void doPost(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter writer = response.getWriter();
        writer.println("<html>");
        writer.println("<head>");
        writer.println("<title>" + TITLE + "</title></head>");
        writer.println("</head>");
        writer.println("<body><h1>" + TITLE + "</h1>");
        writer.println("<table>");
        writer.println("<tr>");
        writer.println("<td>Name:</td>");
        writer.println("<td>" + request.getParameter("name")
                + "</td>");
        writer.println("</tr>");
        writer.println("<tr>");
        writer.println("<td>Address:</td>");
        writer.println("<td>" + request.getParameter("address")
                + "</td>");
        writer.println("</tr>");
        writer.println("<tr>");
        writer.println("<td>Country:</td>");
        writer.println("<td>" + request.getParameter("country")
                + "</td>");
        writer.println("</tr>");
        writer.println("<tr>");
        writer.println("<td>Shipping Instructions:</td>");
        writer.println("<td>");
        String[] instructions = request
                .getParameterValues("instruction");
        if (instructions != null) {
            for (String instruction : instructions) {
                writer.println(instruction + "<br/>");
            }
        }
        writer.println("</td>");
        writer.println("</tr>");
        writer.println("<tr>");
        writer.println("<td>Delivery Method:</td>");
        writer.println("<td>"
                + request.getParameter("deliveryMethod")
                + "</td>");
        writer.println("</tr>");
        writer.println("<tr>");
        writer.println("<td>Catalog Request:</td>");
        writer.println("<td>");
        if (request.getParameter("catalogRequest") == null) {
            writer.println("No");
        } else {
            writer.println("Yes");
        }
        writer.println("</td>");
        writer.println("</tr>");
        writer.println("</table>");
        writer.println("<div style='border:1px solid #ddd;" +
                "margin-top:40px;font-size:90%'>");

        writer.println("Debug Info<br/>");
        Enumeration<String> parameterNames = request
                .getParameterNames();
        while (parameterNames.hasMoreElements()) {
            String paramName = parameterNames.nextElement();
            writer.println(paramName + ": ");
            String[] paramValues = request
                    .getParameterValues(paramName);
            for (String paramValue : paramValues) {
                writer.println(paramValue + "<br/>");
            }
        }
        writer.println("</div>");
        writer.println("</body>");
        writer.println("</html>");
    }
}
  • 空白的订单表单:
    这里写图片描述
  • 填写完表单,并单击Submit(提交)按钮.在表单中输入的值将会通过HTTP POST方法发送到服务器,这样就会调用Servlet的doPost方法.在订单表单中输入的值:
    这里写图片描述

11.使用部署描述符

使用部署描述符是配置Servlet应用程序的另一种方法,部署描述符总是命名为web.xml,并放在WEB-INF目录下.

栗子:有两个没有@WebServlet进行标注的Servlet:分别为:SimpleServlet和WelcomeServlet.以及部署描述符文件:web.xml:

  • 未标注的SimpleServlet类:
package app01c;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class SimpleServlet extends HttpServlet {
    private static final long serialVersionUID = 8946L;

    @Override
    public void doGet(HttpServletRequest request,
            HttpServletResponse response) 
            throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter writer = response.getWriter();
        writer.print("<html><head></head>" +
                "<body>Simple Servlet</body></html");
    }
}
  • 未标注的WelcomeServlet类
package app01c;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class WelcomeServlet extends HttpServlet {
    private static final long serialVersionUID = 27126L;

    @Override
    public void doGet(HttpServletRequest request,
            HttpServletResponse response) 
            throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter writer = response.getWriter();
        writer.print("<html><head></head>"
                + "<body>Welcome</body></html>");
    }
}
  • 部署描述符web.xml:
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0"> 

    <servlet>
        <servlet-name>SimpleServlet</servlet-name>
        <servlet-class>app01c.SimpleServlet</servlet-class>
        <load-on-startup>10</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>SimpleServlet</servlet-name>
        <url-pattern>/simple</url-pattern>
    </servlet-mapping>  


    <servlet>
        <servlet-name>WelcomeServlet</servlet-name>
        <servlet-class>app01c.WelcomeServlet</servlet-class>
        <load-on-startup>20</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>WelcomeServlet</servlet-name>
        <url-pattern>/welcome</url-pattern>
    </servlet-mapping>
</web-app>

部署描述符的好处:

  • 可以包含@WebServlet中没有的元素,如load-on-startup表示在应用程序启动时加载Servlet,而不是第一次调用Servlet时加载.
  • 如果需要修改配置值,如Servlet路径,就不需要重新编译Servlet类.
  • 可以将初始参数传给一个Servlet,并且不需要重新编译Servlet类就可以对它们进行编译.
  • 部署描述符还允许覆盖Servlet注解中指定的值.可以在标注完Servlet之后,又在同一个应用程序的部署描述符中声明Servlet.

www.yaojiawei.cc

                                                未完待续...
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐