Servlet API-Servlets(一)

概述

Servlet是一种技术标准,不同的Servlet容器包含Servlet接口,应用实现Servlet接口即可(比如SpringMVC实现了Servlet的相关接口)。

Servlet API有以下4个Java包:

  • javax.servlet,其中包含定义Servlet和Servlet容器之间契约的类和接口。
  • javax.servlet.http,其中包含定义HTTP Servlet和Servlet容器之间契约的类和接口。
  • javax.servlet.annotation,其中包含标注Servlet、Filter、Listener的标注。它还未被标注元件定义元数据。
  • javax.servlet.descriptor,其中包含提供程序化登录web应用程序的配置信息的类型。
    可以参考tomcat的实现。
    在这里插入图片描述
  • Servlet技术的核心是Servlet接口,它是所有Servlet类必须直接或间接实现的一个接口。在编写实现Servlet的Servlet类时,直接实现它。在扩展实现这个接口的类时,间接实现它。
  • Servlet接口定义了Servlet与Servlet容器之间的契约(协议)。这个契约归结起来就是,Servlet容器将Servlet类载入内存,并在Servlet实例上调用具体的方法。在一个应用程序中,每种Servlet类型只能有一个实例。
  • 用户请求致使Servlet容器调用Servlet的Services方法,并传入一个ServletRequest实例和一个ServletResponse实例。ServletRequest中封装了当前的HTTP请求,因此,Servlet开放人员不必解析和操作原始的HTTP数据。ServletResponse表示当前用户的HTTP响应,使得将响应发回给用户变得十分容易。
  • 对应每个应用程序,Servlet容器还会创建一个ServletContext实例。这个对象中封装了上下文(应用程序)的环境详情。每个上下文只有一个ServletContext实例。每个Servlet实例也都有一个封装Servlet配置的ServletConfig实例。

Servlet

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

public interface Servlet {

    public void init(ServletConfig config) throws ServletException;

    public ServletConfig getServletConfig();

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

    public String getServletInfo();

    public void destroy();
}

init、service和destroy是生命周期方法。Servlet容器根据以下规则调用这3个方法:

  • init,当该Servlet第一次被请求时,Servlet容器会调用这个方法。这个方法在后续请求中不会再被调用。我们可以利用这个方法执行相应初始化工作。调用这个方法时,Servlet容器会传入一个ServletConfig。一般来说,你会将ServletConfig赋给一个类级变量,因此这个对象可以通过Servlet类的其他点来使用。
  • service,每当请求Servlet时,Servlet容器就会调用这个方法。编码代码时,是假设Servlet要在这里被请求。第一次请求Servlet时,Servlet容器调用init方法和Service方法。后续的请求将只调用Service方法。
  • destroy,当要销毁Servlet时,Servlet容器就会调用这个方法。当要卸载应用程序,或者当要关闭Servlet容器时,就会发生这种情况。一般会在这个方法中编写请求代码。
    Servlet中的另外两个方法时非生命中期方法,即getServletInfo和getServletConfig。
  • getServletInfo,这个方法会返回Servlet的描述。可以返回有用或为null的任意字符串。
  • getServletConfig,这个方法会返回由Servlet容器传给init方法的ServletConfig。但是给一个类级别变量。

注意线程安全性。Servlet实例会被一个应用程序的所有用户共享,因此不建议使用类级变量。除非他们是只读的或者是java.util.concurrent.atomic包的成员。
servlet编写的应用的程序注意并发编程的安全性问题。

编写基础的Servlet应用程序

编写一个简单的Servlet应用程序出奇的简单。只需要创建一个简单的目录结构,并把Servlet类放入某个目录下即可。
要运行Servlets,还需要一个Servlet容器。Tomcat是一个开源的Servlet容器。

编写和编译Servlet类

目录结构如下

在这里插入图片描述

代码如下

pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>servletStudy</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>servletStudy</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope> // 表示外部已经提供,不需要打进包里
    </dependency>
  </dependencies>

  <build>
    <finalName>servletStudy</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

MyServlet类代码

package com.study.servletstudy;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @Description : servlet 测试
 * @Author : 
 * @Version : V1.0.0
 * @Date : 2021/10/21 21:26
 */
@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;
        System.out.println("servlet init");
    }

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

    @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>");

        System.out.println("call servlet service");
    }

    @Override
    public String getServletInfo() {
        System.out.println("get servlet info");
        return "My Servlet study";
    }

    @Override
    public void destroy() {
        System.out.println("servlet destory");
    }
}

