目录
2.开发Servlet的正确姿势(HttpServlet) :
一、Servlet快速入门
1.为什么需要Servlet?
当浏览器向Web服务器请求静态web资源(HTML,CSS,JS,图片等)时,Tomcat可以对请求进行解析后,拿到资源,以响应的方式直接返回给浏览器,不与数据库发生关系。此时Tomcat只提供普通的web服务。
但是,当浏览器发出动态地与用户进行交互的请求时,比如用户留言,用户评论等直接与数据库挂钩的需求,就需要Servlet去调用其他Java程序来实现,Java程序通常会分层(Service,DAO等)。此时Tomcat作为了Servlet的容器(也称作引擎),因为Tomcat支持Servlet,能够完成对Servlet的解析。
2.什么是Servlet?
Servlet在开发动态WEB工程中,得到广泛的应用,Servlet是SpringMVC,SpringBoot的底层基础。
Servlet本义是java服务器小程序,特点如下——
①由服务器端调用和执行(eg : 由Tomcat解析和执行)。②用java语言编写,本质就是Java类,供其他Java程序(Servlet引擎)调用,不能独立运行。
③按照Servlet规范开发(类似于JDBC接口的规范,符合规范的Web服务器例如Tomcat, WebLogic都可以支持Servlet)。
④Servlet功能强大,可以完成几乎所有的网站功能,但对于技术栈的要求很高。
3.Servlet开发说明 :
servlet3.0前使用 web.xml;servlet3.0 版本以后 ( 包括 3.0) 支持注解(实际使用更多) ,同时支持 web.xml配置 。
PS : 原生的 Servlet 在项目中使用很少。
4.入门案例 :
首先,新建一个项目,引入Web Application框架支持后,在WEB-INF目录下新建lib目录。如下图所示 :
然后,导入Servlet所需的jar包"servlet-api.jar",在Tomcat的lib目录下,如下图所示 :
导入后要部署到当前项目,如下图所示 :
接着配置Tomcat。
然后,新建一个类,实现Servlet接口并重写Servlet接口的五个方法。
TryServlet类代码如下 : (注意看注释)
package intro;
import jakarta.servlet.*;
import java.io.IOException;
/**
* @author : Cyan_RA9
* @version : 21.0
*/
/** 一个类只有实现了Servlet接口,才能被当作Servlet使用。 */
public class TryServlet implements Servlet {
//创建TryServlet实例时会调用init方法,该方法只会被调用一次。
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("init方法被调用");
}
//返回ServletConfig对象,即返回Servlet的配置信息
@Override
public ServletConfig getServletConfig() {
return null;
}
/*
(1)service方法用于处理浏览器的请求(包括get/post)
(2)每当浏览器请求一次Servlet时,都会调用一次service方法。
(3)当Tomcat调用该方法时,会将请求的数据封装成实现了ServletRequest接口的servletRequest对象,
通过servletRequest对象,可以得到用户提交的数据。
(4)servletResponse对象可以用于返回数据给Tomcat,Tomcat解析得到对应的数据后,
会再把数据封装成HTTP响应的方式,发送给浏览器,浏览器再解析并显示。
*/
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("This is the first Servlet try~");
}
//返回servlet信息,使用较少
@Override
public String getServletInfo() {
return null;
}
//该方法是在servlet被销毁时被Tomcat调用,只调用一次
@Override
public void destroy() {
System.out.println("destroy方法被调用。");
}
}
在web.xml中配置TryServlet,即:给TryServlet提供对外访问地址。web.xml主要用于配置该web应用使用到的Servlet。web.xml代码如下 :
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>TryServlet</servlet-name>
<servlet-class>intro.TryServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TryServlet</servlet-name>
<url-pattern>/tryServlet</url-pattern>
</servlet-mapping>
</web-app>
<!--
1.<servlet>:
(1)<servlet-name>指给当前Servlet取名(由程序员手动指定,但一般取类名),该名字唯一。
(2)<servlet-class>此处为Servlet类的全路径(全类名);Tomcat在反射生成该Servlet时需要使用。
2.<servlet-mapping>:
(1)<servlet-name>同上
(2)<url-pattern>是该servlet访问URL的配置(路径)
当前访问形式:http://localhost:8080/Servlet_Demo/tryServlet
-->
运行效果(如下GIF图):
二、 Servlet生命周期
1.浏览器请求Servlet的流程分析(重要) :
1° 浏览器会先从URL中解析出主机名,eg : www.baidu.com。
2° 浏览器到本机C:\Windows\System32\drivers\etc目录下的hosts文件中,去查询有无主机名对应的IP。如下图所示 :
3° 若本机的hosts文件中没有找到域名对应的IP,就会转向外网的DNS(域名系统)继续查找,如果仍然没有找到,就会提示网站找不到,如下图所示 :
若浏览器在hosts文件中或者在DNS服务器中成功查找到了主机名对应的IP;则会根据获取到的IP,向Tomcat发出HTTP请求。
4° Tomcat收到HTTP请求后会进行判断——如果是第一次请求,Tomcat会去查询web.xml配置文件,查看请求的资源(eg : /tryServlet) 是否在<servlet-mapping>的url-pattern配置中(即web.xml文件中是否已经配置了该Servlet)。如果找到了对应的url-pattern,就会得到<servlet-name>(eg : TryServlet);如果没有,Tomcat会返回给浏览器一个404页面(找不到资源)。
Tomcat维护了至少两个牛逼的大的HashMap容器,其中一个是HashMap<id,Servlet>,id以<servlet-name>(不可重复)来存放,Servlet则存放Servlet的实例。Tomcat会通过id查询该HashMap, 确认该Servlet实例(反射创建)是否存在。
另一个HashMap容器中, key用来存放web.xml配置文件中的<servlet-mapping>中的<url-pattern>;value则存放<servlet-name>。(所以说,先得到url-pattern才会得到对应的<servlet-name>。)
如果没有查找到<servlet-name>对应的id,即没有该Servlet实例。Tomcat就会根据<servlet-name>来拿到<servlet-class>中的全类名,然后通过反射技术,实例化该Servlet(调用init方法),然后将Servlet实例放入到维护的HashMap集合中。
5° 接着,Tomcat调用service方法,由对应的Servlet实例来处理service.对于每次访问请求,Servlet 引擎都会创建一个新的 HttpServletRequest 请求对象和一个新的HttpServletResponse 响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service 方法会根据请求方式,调用doGet | dePost | 其他方法
6° 如果在HashMap集合中查到了该Servlet实例,就直接调用该Servlet的service方法;可见,Servlet常驻内存,是单例的(仅一个实例)。
7° servlet实例处理完毕service方法后,会以HttpServletResponse对象的形式将数据返回给Tomcat;
8° Tomcat会对HttpServletResponse对象进行解析,得到数据后再把数据打包到HTTP响应中,以HTTP响应的方式返回给浏览器;
9° 浏览器解析返回的结果并显示。
PS :
浏览器对静态Web资源(html,css,js,图片等)的请求实际是由Tomcat的DefaultServlet来完成的,当其他的utl-pattern都匹配不上时,Tomcat就通过DefaultServlet来拦截到其它静态资源。
2.生命周期 :
1° 初始化阶段
向Tomcat这样的Servlet 容器,加载 Servlet完成后,Servlet 容器会创建一个 Servlet 实例并调用 init()方法,init()方法只会调用一次(因为Servlet是单例的,一个类只会被加载一次)。对每次请求都导致 Servlet 引擎调用一次 servlet 的 service 方法
Servlet 容器在下面的情况装载 Servlet:
(1)Servlet 容器(Tomcat)启动时自动装载某些 servlet,需要在 web.xml 文件中添加<load-on-startup>1</load-on-startup>,其中1 表示该Servlet装载的顺序。
(2)在 Servlet 容器启动后,浏览器首次发送Servlet 的请求
(3)Servlet 重新装载后(eg: Tomcat进行 redeploy操作时 [redeploy操作会销毁当前Tomcat维护的HashMap集合中——所有的 Servlet 实例] ),浏览器再次向 Servlet 发送请求的第 1 次,也会导致Servlet的装载。
2° 处理请求阶段
1. 处理请求阶段,就是Servlet对service方法的调用。2. 每收到一个 HTTP 请求,服务器就会产生一个新的线程去处理该HTTP请求。3. 创建一个用于封装 HTTP 请求消息的 ServletRequest 对象和一个代表 HTTP 响应消息的 ServletResponse 对象。4. 然后调用 Servlet 的 service() 方法并将请求对象和响应对象作为参数进行传递。
3° 终止阶段
当web 应用被终止,或者 Servlet 容器终止运行,或者 Servlet 类重新装载时,会调用 destroy() 方法 , 比如重启 tomcat , 或者 redeploy web 应用。
三、Servlet基本使用
1.Get请求和Post请求的处理 :
以提交一个简单的form表单为例,register.html代码如下 :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Register</title>
</head>
<body>
<h1>User Register</h1>
<form action="http://localhost:8080/Servlet_Demo/tryServlet" method="post">
username:<input type="text" name="username"/> <br/><br/>
<input type="submit" value="register"/>
</form>
</body>
</html>
在TryServlet类中定义doGet和doPost方法,分别用于处理GET和POST请求,改进后的TryServlet类代码如下 :
package intro;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* @author : Cyan_RA9
* @version : 21.0
*/
/** 一个类只有实现了Servlet接口,才能被当作Servlet使用。 */
public class TryServlet implements Servlet {
//创建TryServlet实例时会调用init方法,该方法只会被调用一次。
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("init方法被调用");
}
//返回ServletConfig对象,即返回Servlet的配置信息
@Override
public ServletConfig getServletConfig() {
return null;
}
/*
(1)service方法用于处理浏览器的请求(包括get/post)
(2)每当浏览器请求一次Servlet时,都会调用一次service方法。
(3)当Tomcat调用该方法时,会将请求的数据封装成实现了ServletRequest接口的request对象,
通过servletRequest对象,可以得到用户提交的数据。
(4)servletResponse对象可以用于返回数据给Tomcat,Tomcat解析得到对应的数据后,
会再把数据封装成HTTP响应的方式,发送给浏览器,浏览器再解析并显示。
*/
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("service is invoked...");
/*
ServletRequest类中没有getMethod方法,
因此要将其转换为它的子接口类型HttpServletRequest.
*/
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String method = httpServletRequest.getMethod();
System.out.println("method = " + method);
if ("GET".equals(method)) {
doGet();
} else if ("POST".equals(method)) {
doPost();
}
}
//处理GET请求
public void doGet() {
System.out.println("doGet() is invoked");
}
//处理POST请求
public void doPost() {
System.out.println("dePost() is invoked");
}
//返回servlet信息,使用较少
@Override
public String getServletInfo() {
return null;
}
//该方法是在servlet被销毁时被Tomcat调用,只调用一次
@Override
public void destroy() {
System.out.println("destroy方法被调用。");
}
}
运行效果 :
2.开发Servlet的正确姿势(HttpServlet) :
HttpServlet类的类图如下 :
HttpServlet直接继承自GenericServlet类(抽象类),而GenericServlet类中实现了Servlet接口的全部五个方法。HttpServlet还提供了doGet和doPost方法,并且在实现的service方法中已经做了对method类型的判断;因此,HttpServlet底层走得——其实还是Servlet那一套,只不过人家帮我们封装起来了。
有了HttpServlet后,开发Servlet的步骤如下 ——
1. 编写一个类去继承 HttpServlet 类2. 根据业务需求重写 doGet 或 doPost 方法3. 在web.xml 中配置 Servlet 程序
以NBServlet为演示类,代码如下 :
package advance;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author : Cyan_RA9
* @version : 21.0
*/
public class NBServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("NBServlet's doGet is invoked~");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("NBServlet's doPost is invoked~");
}
}
web.xml重新配置后,代码如下 :
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>TryServlet</servlet-name>
<servlet-class>intro.TryServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>TryServlet</servlet-name>
<url-pattern>/tryServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>NBServlet</servlet-name>
<servlet-class>advance.NBServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>NBServlet</servlet-name>
<url-pattern>/nBServlet</url-pattern>
</servlet-mapping>
</web-app>
<!--
1.<servlet>:
(1)<servlet-name>指给当前Servlet取名(由程序员手动指定,但一般取类名),该名字唯一。
(2)<servlet-class>此处为Servlet类的全路径(全类名);Tomcat在反射生成该Servlet时需要使用。
2.<servlet-mapping>:
(1)<servlet-name>同上
(2)<url-pattern>是该servlet访问URL的配置(路径)———一般采取类名首字母小写的形式.
当前访问形式:http://localhost:8080/Servlet_Demo/tryServlet
-->
运行效果(如下GIF图):
3.注解方式开发Servlet :
使用@WebServlet注解代替web.xml配置文件,eg : @WebServlet(urlPatterns = {"/nbServlet", "/nBServlet"}),可以同时配置多个url,同时生效。
@WebServlet注解类源码如下 :
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package jakarta.servlet.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
String name() default "";
String[] value() default {};
String[] urlPatterns() default {};
int loadOnStartup() default -1;
WebInitParam[] initParams() default {};
boolean asyncSupported() default false;
String smallIcon() default "";
String largeIcon() default "";
String description() default "";
String displayName() default "";
}
若使用@WebServlet的注解方式,首次向Tomcat发送请求时,Tomcat会在IDEA的包下进行扫描,如果发现某个类使用了@WebServlet注解进行修饰,就认为该类是一个Servlet类,继而会读取@WebServlet注解中的urlPattern属性。注意——Tomcat在读取@WebServlet的过程中,就会获取到该Servlet的全类名(全路径,正名)。
其余的步骤与“web.xml”配置的方式大同小异。
以Test类为演示类,代码如下 :
package advance;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebInitParam;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author : Cyan_RA9
* @version : 21.0
*/
@WebServlet(urlPatterns = {"/TTT", "/tee"},
loadOnStartup = 1,
initParams = {@WebInitParam(name="n1", value="v1"),
@WebInitParam(name = "n2", value = "v2")})
public class Test extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Test's doGet is invoked");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Test's doPost is invoked");
}
}
运行效果(如下GIF图):
PS :
WebInitParam注解类源码如下 :
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package jakarta.servlet.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebInitParam {
String name();
String value();
String description() default "";
}
4.URL匹配的4种方式:
1° 精确匹配
eg : @WebServlet(urlPatterns={"/TTT", "/tee"})
精确匹配模式下,只有输入的URL与注解定义的URL完全匹配才能成功找到Servlet资源。(推荐)
2° 目录匹配
eg : @WebServlet("/TTT/*")
目录匹配模式下,只要符合/*前面的目录即可,*表示可以后面可以任意写,并且支持多级目录。
3° 扩展名匹配
eg : @WebServlet("*.action")
扩展名匹配模式下,前面随便写,只要符合指定的后缀名即可。注意——使用扩展名匹配时前面不能带 / , 否则 tomcat 报错(与带/的匹配模式冲突)
4° 任意匹配
eg : @WebServlet("/") or @WebServlet("/*")
/ 和 /*的配置,会匹配所有的请求(不推荐)
5° Δ注意事项Δ
(1) 当Servlet 配置了 "/", 会覆盖掉Tomcat 的DefaultServlet,会导致无法访问静态资源。(因为/的优先级比DefaultServlet高,浏览器发出的对静态资源的请求会被当做是对Servlet的请求而匹配);可以到 tomcat/conf/web.xml下查看默认 配置的 DefaultServlet。因此,不建议使用任意匹配/ 和 /*(2) 4种URL匹配模式的优先级遵守 : 精确路径 > 目录路径 > 扩展名路径 > /* > / > DefaultServlet。
四、Servlet(上)总结
(1) 了解最基本的“实现Servlet”接口的开发方式;
(2) 掌握浏览器请求Servlet的流程分析(联系浏览器请求静态资源的流程分析);
(3) 熟悉Servlet调用的生命周期;
(4) 掌握“继承HttpServlet类”的开发方式;且明白其底层原理
(5) 熟悉注解方式开发Servlet的使用,以及与web.xml方式的区别。
System.out.println("END--------------------------------------------------------------------------------");