Servlet-授课
1 Servlet
1.1 Servlet概述
-
Servlet 是运行在 Java 服务器端的程序,用于接收和响应来自客户端基于 HTTP 协议的请求
-
如果想实现 Servlet 的功能,可以通过实现 javax.servlet.Servlet 接口或者继承它的实现类
-
核心方法:service(),任何客户端的请求都会经过该方法
-
Servlet是SUN公司提供的一套规范,名称就叫Servlet规范,它也是JavaEE规范之一
-
我们可以像学习Java基础一样,通过API来学习Servlet
-
这里需要注意的是,在我们之前JDK的API中是没有Servlet规范的相关内容,需要使用JavaEE的API
-
目前在Oracle官网中的最新版本是
JavaEE8
,该网址中介绍了JavaEE8的一些新特性 -
当然,我们可以通过访问
官方API
,学习和查阅里面的内容 -
打开官方API网址,在左上部分找到javax.servlet包,在左下部分找到Servlet,如下图显示:
-
通过阅读API,我们得到如下信息:
-
第一:Servlet是一个运行在web服务端的java小程序
-
第二:它可以用于接收和响应客户端的请求
-
第三:要想实现Servlet功能,可以实现Servlet接口,继承GenericServlet或者HttpServlet
-
第四:每次请求都会执行service方法
-
第五:Servlet还支持配置
-
具体请看下图:
1.2 Servlet入门 ***
1.2.1 Servlet快速入门
-
创建一个 WEB 项目
- servlet_demo1
-
创建一个类继承 GenericServlet
-
包:com.itheima.servlet
-
类:ServletDemo1
-
GenericServlet介绍:implements Servlet,实现了Servlet里的大部分方法,只有一个service方式是抽象的,我们只需要实现这个方法即可
-
-
重写 service 方法
package com.itheima.servlet; import javax.servlet.GenericServlet; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.IOException; /* Servlet快速入门1 */ public class ServletDemo01 extends GenericServlet{ @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("service方法执行了..."); } }
- service有两个参数,servletRequest,servletResponse ,这俩分别是处理请求和响应的,后续会详细介绍
-
在 web.xml 中配置 Servlet
<!--配置快速入门1Servlet--> <servlet> <servlet-name>servletDemo01</servlet-name> <servlet-class>com.itheima.servlet.ServletDemo01</servlet-class> </servlet> <servlet-mapping> <servlet-name>servletDemo01</servlet-name> <url-pattern>/servletDemo01</url-pattern> </servlet-mapping>
-
部署并启动项目:配置项目虚拟路径:/demo1
-
通过浏览器测试
-
分析图:(下图与实际案例有出入,但是我们只需要搞清楚Servlet访问流程即可)
1.2.2 Servlet执行过程分析
- 我们通过浏览器发送请求,请求首先到达Tomcat服务器
- 由服务器解析请求URL,然后在部署的应用列表中找到我们的应用
- 接下来,在我们的应用中找应用里的web.xml配置文件
- 在web.xml中找到Servlet的配置
- 找到后执行service方法,最后由ServletDemo1响应客户浏览器
整个过程如下图所示:
一句话总结执行过程:
浏览器——>Tomcat服务器——>我们的应用——>应用中的web.xml——>Servlet——>响应浏览器
1.2.3 Servlet关系视图
-
关系视图如下:
- Servlet,GenericServlet,HTTPServlet,他们都有service方法
- service方法都有俩参数,ServletRequest,ServletResponse
- HTTPServlet的参数是HttpServletRequest,HttpServletResponse,他们分别继承自ServletRequest,ServletResponse
- 这俩参数都是接口,分别处理请求,响应
- ServletConfig,处理配置
- ServletContext,处理多个Servlet之间信息共享
1.2.4 Servlet实现方式
1)实现方式说明
-
第一种
实现 Servlet 接口,实现所有的抽象方法。该方式支持最大程度的自定义。 -
第二种
继承 GenericServlet 抽象类,必须重写 service 方法,其他方法可选择重写。该方式让我们开发 Servlet 变得简单。但是这种方式和 HTTP 协议无关。 -
第三种
继承 HttpServlet 抽象类,需要重写 doGet 和 doPost 方法。该方式表示请求和响应都需要和 HTTP 协议相关。
- 上述前两种都给大家演示过了,我们接下来来试一下第三种
2)继承HttpServlet ***
-
步骤
-
创建一个类继承 HttpServlet
//servlet_demo1新建:ServletDemo02.java package com.itheima.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /* Servlet快速入门2 */ public class ServletDemo02 extends HttpServlet { }
-
重写 doGet 和 doPost 方法
public class ServletDemo02 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("方法执行了..."); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //post请求方式一般与get请求方式处理的逻辑一样,所以直接调用doGet doGet(req,resp); } }
-
在 web.xml 中配置 Servlet
<!--配置快速入门2Servlet--> <servlet> <servlet-name>servletDemo02</servlet-name> <servlet-class>com.itheima.servlet.ServletDemo02</servlet-class> </servlet> <servlet-mapping> <servlet-name>servletDemo02</servlet-name> <url-pattern>/servletDemo02</url-pattern> </servlet-mapping>
-
部署并启动项目
-
通过浏览器测试
-
1.3 Servlet使用细节
1.3.1 Servlet的生命周期
-
对象的生命周期,就是对象从出生到死亡的过程。即:出生 -> 活着 -> 死亡。官方说法是对象创建到销毁的过程
-
出生:请求第一次到达 Servlet 时,对象就创建出来,并且初始化成功。只出生(创建)一次,将对象放到内存中
-
活着:服务器提供服务的整个过程中,该对象一直存在,每次都是执行 service 方法
-
死亡:当服务停止时,或者服务器宕机时,对象死亡
-
结论:Servlet 对象只会创建一次,销毁一次。所以 Servlet 对象只有一个实例。如果一个对象实例在应用中是唯一的存在,那么我们就称它为单例模式
-
代码
-
创建ServletDemo03
package com.itheima.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /* Servlet生命周期 */ public class ServletDemo03 extends HttpServlet { @Override public void init() throws ServletException { System.out.println("对象创建并初始化了..."); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("接收到了客户端的请求..."); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } @Override public void destroy() { System.out.println("对象销毁了..."); } }
-
配置Servlet
<!--演示Servlet生命周期的配置--> <servlet> <servlet-name>servletDemo03</servlet-name> <servlet-class>com.itheima.servlet.ServletDemo03</servlet-class> </servlet> <servlet-mapping> <servlet-name>servletDemo03</servlet-name> <url-pattern>/servletDemo03</url-pattern> </servlet-mapping>
-
部署并启动
- 说明:init只会执行一次
-
停止服务
- 停止tomcat,执行destory
-
1.3.2 Servlet的线程安全 **难点
-
由于 Servlet 采用的是单例模式,也就是整个应用中只有一个实例对象。所以我们需要分析这个唯一的实例对象中的类成员是否线程安全
-
模拟用户登录功能来查看 Servlet 线程是否安全
-
结论:一个浏览器代表一个线程,多个浏览器代表多个线程。按理说我们期望的应该是每个浏览器查看的都应该是自己的用户名。而现在的结果是浏览器中数据混乱。因此,我们可以认为 Servlet 是线程不安全的!
-
解决:定义类成员要谨慎。如果是共用的,并且只会在初始化时赋值,其他时间都是获取的话,那么是没问题的。如果不是共用的,或者每次使用都有可能对其赋值,那就要考虑线程安全问题了,可以将其定义到 doGet 或 doPost 方法内或者使用同步功能即可。
-
案例演示
-
新建ServletDemo4
package com.itheima.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /* Servlet线程安全 */ public class ServletDemo04 extends HttpServlet{ //1.定义用户名成员变量 private String username = null; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //2.获取用户名 username = req.getParameter("username"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } //3.获取输出流对象 PrintWriter pw = resp.getWriter(); //4.响应给客户端浏览器 pw.print("welcome:" + username); //5.关流 pw.close(); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } }
-
Servlet配置
<!--演示Servlet线程安全的配置--> <servlet> <servlet-name>servletDemo04</servlet-name> <servlet-class>com.itheima.servlet.ServletDemo04</servlet-class> </servlet> <servlet-mapping> <servlet-name>servletDemo04</servlet-name> <url-pattern>/servletDemo04</url-pattern> </servlet-mapping>
-
演示
-
因为需要演示线程安全,所以需要两个浏览器模拟两个线程,所以要开两个浏览器
-
谷歌浏览器中url传递参数username=aaa
-
火狐浏览器中url传递参数username=bbb
-
然后谷歌浏览器先访问,紧接着火狐浏览器访问
-
结果现象是,俩浏览器都是welcome:bbb
-
-
分析
-
解决1:将username由成员变量,放到方法中
package com.itheima.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /* Servlet线程安全 */ public class ServletDemo04 extends HttpServlet{ //1.定义用户名成员变量 //private String username = null; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username = null;//谷歌浏览器进来有一个username,火狐进来也有一个username,所以不会覆盖 //2.获取用户名 username = req.getParameter("username"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } //3.获取输出流对象 PrintWriter pw = resp.getWriter(); //4.响应给客户端浏览器 pw.print("welcome:" + username); //5.关流 pw.close(); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } }
-
使用同步代码块
package com.itheima.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /* Servlet线程安全 */ public class ServletDemo04 extends HttpServlet{ //1.定义用户名成员变量 //private String username = null; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // String username = null; synchronized (this) { //锁需要唯一,Servlet对象就是唯一的,所以用this //2.获取用户名 username = req.getParameter("username"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } //3.获取输出流对象 PrintWriter pw = resp.getWriter(); //4.响应给客户端浏览器 pw.print("welcome:" + username); //5.关流 pw.close(); } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } }
-
1.3.3 不同映射方式
1)介绍
-
第一种
具体名称的方式。访问的资源路径必须和映射配置完全相同 -
第二种
/ 开头 + 通配符的方式。只要符合目录结构即可,不用考虑结尾是什么 -
第三种
通配符 + 固定格式结尾的方式。只要符合固定结尾格式即可,不用考虑前面的路径
注意
:优先级问题。越是具体的优先级越高,越是模糊通用的优先级越低。第一种 -> 第二种 -> 第三种
2)第一种:具体名称的方式 (精准匹配)***
-
此种方式,只有和映射配置一模一样时,Servlet才会接收和响应来自客户端的请求。
-
例如:映射为:/servletDemo5
-
访问URL:http://localhost:8080/demo1/servletDemo5
-
新建ServletDemo5
package com.itheima.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /* Servlet不同映射方式 */ public class ServletDemo05 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("ServletDemo05执行了..."); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } }
-
配置Servlet
<!--演示Servlet不同映射方式--> <!--具体名称的方式--> <servlet> <servlet-name>servletDemo05</servlet-name> <servlet-class>com.itheima.servlet.ServletDemo05</servlet-class> </servlet> <servlet-mapping> <servlet-name>servletDemo05</servlet-name> <url-pattern>/servletDemo05</url-pattern> </servlet-mapping>
-
访问:
3)第二种:/开头+通配符的方式
-
此种方式,只要符合目录结构即可,不用考虑结尾是什么。
-
例如:映射为:/servlet/*
-
访问URL:http://localhost:8080/demo1/servlet/itheima
http://localhost:8080/demo1/servlet/itcast
-
这两个URL都可以。因为用的*,表示/servlet/后面的内容是什么都可以。
-
我们还是使用ServletDemo5,只需要修改配置即可(把上一个具体名称的配置屏蔽掉)
<!--/开头+通配符的方式--> <servlet> <servlet-name>servletDemo05</servlet-name> <servlet-class>com.itheima.servlet.ServletDemo05</servlet-class> </servlet> <servlet-mapping> <servlet-name>servletDemo05</servlet-name> <url-pattern>/servlet/*</url-pattern> </servlet-mapping>
-
访问
4) 第三种:通配符+固定格式结尾
-
此种方式,只要符合固定结尾格式即可,其前面的访问URI无须关心(注意协议,主机和端口必须正确)
-
例如:映射为:*.do
-
访问URL:http://localhost:8080/demo1/aaa.do
http://localhost:8080/demo1/bbb.do
-
这两个URL都可以方法。因为都是以.do作为结尾,而前面用*号通配符配置的映射,所有无须关心。
-
依然使用ServletDemo5,修改配置即可
<!--通配符+固定格式结尾的方式--> <servlet>2 <servlet-name>servletDemo05</servlet-name> <servlet-class>com.itheima.servlet.ServletDemo05</servlet-class> </servlet> <servlet-mapping> <servlet-name>servletDemo05</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>
-
访问
1.3.4 Servlet多映射的使用场景
-
我们可以给一个 Servlet 配置多个访问映射,从而根据不同的请求路径来实现不同的功能
-
场景分析:
- 如果访问的资源路径是 /vip 商品价格打9折
- 如果访问的资源路径是 /vvip 商品价格打5折
- 如果访问的资源路径是其他 商品价格不打折
-
案例:新建ServletDemo6
package com.itheima.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /* Servlet 多路径映射 */ public class ServletDemo06 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1. 定义一个商品金额 int money = 1000; //2. 获取访问的资源路径 String name = req.getRequestURI(); name = name.substring(name.lastIndexOf("/")); //3. 条件判断 if("/vip".equals(name)) { //如果访问资源路径是/vip 商品价格为9折 System.out.println("商品原价为:" + money + "。优惠后是:" + (money*0.9)); } else if("/vvip".equals(name)) { //如果访问资源路径是/vvip 商品价格为5折 System.out.println("商品原价为:" + money + "。优惠后是:" + (money*0.5)); } else { //如果访问资源路径是其他 商品价格原样显示 System.out.println("商品价格为:" + money); } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } }
-
配置Servlet
<!--演示Servlet多路径映射--> <servlet> <servlet-name>servletDemo06</servlet-name> <servlet-class>com.itheima.servlet.ServletDemo06</servlet-class> </servlet> <servlet-mapping> <servlet-name>servletDemo06</servlet-name> <url-pattern>/itheima/*</url-pattern> </servlet-mapping>
-
访问
1.3.5 Servlet创建时机
- 第一次访问时创建
优势:减少对服务器内存的浪费。提高了服务器启动的效率
弊端:如果有一些要在应用加载时就做的初始化操作,无法完成
2. 服务器加载时创建
优势:提前创建好对象,提高了首次执行的效率。可以完成一些应用加载时要做的初始化操作
弊端:对服务器内存占用较多,影响了服务器启动的效率
-
修改 Servlet 创建时机。在
<servlet>
标签中,添加
<load-on-startup>
标签。
-
正整数代表服务器加载时创建,值越小、优先级越高。 负整数或不写代表第一次访问时创建
- <load-on-startup>加载顺序的序号</load-on-startup>
- 序号为1,就是服务器启动时第一个加载
- 序号为2,就是服务器启动时第二个加载
-
如果两个Servlet都要配置为正整数,那么值小的优先级高
-
配置:修改ServletDemo3的配置,增加load-on-startup
<!--演示Servlet生命周期的配置--> <servlet> <servlet-name>servletDemo03</servlet-name> <servlet-class>com.itheima.servlet.ServletDemo03</servlet-class> <!--配置Servlet启动时机 正整数代表服务器启动时创建,负数或不写代表第一次访问时创建--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>servletDemo03</servlet-name> <url-pattern>/servletDemo03</url-pattern> </servlet-mapping>
-
效果:如果不配置,是在访问ServletDemo3 的时候初始化,如果配置,那就是在启动tomcat的时候初始化
-
下图是配置后,启动tomcat打印的
-
下图是配置后,启动tomcat打印的
1.3.6 默认Servlet
- 默认Servlet是由服务器提供的一个Servlet,它配置在Tomcat的conf目录下的web.xml中。如下图所示:
-
它的映射路径是
<url-pattern>/<url-pattern>
,我们在发送请求时,首先会在我们项目中的 web.xml 中查找映射配置,找到则执行 -
但是当找不到对应的 Servlet 路径时,就去找默认的 Servlet,由默认 Servlet 处理。所以,一切都是 Servlet。
-
访问一个不存在的url
- 这个404界面,其实就是tomcat配置的默认的Servlet处理的结果
2 ServletConfig
2.1 ServletConfig介绍
2.1.1 基本概念 ***
-
ServletConfig 是 Servlet 的配置参数对象
,在 Servlet 的规范中,允许为每一个 Servlet 都提供一些初始化的配置。所以,每个 Servlet 都有一个自己的 ServletConfig -
作用
:在 Servlet 的初始化时,把一些配置信息传递给 Servlet
2.1.2 生命周期
-
生命周期:和 Servlet 相同
-
由于它是在初始化阶段读取了web.xml中为Servlet准备的初始化配置,并把配置信息传递给Servlet,所以生命周期与Servlet相同
-
这里需要注意的是,如果Servlet配置了
<load-on-startup>1</load-on-startup>
,那么ServletConfig也会在应用加载时创建
-
ServletConfig的配置信息都是键值对的形式
2.2 ServletConfig的使用
2.2.1 配置方式
-
在
<servlet>
标签中,通过
<init-param>
标签来配置。有两个子标签。 -
<param-name>
:代表初始化参数的 key。 -
<param-value>
:代表初始化参数的 value。
- 一个init-param配置一个信息,一个信息由name和value组成
-
案例
-
新建项目:servlet_demo2
-
src中新建包:com.itheima.servlet
-
新建类:ServletConfigDemo
package com.itheima.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /* ServletConfig的使用 */ public class ServletConfigDemo extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } }
-
配置Servlet
<!--配置Servlet--> <servlet> <servlet-name>servletConfigDemo</servlet-name> <servlet-class>com.itheima.servlet.ServletConfigDemo</servlet-class> <!--配置ServletConfig初始化参数--> <init-param> <!--用于获取初始化参数的key--> <param-name>encoding</param-name> <!--初始化参数的值--> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>desc</param-name> <param-value>This is ServletConfig</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>servletConfigDemo</servlet-name> <url-pattern>/servletConfigDemo</url-pattern> </servlet-mapping>
-
2.2.2 常用方法
-
常用方法:
- 掌握getInitParameter()方法
-
代码:接着在ServletConfigDemo中写代码:
public class ServletConfigDemo extends HttpServlet { //声明ServletConfig配置对象 private ServletConfig config; /* 通过init方法来为ServletConfig配置对象赋值 */ @Override public void init(ServletConfig config) throws ServletException { this.config = config; } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //根据key获取value String encodingValue = config.getInitParameter("encoding"); System.out.println(encodingValue); //获取Servlet的名称 String servletName = config.getServletName(); System.out.println(servletName); //获取所有的key Enumeration<String> names = config.getInitParameterNames(); //遍历得到的key while(names.hasMoreElements()) { //获取每一个key String name = names.nextElement(); //通过key获取value String value = config.getInitParameter(name); System.out.println("name:" + name + ",value:" + value); } //获取ServletContext对象 ServletContext context = config.getServletContext(); System.out.println(context); //获取ServletContextDemo设置共享的数据 , 这个是在写下个案例的时候添加的 //Object username = context.getAttribute("username"); //System.out.println(username); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } }
-
部署项目,配置虚拟目录为demo2,启动tomcat
-
效果
3 ServletContext
3.1 ServletContext介绍
3.1.1 基本介绍 ***
-
ServletContext 是应用上下文对象
。
每一个应用中只有一个 ServletContext 对象
。 -
作用
:可以获得应用的全局初始化参数和达到 Servlet 之间的数据共享。 -
上下文理解
:环境,不同环境给我们带来的信息是不一样的。所以环境中有很多信息,数据,也就是环境是用于存储数据的。 -
生命周期:应用一加载则创建,应用被停止则销毁。
- 出生——活着——死亡
- 出生: 应用一加载,该对象就被创建出来了。一个应用只有一个实例对象。(Servlet和ServletContext都是单例的)
- 活着:只要应用一直提供服务,该对象就一直存在。
- 死亡:应用被卸载(或者服务器挂了),该对象消亡。
-
ServletContext图示:
3.1.2 域对象概念 ***
-
域对象指的是对象有作用域
。
也就是有作用范围
-
域对象可以实现数据的共享
- 不同作用范围的域对象,共享数据的能力也不一样
-
在 Servlet 规范中,一共有 4 个域对象
- ServletContext 就是其中的一个
-
它也是 web 应用中最大的作用域,也叫 application 域
- 在整个项目范围都可以使用 应用域共享的数据
- 它可以实现整个应用之内的数据共享
-
ServletContext是一个接口,程序运行起来之后打印ServletContext的实例对象,其实是一个ApplicationContextFacade对象
- ApplicationContextFacade是ServletContext的实现类
3.2 ServletContext的使用
3.2.1 配置方式
-
ServletContext 并不属于某个 Servlet 的配置,而是针对于整个应用的配置,也叫全局的初始化参数
-
在
<web-app>
标签中,通过
<context-param>
标签来配置。有两个子标签 -
<param-name>
:代表全局初始化参数的 key -
<param-value>
:代表全局初始化参数的 value
-
案例:新建ServletContextDemo
package com.itheima.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class ServletContextDemo extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } }
-
配置Servlet,并且配置ServletContext
<web-app> .... <!--配置Servlet--> <servlet> <servlet-name>servletContextDemo</servlet-name> <servlet-class>com.itheima.servlet.ServletContextDemo</servlet-class> </servlet> <servlet-mapping> <servlet-name>servletContextDemo</servlet-name> <url-pattern>/servletContextDemo</url-pattern> </servlet-mapping> <!--配置ServletContext--> <context-param> <param-name>globalEncoding</param-name> <param-value>UTF-8</param-value> </context-param> <context-param> <param-name>globalDesc</param-name> <param-value>This is ServletContext</param-value> </context-param> </web-app>
- 注意ServletContext的配置是在wep-app节点下,与servlet配置同级别
3.2.2 常用方法1 ***
-
常用方法
- 掌握:getContextPath和getRealPath
-
准备工作:新建三个空的txt文件,如下
-
代码:继续在ServletContextDemo中写:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取ServletContext对象 ServletContext context = getServletContext(); //获取全局配置的globalEncoding String value = context.getInitParameter("globalDesc"); System.out.println(value); //获取应用的访问虚拟目录 String contextPath = context.getContextPath(); System.out.println(contextPath); //根据虚拟目录获取应用部署的磁盘绝对路径 String realPath = context.getRealPath("/"); System.out.println(realPath); //获取b.txt文件的绝对路径 String b = context.getRealPath("/b.txt"); System.out.println(b); //获取c.txt文件的绝对路径 String c = context.getRealPath("/WEB-INF/c.txt"); System.out.println(c); //获取a.txt文件的绝对路径 String a = context.getRealPath("/WEB-INF/classes/a.txt"); System.out.println(a); }
-
效果
- context.getRealPath(“/”);获取到的就是当前项目发布的路径
3.2.3 常用方法2 ***
-
常用方法2
-
代码:
//修改ServletContextDemo:存储数据 //向域对象中存储数据 context.setAttribute("username","zhangsan"); //修改ServletConfigDemo:获取数据 //获取ServletContextDemo设置共享的数据 Object username = context.getAttribute("username"); System.out.println(username);
-
效果
- 先访问contextdemo存储数据
-
再访问configdemo获取数据
4 注解开发Servlet
4.1 Servlet3.0规范
-
我们使用的是 Tomcat 9 版本。JavaEE 规范要求是 8 。对应的 Servlet 版本应该是 4.x 版本。但是,在企业开发中,稳定要远比追求新版本要重要。所以我们会降版本使用,用的是 Servlet 3.1 版本
-
其实我们之前的操作全都是基于 Servlet 2.5 版本规范的,也就是借助于配置文件的方式。后来随着软件开发逐步的演变,基于注解的配置开始流行。而 Servlet 3.0 版本也就开始支持注解开发了
-
Servlet 3.0 版本既保留了 2.5 版本的配置方式,同时又支持了全新的注解配置方式。它可以完全不需要 web.xml 配置文件,就能实现 Servlet 的配置,同时还有一些其他的新特性,我们在后面的课程中会慢慢学习到
-
总结:
- 之前基于配置文件方式,这个是Servlet2.5版本的规范
- 但是每添加一个Servlet,就需要自己配置一个,感觉有点繁琐
- Servlet3.0就出现了注解方式,可以省去配置
4.2 注解开发
4.2.1 自动注解开发Servlet ***
-
新建项目:servlet_demo3
-
配置Java EE8
- 剩余两步省略
-
新建之后,项目目录如下
-
web下没有WEB-INF了,web.xml也没有了
- 虽然web.xml不用了,但是WEB-INF还需要,所以WEB-INF需要自己创建出来
- index.jsp没用,删除即可
-
web下没有WEB-INF了,web.xml也没有了
-
新建类:com.itheima.servlet.ServletDemo1
package com.itheima.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /* 自动注解配置Servlet @WebServlet("Servlet路径") */ @WebServlet("/servletDemo1") public class ServletDemo1 extends HttpServlet{ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("servlet执行了..."); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } }
-
可以正常访问
4.2.2 注解详解
-
注解详解
4.2.3 手动创建容器(了解)
1)前置说明
-
Servlet 3.0 规范除了使用自动注解的配置方式外,还支持手动创建 Servlet 容器的方式
-
如果使用必须遵循其编写规范。在 3.0 版本加入了一个新的接口:
2)编写步骤
-
定义一个类ServletDemo2,继承 HttpServlet
-
重写 doGet 和 doPost 方法
package com.itheima.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /* 手动创建容器配置Servlet */ public class ServletDemo2 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("servlet执行了..."); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } }
-
定义一个类,实现 ServletContainerInitializer 接口
package com.itheima.servlet; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletRegistration; import java.util.Set; /* 注册配置Servlet的功能类 */ public class MyRegister implements ServletContainerInitializer { @Override public void onStartup(Set<Class<?>> set, ServletContext servletContext) { //完成Servlet的创建和配置 } }
-
在 src 目录下创建一个 META-INF 的
包
-
在 META-INF 包下创建一个 services 的包
-
在 services 包下创建一个 javax.servlet.ServletContainerInitializer 的文件
-
文件中的内容为容器实现类的全类名
com.itheima.servlet.MyRegister
-
在容器实现类中的 onStartup 方法中完成注册 Servlet
public void onStartup(Set<Class<?>> set, ServletContext servletContext) { //完成Servlet的创建和配置 //1.创建Servlet对象 ServletDemo2 servletDemo2 = new ServletDemo2(); //2.在ServletContext中添加Servlet,并得到Servlet的配置对象 ServletRegistration.Dynamic registration = servletContext.addServlet("servletDemo2", servletDemo2); //3.配置Servlet registration.setLoadOnStartup(0); //Servlet加载时机 registration.addMapping("/servletDemo2"); //映射访问资源路径 }
-
部署并启动项目
-
通过浏览器测试
5 Servlet应用案例-学生管理系统 ***
5.1 案例效果介绍
-
效果
- 访问案例首页,看到一个可以保存学生信息的界面
- 输入内容,点击保存,通过java服务器,然后最终保存到txt中
- 最后java服务器返回成功结果
5.2 案例实现
-
创建一个 web 项目:servlet_test,配置虚拟目录/stu
- 选择javaee 7,这里我们还是用web.xml
-
创建一个用于保存学生信息的 html 文件:web下新建addStudent.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>添加学生</title> </head> <body> -- ?username=张三&age=18&score=718 <form action="/stu/studentServlet" method="get" autocomplete="off"> 学生姓名:<input type="text" name="username"> <br/> 学生年龄:<input type="number" name="age"> <br/> 学生成绩:<input type="number" name="score"> <br/> <button type="submit">保存</button> </form> </body> </html>
-
创建一个类com.itheima.servlet.StudentServlet,继承 HttpServlet
-
重写 doGet 和 doPost 方法
package com.itheima.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; public class StudentServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } }
-
在 web.xml 文件中修改默认主页和配置 Servlet
<!--修改默认主页--> <welcome-file-list> <welcome-file>/addStudent.html</welcome-file> </welcome-file-list> <!--配置Servlet--> <servlet> <servlet-name>studentServlet</servlet-name> <servlet-class>com.itheima.servlet.StudentServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>studentServlet</servlet-name> <url-pattern>/studentServlet</url-pattern> </servlet-mapping>
-
在 doGet 方法中接收表单数据保存到文件中,并响应给浏览器结果
// -- ?username=张三&age=18&score=718 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取表单中的数据 String username = req.getParameter("username"); // 获取url后边的?的参数 String age = req.getParameter("age"); String score = req.getParameter("score"); //将数据保存到stu.txt文件中 BufferedWriter bw = new BufferedWriter(new FileWriter("d:\\stu.txt",true)); bw.write(username + "," + age + "," + score); bw.newLine(); bw.close(); //给浏览器回应 PrintWriter pw = resp.getWriter(); pw.println("Save Success~"); pw.close(); }
-
部署并启动项目
-
通过浏览器测试