文章素材来自老杜的课程
什么是Servlet
宏观地讲,Servlet 是连接 Web 服务器与服务端 Java 程序的协议,是一种通信规范。这个规范是以一套接口的形式体现的。微观地讲,Servlet 是 Servlet 接口实现类的一个实例对象,是运行在服务器上的一段 Java小程序(Server Applet)
Servlet有什么用? Servlet 的主要功能是根据客户端提交的请求,调用服务器端相关 Java 代码,完成对请求的处理与运算
创建带Servlet的WebApp
1、新建web工程 2、创建类实现Servlet接口
public class HelloServlet implements Servlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("HelloServlet");
}
public void init(ServletConfig servletConfig) throws ServletException {}
public void destroy() {}
public String getServletInfo() { return null; }
public ServletConfig getServletConfig() { return null;}
}
3、配置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>hello</servlet-name>
<servlet-class>HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/abc</url-pattern>
</servlet-mapping>
</web-app>
4、部署并修改Application Context路径 5、启动 每发送一次请求 service()方法就会执行一次
[2022-03-12 08:31:02,259] Artifact ch02-servlet:war exploded: Artifact is deployed successfully
[2022-03-12 08:31:02,259] Artifact ch02-servlet:war exploded: Deploy took 1,215 milliseconds
12-Mar-2022 20:31:10.512 淇℃伅 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory 鎶妛eb 搴旂敤绋嬪簭閮ㄧ讲鍒扮洰褰� [C:\tomcat\apache-tomcat-8.5.69\webapps\manager]
12-Mar-2022 20:31:10.605 淇℃伅 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Web搴旂敤绋嬪簭鐩綍[C:\tomcat\apache-tomcat-8.5.69\webapps\manager]鐨勯儴缃插凡鍦╗93]姣鍐呭畬鎴�
init
HelloServlet
HelloServlet
HelloServlet
HelloServlet
Servlet3.0 规范允许 Web 项目没有 web.xml 配置文件。Servlet3.0 规范中使用@WebServlet()注解来注册当前的 Servlet 类
- urlPatterns String[] 相当于<url-pattern/>的值
- value String[] 与 urlPatterns 意义相同,但此属性名可以省略。不能与 urlPatterns 属性同时使用
- name String 相当于<servlet-name/>的值
- loadOnStartup int 相当于<loadOnStartup/>,默认值为-1
- initParams WebInitParam[] 相当于标签。其类型为另一个注解 WebInitParam 数组
@WebServlet(value = {"/abc","/hello"},
name = "helloServlet",
loadOnStartup = 1,
initParams = {@WebInitParam(name = "name",value = "zs"),
@WebInitParam(name = "sex",value = "man")}
)
public class HelloServlet implements Servlet { ... }
Servlet的生命周期
Servlet 生命周期是指Servlet 对象的创建、Servlet 对象的初始化、Servlet 对象服务的执行,及最终 Servlet 对象被销毁的整个过程 Servlet 的整个生命周期过程的执行均由 Web 服务器负责管理程序员无法控制其执行流程。但我们可以获取到 Servlet 的这些生命周期时间点,并可以指定让 Servlet 做一些具体业务相关的事情
生命周期方法执行流程
1、当请求发送到 Web 容器后,Web 容器会解析请求 URL并从中分离出 Servlet 对应的URI。 2、根据分离出的 URI通过 web.xml 中配置的 URI 与 Servlet 的映射关系,找到要执行的Servlet即找到用于处理该请求的 Servlet。 3、若该 Servlet 不存在则调用该 Servlet 的无参构造器、init()方法实例化该 Servlet。然后执行 service()方法。 4、若该 Servlet 已经被创建,则直接调用 service()方法。 5、当 Web 容器被关闭或该应用被关闭,则调用执行 destroy()方法销毁 Servlet 实例。
Servlet的特点
- Servlet 是单例多线程的。
- 每个 Servlet 实例只会执行一次无参构造器与 init()方法,并且是在第一次访问时执行。
- 用户每提交一次对当前 Servlet 的请求,就会执行一次 service()方法。
- 每个 Servlet 实例只会执行一次 destroy()方法,在应用停止时执行。
- 由于 Servlet 是单例多线程的,所以为了保证其线程安全性,一般情况下是不为 Servlet类定义可修改的成员变量的。因为每个线程均可修改这个成员变量,会出现线程安全问题。
- 默认情况下,Servlet 在 Web 容器启动时是不会被实例化的
Web 容器中的两个 Map
当 Servlet 实例被创建好后被放在了哪里? 当 Servlet 实例被创建好后,会将该 Servlet 实例的引用存放到一个 Map 集合中。该 Map集合的 key 为 URI,而 value 则为 Servlet 实例的引用,即 Map<String, Servlet>。当 Web 容器从用户请求中分离出 URI 后,会首先到这个 Map 中查找是否存在其所对应的 value。若存在则直接调用其 service()方法。若不存在则需要创建该 Servlet 实例。 若请求的 Servlet 实例不存在,Web 容器又是根据什么创建这个 Servlet 实例的呢?在Web 容器的内存中,还存在一个 Map 集合。该 Map 集合的 key 为 URI,而 value 则为 web.xml中配置的与之对应的 Servlet 的全限定性类名,即 Map<String, String>。当Web容器从用户请求中分离出URI后,到第一个Map中又没有找到其所对应的Servlet实例,则会马上查找这第二个 Map,从中找到其所对应的类名,再根据反射机制,创建这个 Servlet 实例。然后再将这个创建好的 Servlet 的引用放入到第一个 Map 中。
ServletConfig
在 Servlet 接口的 init()方法中具有唯一的一个参数 ServletConfig。ServletConfig 是个接口,顾名思义,就是 Servlet 配置,即在 web.xml 中对当前 Servlet 类的配置信息。Servlet 规范将Servlet 的配置信息全部封装到了 ServletConfig 接口对象中。 在 Web 容器调用 init()方法时,Web 容器首先会将 web.xml 中当前 Servlet 类的配置信息封装为一个对象。这个对象的类型实现了 ServletConfig 接口,Web 容器会将这个对象传递给init()方法中的 ServletConfig 参数。
ServletConfig 中的方法
- String getInitParameter():获取指定名称的初始化参数值。
- Enumeration getInitParameterNames():获取当前 Servlet 所有的初始化参数名称。其返回值为枚举类型 Enumeration。
- String getServletName():获取当前 Servlet 的中指定的 Servlet 名称。
- ServletContext getServletContext():获取到当前 Servlet 的上下文对象 ServletContext。这是个非常重要的对象。
对于不同的Servlet,Tomcat会为其创建不同的ServletConfig,用于封装各自的配置信息。也就是说,一个 Servlet 就会有其对应的一个 ServletConfig 对象;有几个 Servlet,将会产生几个 ServletConfig 对象。
ServletContext
ServletContext,即 Servlet 上下文,是个接口,是 Web 应用中所有 Servlet 在 Web 容器中的运行时环境。这个运行时环境随着 Web 应用的启动而创建,随着 Web 应用的关闭而销毁。也就是说,一个 Web 应用,就一个 Servlet 运行时环境,即一个 Servlet 上下文,即一个ServletContext 对象。这个 Servlet 运行环境中不仅包含了 web.xml 文件中的配置信息,还包含了当前应用中所有Servlet 可以共享的数据。
ServletContext 中的方法
- String getInitParameter ():获 取 web.xml 文 件 的 中 指 定 名 称 的 上 下 文 参 数 值 。
- Enumeration getInitParameterNames():获取 web.xml 文件的中的所有的上下文参数名称。其返回值为枚举类型 Enumeration。
- void setAttribute(String name, Object object):在 ServletContext 的公共数据空间中,也称为域属性空间,放入数据。这些数据对于 Web应用来说,是全局性的,与整个应用的生命周期相同。
- Object getAttribute(String name):从 ServletContext 的域属性空间中获取指定名称的数据。
- void removeAttribute(String name):从 ServletContext 的域属性空间中删除指定名称的数据。
- String getRealPath(String path):获取当前 Web 应用中指定文件或目录在本地文件系统中的路径,是基于盘符的路径。
- String getContextPath():获取当前应用在 Web 容器中的名称。
- InputStream getResourceAsStream(String path): 直接获取文件输入流
一个 Web 应用就一个 ServletContext 对象。放入到 ServletContext 域属性空间中的数据对于当前应用中所有的 Servlet 是共享的。所谓上下文路径其实就是项目名称。realPath 就是本地文件系统的基于盘符的路径。
欢迎页面设置
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<welcome-file-list>
<welcome-file>index1.jsp</welcome-file>
<welcome-file>index2.jsp</welcome-file>
<welcome-file>index3.jsp</welcome-file>
</welcome-file-list>
HttpServlet
HttpServletRequest
javax.servlet.http.HttpServletRequest 是 SUN制定的 Servlet规范,是一个接口,表示请求,其父接口是 javax.servlet.ServletRequest。“HTTP 请求协议”的完整内容都被封装到 request对象中。
请求的生命周期
HttpServletRequest 实例对象是什么时候创建和销毁的呢? 当客户端浏览器将请求(字符序列)发送到服务器后,服务器会根据 HTTP 请求协议的格式对请求进行解析。同时,服务器会创建 HttpServletRequest 的实现类 RequestFacade 的对象,即请求对象。然后再调用相应的 set 方法,将解析出的数据封装到请求对象中。此时HttpServletRequest 实例就创建并初始化完毕了。也就是说,请求对象是由服务器创建。当服务器向客户端发送响应结束后,HttpServletRequest 实例对象被服务器销毁。一次请求对应一个请求对象,另外一次请求对应另外一个请求对象,与之前的请求对象没有任何关系。HttpServletRequest 实例的生命周期很短暂。
HttpServletRequest常用方法
HttpServletRequest 对于请求中所携带的参数是以 Map 的形式接收的,并且该 Map 的 key为 String,value 为 String 数组。注意,是 String 数组
- Map<String,String[]> getParameterMap():
获取包含所有请求参数及值的 Map 对象。需要注意,该 Map 的 value 为 String[],即一个参数所对应的值为一人数组。说明一个参数可以对应多个值 - Enumeration getParameterNames():
获取请求参数 Map 的所有 key,即获取所有请求参数名 name - String[] getParameterValues(String name):
根据指定的请求参数名称,获取其对应的所有值。这个方法一般用于获取复选框数据 - String getParameter(String name):
根据指定的请求参数名称,获取其对应的值。若该参数名称对应的是多个值,则该方法获取到的是第一个值。这个方法是最常用的方法
在 Request 中也存在域属性空间,用于存放有名称的数据。该数据只在当前 Request 请求中可以进行访问
- void setAttribute(String name, Object object):
在 Request 域属性空间中放入数据。其生命周期与 Request 的生命周期相同 - Object getAttribute(String name):
从 Request 的域属性空间中获取指定名称的数据 - void removeAttribute(String name):
从 Request 的域属性空间中删除指定名称的数据 - RequestDispatcher getRequestDispatcher(String path):
该方法用于创建请求转发器,而该请求转发器有一个方法 forward(),用于完成将请求对象转发给下一个资源。forward()方法的原型如下: void forward(HttpServletRequest request, HttpServletResponse response);
HttpRequest还可以获取服务端相关信息
- StringBuffer getRequestURL():
获取请求的 URL,即从请求协议开始的绝对路径。其方法返回值一般与 toString()方法连用,转为 String 类型 - String getRequestURI():
获取请求的 URI,即从项目名称开始的请求路径 - String getContextPath():
获取当前应用在 Web 容器中的名称 - String getServletPath() 与 String getPathInfo():
获取请求路径中与 web.xml 中注册 Servlet 时的相匹配的路径信息。其中 serveltPath 指的是与精确部分相匹配的路径,而 pathInfo 则是与非精确部分相匹配的路径 - String getMethod():
获取请求方式,是 GET 请求,还是 POST 请求 - String getRemoteAddr():
获取客户端浏览器的 IP,对于服务端来说,客户端就是远程端,而自己则是本地端
中文乱码问题
乱码可能出现在哪里? 1、数据“展示”过程中的乱码 2、数据“保存”过程中的乱码 3、数据“传递”过程中的乱码
数据“展示”过程中的乱码
在response获取标准输出流之后,使用标准输出流输出到浏览器上中文的时候,出现的乱码 如何解决?
response.setContentType("text/html;charset=UTF-8");
没有执行Java程序直接访问html页面出现乱码
<meta content="text/html;charset=UTF-8">
数据“保存”过程中的乱码
1、数据最终保存到数据库的时候,表中存储的数据出现乱码问题 ???? 可能是在保存之前就已经出现乱码,保存到数据库表中的数据一定是乱码 ???? 保存之前数据没有出现乱码,但是数据库本身不支持简体中文,也可能出现乱码 如何解决?
- 若保存之前就是乱码,和数据库没有关系,先解决保存之前的乱码问题
- 让数据库本身支持简体中文
数据“传递”过程中的乱码
浏览器提交数据给服务器,在传送的过程中出现中文乱码问题 如何解决?
- 第一种方案:万能方案,适合于所有的请求,包括GET请求和POST请求
将从request对象中获取到的数据,经过ISO-8859-1的编码方式进行解码还原,回归最原始的二进制码。再找一种编码方式,这种编码方式和浏览器页面的编码方式相同,进行重新编码
String name = request.getParameter("name");
byte[] bytes = name.getBytes("ISO-8859-1");
name = new String(bytes,"UTF-8");
request.setCharacterEncoding("UTF-8");
- 第三种方案:只能解决GET请求中的乱码问题
修改CATALINA_HOME/conf/server.xml文件
<Connector port=“80” protocol=“HTTP/1.1”
connectionTimeout=“20000”
redirectPort=“8443” URIEncoding=“UTF-8”/>
HttpServletResponse
Web 服务器收到一个 Http 请求后,会针对每个请求创建一个 HttpServletRequest 对象和HttpServletResponse 对 象 。若需要获取客户端提交请求的相关信息,则需要从HttpServletRequest 对象中获取;若需要向客户端发送数据,则需要通过 HttpServletResponse对象来完成。ponse 接口有一个方法 getWriter(),用于获取到一个输出流对象 PrintWriter,该输出流对象是专门用于向客户端浏览器中输出字符数据的,称为标准输出流。
转发和重定向
通过 HttpServletRequest 获取到的 RequestDispatcher 对象的 forward()方法,可以完成请求转发功能。而通过 HttpServletResponse 的 sendRedirect()方法,可以完成重定向功能。请求转发与重定向均是指的请求的跳转方式。
request.getRequestDispatcher("/b").forward(request,response);
response.sendRedirect(request.getContextPath() + "/saveSuccess.html");
转发和重定向的区别
1、转发是一次请求,重定向是两次请求 2、转发是request对象触发的,重定向是response对象触发的 3、转发的资源路径中不需要添加contextPath,重定向的资源路径需要添加contextPath 4、转发只能完成项目内部资源的跳转,重定向可以完成跨项目资源跳转 5、转发浏览器地址栏上的地址始终不变,重定向浏览器地址栏上的地址会改变,也正因为如此重定向的一个很重要作用是:防止表单重复提交 6、请求所转发到的资源中可以直接获取到请求中所携带的数据,重定向到的资源不能直接获取到用户提交请求中所携带的数据
重定向中的中文乱码问题
String name = request.getParameter("name");
name = new String(name.getBytes("ISO-8859-1"),"UTF-8");
name = URLEncoder.encode(name,"UTF-8");
response.sendRedirect(request.getContextPath() + "otherServlet?name=" + name);
String name = request.getParameter("name");
name = URLEncoder.decode(name,"UTF-8");
name = new String(name.getBytes("ISO-8859-1"),"UTF-8");
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("name="+name);
Cookie
Cookie 是 1993 年由网景公司(Netscape)前雇员发明的一种进行网络会话状态跟踪的技术。会话是由一组请求与响应组成,是围绕着一件相关事情所进行的请求与响应。所以这些请求与响应之间一定是需要有数据传递的,即是需要进行会话状态跟踪的。然而 HTTP 协议是一种无状态协议,在不同的请求间是无法进行数据传递的。此时就需要一种可以进行请求间数据传递的会话跟踪技术,而 Cookie 就是一种这样的技术。 Cookie 是由服务器生成,保存在客户端的一种信息载体。这个载体中存放着用户访问该站点的会话状态信息。只要 Cookie 没有被清空,或都 Cookie 没有失效,那么,保存在其中的会话状态就有效。用户在提交第一次请求后,由服务器生成 Cookie,并将其封装到响应头中,以响应的形式发送给客户端。客户端接收到这个响应后,将 Cookie 保存到客户端。当客户端再次发送同类请求后,在请求中会携带保存在客户端的 Cookie 数据,发送到服务端,由服务器对会话进行跟踪。
创建Cookie
在java中Cookie被当做类来处理,使用new运算符可以创建Cookie对象,而且Cookie由两部分组成, 分别是Cookie的name和value,name和value都是字符串类型String。
Cookie cookie = new Cookie(String cookieName,String cookieValue);
默认情况下,服务器发送Cookie给浏览器之后,浏览器将Cookie保存在缓存当中,只要不关闭浏览器,Cookie永远存在,并且有效。当浏览器关闭之后,缓存中的Cookie被清除。
Cookie绑定路径
默认绑定路径
访问路径由资源路径与资源名称构成。默认情况下,Cookie 与访问路径中的资源路径绑定。只要用户发出带有绑定资源路径的请求,则在请求头部,将自动会携带与之绑定的 Cookie数据。 假设通过/pro-servlet/test/createAndSendCookieToBrowser请求服务器,服务器生成Cookie,并将Cookie发送给浏览器客户端,这个浏览器中的Cookie会默认和“test/”这个路径绑定在一起。也就是说,以后只要发送“test/”请求,Cookie一定会提交给服务器。
设置绑定路径
Cookie cookie1 = new Cookie("username","zhangshang");
Cookie cookie2 = new Cookie("password","1234567890");
cookie1.setPath(request.getContextPath() + "/abc");
cookie2.setPath(request.getContextPath() + "/abc");
将 Cookie 保存到硬盘
默认情况下,没有设置Cookie的有效时长,该Cookie被默认保存在浏览器的缓存当中,只要浏览器不关闭Cookie存在,只要关闭浏览器Cookie消失,我们可以通过设置Cookie的有效时长,以保证Cookie保存在硬盘文件当中。但是这个有效时长必须是>0的。换句话说,只要设置Cookie的有效时长大于0,则该Cookie会被保存在客户端硬盘文件当中。有效时长过去之后,则硬盘文件当中的Cookie失效。
- cookie有效时长 = 0 直接被删除
- cookie有效时长 < 0 不会被存储
- cookie有效时长 > 0 存储在硬盘文件当中
cookie.setMaxAge(60 * 60);
服务端读取请求中的 Cookie
Cookie[] cookies = request.getCookies();
if(cookies != null){
for(Cookie cookie : cookies){
String cookieName = cookie.getName();
String cookieValue = cookie.getValue();
System.out.println(cookieName + "=" + cookieValue);
}
}
禁用Cookie
浏览器是可以禁用Cookie 表示服务器发送过来的Cookie,我浏览器不要,不接收。 服务器还是会发送Cookie的,只不过浏览器不再接收。
HttpSession
Session即会话,是 Web 开发中的一种会话状态跟踪技术。 Cookie也是一种会话跟踪技术。不同的是 Cookie 是将会话状态保存在了客户端,而 Session 则是将会话状态保存在了服务器端。在 JavaWeb开发中,Session 是以 javax.servlet.http.HttpSession 的接口对象的形式出现的。
Session 对象的创建
若要对 Session 进行操作,则可以通过 HttpServletRequest 的 getSession()方法获取。该方法具有两个重载的方法。
- HttpSession getSession(boolean create):
该方法用于创建 Session。若参数 create 为 true,则表示若当前没有 Session,则新建 Session,若当前存在 Session 则使用当前的 Session。若参数 create 为 false 表示若当前没有 Session,则直接返回 null。 - HttpSession getSession():
该方法用于创建 Session。相当于 getSession(true),即没有 Session 则创建新的 Session。
HttpSession session = request.getSession();
HttpSession session = request.getSession(true);
HttpSession session = request.getSession(false);
何时使用 getSession(true),何时使用 getSession(false)呢? 若要向 Session 中存放数据, 则使用 getSession(true)。若要从 Session 中获取数据,则一般使用 getSession(false)。
对 Session 域属性空间的操作
Session 是一个专门用于存放数据的集合,我们一般称这个用于存放数据的内存空间为域属性空间,简称域。HttpSession 中具有三个方法,是专门用于对该域属性空间中数据进行写、读操作的。
- void setAttribute(String name, Object value):
该方法用于向 Session 的域属性空间中放入指定名称、指定值的域属性。 - Object getAttribute(String name):
该方法用于从 Session 的域属性空间中读取指定名称为域属性值。 - void removeAttribute(String name):
该方法用于从 Session 的域属性空间中删除指定名称的域属性。
Session对象的销毁
浏览器关闭之后,服务器不会销毁session对象。web系统中引入了session超时的概念。当很长一段时间(这个时间可以配置)没有用户再访问该session对象,此时session对象超时,web服务器自动回收session对象。
<session-config>
<session-timeout>120</session-timeout>
</session-config>
session也可以手动销毁
HttpSession session = request.getSession(false);
if(session != null){
session.invalidate()
}
Session 的工作原理
在服务器中系统会为每个会话维护一个 Session。不同的会话,对应不同的 Session 1、打开浏览器,在浏览器上发送首次请求 2、服务器会创建一个HttpSession对象,该对象代表一次会话 3、 同时生成HttpSession对象对应的Cookie对象,并且Cookie对象的name是JSESSIONID,Cookie的value是32位长度的字符串 4、服务器将Cookie的value和HttpSession对象绑定到session列表中 5、服务器将Cookie完整发送给浏览器客户端 6、浏览器客户端将Cookie保存到缓存中 7、只要浏览器不关闭,Cookie不会消失 8、当再次发送请求的时候,会自动提交缓存当中的Cookie 9、服务器接收到Cookie,验证该Cookie的name确实是:JSESSIONID,然后获取该Cookie的value 10、通过Cookie的value去session列表中检索对应的HttpSession对象。 浏览器禁用Cookie会出现什么问题?
浏览器禁用Cookie,则浏览器缓存中不再保存Cookie 导致在同一个会话中,无法获取到对应的会话对象 禁用Cookie之后,每一次获取的会话对象都是新的
怎么解决? 浏览器禁用Cookie之后,若还想拿到对应的Session对象,必须使用URL重写机制,怎么重写URL:http://localhost/prj-servlet-21/user/accessMySelfSession;jsessionid=D3E9985BC5FD4BD05018BF2966863E94
重写URL会给编程带来难度/复杂度,所以一般的web站点是不建议禁用Cookie的。建议浏览器开启Cookie
ServletContext、HttpSession、HttpServletRequest接口的对比
- ServletContext application; 是应用范围
- HttpSession session; 是会话范围
- HttpServletRequest request; 是请求范围
三个范围的排序:application > session > request
application完成跨会话共享数据、 session完成跨请求共享数据,但是这些请求必须在同一个会话当中、 request完成跨Servlet共享数据,但是这些Servlet必须在同一个请求当中(转发)
Listener监听器
Servlet 规范中定义了八个监听器接口,它们要监听的对象分别是 request、session、servletContext 对象,触发监听器的事件是这三个对象的创建与销毁,它们的域属性空间中属性的添加、删除、修改,及 session 的钝化与活化操作。可以在这些动作前后增加处理实现监听如在线人数统计
- 监听声明周期
ServletRequestListener HttpSessionListener ServletContextListener - 监听值的变化
ServletRequestAttributeListener HttpSessionAttributeListener ServletContextAttributeListener - 针对session中的对象
HttpSessionBindingListener HttpSessionActivationListener
ServletRequestListener
该监听器用于完成对 Request 对象的创建及销毁的监听,即当 Request 对象被创建或被销毁时,会触发该监听器中相应方法的执行。
定义监听器
public class MyServletRequestListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
System.out.println("request对象被销毁了");
}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
System.out.println("request对象被创建了");
}
}
注册监听器
<listener>
<listener-class>com.why.listner.MyServletRequestListener</listener-class>
</listener>
使用注解的方式注册监听器(Servlet3.0新特性)
@WebListener
public class MyServletRequestListener implements ServletRequestListener {
}
ServletRequestAttributeListener
该监听器用于完成对 request 域属性空间中属性的添加、修改、删除操作的监听。
定义监听器
public class MyServletRequestAttributeListener implements ServletRequestAttributeListener {
@Override
public void attributeAdded(ServletRequestAttributeEvent servletRequestAttributeEvent) {
System.out.println("request作用域中添加了一个属性");
System.out.println("属性名为:" + servletRequestAttributeEvent.getName());
System.out.println("属性值为:" + servletRequestAttributeEvent.getValue());
}
@Override
public void attributeRemoved(ServletRequestAttributeEvent servletRequestAttributeEvent) {
System.out.println("request作用域中删除了一个属性");
System.out.println("属性名为:" + servletRequestAttributeEvent.getName());
System.out.println("属性值为:" + servletRequestAttributeEvent.getValue());
}
@Override
public void attributeReplaced(ServletRequestAttributeEvent servletRequestAttributeEvent) {
System.out.println("request作用域中修改了一个属性");
System.out.println("属性名为:" + servletRequestAttributeEvent.getName());
System.out.println("属性值为:" + servletRequestAttributeEvent.getValue());
}
}
注册监听器
<listener>
<listener-class>com.why.listner.MyServletRequestAttributeListener</listener-class>
</listener>
使用注解的方式注册监听器(Servlet3.0新特性)
@WebListener
public class MyServletRequestAttributeListener implements ServletRequestAttributeListener {
}
JSP页面
<body>
<%
request.setAttribute("name","张三");
request.setAttribute("name","李四");
request.removeAttribute("name");
%>
</body>
下面的例子就不写xml注册监听器了
HttpSessionListener
该监听器用于完成对 Session 对象的创建及销毁的监听。
@WebListener
public class MyHttpSessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
System.out.println("session对象被创建了");
}
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
System.out.println("session对象被销毁了");
}
}
HttpSessionAttributeListener
该监听器用于完成对 session 域属性空间中属性的添加、修改、删除操作的监听。
@WebListener
public class MyHttpSessionAttributeListener implements HttpSessionAttributeListener {
@Override
public void attributeAdded(HttpSessionBindingEvent httpSessionBindingEvent) {
System.out.println("session作用域中添加了一个属性" + httpSessionBindingEvent.getName() + httpSessionBindingEvent.getValue());
}
@Override
public void attributeRemoved(HttpSessionBindingEvent httpSessionBindingEvent) {
System.out.println("session作用域中删除了一个属性");
}
@Override
public void attributeReplaced(HttpSessionBindingEvent httpSessionBindingEvent) {
System.out.println("session作用域中修改了一个属性");
}
}
ServletContextListener
该监听器用于完成对 ServletContext 对象的创建及销毁的监听。不过需要注意,由于ServletContext 在一个应用中只有一个,且是在服务器启动时创建。另外,ServletConetxt 的生命周期与整个应用的相同,所以当项目重新部署或 Tomcat 正常关闭时,可以销毁 ServletContext。
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("ServletContext被创建了");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("ServletContext被销毁了了");
}
}
ServletContextAttributeListener
该监听器用于完成对 servletContext 域属性空间中属性的添加、修改、删除操作的监听。
@WebListener
public class MyServletContextAttributeListener implements ServletContextAttributeListener {
@Override
public void attributeAdded(ServletContextAttributeEvent servletContextAttributeEvent) {
System.out.println("servletContext作用域中添加了一个属性" + servletContextAttributeEvent.getName() + servletContextAttributeEvent.getValue());
}
@Override
public void attributeRemoved(ServletContextAttributeEvent servletContextAttributeEvent) {
System.out.println("servletContext作用域中删除了一个属性" + servletContextAttributeEvent.getName() + servletContextAttributeEvent.getValue());
}
@Override
public void attributeReplaced(ServletContextAttributeEvent servletContextAttributeEvent) {
System.out.println("servletContext作用域中修改了一个属性" + servletContextAttributeEvent.getName() + servletContextAttributeEvent.getValue());
}
}
HttpSessionBindingListener
该监听器用于监听指定类型对象与 Session 的绑定与解绑,即该类型对象被放入到Session 域中,或从 Session 域中删除该类型对象,均会引发该监听器中相应方法的执行。 它与 HttpSessionAttributeListener 的不同之处是,该监听器监听的是指定类型的对象在Session 域中的操作,而 HttpSessionAttributeListener 监听的是 Session 域属性空间的变化,无论是什么类型的对象。 该监听器是由实体类实现 该监听器无需在 web.xml 中注册
public class Student implements HttpSessionBindingListener {
private String name;
private int age;
@Override
public void valueBound(HttpSessionBindingEvent httpSessionBindingEvent) {
System.out.println("Student对象被置入到Session中");
}
@Override
public void valueUnbound(HttpSessionBindingEvent httpSessionBindingEvent) {
System.out.println("Student对象从Session中删除");
}
}
JSP页面
<body>
<%
Student student = new Student();
session.setAttribute("student",student);
session.removeAttribute("student");
%>
</body>
HttpSessionActivationListener
该监听器用于监听在 Session 中存放的指定类型对象的钝化与活化。 钝化是指将内存中的数据写入到硬盘中,而活化是指将硬盘中的数据恢复到内存。 对于监听 Session 中对象数据的钝化与活化,需要注意以下几点:
- 实体类除了要实现 HttpSessionActivationListener 接口外,还需要实现 Serializable 接口。
- 钝化指的是 Session 中对象数据的钝化,并非是 Session 的钝化。所以 Session 中有几个可以钝化的对象,就会发生几次钝化。
- HttpSessionActivationListener 监听器是不需要在 web.xml 中注册的。
public class Student01 implements HttpSessionActivationListener, Serializable {
private String name;
private int age;
@Override
public void sessionWillPassivate(HttpSessionEvent httpSessionEvent) {
System.out.println("session已被钝化");
}
@Override
public void sessionDidActivate(HttpSessionEvent httpSessionEvent) {
System.out.println("session已被活化");
}
}
Filter过滤器
Filter 是 Servlet 规范的三大组件之一,用于Servlet之外对Request和Response进行修改,它主要用于对用户请求进行预处理也可以对HttpServletResponse进行后处理 多个过滤器
Filter 的生命周期
Filter 的生命周期与 Servlet 的生命周期类似,其主要生命周期阶段有四个:Filter 对象的创建、Filter 对象的初始化、Filter 执行 doFilter()方法,及最终 Filter 对象被销毁。Filter 的整个生命周期过程的执行,均由 Web 服务器负责管理。即 Filter 从创建到销毁的整个过程中方法的调用,都是由 Web 服务器负责调用执行,程序员无法控制其执行流程。
创建Filter过滤器
定义过滤器
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("MyFilter过滤器被初始化");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("过滤用户请求");
chain.doFilter(request,response);
System.out.println("过滤服务器返回的响应");
}
@Override
public void destroy() {
System.out.println("MyFilter过滤器被销毁了");
}
}
注册过滤器
<filter>
<filter-name>one-filter</filter-name>
<filter-class>com.why.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>one-filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
在Servlet3.0以后可以通过注解的方式注册过滤器
@WebFilter("/*")
public class MyFilter implements Filter {
// ...
}
FilterConfig
与 ServletConfig 类似,FilterConfig 指的是当前 Filter 在 web.xml 中的配置信息。同样是一个 Filter 对象一个 FilterConfig 对象,多个 Filter 对象会有多个 FilterConfig 对象。
FilterConfig 中的方法
- String getFilterName();
- String getInitParameter(String name);
- Enumeration getInitParameterNames();
- ServletContext getServletContext();
Filter 的<url-pattern/>
全路径匹配不支持/
对于 Servlet 中的的全路径匹配设置方式,可以设置为/,也可以设置为/。但对于 Filter 的<url-pattern/>的全路径匹配设置,只支持/,而不支持/。
Filter 可以不指定<url-pattern/>
<filter-mapping/>中可以不指定<url-pattern/>,但需要使用<servlet-name/>标签。该标签用来指定该 Filter 专门过滤指定 Servlet 的请求。即相当于当前 Filter 的的值与指定 Servlet 的的值相同。
<filter>
<filter-name>one-filter</filter-name>
<filter-class>com.why.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>one-filter</filter-name>
<servlet-name>myServlet</servlet-name>
</filter-mapping>
<dispatcher/>标签
在<filter-mapping/>中还有一个子标签<dispatcher/>,用于设置过滤器所过滤的请求类型。 其有四种取值:REQUEST、FORWARD、INCLUDE、ERROR。
- REQUEST :默认值,只要请求不是由 RequestDispatcher 的 forward()方法或 include()方法转发的,那么该 Filter均会被拦截,即使是向错误页面的跳转请求,同样会被拦截。
- FOEWARD :当前 Filter 只会拦截由RequestDispatcher 的 forward()方法所转发的请求
- INCLUDE :当前 Filter 只会拦截由 RequestDispatcher 的 include()方法所转发的请求。
- ERROR: 当前过滤器 Filter 只会拦截转向错误页面的请求
该标签对应@WebFilter注解中的dispatcherTypes 属性
@WebFilter(value = {"/s01","/s02"},dispatcherTypes = DispatcherType.REQUEST)
一个 Map 与一个数组
一个Map
像存放 Servlet 信息的两个 Map 一样,在服务器中同样存在用于存放 Filter 相关信息的Map。这个 Map 的 key 是 Filter 的<url-pattern/>或 <servlet-name/>。Map 的 value 为该 Filter 的引用。在应用被启动时,服务器会自动将所有的 Filter 实例创建,并将它们的引用放入到相应 Map 的 value 中。
一个数组
在服务器中,对于每一个请求,还存在着一个数组,用于存放满足当前请求的所有 Filter及最终的目标资源。当请求到达服务器后,服务器会解析出 URI,然后会先从 Filter 的 Map 中查找所有与该请求匹配的 Filter,每找到一个就将其引用存放到数组中,然后继承查找。直到将所有匹配的 Filter 全部找到并添加到数组中。
利用过滤器统一解决字符编码问题
要从根本上解决这个乱码问题,我们可以在参数存放到 ParameterMap 之前,将字符序列按照其原有的中文编码(UTF-8)进行解码,正确解码后的字符再存放到 ParameterMap中。这样 Servlet 再从 ParameterMap 中读取参数,就不会出现乱码了。
向 ParameterMap 中存放数据是由服务器自动完成的 如何解决?
自定义一个HttpServletRequest 类型,该类型是 HttpServletRequest 的一个装饰者。让这个装饰者重写HttpServletRequest 中请求参数相关方法。也就是说,将来整个应用中所有的请求对象将使用我们自定义的这个请求的装饰者。这个替换工作可以通过过滤器完成,即所有请求到达应用后,首先经过这个过滤器,将HttpServletRequest 请求替换为装饰者。
public class MyRequest extends HttpServletRequestWrapper {
private HttpServletRequest request;
public MyRequest(HttpServletRequest request) {
super(request);
}
@Override
public Map<String, String[]> getParameterMap() {
Map<String,String[]> originalMap = super.getParameterMap();
Map<String,String[]> newMap = new HashMap<>();
for(String key : originalMap.keySet()){
String[] values = originalMap.get(key);
for (int i = 0; i < values.length; i++) {
byte[] bytes = new byte[0];
try {
bytes = values[i].getBytes("ISO-8859-1");
values[i] = new String(bytes,"UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
newMap.put(key,values);
}
return newMap;
}
@Override
public String[] getParameterValues(String name) {
return this.getParameterMap().get(name);
}
@Override
public String getParameter(String name) {
return this.getParameterValues(name)[0];
}
@Override
public Enumeration<String> getParameterNames() {
Set<String> keySey = this.getParameterMap().keySet();
Vector<String> vector = new Vector<>(keySey);
return vector.elements();
}
}
定义过滤器CharacterEncodingFilter
@WebFilter(”\/*“)
public class MyFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {}
public void destroy() {}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
MyRequest myRequest = new MyRequest(req);
response.setContentType("html/text,charset=UTF-8");
chain.doFilter(myRequest,response);
}
}
过滤器还可以用来做访问权限过滤
|