概述
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不具备的:
- 不用覆盖Servcie方法,而是覆盖doGet或者doPost。在少数情况下,还会覆盖以下任意方:doHead、doPut、doTrace、doOptions和doDelete。
- 使用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的子类。