1 Servlet技术
1.1 Servlet简介
Servlet(Server Applet),全称Java Servlet,未有中文译文。是用Java语言编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。特定如下:
- Servlet是JavaEE规范之一。
- Servlet是JavaWeb三大组件之一。三大组件分别是:Servlet程序、Filter过滤器、Listener监听器。
- Servlet是运行在服务器上的一个程序,用于接收和相应客户端的请求。
1.2 Servlet API概述
Servlet API 包含以下4个Java包:
- javax.servlet 其中包含定义servlet和servlet容器之间契约的类和接口。
- javax.servlet.http 其中包含定义HTTP Servlet 和Servlet容器之间的关系。
- javax.servlet.annotation 其中包含标注servlet,Filter,Listener的标注。它还为被标注元件定义元数据。
- javax.servlet.descriptor,其中包含提供程序化登录Web应用程序的配置信息的类型。
1.3 实现Servlet程序
步骤:
- 自定义一个类,并实现Servlet接口。
- 实现service方法,用于处理和相应数据。
- 在web.xml中配置servlet程序的访问地址。
代码:
-
自定义类
public class Demo01FirstServlet implements Servlet { /** service方法是用来处理请求和相应的 */ @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("Demo01FirstServlet 被访问了。"); } }
-
配置方法
<url-pattern>标签配置方式: 1.完全匹配: /demo03 2.目录名匹配: /aaa/bbb/*(localhost:8080/day23/aaa/bbb/任意内容 即正确) 3.后缀名匹配: *.abc (URL:localhost:8080/day23/任意.abc 即正确) 4.缺省匹配 以上三种路径都没有匹配成功的时候 默认执行缺省匹配 语法: /(在Tomcat中的webxml全局配置文件中默认自带缺省匹配) 注意: 目录名匹配和后缀名匹配不能一起使用,会有冲突 eg:/aaa/bbb/*.abc
-
配置文件
<?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标签给Tomcat配置Servlet程序 --> <servlet> <!-- servlet-name 标签给Servlet程序起一个别名 --> <servlet-name>FirstServlet</servlet-name> <!-- servlet-class 标签是Servlet程序的全类名 --> <servlet-class>demo01FirstServlet.Demo01FirstServlet</servlet-class> </servlet> <!-- 给Servlet配置详细信息 --> <servlet-mapping> <!-- servlet-name 表明是给哪一个Servlet程序配置的 --> <servlet-name>FirstServlet</servlet-name> <!-- url-pattern 标签配置访问地址 --> <!-- / 斜杠在服务器解析的时候,表示地址为:http://ip:port/工程路径 --> <!-- /hello 表示地址为:http://ip:port/工程路径/hello --> <url-pattern>/FirstServlet</url-pattern> </servlet-mapping> </web-app>
1.4 访问Servlet程序
图解
1.5 Tomcat中的web.xml全局配置文件
文件路径:apache-tomcat-8.0.50\conf\web.xml
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- The mapping for the default servlet -->
<!--
默认的Servlet
<url-pattern>/</url-pattern>:缺省匹配
前端访问后台的Servlet,没有匹配上对应的Servlet,就会访问默认的Servlet
eg:找不到指定的资源则返回404
-->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--
Tomcat支持javaEE13规范中的两个:Servlet,jsp
jsp的本质就是一个Serlvet
-->
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
<!--
配置的session的超时访问时间,默认是30分钟,时间单位默认是分钟
登录的时候:输入用户名和密码,会把用户名和密码存储到session对象中
如果我们不访问页面,默认存储用户名和密码的时间就是30分钟
-->
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<!--
<extension>avi</extension>:视频文件的扩展名是.avi,是人看的,浏览器并不认识
<mime-type>video/x-msvideo</mime-type>:视频的文件类型,给浏览器识别的,浏览器遇到video/x-msvideo类型,就会把这个文件以视频播放
-->
<mime-mapping>
<extension>avi</extension>
<mime-type>video/x-msvideo</mime-type>
</mime-mapping>
<mime-mapping>
<extension>txt</extension>
<mime-type>text/plain</mime-type>
</mime-mapping>
<mime-mapping>
<extension>jpg</extension>
<mime-type>image/jpeg</mime-type>
</mime-mapping>
<!--
默认欢迎页面的列表
访问项目 http://localhost:8080/day23 默认打开欢迎页面
-->
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
1.6 Servlet中的方法和其声明周期
1.6.1 相关方法
/**
Servlet接口
*/
public interface Servlet {
/*
当Servlet第一次被请求时,Servlet容器就会开始调用这个方法来初始化一个Servlet对象出来,但是 这个方法只会在第一次被请求时调用一次。
*/
void init(ServletConfig var1) throws ServletException;
/*
这个方法会返回由Servlet容器传给init( )方法的ServletConfig对象。
*/
ServletConfig getServletConfig();
/*
每当请求Servlet时,Servlet容器就会调用这个方法。用来处理请求和相应信息。
*/
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
/*
这个方法会返回Servlet的一段描述,可以返回一段字符串。
*/
String getServletInfo();
/*
当要销毁Servlet时,Servlet容器就会调用这个方法,一般用作一些资源清理的工作
*/
void destroy();
}
1.6.2 生命周期
init( ),service( ),destroy( )是Servlet生命周期的方法。代表了Servlet从“出生”到“工作”再到“死亡 ”的过程。Servlet容器(例如TomCat)会根据下面的规则来调用这三个方法:
- init( ),当Servlet第一次被请求时,Servlet容器就会开始调用这个方法来初始化一个Servlet对象出来,但是这个方法在后续请求中不会在被Servlet容器调用,就像人只能“出生”一次一样。我们可以利用init( )方法来执行相应的初始化工作。调用这个方法时,Servlet容器会传入一个ServletConfig对象进来从而对Servlet对象进行初始化。
- service( )方法,每当请求Servlet时,Servlet容器就会调用这个方法。就像人一样,需要不停的接受老板的指令并且“工作”。第一次请求时,Servlet容器会先调用init( )方法初始化一个Servlet对象出来,然后会调用它的service( )方法进行工作,但在后续的请求中,Servlet容器只会调用service方法了。
- destory,当要销毁Servlet时,Servlet容器就会调用这个方法,就如人一样,到时期了就得死亡。在卸载应用程序或者关闭Servlet容器时,就会发生这种情况,一般在这个方法中会写一些清除代码。
1.7 GenericServlet抽象类
前面我们编写Servlet一直是通过实现Servlet接口来编写的,但是,使用这种方法,则必须要实现Servlet接口中定义的所有的方法,即使有一些方法中没有任何东西也要去实现,并且还需要自己手动的维护ServletConfig这个对象的引用。因此,这样去实现Servlet是比较麻烦的。
GenericServlet抽象类的出现很好的解决了这个问题。本着尽可能使代码简洁的原则,GenericServlet实现了Servlet和ServletConfig接口,下面是GenericServlet抽象类的具体代码:
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
private static ResourceBundle lStrings =
ResourceBundle.getBundle("javax.servlet.LocalStrings");
private transient ServletConfig config;
public GenericServlet() {
}
public void destroy() {
}
public String getInitParameter(String name) {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getInitParameter(name);
}
}
public Enumeration<String> getInitParameterNames() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getInitParameterNames();
}
}
public ServletConfig getServletConfig() {
return this.config;
}
public ServletContext getServletContext() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getServletContext();
}
}
public String getServletInfo() {
return "";
}
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
public void log(String msg) {
this.getServletContext().log(this.getServletName() + ": " + msg);
}
public void log(String message, Throwable t) {
this.getServletContext().log(this.getServletName() + ": " + message, t);
}
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
public String getServletName() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getServletName();
}
}
}
从源码中可以看出,GenericServlet抽象类对Servlet接口中的一些方法提供了一些默认实现,所以使用GenericServlet抽象类有下面几个优点:
- 为Servlet接口中的所有方法提供了默认的实现,则程序员需要什么就直接改什么,不再需要把所有的方法都自己实现了。
- 提供方法,包括ServletConfig对象中的方法。
- 将init( )方法中的ServletConfig参数赋给了一个内部的ServletConfig引用从而来保存ServletConfig对象,不需要程序员自己去维护ServletConfig了。
但是,在源码中除了维护ServletConfig的Init(ServletConfig config)方法外还有一个空参的Init()方法,那么这个空参构造方法的作用是什么呢?
我们知道,抽象类是无法直接产生实例的,需要另一个类去继承这个抽象类,那么就会发生方法覆盖的问题,如果在类中覆盖了GenericServlet抽象类的init()方法,那么程序员就必须手动的去维护ServletConfig对象了,还得调用super.init(servletConfig)方法去调用父类GenericServlet的初始化方法来保存ServletConfig对象,这样会给程序员带来很大的麻烦。GenericServlet提供的第二个不带参数的init( )方法,就是为了解决上述问题的。
这个不带参数的init()方法,是在ServletConfig对象被赋给ServletConfig引用后,由第一个带参数的init(ServletConfig servletconfig)方法调用的,那么这意味着,当程序员如果需要覆盖这个GenericServlet的初始化方法,则只需要覆盖那个不带参数的init( )方法就好了,此时,servletConfig对象仍然有GenericServlet保存着。
1.8 HttpServlet抽象类
HttpServlet是由GenericServlet抽象类扩展而来的,HttpServlet之所以运用广泛的另一个原因是现在大部分的应用程序都要与HTTP结合起来使用。这意味着我们可以利用HTTP的特性完成更多更强大的任务。
Javax.servlet.http包是ServletAPI中的第二个包,其中包含了用于编写Servlet应用程序的类和接口。
Javax.servlet.http中的许多类型都覆盖了Javax.servlet中的类型。如图:
1.8.1 基础HttpServlet实现Servlet程序
步骤:
- 自定义类继承HttpServlet类。
- 根据需求重写doGet和doPost方法。
- 在web.xml中配置Servlet程序的访问地址。
代码
public class Demo02HttpServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Demo02HttpServlet的doGet方法");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Demo02HttpServlet的doPost方法");
}
}
配置文件
<servlet>
<servlet-name>HttpServlet</servlet-name>
<servlet-class>demo02HttpServlet.Demo02HttpServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HttpServlet</servlet-name>
<url-pattern>/HttpServlet</url-pattern>
</servlet-mapping>
1.8.2 源码分析
HttpServlet抽象类覆盖了GenericServlet抽象类中的Service( )方法,并且添加了一个自己独有的Service(HttpServletRequest request,HttpServletResponse方法。
首先来看GenericServlet抽象类中是如何定义service方法的:
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
GenericServlet抽象类中的service方法也是一个抽象方法,需要子类自己定义。在HttpServlet中service方法:
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest)req;
response = (HttpServletResponse)res;
} catch (ClassCastException var6) {
throw new ServletException("non-HTTP request or response");
}
this.service(request, response);
}
我们发现,HttpServlet中的service方法把接收到的ServletRequsest类型的对象转换成了HttpServletRequest类型的对象,把ServletResponse类型的对象转换成了HttpServletResponse类型的对象。之所以能够这样强制的转换,是因为在调用Servlet的Service方法时,Servlet容器总会传入一个HttpServletRequest对象和HttpServletResponse对象,预备使用HTTP。因此,转换类型当然不会出错了。
转换之后,service方法把两个转换后的对象传入了另一个service方法,那么我们再来看看这个方法是如何实现的:
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 = req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified) {
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);
}
}
这个service方法的参数是HttpServletRequest对象和HttpServletResponse对象,刚好接收了上一个service方法传过来的两个对象。在service方法中还是没有任何的服务逻辑,但是却在解析HttpServletRequest中的方法参数,并调用以下方法之一:doGet,doPost,doHead,doPut,doTrace,doOptions和doDelete。这7种方法中,每一种方法都表示一个Http方法。doGet和doPost是最常用的。所以,如果我们需要实现具体的服务逻辑,不再需要覆盖service方法了,只需要覆盖doGet或者doPost就好了。
而HttpServlet有两个特性是GenericServlet所不具备的:
- 不用覆盖service方法,而是覆盖doGet或者doPost方法。在少数情况,还会覆盖其他的5个方法。
- 使用的是HttpServletRequest和HttpServletResponse对象。
1.9 使用IDEA创建Servlet程序
优点:会自动补全一部分代码以及部分配置文件。
代码
public class Demo03IdeaServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
配置文件
<!-- IDEA自动完成servlet标签 -->
<servlet>
<servlet-name>IdeaServlet</servlet-name>
<servlet-class>demo03IdeaServlet.Demo03IdeaServlet</servlet-class>
</servlet>
<!-- 自己配置servlet-mapping标签内容 -->
<servlet-mapping>
<servlet-name>IdeaServlet</servlet-name>
<url-pattern>/IdeaServlet</url-pattern>
</servlet-mapping>
注意:为了使用方便,我们一般把参数设置为req和resp,但是自动生成的可能是request和response,我们可以对自动生成的模板进行设置:
2 ServletConfig类
ServletConfig是Servlet 程序的配置信息类。
当Servlet容器初始化Servlet时,Servlet容器会给Servlet的init( )方式传入一个ServletConfig对象。仅仅在初次创建时创建一个,及整个Servlet生命周期内公用。
其中几个方法如下:
public interface ServletConfig {
String getServletName();//获取在web.xml配置文件中的name(虚拟名)值
ServletContext getServletContext();//获取Context对象
String getInitParameter(String var1);//获取初始化参数
Enumeration<String> getInitParameterNames();//获取所有的初始化名称
}
2.1 ServletConfig 类的三大作用
- 可以获取 Servlet 程序的别名 servlet-name 的值
- 获取初始化参数 init-param
- 获取 ServletContext 对象
配置文件
<!-- Servlet标签给Tomcat配置Servlet程序 -->
<servlet>
<!-- servlet-name 标签给Servlet程序起一个别名 -->
<servlet-name>FirstServlet</servlet-name>
<!-- servlet-class 标签是Servlet程序的全类名 -->
<servlet-class>demo01FirstServlet.Demo01FirstServlet</servlet-class>
<!--使用init-param标签配置信息-->
<init-param>
<!-- param-name标签配置KEY -->
<param-name>username</param-name>
<!-- param-value标签配置VALUE -->
<param-value>url</param-value>
</init-param>
<init-param>
<param-name>pwd</param-name>
<param-value>123456</param-value>
</init-param>
</servlet>
代码
public class Demo01FirstServlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
// 1、可以获取 Servlet 程序的别名 servlet-name 的值
String servletName = servletConfig.getServletName();
System.out.println("Demo01FirstServlet别名:" + servletName);
// 2、获取初始化参数 init-param
Enumeration<String> initParameterNames = servletConfig.getInitParameterNames();
Iterator<String> stringIterator = initParameterNames.asIterator();
while (stringIterator.hasNext()){
System.out.println(stringIterator.next() + "==>" + servletConfig.getInitParameter(stringIterator.next()));
}
// 3、获取 ServletContext 对象
ServletContext servletContext = servletConfig.getServletContext();
System.out.println(servletContext);
}
}
结果
Demo01FirstServlet别名:FirstServlet
pwd==>123456
username==>url
org.apache.catalina.core.ApplicationContextFacade@1581b8a8
Demo01FirstServlet 被访问了。
2.2 继承HttpServlet类获取Config对象
public class Demo02HttpServlet extends HttpServlet {
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);//超类中维护了一个ServletConfig对象,不调用超类方法则需要自己保存
// 1、可以获取 Servlet 程序的别名 servlet-name 的值
String servletName = config.getServletName();
System.out.println("Demo01FirstServlet别名:" + servletName);
// 2、获取初始化参数 init-param
//只能访问本Servlet的配置文件,不能访问其他Servlet的
Enumeration<String> initParameterNames = config.getInitParameterNames();
Iterator<String> stringIterator = initParameterNames.asIterator();
while (stringIterator.hasNext()){
String next = stringIterator.next();
System.out.println(next + "==>" + config.getInitParameter(next));
}
// 3、获取 ServletContext 对象
ServletContext servletContext = config.getServletContext();
System.out.println(servletContext);
}
}
3 ServletContext类
ServletContext对象表示Servlet应用程序。每个Web应用程序都只有一个ServletContext对象。在将一个应用程序同时部署到多个容器的分布式环境中,每台Java虚拟机上的Web应用都会有一个ServletContext对象。
通过在ServletConfig中调用getServletContext方法,也可以获得ServletContext对象。
有了ServletContext对象,就可以共享从应用程序中的所有资料处访问到的信息,并且可以动态注册Web对象。前者将对象保存在ServletContext中的一个内部Map中。保存在ServletContext中的对象被称作属性。相关方法如下:
Object getAttribute(String var1);
Enumeration<String> getAttributeNames();
void setAttribute(String var1, Object var2);
void removeAttribute(String var1);
3.1 ServletContext的特点
- ServletContext 是一个接口,它表示 Servlet 上下文对象
- 一个 web 工程,只有一个 ServletContext 对象实例。
- ServletContext 对象是一个域对象。
- ServletContext 是在 web 工程部署启动的时候创建。在 web 工程停止的时候销毁。
3.2 ServletContext的作用
- 获取 web.xml 中配置的上下文参数 context-param
- 获取当前的工程路径,格式: /工程路径
- 获取工程部署后在服务器硬盘上的绝对路径
- 像 Map 一样存取数据
配置文件
<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">
<!-- context-param 用来配置全局文件,形式为键值对 -->
<context-param>
<param-name>key1</param-name>
<param-value>value1</param-value>
</context-param>
<context-param>
<param-name>key2</param-name>
<param-value>value2</param-value>
</context-param>
<!-- 省略其它Servlet配置信息 -->
</web-app>
代码
public class ContextServlet1 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取Context对象
ServletContext context = getServletContext();
// 1. 获取 web.xml 中配置的上下文参数 context-param
Enumeration<String> names = context.getInitParameterNames();
Iterator<String> iterator = names.asIterator();
while (iterator.hasNext()) {
String next = iterator.next();
System.out.println(next + "==>" + context.getInitParameter(next));
}
// 2. 获取当前的工程路径,格式: /工程路径
String contextPath = context.getContextPath();
System.out.println("当前工程路径:" + contextPath);
// 3. 获取工程部署后在服务器硬盘上的绝对路径
String realPath = context.getRealPath("/");
System.out.println("当前工程的绝对路径:" + realPath);
// 4. 像 Map 一样存取数据
System.out.println("保存之前: 获取 key1 的值是:"+ context.getAttribute("addKey"));//存储前无法获取数据
context.setAttribute("addKey","addValue");
System.out.println("保存之后: 获取 key1 的值是:"+ context.getAttribute("addKey"));//存储后可以获取数据
}
}
代码
public class ContextServlet2 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取Context对象
ServletContext context = getServletContext();
//获取context中的数据
System.out.println("ContextServlet2: 获取 key1 的值是:"+ context.getAttribute("addKey"));//可以获取其他Servlet存入的数据
}
}
结果
key1==>value1
key2==>value2
当前工程路径:/01StudyServlet
当前工程的绝对路径:D:\0001TestNode\StudyJavaWeb\out\artifacts\01StudyServlet_war_exploded\
保存之前: 获取 key1 的值是:null
保存之后: 获取 key1 的值是:addValue
ContextServlet2: 获取 key1 的值是:addValue
4 HTTP协议
4.1 什么是HTTP协议
网络程序开发,客户端和服务器实现数据交换,通信双方必须遵守通信协议.
HTTP协议,(HTTP,HyperText Transfer Protocol) 超文本传输协议,互联网程序开发基础,使用的版本1.1
- HTTP/1.0,发送请求,创建一次连接,获得一个web资源,连接断开。
- HTTP/1.1,发送请求,创建一次连接,获得多个web资源,连接断开。
HTTP的内容:规定了通信双方需要遵守的内容
HTTP的请求:客户端主动向服务器发送数据的请求
HTTP的响应:服务器将数据发回到客户端
4.2 客户端GET请求
- 请求行
- 提交方式
- 请求服务器的地址和参数
- 协议版本
- 请求头
- 键值对数据,客户端指导服务器信息
- 无请求体
4.3 客户端POST请求
- 请求行
- 提交方式
- 服务器地址
- 协议版本
- 请求头
- 键值对数据,客户端指导服务器信息
- 请求体
- POST请求,提交的参数放在请求体中
- 请求头和请求体之间有空行分割
4.4 服务器响应内容
- 响应行
- 协议版本
- 状态码
- 200 请求成功(找到了服务器上的资源)
- 302 请求重定向
- 304 请求资源没有改变,访问本地缓存
- 404 请求的资源不存在,通常是用户路径编写错误,也可能是服务器资源已删除。
- 500 服务器内部错误,通常程序抛异常
- 响应头
- 指导性信息,服务器指导浏览器,数据格式k:v
- 响应体
- 页面正文部分,浏览器要显示内容,和响应头有空行分割
4.5 数据类型:MIME
MIME 是 HTTP 协议中数据类型。
MIME 的英文全称是"Multipurpose Internet Mail Extensions" 多功能 Internet 邮件扩充服务。MIME 类型的格式是“大类型/小类型”,并与某一种文件的扩展名相对应。
常见的MIME类型:
文件 | 原类型 | MIME 类型 |
---|---|---|
超文本标记语言文本 | .html , .htm | text/html |
普通文本 | .txt | text/plain |
RTF 文本 | .rtf | application/rtf |
GIF 图形 | .gif | image/gif |
JPEG 图形 | .jpeg,.jpg | image/jpeg |
au 声音文件 | .au | audio/basic |
MIDI 音乐文件 | mid,.midi | audio/midi,audio/x-midi |
RealAudio 音乐文件 | .ra, .ram | audio/x-pn-realaudio |
MPEG 文件 | .mpg,.mpeg | video/mpeg |
AVI 文件 | .avi | video/x-msvideo |
GZIP 文件 | .gz | application/x-gzip |
TAR 文件 | .tar | application/x-tar |
5 HttpServletRequest类
5.1 ServletRequset接口
Servlet容器对于接受到的每一个Http请求,都会创建一个ServletRequest对象,并把这个对象传递给Servlet的Sevice( )方法。其中,ServletRequest对象内封装了关于这个请求的许多详细信息。其中的方法:
public interface ServletRequest {
int getContentLength();//返回请求主体的字节数
String getContentType();//返回主体的MIME类型
String getParameter(String var1);//返回请求参数的值
}
其中,getParameter是在ServletRequest中最常用的方法,可用于获取查询字符串的值。
5.2 HttpServletRequest类的作用
每次只要有请求进入 Tomcat 服务器,Tomcat 服务器就会把请求过来的 HTTP 协议信息解析好封装到 Request 对象中。然后传递到 service 方法(doGet 和 doPost)中给我们使用。我们可以通过 HttpServletRequest 对象,获取到所有请求的信息。
5.3 常用方法
String getRequestURI(); 获取请求的资源路径
StringBuffer getRequestURL(); 获取请求的统一资源定位符(绝对路径)
String getRemoteHost() 获取客户端的 ip 地址
String getHeader(String var1); 获取请求头
String getParameter() 获取请求的参数
String[] getParameterValues() 获取请求的参数(多个值的时候使用)
String getMethod(); 获取请求的方式 GET 或 POST
boolean setAttribute(key, value); 设置域数据
String getAttribute(key); 获取域数据
Servlet getRequestDispatcher() 获取请求转发对象
代码
<!DOCTYPE html>
<html >
<head>
<meta charset="UTF-8">
<title>form</title>
</head>
<body>
<form action="http://localhost:8088/01StudyServlet/demo05Req" method="get">
用户名:<input type="text" name="username"><br/>
密码:<input type="password" name="password"><br/>
兴趣爱好:<input type="checkbox" name="hobby" value="cpp">C++
<input type="checkbox" name="hobby" value="java">Java
<input type="checkbox" name="hobby" value="js">JavaScript<br/>
<input type="submit" value="提交">
</form>
</body>
</html>
代码
public class Demo05Req extends HttpServlet {
/*
测试部分方法
String getRequestURI(); 获取请求的资源路径
StringBuffer getRequestURL(); 获取请求的统一资源定位符(绝对路径)
String getRemoteHost() 获取客户端的 ip 地址
String getHeader(String var1); 获取请求头
String getMethod(); 获取请求的方式 GET 或 POST
String getParameter() 获取请求的参数
String[] getParameterValues() 获取请求的参数(多个值的时候使用)
*/
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//String getRequestURI(); 获取请求的资源路径
String uri = req.getRequestURI();
System.out.println("请求的资源路径:" + uri);
//StringBuffer getRequestURL(); 获取请求的统一资源定位符(绝对路径)
StringBuffer url = req.getRequestURL();
System.out.println("统一资源定位符:" + url);
//String getRemoteHost() 获取客户端的 ip 地址
String id = req.getRequestedSessionId();
System.out.println("ID:" + id);
//String getHeader(String var1); 获取请求头信息 以key = User-Agent 为例
String header_User_Agent = req.getHeader("User-Agent");
System.out.println("获取请求头信息(User-Agent):" + header_User_Agent);
//String getMethod(); 获取请求的方式 GET 或 POST
String method = req.getMethod();
System.out.println("请求的方式:" + method);
//String getParameter() 获取请求的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println("用户名:" + username);
System.out.println("密码:" + password);
//String[] getParameterValues() 获取请求的参数(多个值的时候使用)
String[] hobbys = req.getParameterValues("hobby");
System.out.println("兴趣爱好:" + Arrays.toString(hobbys));
}
}
结果
请求的资源路径:/01StudyServlet/demo05Req
统一资源定位符:http://localhost:8088/01StudyServlet/demo05Req
ID:null
获取请求头信息(User-Agent):Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0
请求的方式:GET
用户名:nxy123
密码:123456
兴趣爱好:[cpp, java, js]
出现的问题:当使用POST请求方式且输入的用户名为中文时出现乱码问题,如下图所示,
解决问题:是编码解码方式不一致导致的,可以使用如下方法解决,
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置请求体的字符集为 UTF-8,从而解决 post 请求的中文乱码问题
req.setCharacterEncoding("UTF-8");
//String getMethod(); 获取请求的方式 GET 或 POST
String method = req.getMethod();
System.out.println("请求的方式:" + method);
//String getParameter() 获取请求的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println("用户名:" + username);
System.out.println("密码:" + password);
//String[] getParameterValues() 获取请求的参数(多个值的时候使用)
String[] hobbys = req.getParameterValues("hobby");
System.out.println("兴趣爱好:" + Arrays.toString(hobbys));
}
如果GET请求中出现乱码,可以使用如下方式解决:
// 获取请求参数
String username = req.getParameter("username");
//1 先以 iso8859-1 进行编码 2 再以 utf-8 进行解码
username = new String(username.getBytes("iso-8859-1"), "UTF-8");
5.4 请求转发
请求转发图示:
特点:
- 浏览器的地址不会发生变化;
- 实质上是一次请求;
- 转发前后的Servlet共享Request中的数据;
- 可以使用请求转发访问到WEB-INF保护目录下的资源;
- 不可以使用请求转发访问工程以外的资源。
代码
public class ForwardServlet01 extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1 获取请求参数
String username = req.getParameter("username");
//2 进行逻辑处理
System.out.println("【1-逻辑处理】ForwardServlet01进行相关逻辑处理");
//3 添加处理标识
req.setAttribute("key1","【ForwardServlet01已经处理】");
//4 创建请求转发对象并进行请求转发
req.getRequestDispatcher("/forwardServlet02").forward(req,resp);
}
public class ForwardServlet02 extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1 获取请求参数
String username = req.getParameter("username");
//2 查看上层处理结果
System.out.println("【2-查看结果】" + req.getAttribute("key1"));
//3 进行逻辑处理
System.out.println("【2-逻辑处理】ForwardServlet02进行相关逻辑处理");
}
}
结果
5.5 一些概念
5.5.1 base标签
<!DOCTYPE html>
<html lang="zh_CN">
<head> <meta charset="UTF-8">
<title>Title</title>
<!--base 标签设置页面相对路径工作时参照的地址 href 属性就是参数的地址值 -->
<base href="http://localhost:8080/07_servlet/a/b/">
</head>
<body>
这是 a 下的 b 下的 c.html 页面<br/>
<a href="../../index.html">跳回首页</a><br/>
</body>
</html>
5.5.2 JavaWeb中绝对路径和相对路径
相对路径:
-
. :表示当前目录
-
…: 表示上一级目录
-
资源名:表示当前目录/资源名
绝对路径:
- http://ip:port/工程路径/资源路径
注意:在实际开发中都使用绝对路径,使用相对路径时也会和base标签一起使用。
5.5.3 JavaWeb中‘/’的意义
在 web 中 / 斜杠 是一种绝对路径。
- / 如果被浏览器解析,得到的地址是:http://ip:port/
- / 如果被服务器解析,得到的地址是:http://ip:port/工程路径
- 特殊情况:response.sendRediect(“/”);本质上是浏览器进行解析,所以为http://ip:port/
6 HttpServletResponse类
6.1 ServletResponse接口
javax.servlet.ServletResponse接口表示一个Servlet响应,在调用Servlet的Service()方法前,Servlet容器会先创建一个ServletResponse对象,并把它作为第二个参数传给Service()方法。ServletResponse隐藏了向浏览器发送响应的复杂过程。其中的方法:
public interface ServletResponse {
String getCharacterEncoding();
String getContentType();
ServletOutputStream getOutputStream() throws IOException;
PrintWriter getWriter() throws IOException;
void setCharacterEncoding(String var1);
void setContentLength(int var1);
void setContentType(String var1);
void setBufferSize(int var1);
int getBufferSize();
void flushBuffer() throws IOException;
void resetBuffer();
boolean isCommitted();
void reset();
void setLocale(Locale var1);
Locale getLocale();
}
其中的getWriter方法,它返回了一个可以向客户端发送文本的的Java.io.PrintWriter对象。默认情况下,PrintWriter对象使用ISO-8859-1编码(该编码在输入中文时会发生乱码)。
在向客户端发送响应时,大多数都是使用该对象向客户端发送HTML。
还有一个方法也可以用来向浏览器发送数据,它就是getOutputStream,从名字就可以看出这是一个二进制流对象,因此这个方法是用来发送二进制数据的。
在发送任何HTML之前,应该先调用setContentType()方法,设置响应的内容类型,并将“text/html”作为一个参数传入,这是在告诉浏览器响应的内容类型为HTML,需要以HTML的方法解释响应内容而不是普通的文本,或者也可以加上“charset=UTF-8”改变响应的编码方式以防止发生中文乱码现象。
6.2 HttpServletResponse 类的作用
HttpServletResponse 类和 HttpServletRequest 类一样。每次请求进来,Tomcat 服务器都会创建一个 Response 对象传递给 Servlet 程序去使用。HttpServletRequest 表示请求过来的信息,HttpServletResponse 表示所有响应的信息,我们如果需要设置返回给客户端的信息,都可以通过 HttpServletResponse 对象来进行设置。
6.3 两个输出流的说明
- 字节流:使用getOutputStream()方法获取,常用于下载(传递二进制数据)
- 字符流:使用getWriter()方法获取,常用于回传字符串(常用)
注意:两个流同时只能使用一个。使用了字节流,就不能再使用字符流,反之亦然,否则就会报错。
作用:使用流往客户端回传数据。
public class DemoResponse extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter writer = resp.getWriter();
writer.write("DemoResponse");
}
}
6.4 响应时乱码问题的解决
解决响应中文乱码方案一(不推荐使用):
//设置服务器字符集为 UTF-8
resp.setCharacterEncoding("UTF-8");
//通过响应头,设置浏览器也使用UTF-8字符集*
resp.setHeader("Content-Type", "text/html;charset=UTF-8");
解决响应中文乱码方案二(推荐):
//它会同时设置服务器和客户端都使用 UTF-8 字符集,还设置了响应头
//此方法一定要在获取流对象之前调用才有效
resp.setContentType("text/html; charset=UTF-8");
6.5 请求重定向
请求重定向,是指客户端给服务器发请求,然后服务器响应客户端一个新的URL。让客户端去新的URL访问。叫请求重定向(因为之前的地址可能已经被废弃)。
图解
重定向的两种方式
请求重定向的第一种方案:
// 设置响应状态码 302 ,表示重定向
resp.setStatus(302);
// 设置响应头,说明 新的地址在哪里
resp.setHeader("Location", "http://localhost:8080");
请求重定向的第二种方案(推荐使用):
resp.sendRedirect("http://localhost:8080");
6.5.1 第一种方式
代码
public class Servlet01 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servlet01被访问了.");
//设置响应码
resp.setStatus(302);
//设置响应头
resp.setHeader("Location","/01StudyServlet/servlet02");
}
}
public class Servlet02 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servlet02被访问了.");
resp.setContentType("text/html;charset=UTF-8");
PrintWriter writer = resp.getWriter();
writer.write("<h1>我是Servlet02的响应</h1>");
}
}
结果
6.5.2 第二种方式
代码
public class Servlet01 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servlet01被访问了.");
resp.sendRedirect("/01StudyServlet/servlet02");
}
}
public class Servlet02 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servlet02被访问了.");
resp.setContentType("text/html;charset=UTF-8");
PrintWriter writer = resp.getWriter();
writer.write("<h1>我是Servlet02的响应</h1>");
}
}
结果
7 Cookie
7.1 概念
Cookie 是服务器通知客户端保存键值对的一种技术。客户端有了 Cookie 后,每次请求都发送给服务器。每个 Cookie 的大小不能超过 4kb。
7.2 创建Cookie
方法
Cookie cookie1 = new Cookie("key","value");
resp.addCookie(cookie1);
代码
@WebServlet(urlPatterns = "/cookieServlet")
public class CookieServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
String order = req.getParameter("order");
switch (order) {
case "createCookie":
createCookie(req, resp);
break;
case "getCookie":
getCookie(req, resp);
break;
default:
System.out.println("找不到命令" + order);
break;
}
}
/**
* 创建Cookie
* @param req
* @param resp
*/
private void createCookie(HttpServletRequest req, HttpServletResponse resp) throws IOException {
Cookie cookie1 = new Cookie("key1","value1");
resp.addCookie(cookie1);
Cookie cookie2 = new Cookie("key2","value2");
resp.addCookie(cookie2);
Cookie cookie3 = new Cookie("key3","value3");
resp.addCookie(cookie3);
resp.getWriter().print("创建成功!");
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
7.3 获取Cookie
方法
Cookie[] cookies = req.getCookies();
代码
/**
* 获取Cookie
* @param req
* @param resp
*/
private void getCookie(HttpServletRequest req, HttpServletResponse resp) throws IOException {
Cookie[] cookies = req.getCookies();
for (Cookie cookie : cookies) {
resp.getWriter().print("【" + cookie.getName() + "=" + cookie.getValue() + "】<br>");
}
}
效果
7.4 修改Cookie
方法
方案一:
1、先创建一个要修改的同名(指的就是 key)的 Cookie 对象
2、在构造器,同时赋于新的 Cookie 值
3、调用 response.addCookie( Cookie )
方案二:
1、先查找到需要修改的 Cookie 对象
2、调用 setValue()方法赋于新的 Cookie 值
3、调用 response.addCookie()通知客户端保存修改
代码
/**
* 修改Cookie的值
* @param req
* @param resp
*/
private void updateCookie(HttpServletRequest req, HttpServletResponse resp) {
//方案一:
//1、先创建一个要修改的同名(指的就是 key)的 Cookie 对象
//2、在构造器,同时赋于新的 Cookie 值
Cookie cookie1 = new Cookie("key1", "newValue");
//3、调用 response.addCookie( Cookie )
resp.addCookie(cookie1);
//方案二:
//1、先查找到需要修改的 Cookie 对象
Cookie[] cookies = req.getCookies();
for (Cookie cookie : cookies) {
if ("key2".equals(cookie.getName())) {
//2、调用 setValue()方法赋于新的 Cookie 值
cookie.setValue("newValue");
//3、调用 response.addCookie()通知客户端保存修改
resp.addCookie(cookie);
}
}
}
效果
7.5 Cookie的生命周期
修改方式:使用setMaxAge()方法进行修改,
- 正数,表示在指定的秒数后过期
- 负数,表示浏览器一关,Cookie 就会被删除(默认值是-1)
- 零,表示马上删除 Cookie
代码
/**
* Cookie的声明周期
* @param req
* @param resp
*/
private void lifeCookie(HttpServletRequest req, HttpServletResponse resp) throws IOException {
//使用setMaxAge()方法进行修改
Cookie[] cookies = req.getCookies();
for (Cookie cookie : cookies) {
if ("key1".equals(cookie.getName())) {
//正数,表示在指定的秒数后过期
cookie.setMaxAge(60 * 60);//设置一个小时的Cookie
resp.addCookie(cookie);
}
if ("key2".equals(cookie.getName())) {
//负数,表示浏览器一关,Cookie 就会被删除(默认值是-1)
cookie.setMaxAge(-1);//设置默认值的Cookie
resp.addCookie(cookie);
}
if ("key3".equals(cookie.getName())) {
//零,表示马上删除 Cookie
cookie.setMaxAge(0);//设置马上删除的Cookie
resp.addCookie(cookie);
}
}
resp.getWriter().print("Cookie的声明周期已经设置");
}
效果
7.6 Cookie的有效路径
Cookie 的 path 属性可以有效的对Cookie进行过滤,从而使哪些 Cookie 可以发送给服务器,哪些不发。path 属性是通过请求的地址来进行有效的过滤。
示例
对Cookie的path进行设置:
CookieA path=/工程路径
CookieB path=/工程路径/abc
请求地址如下:
http://ip:port/工程路径/a.html
CookieA 发送
CookieB 不发送
http://ip:port/工程路径/abc/a.html
CookieA 发送
CookieB 发送
8 Session
8.1 概念
- Session 就一个接口(HttpSession)。
- Session 就是会话,这种技术用来维护一个客户端和服务器之间关联。
- 每个客户端都有自己的一个 Session 会话。
- Session 会话中,我们经常用来保存用户登录之后的信息
8.2 Session的创建和获取
方法
创建和获取Session的 API 是一样的。
request.getSession()
第一次调用是:创建 Session 会话
之后调用都是:获取前面创建好的 Session 会话对象。
isNew(); 判断到底是不是刚创建出来的(新的)
true 表示刚创建
false 表示获取之前创建
每个会话都有一个ID值,这个 ID 是唯一的。
getId() 得到 Session 的会话 id 值。
代码
@WebServlet(urlPatterns = "/sessionServlet")
public class SessionServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
String order = req.getParameter("order");
switch (order) {
case "createSession":
createSession(req, resp);
break;
case "addDateSession":
addDateSession(req, resp);
break;
case "getDateSession":
getDateSession(req, resp);
break;
default:
System.out.println("找不到命令" + order);
break;
}
}
/**
* 获取一个Session
* @param req
* @param resp
*/
private void createSession(HttpServletRequest req, HttpServletResponse resp) throws IOException {
//获取Session
HttpSession session = req.getSession();
//判断Session是不是刚刚创建的
boolean aNew = session.isNew();
resp.getWriter().print("Session是不是先创建的:" + aNew + "<br>");
//获取Session的ID
String id = session.getId();
resp.getWriter().print("Session的ID:" + id);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
效果
8.3 Session数据读取
代码
/**
* 获取Session的数据
* @param req
* @param resp
*/
private void getDateSession(HttpServletRequest req, HttpServletResponse resp) throws IOException {
//获取Session
HttpSession session = req.getSession();
//获取数据
Object session1 = session.getAttribute("Session1");
//响应数据给客户端
resp.getWriter().print("Session的数据为:" + session1);
}
/**
* 给Session添加数据
* @param req
* @param resp
*/
private void addDateSession(HttpServletRequest req, HttpServletResponse resp) throws IOException {
//获取Session
HttpSession session = req.getSession();
//添加数据
session.setAttribute("Session1","Session1");
//返回提示信息
resp.getWriter().print("已经添加数据至服务器。");
}
效果
8.4 Session的生命周期
设置方法:
-
给所有Session设置失效时间
Session 默认的超时时间长为 30 分钟。因为在 Tomcat 服务器的配置文件 web.xml中默认有以下的配置,它就表示配置了当前 Tomcat 服务器下所有的 Session 超时配置默认时长为:30 分钟: <session-config> <session-timeout>30</session-timeout> </session-config> 你希望你的 web 工程,默认的 Session 的超时时长为其他时长。你可以在你自己的 web.xml 配置文件中做 以上相同的配置。就可以修改你的 web 工程所有 Seession 的默认超时时长。 <!--表示当前 web 工程。创建出来 的所有 Session 默认是 20 分钟 超时时长--> <session-config> <session-timeout>20</session-timeout> </session-config>
-
给个别Session设置失效时间
public int getMaxInactiveInterval()获取 Session 的超时时间 值为正数的时候,设定 Session 的超时时长(以秒为单位)。 负数表示永不超时(极少使用) public void invalidate() 让当前 Session 会话马上超时无效。
8.5 Session的底层实现
- 第一次获取Session时,会创建一个Session示例保存在服务器的内存中,并且在响应时候以Cookie技术响应给客户端一个Session的ID值。
- 客户端浏览器会维护这个Cookie,以便在服务器能够取得同一个Session对象。
9 Listener监听器
9.1 概念
- Listener 监听器它是 JavaWeb 的三大组件之一。JavaWeb 的三大组件分别是:Servlet 程序、Filter 过滤器、Listener 监听器。
- Listener 它是 JavaEE 的规范,就是接口
- 监听器的作用是,监听某种事物的变化。然后通过回调函数,反馈给客户(程序)去做一些相应的处理。
9.2 ServletContextListener 监听器
ServletContextListener 它可以监听 ServletContext 对象的创建和销毁。ServletContext 对象在 web 工程启动的时候创建,在 web 工程停止的时候销毁。监听到创建和销毁之后都会分别调用 ServletContextListener 监听器的方法反馈。
接口中的两个抽象方法:
public interface ServletContextListener extends EventListener {
/**
在 ServletContext 对象创建之后马上调用,做初始化
*/
public void contextInitialized(ServletContextEvent sce);
/**
在 ServletContext 对象销毁之后调用
*/
public void contextDestroyed(ServletContextEvent sce);
}
实现类
public class MyServletContextListenerImpl implements ServletContextListener { @Override
public void contextInitialized(ServletContextEvent sce) { System.out.println("ServletContext 对象被创建了");
}
@Override
public void contextDestroyed(ServletContextEvent sce) { System.out.println("ServletContext 对象被销毁了");
}
web.xml配置
<!--配置监听器-->
<listener>
<listener-class>com.atguigu.listener.MyServletContextListenerImpl</listener-class> </listener>
10 Filter过滤器
10.1 概念
- Filter 过滤器它是 JavaWeb 的三大组件之一。三大组件分别是:Servlet 程序、Listener 监听器、Filter 过滤器。
- Filter 过滤器它是 JavaEE 的规范,也就是接口 。
- Filter 过滤器它的作用是:拦截请求,过滤响应。
常见的应用场景有:权限检查、日记操作、事务管理等。
10.2 创建Filter——以登录拦截为例
登录页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<form action="http://localhost:8088/01StudyServlet/loginServlet" method="get">
用户名:<input type="text" name="username"> <br>
密 码:<input type="password" name="password"> <br>
<input type="submit"/>
</form>
</body>
</html>
主页
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>主页</title>
</head>
<body>
这个是主页。
</body>
</html>
处理登录请求的Servlet
@WebServlet(urlPatterns = "/loginServlet")
public class LoginServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
//创建变量存储用户名和密码
String username = null;
String password = null;
//获取Session
HttpSession session = req.getSession();
//Session中没有信息,处理登录请求
username = req.getParameter("username");
password = req.getParameter("password");
//判断请求
if ("user".equals(username) && "1234".equals(password)) {
//登录成功,添加用户信息
session.setAttribute("user",username);
//存储用户名和密码至Session中
session.setAttribute("username",username);
session.setAttribute("password",password);
//进行页面跳转,index
resp.getWriter().print("3秒后跳转到主页");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
resp.sendRedirect("/01StudyServlet/damin/index.html");
}else{
req.getRequestDispatcher("/01StudyServlet/login.html").forward(req,resp);
}
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
过滤器Filter
public class LoginFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
HttpServletRequest httpServletRequest = (HttpServletRequest) req;
HttpSession session = httpServletRequest.getSession();
Object user = session.getAttribute("user");
if (user != null){ //登录成功
chain.doFilter(req,resp);
}else {
httpServletRequest.getRequestDispatcher("/01StudyServlet/login.html").forward(req,resp);
}
}
public void init(FilterConfig config) throws ServletException {
}
}
Filter的配置文件(在web.xml中)
<!--filter 标签用于配置一个 Filter 过滤器-->
<filter>。
<!--给 filter 起一个别名-->
<filter-name>loginFilter</filter-name>
<!--配置 filter 的全类名-->
<filter-class>demo11login.LoginFilter</filter-class>
</filter>
<!--filter-mapping 配置 Filter 过滤器的拦截路径-->
<filter-mapping>
<!--filter-name 表示当前的拦截路径给哪个 filter 使用-->
<filter-name>loginFilter</filter-name>
<!--url-pattern 配置拦截路径 / 表示请求地址为:http://ip:port/工程路径/ 映射到 IDEA 的 web 目录
/admin/* 表示请求地址为:http://ip:port/工程路径/admin/* -->
<url-pattern>/damin/*</url-pattern>
</filter-mapping>
10.3 Filter生命周期
Filter 的生命周期包含几个方法:
- 构造器方法
- init 初始化方法:第 1,2 步,在 web 工程启动的时候执行(Filter 已经创建) 。
- doFilter 过滤方法,每次拦截到请求,就会执行 。
- destroy 销毁,停止 web 工程的时候,就会执行(停止 web 工程,也会销毁 Filter 过滤器)。
10.4 FilterConfig 类
FilterConfig 类是 Filter 过滤器的配置文件类。Tomcat 每次创建 Filter 的时候,也会同时创建一个 FilterConfig 类,这里包含了 Filter 配置文件的配置信息。
作用:获取 filter 过滤器的配置内容,
- 获取 Filter 的名称 filter-name 的内容
- 获取在 Filter 中配置的 init-param 初始化参数
- 获取 ServletContext 对象
方法
获取 Filter 的名称 filter-name 的内容
getFilterName()
获取在 Filter 中配置的 init-param 初始化参数
getInitParameter("username")
获取 ServletContext 对象
getServletContext()
配置文件
<filter>
<filter-name>loginFilter</filter-name>
<filter-class>demo11login.LoginFilter</filter-class>
<init-param>
<param-name>username</param-name>
<param-value>root</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>123456</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>loginFilter</filter-name>
<url-pattern>/damin/*</url-pattern>
</filter-mapping>
代码
public class LoginFilter implements Filter {
//省略其他方法
public void init(FilterConfig config) throws ServletException {
//获取 Filter 的名称 filter-name 的内容 getFilterName()
String filterName = config.getFilterName();
System.out.println("filterName:" + filterName);
//获取在 Filter 中配置的 init-param 初始化参数 getInitParameter("username")
System.out.println("初始化参数 username 的值是:" + config.getInitParameter("username"));
System.out.println("初始化参数 password 的值是:" + config.getInitParameter("password"));
//获取 ServletContext 对象 getServletContext()
System.out.println(config.getServletContext());
}
}
效果
10.5 FilterChain过滤器链
chain.doFilter(request, response)的作用:
- 如果有下一个Filter,则执行下一个Filter。
- 如果没有下一个Filter,则访问目标资源。
多个Filter执行的特点:
- 所有的Filter都在同一个线程中执行。
- 所有的Filter共用一个request对象。
- 执行的顺序与在配置文件中的配置顺序相同。
配置文件
<filter>
<filter-name>Filter1</filter-name>
<filter-class>demo12Filter.Filter1</filter-class>
</filter>
<filter-mapping>
<filter-name>Filter1</filter-name>
<url-pattern>/test.jsp</url-pattern>
</filter-mapping>
<filter>
<filter-name>Filter2</filter-name>
<filter-class>demo12Filter.Filter2</filter-class>
</filter>
<filter-mapping>
<filter-name>Filter2</filter-name>
<url-pattern>/test.jsp</url-pattern>
</filter-mapping>
filter1
public class Filter1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("Filter1前置代码。");
chain.doFilter(request, response);
System.out.println("Filter1后置代码。");
}
@Override
public void destroy() {
}
}
filter2
public class Filter2 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("Filter2前置代码。");
chain.doFilter(request, response);
System.out.println("Filter2后置代码。");
}
@Override
public void destroy() {
}
}
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试JSP</title>
</head>
<body>
<%
System.out.println("测试JSP代码");
%>
</body>
</html>
效果
10.6 Filter的拦截路径
精确匹配
<url-pattern>/target.jsp</url-pattern>
以上配置的路径,表示请求地址必须为:http://ip:port/工程路径/target.jsp
目录匹配
<url-pattern>/admin/*</url-pattern>
以上配置的路径,表示请求地址必须为:http://ip:port/工程路径/admin/*
后缀名匹配
<url-pattern>*.html</url-pattern>
以上配置的路径,表示请求地址必须以.html 结尾才会拦截到
<url-pattern>*.do</url-pattern>
以上配置的路径,表示请求地址必须以.do 结尾才会拦截到
<url-pattern>*.action</url-pattern>
以上配置的路径,表示请求地址必须以.action 结尾才会拦截到