一、Servlet概述
1、 什么是 Servlet
Servlet 是基于 Jakarta 技术的 Web 组件,由容器管理,可生成动态内容。与其他基于 Jakarta 技术的组件一样,servlet 是独立于平台的 Java 类,它们被编译为与平台无关的字节码,这些字节码可以动态加载到支持 Jakarta 技术的 Web 服务器中并由其运行。容器,有时也称为 servlet 引擎,是提供 servlet 功能的 Web 服务器扩展。Servlet 通过 servlet 容器实现的请求/响应范式与 Web 客户端交互。
2、 什么是 Servlet 容器
Servlet 容器是 Web 服务器或应用程序服务器的一部分,它提供发送请求和响应的网络服务、解码基于 MIME 的请求以及格式化基于 MIME 的响应。Servlet 容器还通过其生命周期包含和管理 Servlet。
Servlet 容器可以内置到主机 Web 服务器中,也可以通过该服务器的本机扩展 API 作为附加组件安装到 Web 服务器。Servlet 容器也可以内置于或可能安装在支持 Web 的应用程序服务器中。
所有 Servlet 容器都必须支持 HTTP 作为请求和响应的协议,但可以支持其他基于请求/响应的协议,例如 HTTPS(基于 SSL 的 HTTP)。容器必须实现的 HTTP 规范的必需版本是 HTTP/1.1 和 HTTP/2。
Java SE 8 是必须用来构建 Servlet 容器的底层 Java 平台的最低版本。
3、 一个例子
以下是一个典型的事件序列:
POST
作为此请求的一部分发送的HTTP参数以及其他相关数据。Servlet 执行它编程的任何逻辑,并生成数据发送回客户端。它通过响应对象将此数据发送回客户端。二、Servlet核心技术
1、Servlet加载时机
在默认情况下,当Web客户第一次请求访问某个Servlet时,Web容器会创建这个Servlet的实例。 当设置了web.xml中的子元素后,Servlet容器在启动Web应用时,将按照指定顺序创建并初始化这个Servlet。设置的数值大于0即可。例如:
HelloServlet
com.ydlclass.servlet.HelloServlet
2
[#]2、Servlet的生命周期
先看与Servlet生命周期有关的三个方法:init(), service(), destroy(). Servlet生命周期可被定义为从创建直到毁灭的整个过程。以下是三个方法分别对应的Servlet过程:
- init():Servlet进行初始化;
- service():Servlet处理客户端的请求;
- destroy():Servlet结束,释放资源;
在调用destroy()方法后,Servlet由JVM的垃圾回首器进行垃圾回收。
现在我们来详细讨论Servlet生命周期的方法:
init()方法:
Servlet被装载后,Servlet容器创建一个Servlet实例并且调用Servlet的init()方法进行初始化在Servlet生命周期中init()方法只被调用一次。
当用户调用一个Servlet时,Servlet容器就会创建一个Servlet实例,每一个用户请求都会产生一个新的线程,init()方法简单的创建或加载一些数据,这些数据将会被用在Servlet的整个生命周期。
init()方法的定义如下:
public void init() throws ServletException {
// 初始化代码...
}
service()方法:
service()方法是执行实际任务的主要方法。Servlet 容器(即 Web 服务器)调用 service()方法来处理来自客户端(浏览器)的请求,并把格式化的响应写回给客户端。
每次服务器接收到一个 Servlet 请求时,服务器会产生一个新的线程并调用服务。service()方法检查 HTTP 请求类型(GET、POST、PUT、DELETE 等),并在适当的时候调用doGet()、doPost()等方法。
service()的定义如下:
public void service(ServletRequest request, ServletResponse response)
throws ServletException, IOException{
// service()代码...
}
destroy()方法:
destroy()方法只会被调用一次,在Servlet生命周期结束时被调用。destroy()方法可以让Servlet关闭数据库连接、停止后台、把cookie列表或点击计数器写入到磁盘,并执行其他类似的清理活动。 在调用destroy()方法之后,Servlet对象被标记为垃圾回收。
destroy()方法的定义如下所示:
public void destroy() {
// 终止化代码...
}
总结:
- 在首次访问某个Servlet时,init()方法会被执行,而且也会执行service()方法。
- 再次访问时,只会执行service()方法,不再执行init()方法。
- 在关闭Web容器时会调用destroy()方法。
3、实现一个Servlet
当服务器接收到一个请求,就要有一个Servlet去处理这个请求,所以完成一个Servlet通常需要两步走。一方面要写一个java程序定义一个Servlet,另一方面要配置一下Servlet确定这个Servlet要处理哪一个请求。
(1)创建Servlet的三种方式
- 实现javax.servlet.Servlet接口。
- 继承javax.servlet.GenericServlet类。
- 继承javax.servlet.http.HttpServlet类。
我们在日常开发中一般会使用第三种方法来进行Servlet的创建,前两种方法理解即可。
**注意:**创建Servlet文件后,需要在web.xml文件中完成Servlet配置,才可以使用。
通过实现Servlet接口,这个接口定义了servlet的生命周期,所有的方法需要我们实现。
public class UserServlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
servletResponse.getWriter().print("hello servlet");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
GenericServlet
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
private static final long serialVersionUID = 1L;
private transient ServletConfig config;
public GenericServlet() {
}
public void destroy() {
}
public String getServletInfo() {
return "";
}
public void init() throws ServletException {
}
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
public String getServletName() {
return this.config.getServletName();
}
....
}
public class UserServlet extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
servletResponse.getWriter().print("hello servlet");
}
}
Http只是会根据请求的类型进行特殊的调用
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package javax.servlet.http;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.ResourceBundle;
import javax.servlet.DispatcherType;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public abstract class HttpServlet extends GenericServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
protected long getLastModified(HttpServletRequest req) {
return -1L;
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_post_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
// 还是会调用它,只是会根据请求的类型进行特殊的调用
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
ifModifiedSince = -1L;
}
if (ifModifiedSince < lastModified / 1000L * 1000L) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
...
}
HttpServletRequest和ServletRequest都是接口
HttpServletRequest继承自ServletRequest
HttpServletRequest比ServletRequest多了一些针对于Http协议的方法。 例如:
getHeader(), getMethod() , getSession()
三、Servlet的匹配规则
1、四种匹配规则
(1) 精确匹配
中配置的项必须与url完全精确匹配。
MyServlet
/user/users.html
/index.html
/user/addUser
当在浏览器中输入如下几种url时,都会被匹配到该servlet http://localhost:8080/appDemo/user/users.htmlopen in new window http://localhost:8080/appDemo/index.htmlopen in new window http://localhost:8080/appDemo/user/addUseropen in new window
注意:
http://localhost:8080/appDemo/user/addUser?username=Tom&age=23open in new window 会被匹配到MyServlet。
#(2) 路径匹配
以“/”字符开头,并以“/*”结尾的字符串用于路径匹配
MyServlet
/user/*
路径以/user/开始,后面的路径可以任意。比如下面的url都会被匹配。
- http://localhost:8080/appDemo/user/users.htmlopen in new window
- http://localhost:8080/appDemo/user/addUseropen in new window
- http://localhost:8080/appDemo/user/bb//sdf/sdf/sdf/updateUseropen in new window
#(3)扩展名匹配
以“*.”开头的字符串被用于扩展名匹配
MyServlet
*.jsp
*.do
则任何扩展名为jsp或action的url请求都会匹配,比如下面的url都会被匹配
- http://localhost:8080/appDemo/user/users.jspopen in new window
- http://localhost:8080/appDemo/toHome.actionopen in new window
#(4) 缺省匹配
MyServlet
/
#2、匹配顺序
精确匹配。
路径匹配,先最长路径匹配,再最短路径匹配。
扩展名匹配。
注意:使用扩展名匹配,前面就不能有任何的路径。
缺省匹配,以上都找不到Servlet,就用默认的Servlet,配置为/
#3、需要注意的问题
路径匹配和扩展名匹配无法同时设置
匹配方法只有三种,要么是路径匹配(以“/”字符开头,并以“/*”结尾),要么是扩展名匹配(以“*.”开头),要么是精确匹配,三种匹配方法不能进行组合,不要想当然使用通配符。
- 如/user/*.action是非法的
- 另外注意:/aa//bb是精确匹配,合法,这里的不是通配的含义
"/*"和"/"含义并不相同
- “/”属于路径匹配,并且可以匹配所有request,由于路径匹配的优先级仅次于精确匹配,所以“/”会覆盖所有的扩展名匹配,很多404错误均由此引起,所以这是一种特别恶劣的匹配模式。
- “/”是servlet中特殊的匹配模式,切该模式有且仅有一个实例,优先级最低,不会覆盖其他任何url-pattern,只是会替换servlet容器的内建default servlet ,该模式同样会匹配所有request。
Tomcat在%CATALINA_HOME%\conf\web.xml文件中配置了默认的Servlet,配置代码如下:
default
org.apache.catalina.servlets.DefaultServlet
debug
0
listings
false
1
jsp
org.apache.jasper.servlet.JspServlet
fork
false
xpoweredBy
false
3
default
/
jsp
*.jsp
*.jspx
- “/*”和“/”均会拦截静态资源的加载,需要特别注意
#4、举例
映射的URL | 对应的Servlet |
---|---|
/hello | servlet1 |
/bbs/admin/* | servlet2 |
/bbs/* | servlet3 |
*.jsp | servlet4 |
/ | servlet5 |
实际请求映射的结果
去掉上下文路径的剩余路径 | 处理请求的Servlet |
---|---|
/hello | servlet1 |
/bbs/admin/login | servlet2 |
/bbs/admin/index.jsp | servlet2 |
/bbs/display | servlet3 |
/bbs/index.jsp | servlet3 |
/bbs | servlet3 |
/index.jsp | servler4 |
/hello/index.jsp | servlet4 |
/hello/index.html | servlet5 |
/news | servlet5 |