目录
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 类开发 Servlet 程序,更加便捷。
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--------------------------------------------------------------------------------");