调用结果

在这里插入图片描述

MyServlet代码解析

@WebServlet(name = "MyServlet", urlPatterns = {"/my"})

WebServlet标注类型用来声明一个Servlet。name指定servlet名称,name属性是可选的,如有通常用Servlet类的名称。
重要的是urlPatterns也是可选的,但是一般都是要指定的。
在MyServlet中,urlPatterns告诉容器,/my样式表示应该调用Servlet。

Servlet的init方法只会被调用一次,并将private transient变量ServletConfig设为传给该方法的ServletConfig对象。
如果想通过Servlet内部使用ServletConfig,只需要将被传入的ServletConfig赋给一个类变量。
Service方法发送给字符串“Hello from MyServlet”给浏览器。对于每个针对Servlet尽量的HTTP请求,都会调用Service方法。

为了编译Servlet,必须将Servlet API中的所有类型都放在类路径下。Tomcat中带有servlet-api.jar文件,其中包含javax.servlet的成员,以及javax.servlet.http包。这个压缩文件放在Tomcat安装目录下的lib目录中。

ServletRequest

对于每一个HTTP请求,Servlet容器都会创建一个ServletRequest实例,并将它传给Servlet的service方法。ServletRequest封装了关于这个请求的信息。
getParameter是在ServletRequest中最常用的方法。该方法通常用于返回HTML表单域的值。
getParameter也可以用于获取查询字符串的值。例如,利用下面的URI调用Servlet:
http://domain/context/servletName?id=123
用下面这个语句,可以通过Servlet内部获取id值。
String id = request.getParameter(“id”);
注意,如果该参数不存在,getParameter将返回null。

ServletResponse

  • javax.servlet.ServletResponse接口表示一个Servlet响应。在调用Servlet的Service方法前,Servlet容器首先创建一个ServletResponse,并将它作为第二个参数传给Servcie方法。ServletResponse隐藏了向浏览器发送响应的复杂过程。
  • 在ServletResponse中定义的方法之一是getWriter方法,它返回了一个可以向客户端发送文本的java.io.PrintWriter。默认情况下,PrintWriter对象使用ISO-8859-1编码。
  • 在向客户端发送响应时,大多数时候是将它作为HTML发送。因此,你必须非常熟悉HTML。

注意:
还有一个方法可以用来向浏览器发送输出,它就是getOutputStream。但这个方法是用于发送二进制数据的,因此,大多数情况使用的是getWriter,而不是getOutputStream。
getOutputStream使用示例:

public void downFile(String lang, String key, String resourcePath, HttpServletResponse resp) {
   try(InputStream inputStream = this.getClass().getResourceAsStream(resourcePath);OutputStream out = resp.getOutputStream();) {
       resp.setContentType("application/x-zip-compressed");
       resp.setCharacterEncoding("UTF-8");
       resp.setHeader("content-disposition", "attachment;filename="
               + new String(MessageUtil.getText(key, lang).getBytes("gb2312"), StandardCharsets.ISO_8859_1) + ".zip");

       IoUtil.copy(inputStream, out);
       out.flush();
   } catch (Exception e) {
       log.error("down load template error", e);
   }
}

在发送任何HTML标签前,应该先掉用setContentType方法,设置响应的内容类型,并将“text/html”作为一个参数传入。这是告诉浏览器,内容类型为HTML。在没有内容类型的情况下,大多数浏览器会默认将响应渲染成HTML。但是,如果没有设置响应内容类型,有些浏览器就会将HTML标签显示为普通文本。

ServletConfig

  • 当Servlet容器初始化Servlet时,Servlet容器会给Servlet的init方法传入一个ServletConfig。ServletConfig封装着通过@WebServlet或者部署描述符(web.xml)传给Servlet的配置信息。这样传入的每一条信息就叫一个初始参数。一个初始参数有key和value两个元件。
  • 为了从Servlet内部获取到初始参数的值,可以通过ServletConfig的getInitParameter方法。
  • ServletConfig还提供了一个很有用的方法:getServletContext。利用这个方法可以从Servlet内部获取ServletContext。

ServletContext

  • ServletContext表示Servlet应用 程序。每个Web应用程序只有一个上下文。在将一个应用程序同时部署到多个容器的分布式环境中,每台Java虚拟机上的Web应用都会有一个ServletContext对象。
  • 通过在ServletConfig中调用getServletContext方法,可以获得ServletContext。
  • 有了ServeltContext,就可以共享从应用程序中的所有资料处访问到信息,并且可以向其中设置对象。动态设置的对象保存在ServletContext中的一个内部Map中。保存在ServletContext中的对象被称作属性。
  • ServletContext中的下列方法负责处理属性:
    java.lang.Object getAttribute(java.lang.String name);
    java.util.Enumeration<java.lang.String> getAttributeNames();
    void setAttribute(java.lang.String name, java.lang.Object object);
    void removeAttribute(java.lang.String name);

GenericServlet

GenericServlet是一个抽象类,该类实现了Servlet和ServletConfig接口,我们自己实现Servlet接口时,可以直接继承该类,方便自定义类的实现。

Http Servlets

javax.servlet.http包是Servlet API中的第二个包,其中包含了用于编写Servlet应用程序的类和接口。

HttpServlet

HttpServlet类覆盖了javax.servlet.GenericServlet类。使用HttpServlet时,还要借助分别代表Servlet请求的Servlet响应的HttpServletRequest和HttpServletResponse对象。HttpServletRequest接口扩展javax.servlet.ServletRequest,HttpServletResponse扩展javax.servlet.ServletResponse。
HttpServlet覆盖GenericServlet中的service方法,并通过下列签名再添加一个Service方法:

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException      

像往常一样,Servlet容器调用javax.servlet.Servlet中原始的Service方法。HttpServlet中的编写方法如下:

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
            HttpServletRequest request = (HttpServletRequest)req;
            HttpServletResponse response = (HttpServletResponse)res;
            this.service(request, response);
        } else {
            throw new ServletException("non-HTTP request or response");
        }
    }
  • 原始的Service方法将Servlet容器的request和response对象分别转换成HttpServletRequest和HttpServletResponse,并调用信息的Service方法。这种转换总是成功的,因为在调用Servlet的Service方法时,Servlet容器总会传入一个HttpServletRequest和一个HttpServletResponse,预备使用HTTP。即便正在实现javax.servlet.Servlet,或者扩展javax.servlet.GenericServlet,也可以将传给Service方法的servlet request和servlet response分别转换成HttpServletRequest和HttpServletResponse。
  • 然后的,HttpServlet中的service方法会检验用来发送请求的HTTP方法(通过调用request.getMethod),并调用以下方法之一:doGet、doPut、doHead、doPut、doTrace、doOptions、doDelete。这7种方法中,每一种方法都表示一个HTTP方法。doGet和doPost是最常用的。因此,不再需要覆盖service方法了,只要覆盖doGet、doPost即可。
  • 总之,HttpServlet有两个特性是GenericServlet不具备的:
    1. 不用覆盖Servcie方法,而是覆盖doGet或者doPost。在少数情况下,还会覆盖以下任意方:doHead、doPut、doTrace、doOptions和doDelete。
    2. 使用HttpServletRequest和HttpServletResponse,而不是ServletRequest和ServletResponse。

HttpServletRequest

HttpServletResponse

使用部署描述符

  • 前面的例子中,编写和部署Servlet都是很容易的事情。部署的一个方面是用一个路径配置Servlet的映射。利用WebServlet标注类型,用一个路径映射了一个Servlet。
  • 利用部署描述符是配置Servlet应用程序的另一种方法,部署描述符总是命名为 web.xml,并且放在WEB-INF目录下。
  • 使用部署描述符有诸多好处。其一,可以将在@WebServlet中没有对等元素的元素赋值,如load-on-startup元素。这个元素使得Servlet在应用程序启动时加载,而不是在第一次调用时加载。如果Servlet的init方法需要花一些时间才能完成的话,使用load-on-startup意味着第一次调用Servlet所花的时间并不比后续的调用长,这项功能就特别有用。
  • 使用部署描述符的另一个好处是,如果需要修改配置,如果Servlet路径,则不需要重新进行编译Servlet,就可以进行编辑。
  • 部署描述符还允许覆盖在Servlet标注中定义的值。Servlet上的WebServlet标注如果同时也在部署描述符中进行声明,那么标注值将不起作用。

小结

Servlet技术是Java EE技术的一部分。所有Servlet都运行在Servlet容器中,容器和Servlet间的接口为javax.servlet.Servlet。javax.servlet包还提供一个名为GenericServlet的Servlet实现类,该类是一个辅助类,以便可以方便的创建一个Servlet。不过,大部分servelt都运行在HTTP环境中,因此派生一个javax.servlet.http.HttpServlet的子类更为有用,注意HttpServlet也是GenericServlet的子类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

融极

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值