WebDay15 EL&&JSTL&&Filter&&Listener

EL&&JSTL&&Filter&&Listener

  • 一 EL
    • 1.1 概述
    • 1.2 使用
      • 1.2.1 获取(域中的)值
      • 1.2.2EL表达式的注意事项
      • 1.2.3 执行运算
      • 1.2.4 隐式对象
      • 1.2.5 了解
    • 1.3 JavaBean
  • 二 JSTL
    • 2.1 概述
    • 2.2 Core标签使用
      • 2.2.1 使用要求
      • 2.2.2 核心标签库
  • 三 Servlet规范中的过滤器-Filter
    • 3.1JavaWeb的三大组件
    • 3.2概述
    • 3.3快速入门
      • 3.31xml配置
      • 3.32注解配置
    • 3.4工作原理
      • 3.41生命周期
      • 3.42 拦截路径
      • 3.43 拦截方式
      • 3.44过滤器链
    • 3.5Filter案例
      • 3.5.1统一网站编码
      • 3.5.2非法字符拦截
  • 四 Servlet规范中的监听器-Listener
    • 4.1概述
    • 4.2快速入门
    • 4.3Servlet规范中的8个监听器简介
    • 4.4案例:模拟spring框架
    • 4.5 案例:统计在线人数
  • 五总结

一 EL

1.1 概述

EL表达式,全称是Expression Language。意为表达式语言。它是Servlet规范中的一部分,是JSP2.0规范加入的内容。其作用是用于在JSP页面中获取数据,从而让我们的JSP脱离java代码块和JSP表达式。

lambda表达式
xml(xpath路径表达式) 
正则表达式  

表达式: 以简短的符号表示复杂的内容

作用:作用:在 JSP 页面中获取数据。替换和简化jsp页面中Java代码的编写
语法${表达式内容}

1.2 使用

1.2.1 获取(域中的)值

EL表达式主要简化从域对象(4个域)中获取数据

语法

* 标准(了解)
	1. ${pageScope.键名} 
			从page域中获取指定键名对应的值

	2. ${requestScope.键名} 
			从request域中获取指定键名对应的值

	3. ${sessionScope.键名} 
			从session域中获取指定键名对应的值

	4. ${applicationScope.键名} 
			从servletContext域中获取指定键名对应的值
		
* 简化(掌握)
	${键名}
		特点:默认从最小域开始找,找到后直接显示,不在继续寻找
		小结:要求四个域键名唯一
	

创建两个实体类,User和Address**

/**
 * 用户的实体类
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
public class User implements Serializable{

	private String name = "黑马程序员";
	private int age = 18;
	private Address address = new Address();
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public Address getAddress() {
		return address;
	}
	public void setAddress(Address address) {
		this.address = address;
	}	
}
/**
 * 地址的实体类
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
public class Address implements Serializable {

	private String province = "北京";
	private String city = "昌平区";
	public String getProvince() {
		return province;
	}
	public void setProvince(String province) {
		this.province = province;
	}
	public String getCity() {
		return city;
	}
	public void setCity(String city) {
		this.city = city;
	}
}
1. 获取字符串
		${键名}
		
2. 获取对象(User)
		${键名.属性名}

3. 获取List(Array)集合
		${键名[索引]}

4. 获取Map集合
		${键名.key}
		${键名["key"]}
		
5. 补充
	el不会出现null和索引角标越界问题
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>demo1</title>
</head>
<body>
	<%--EL表达式概念:
				它是Expression Language的缩写。它是一种替换jsp表达式的语言。
			EL表达式的语法:
				${表达式}
				表达式的特点:有明确的返回值。
				EL表达式就是把内容输出到页面上
			EL表达式的注意事项:
				1.EL表达式没有空指针异常
				2.EL表达式没有数组下标越界
				3.EL表达式没有字符串拼接
			EL表达式的数据获取:
				它只能在四大域对象中获取数据,不在四大域对象中的数据它取不到。
				它的获取方式就是findAttribute(String name)
<h3>el表达式基本语法</h3>
 <br/>-----------获取对象数据---------------------<br/>
		 <% //1.把用户信息存入域中
		 	User user = new User();
		 	pageContext.setAttribute("u",user);
		  %>
		  ${u}===============输出的是内存地址<%--就相当于调用此行代码<%=pageContext.findAttribute("u")%> --%><br/>
		  ${u.name}<%--就相当于调用此行代码<% User user = (User) pageContext.findAttribute("u");out.print(user.getName());%> --%><br/>
		  ${u.age}
		 <br/>-----------获取关联对象数据------------------<br/>
		 ${u.address}==========输出的address对象的地址<br/>
		 ${u.address.province}${u.address.city}<br/>
		 ${u["address"]['province']}
		 <br/>-----------获取数组数据---------------------<br/>
		 <% String[] strs = new String[]{"He","llo","Expression","Language"}; 
		 	pageContext.setAttribute("strs", strs);
		 %>
		 ${strs[0]}==========取的数组中下标为0的元素<br/>
		 ${strs[3]}
		 ${strs[5]}===========如果超过了数组的下标,则什么都不显示<br/>
		 ${strs["2"]}=========会自动为我们转换成下标<br/>
		 ${strs['1']}
		 <br/>-----------获取List集合数据-----------------<br/>
		 <% List<String> list = new ArrayList<String>();
		 	list.add("AAA");
		 	list.add("BBB");
		 	list.add("CCC");
		 	list.add("DDD");
		 	pageContext.setAttribute("list", list);
		  %>
		 ${list}<br/>
		 ${list[0] }<br/>
		 ${list[3] }<br/>	 
		 <br/>-----------获取Map集合数据------------------<br/>
		 <% Map<String,User> map = new HashMap<String,User>();
		 	map.put("aaa",new User());
		 	pageContext.setAttribute("map", map);
		  %>
		  ${map}<br/>
		  ${map.aaa}<%--获取map的value,是通过get(Key) --%><br/>
		  ${map.aaa.name}${map.aaa.age}<br/>
		  ${map["aaa"].name }
	</body>
</html>

WebDay15 EL&&JSTL&&Filter&&Listener_第1张图片

1.2.2EL表达式的注意事项

在使用EL表达式时,它帮我们做了一些处理,使我们在使用时可以避免一些错误。它没有空指针异常,没有数组下标越界,没有字符串拼接。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>EL表达式的注意事项</title>
  </head>
  <body>
    <%--EL表达式的三个没有--%>
    第一个:没有空指针异常<br/>
    <% String str = null;
       request.setAttribute("testNull",str);
    %>
    ${testNull}
    <hr/>
    第二个:没有数组下标越界<br/>
    <% String[] strs = new String[]{"a","b","c"};
       request.setAttribute("strs",strs);
    %>
    取第一个元素:${strs[0]}
    取第六个元素:${strs[5]}
    <hr/>
    第三个:没有字符串拼接<br/>
    <%--${strs[0]+strs[1]}--%>
    ${strs[0]}+${strs[1]}
  </body>
</html>

1.2.3 执行运算

WebDay15 EL&&JSTL&&Filter&&Listener_第2张图片
但是有两个特殊的运算符,使用方式的代码如下:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ page import="com.itheima.domain.User" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
	<head>
		<title>EL两个特殊的运算符</title>
	</head>
	<body>
		<%--empty运算符:
			它会判断:对象是否为null,字符串是否为空字符串,集合中元素是否是0--%>
		<% String str = null;
		  String str1 = "";
		  List<String> slist = new ArrayList<String>();
		  pageContext.setAttribute("str", str);
		  pageContext.setAttribute("str1", str1);
		  pageContext.setAttribute("slist", slist);
		%>
		${empty str}============当对象为null返回true<br/>
		${empty str1 }==========当字符串为空字符串是返回true(注意:它不会调用trim()方法)<br>
		${empty slist}==========当集合中的元素是0个时,是true
		<hr/>
		<%--三元运算符 
			 条件?:--%>
		<% request.setAttribute("gender", "female"); %>
		<input type="radio" name="gender" value="male" ${gender eq "male"?"checked":""} ><input type="radio" name="gender" value="female" ${gender eq "female"?"checked":""}></body>
</html>

WebDay15 EL&&JSTL&&Filter&&Listener_第3张图片

1.2.4 隐式对象

隐式对象介绍

EL表达式也为我们提供隐式对象,可以让我们不声明直接来使用,十一个对象见下表,需要注意的是,它和JSP的隐式对象不是一回事:

	1. pageContext
		就是jsp九大内置对象之一,这哥们可以获得其他八个内置对象
	2. cookie
		可以获取浏览器指定cookie名称的值
EL中的隐式对象 类型 对应JSP隐式对象 备注
PageContext Javax.serlvet.jsp.PageContext PageContext 完全一样
ApplicationScope Java.util.Map 没有 应用层范围
SessionScope Java.util.Map 没有 会话范围
RequestScope Java.util.Map 没有 请求范围
PageScope Java.util.Map 没有 页面层范围
Header Java.util.Map 没有 请求消息头key,值是value(一个)
HeaderValues Java.util.Map 没有 请求消息头key,值是数组(一个头多个值)
Param Java.util.Map 没有 请求参数key,值是value(一个)
ParamValues Java.util.Map 没有 请求参数key,值是数组(一个名称多个值)
InitParam Java.util.Map 没有 全局参数,key是参数名称,value是参数值
Cookie Java.util.Map 没有 Key是cookie的名称,value是cookie对象
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>demo4</title>
</head>
<body>
<h3>el隐式对象..</h3>
${pageContext.request.contextPath}  动态获取:项目名称(虚拟路径) <br>

${cookie.JSESSIONID.value} 获取指定cookie名称的值... <br>
</body>
</html>

1.2.5 了解

* jsp默认支持el表达式
		servlet2.3规范中,默认不支持el表达式

* 如果要忽略el表达式
	1)忽略当前jsp页面中所有的el表达式
		设置jsp中page指令中:isELIgnored="true" 属性
	2)忽略单个el表达式
		\${表达式}
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
    <title>demo4</title>
</head>
<body>
<h3>el隐式对象..</h3>
${pageContext.request.contextPath}  动态获取:项目名称(虚拟路径) <br>

\${cookie.JSESSIONID.value} 获取指定cookie名称的值... <br>
</body>
</html>

1.3 JavaBean

所以的javaBean指的是符合特定规范的java标准类

使用规范

  1. 所有字段(成员变量)为private (最好用引用类型,比如int -> Integer)
  2. 提供public无参构造方法
  3. 提供getter、setter的方法
  4. 实现serializable接口

例如:下面User类,有四个字段(成员变量),有无参构造方法,有一个属性(username)

public class User implements Serializable {

    private String username;

    private Integer age;

    private String gender;

    public User() {
    }



    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

}

二 JSTL

2.1 概述

Jsp 标准标签库(Jsp Standard Tag Library),是由Apache组织提供的开源的jsp标签库

作用:替换和简化jsp页面中java代码的编写
一般要配合EL表达式一起使用,来实现在jsp中不出现java代码段。

问题: jsp过度使用,html和java代码混杂,不利于阅读和长期维护

书写: html + java -> html标签

解决: 标签库(用标签代替java代码书写)

写起来像html,本质还是servlet

JSTL标准标签库有5个子库,但随着发展,目前常使用的是它的核心库(Core)

标签库 标签库的URI 前缀
Core http://java.sun.com/jsp/jstl/core c
国际化(几乎不用) http://java.sun.com/jsp/jstl/fmt fmt
SQL(过时) http://java.sun.com/jsp/jstl/sql sql
XML(过时) http://java.sun.com/jsp/jstl/xml x
Functions(几乎不用) http://java.sun.com/jsp/jstl/functions fn

2.2 Core标签使用

2.2.1 使用要求

1.要想使用JSTL标签库,在javaweb工程中需要导入坐标。首先是在工程的WEB-INF目录中创建一个lib目录,接下来把jstl的jar拷贝到lib目录中,最后在jar包上点击右键,然后选择【Add as Libary】添加。如下图所示:

WebDay15 EL&&JSTL&&Filter&&Listener_第4张图片
2.当前jsp页面tablib指令引入
WebDay15 EL&&JSTL&&Filter&&Listener_第5张图片

2.2.2 核心标签库

WebDay15 EL&&JSTL&&Filter&&Listener_第6张图片
① c:if标签

* 相当于,java中 if(){}
	语法
		<c:if test="boolean值">c:if>
			true:显示标签体内容
			false:隐藏标签体内容
		通常与el表达式一起使用
	注意:此标签没有else功能,如果想实现else效果,请让条件取反
<%@ page import="cn.itcast.domain.User" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>demo1</title>
</head>
<body>
<%
    User user = new User();
    user.setUsername("jack");
    request.setAttribute("user", user);
%>

<c:if test="${empty user}">
    你好,请登录...
</c:if>

<c:if test="${not empty user}">
    你好,${user.username}
</c:if>

 <c:choose>
    	<c:when test="${pageScope.score eq 'A' }">
    		AAA
    	</c:when>
    	<c:when test="${pageScope.score eq 'B' }">BBB
    	</c:when>
    	<c:when test="${pageScope.score eq 'C' }">CCC
    	</c:when>
    	<c:when test="${pageScope.score eq 'D' }">DDD
    	</c:when>
    	<c:otherwise>其他</c:otherwise>
    </c:choose>
</body>
</html>

② c:forEach标签

* 相当于java中的for循环
	1)普通for
		for(int i=1; i<=5; i++){
            i
		}
		<c:forEach begin="1" end="5" step="1" var="i">
			${i}
		c:forEach>
			begin="1" 起始值(包含)
			end="5"   结束值(包含)
			step="1"  步长为1
			var="i"   当前输出临时变量
	2)增强for
		for(User user : list){
            user
		}
		<c:forEach items="${list}" var="user" varStatus="vs">
			${user}
		c:forEach>
			items="list" 集合
			var="user"   当前输出的临时变量
			varStatus="vs" 变量状态
				index 当前索引 从0开始
				count 计数器   从1开始
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.Collections" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%--
    1. prefix : 前缀 (区分不同的库)
    2. uri : 库的标识
        core : java核心代码  -> 标签
        xml : xml语法  -> 标签
            <c:if>
            <x:if>
--%>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <!--
        使用jstl标签库
            1. web-inf/lib 导入jstl两个库
            2. 指令 taglib

        学习两个标签
            1. c:if
                a. 必要属性 test= el表达式
                b. test为true,才会执行标签体的内容

            2. c:foreach
                a. 普通for循环
                b. 增强for循环
    -->
    <div>
        <%
            int n = 10;
            request.setAttribute("n",n);
        %>
        <%
            if(n > 5){
                out.print("呵呵");
            }
        %>
        <c:if test="${n > 6}">
            嘻嘻
        </c:if>
    </div>
        <hr>
        <div>
            <%--
              2. c:foreach
                a. 普通for循环
                    var : variable 被循环赋值的变量
                        (会被存进pageContext域对象中,所以用el表达式取)
                    begin: 起始值
                    end : 结束值
                    step : 步长(默认为1)

                b. 增强for循环
                    var : 同上
                    items : 被遍历的集合或数组
                    varStatus : 记录当前的循环的状态
                        index : 索引
                        count : 计数
            --%>
            <%
                for (int i = 0; i <= 9; i+=2) {
                    out.print(i + " ");
                }
            %>
                <br>
            <c:forEach var="i" begin="0" end="9" step="2">
                ${i}
            </c:forEach>
                <br>
            <%
                ArrayList<String> list = new ArrayList<>();
                Collections.addAll(list,"张三","李四","王五");

                request.setAttribute("list",list);

                for (String element : list) {
                    out.print(element + " ");
                }
            %>
                <br>
            <c:forEach var="element" items="${list}" varStatus="status">
                ${status.index}, ${status.count}, ${element} <br>
            </c:forEach>
        </div>
</body>
</html>

三 c:out输出和c:set往域中设置值

<%-- 向域对象中设置值,默认pageScope --%>

<c:set var="username" value="abc"/>

<%-- 向指定的域对象中设置值--%>

<c:set var="username" value="bcd" scope="application"/>

<%-- 字符串拼接--%>

<c:set var="username" value="${'123'.concat('456')}" scope="request"/>

<%-- 获取值 --%>

<br><br><br><br><br>

username:${username} <br>

pageScope.username:${pageScope.username} <br>

requestScope.username:${requestScope.username} <br>

sessionScope.username:${sessionScope.username} <br>

applicationScope.username:${applicationScope.username} <br>
<%-- 向域对象中设置值,默认pageScope --%>

<c:set var="username" value="abc"/>

<%-- 向指定的域对象中设置值--%>

<c:set var="username" value="bcd" scope="application"/>

<%-- 字符串拼接--%>

<c:set var="username" value="${'123'.concat('456')}" scope="request"/>

<%-- 获取值 --%>

<br><br><br><br><br>

username:${username} <br>

pageScope.username:${pageScope.username} <br>

requestScope.username:${requestScope.username} <br>

sessionScope.username:${sessionScope.username} <br>

applicationScope.username:${applicationScope.username} <br>

三 Servlet规范中的过滤器-Filter

3.1JavaWeb的三大组件

组件 作用 实现接口
Servlet 小应用程序,在JavaWeb中主要做为控制器来使用 可以处理用户的请求并且做出响应 javax.servlet.Servlet
Filter 过滤器,对用户发送的请求或响应进行集中处理,实现请求的拦截 javax.servlet.Filter
Listener 监听器,在某些框架中会使用到监听器(比如spring),在Web执行过程中,引发一些事件,对相应事件进行处理 javax.servlet.XxxListener 每个事件有一个接口

3.2概述

过滤器——Filter,它是JavaWeb三大组件之一。另外两个是Servlet和Listener。

它是在2000年发布的Servlet2.3规范中加入的一个接口。是Servlet规范中非常实用的技术。

它可以对web应用中的所有资源进行拦截,并且在拦截之后进行一些特殊的操作。

常见应用场景:URL级别的权限控制;过滤敏感词汇;中文乱码问题等等。
生活中的过滤器

净水器、空气净化器、地铁安检

web中的过滤器

当用户访问服务器资源时,过滤器将请求拦截下来,完成一些通用的操作

应用场景

如:登录验证、统一编码处理、敏感字符过滤
WebDay15 EL&&JSTL&&Filter&&Listener_第7张图片

3.3快速入门

//@WebServlet(value = {"/MyServlet"})
//@WebServlet(value = "/MyServlet") //注解的数组属性,只有一个值,可以去掉{}
//@WebServlet(value = "/MyServlet") //注解只有一个属性需要赋值,并且属性名为value,value=可以省略
//@WebServlet("/MyServlet")
@WebServlet(value = {"/MyServlet","/MyServlet2"}) //一个servlet配置多个虚拟路径
public class MyServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req,resp);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("资源: servlet被访问了");
    }
}

3.31xml配置

① 编写java类,实现filter接口

/*
*   1. 定义一个类实现Filter接口(注意包名)
*   2. 配置web.xml(或注解)
* */
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest,
                         ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
        System.out.println("过滤器拦截请求了");
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}

② 配置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_3_1.xsd"
         version="3.1">

    <!--
        filter的web.xml配置
        1. filter和filter-mapping的子标签filter-name必须一致(可以自定义,通常与类名相同)
        2. url-pattern : 当前filter要拦截的虚拟路径
    -->
    <filter>
        <filter-name>MyFilter</filter-name>
        <filter-class>com.itheima01.filter.MyFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>MyFilter</filter-name>
        <url-pattern>/MyServlet</url-pattern>
    </filter-mapping>
</web-app>

3.32注解配置

编写java类,实现filter接口

@WebFilter(value = "/MyServlet2")
public class MyFilter2 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("过滤器拦截请求了2");
        //放行
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}

3.4工作原理

1)Filter
WebDay15 EL&&JSTL&&Filter&&Listener_第8张图片
WebDay15 EL&&JSTL&&Filter&&Listener_第9张图片
2)FilterConfig
WebDay15 EL&&JSTL&&Filter&&Listener_第10张图片
3)FilterChain
WebDay15 EL&&JSTL&&Filter&&Listener_第11张图片

  1. 用户发送请求,请求Web资源(包括index,jsp,servlet等)
  2. 如果Web资源的地址,匹配filter的地址,请求将先经过filter,并执行doFilter()
  3. doFilter()方法中如果调用chain.doFilter(),则放行执行下一个Web资源。
  4. 访问Web资源,响应回来会再次经过filter,执行过滤器中的代码,到达浏览器端

3.41生命周期

1)生命周期

出生——活着——死亡

**出生:**当应用加载的时候执行实例化和初始化方法。

**活着:**只要应用一直提供服务,对象就一直存在。

**死亡:**当应用卸载时,或者服务器宕机时,对象消亡。

Filter的实例对象在内存中也只有一份。所以也是单例的。

// 初始化方法
public void init(FilterConfig config);

// 执行拦截方法
public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain);

// 销毁方法
public void destroy();
* 创建
		服务器启动项目加载,创建filter对象,执行init方法(只执行一次)
		
* 运行(过滤拦截)
		用户访问被拦截目标资源时,执行doFilter方法

* 销毁
		服务器关闭项目卸载时,销毁filter对象,执行destroy方法(只执行一次)
		
* 补充:
	过滤器一定是优先于servlet创建的,后于Servlet销毁
/*
*  Filter的生命周期方法
*  1. init
*       a. filter是tomcat启动加载时创建(先于servlet创建)
*       b. 此方法只会执行一次, 一般用来初始化数据的
*  2. doFilter
*       a. 浏览器每访问一次就会执行一次
*       b. 其中有一个方法 chain.doFilter(req, resp);
*           如果不调用,后续资源不再执行
*           如果调用,后续资源会执行
*       c. 此方法会拦截请求, 也会拦截响应
*  3. destroy
*       a. tomcat关闭之前,调用此方法(后于servlet销毁的)
*       b. 此方法只会执行一次,一般用来序列化数据和释放资源的
* */
@WebFilter(urlPatterns = "/LifeServlet")
public class LifeFilter implements Filter {

    public void init(FilterConfig config) throws ServletException {
        System.out.println("filter init");
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        System.out.println("filter doFilter before");
        /*
            放行: 允许请求继续往后传递
                a. 很像之前的请求转发
                b. 如果后续资源是servlet的话,执行service方法
         */
        chain.doFilter(req, resp);
        System.out.println("filter doFilter after");
    }

    public void destroy() {
        System.out.println("filter destroy");
    }
}
// load-on-starup : tomcat启动就加载
@WebServlet(value = "/LifeServlet",loadOnStartup = 4)
public class LifeServlet implements Servlet {

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("servlet init");
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("servlet service");
    }

    @Override
    public void destroy() {
        System.out.println("servlet destroy");
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }
    
    @Override
    public String getServletInfo() {
        return null;
    }
}

3.42 拦截路径

​ 在开发时,我们可以指定过滤器的拦截路径来定义拦截目标资源的范围

* 精准匹配
		用户访问指定目标资源(/show.jsp)时,过滤器进行拦截
		
* 目录匹配
		用户访问指定目录下(/user/*)所有资源时,过滤器进行拦截

* 后缀匹配
		用户访问指定后缀名(*.html)的资源时,过滤器进行拦截

* 匹配所有
		用户访问该网站所有资源(/*)时,过滤器进行拦截
/*

   #拦截路径的匹配模式

* 精准匹配
		用户访问指定目标资源(/show.jsp)时,过滤器进行拦截
* 目录匹配
		用户访问指定目录下(/user/*)所有资源时,过滤器进行拦截
* 后缀匹配
		用户访问指定后缀名(*.html)的资源时,过滤器进行拦截
* 匹配所有
		用户访问该网站所有资源(/*)时,过滤器进行拦截

    url格式:
        协议://ip:port/资源位置
* */
//@WebFilter(urlPatterns = "/doc/hello.html") //精准拦截
//@WebFilter(urlPatterns = "/doc/*") //拦截doc目录下的所有资源
//@WebFilter("*.html") // 拦截所有为html所有资源
//@WebFilter("/*")
@WebFilter(urlPatterns = {"/doc/hello.html","/index.jsp"})
public class UrlFilter implements Filter {

    public void init(FilterConfig config) throws ServletException {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        System.out.println("拦截了");
        chain.doFilter(req, resp);
    }

    public void destroy() {
    }
}

3.43 拦截方式

在开发时,我们可以指定过滤器的拦截方式来处理不同的应用场景,比如:只拦截从浏览器直接发送过来的请求,或者拦截内部转发的请求

总共有五种不同的拦截方式,我们这里学习常见的两种

1. request(默认拦截方式)
		浏览器直接发送请求时,拦截
2. forward
		请求转发的时候,拦截
		比如: 资源A转发到资源B时
		
我们可以配置 二个同时存在...

① xml版本

public class MethodFilter implements Filter {

    public void init(FilterConfig config) throws ServletException {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        System.out.println("method 拦截了");
        chain.doFilter(req, resp);
    }

    public void destroy() {
    }
}
   <filter>
       <filter-name>MethodFilterfilter-name>
       <filter-class>com.itheima04.method.MethodFilterfilter-class>
   filter>
    <filter-mapping>
        <filter-name>MethodFilterfilter-name>
        <url-pattern>/doc/hello.htmlurl-pattern>
        
        <dispatcher>REQUESTdispatcher>
        <dispatcher>FORWARDdispatcher>
    filter-mapping>

② 注解版本

//@WebFilter(urlPatterns = "/doc/hello.html",dispatcherTypes = {DispatcherType.FORWARD,DispatcherType.REQUEST})
@WebFilter(urlPatterns = "/doc/hello.html",dispatcherTypes = DispatcherType.FORWARD)
public class MethodFilter implements Filter {

    public void init(FilterConfig config) throws ServletException {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        System.out.println("method 拦截了");
        chain.doFilter(req, resp);
    }

    public void destroy() {
    }
}

<!--配置过滤器-->
<filter>
    <filter-name>FilterDemo1</filter-name>
    <filter-class>com.itheima.web.filter.FilterDemo1</filter-class>
    <!--配置开启异步支持,当dispatcher配置ASYNC时,需要配置此行-->
    <async-supported>true</async-supported>
</filter>
<filter-mapping>
    <filter-name>FilterDemo1</filter-name>
    <url-pattern>/ServletDemo1</url-pattern>
    <!--过滤请求:默认值。-->
    <dispatcher>REQUEST</dispatcher>
    <!--过滤全局错误页面:当由服务器调用全局错误页面时,过滤器工作-->
    <dispatcher>ERROR</dispatcher>
    <!--过滤请求转发:当请求转发时,过滤器工作。-->
    <dispatcher>FORWARD</dispatcher>
    <!--过滤请求包含:当请求包含时,过滤器工作。它只能过滤动态包含,jsp的include指令是静态包含-->
    <dispatcher>INCLUDE</dispatcher>
    <!--过滤异步类型,它要求我们在filter标签中配置开启异步支持-->
    <dispatcher>ASYNC</dispatcher>
</filter-mapping>

3.44过滤器链

在一次请求中,若我们请求匹配到了多个filter,通过请求就相当于把这些filter串起来了,形成了过滤器链
* 需求
	用户访问目标资源 show.jsp时,经过 FilterA  FilterB
	
* 过滤器链执行顺序 (先进后出)
	1.用户发送请求
	2.FilterA拦截,放行
	3.FilterB拦截,放行
	4.执行目标资源 show.jsp
	5.FilterB增强响应
	6.FilterA增强响应
	7.封装响应消息格式,返回到浏览器
	
* 过滤器链中执行的先后问题....
	配置文件
		谁先声明,谁先执行
			<filter-mapping>
	注解【不推荐】
		根据过滤器类名进行排序,值小的先执行
			FilterA  FilterB  进行比较, FilterA先执行...

WebDay15 EL&&JSTL&&Filter&&Listener_第12张图片

//@WebFilter(urlPatterns = "/ServletA")
public class FilterA implements Filter {

    public void init(FilterConfig config) throws ServletException {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        System.out.println("FilterA执行了");
        /*
         *  放行: 允许请求往后继续传递
         *     1. 等价于请求转发
         *     2. 如果后续还有过滤器,先执行过滤器, 知道过滤器执行完毕,才到资源里去
         * */
        chain.doFilter(req, resp);
    }

    public void destroy() {
    }
}
//@WebFilter(urlPatterns = "/ServletA")
public class FilterB implements Filter {

    public void init(FilterConfig config) throws ServletException {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        System.out.println("FilterB执行了");

        chain.doFilter(req, resp);
    }

    public void destroy() {
    }
}
 <filter>
        <filter-name>FilterB</filter-name>
        <filter-class>com.itheima05.chain.FilterB</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>FilterB</filter-name>
        <url-pattern>/ServletA</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>FilterA</filter-name>
        <filter-class>com.itheima05.chain.FilterA</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>FilterA</filter-name>
        <url-pattern>/ServletA</url-pattern>
    </filter-mapping>

多个过滤器的先后执行顺序
web.xml配置
和配置文件的编写顺序决定运行的顺序,准确的说是,根据mapping的顺序决定(由上到下执行)
注解开发
注解开发没有配置文件的
按照类名的自然顺序决定:A-B-C
如果存在配置文件,配置文件优先

3.5Filter案例

3.5.1统一网站编码

需求

tomcat8.5版本中已经将get请求的中文乱码解决了,但是post请求还存在中文乱码

浏览器发出的任何请求,通过过滤器统一处理中文乱码

需求分析
WebDay15 EL&&JSTL&&Filter&&Listener_第13张图片
代码实现

  • 真实场景中,过滤器不会统一响应mime类型
@WebFilter(urlPatterns = "/*") // 全站拦截
public class PostFilter implements Filter {

    public void init(FilterConfig config) throws ServletException {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {

        HttpServletRequest request = (HttpServletRequest) req; // 引用类型 req地址 0x0001
        HttpServletResponse response = (HttpServletResponse) resp;

        String method = request.getMethod();
        if("POST".equalsIgnoreCase(method)){
            request.setCharacterEncoding("utf-8");
        }
        chain.doFilter(req, resp);
    }

    public void destroy() {
    }
}

3.5.2非法字符拦截

需求

当用户发出非法言论的时候,提示用户言论非法警告信息

需求分析
WebDay15 EL&&JSTL&&Filter&&Listener_第14张图片
代码实现
① 非法词库
WebDay15 EL&&JSTL&&Filter&&Listener_第15张图片
注意修改读properties的格式 ,统一utf-8,不然会乱码
② WordsFilter

@WebFilter("/WordsServlet")
public class WordsFilter implements Filter {

    private List<String> wordList;


    public void init(FilterConfig config) throws ServletException {
        // 1.加载配置文件
        /*
            ResourceBundle专门读取src目录下的properties配置文件,不需要写后缀名
         */
        ResourceBundle words = ResourceBundle.getBundle("words");
        // 2.读取keyword关键字内容
        String keyword = words.getString("keyword"); // 傻叉,大爷的,二大爷的
        // 3.split切割,转为list集合
        wordList = Arrays.asList(keyword.split(","));
        System.out.println("加载非法词库:"+wordList);

    }

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws ServletException, IOException {
        // 向下转型
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        // 1.获取用户输入的值
        String content = request.getParameter("content");
        // 2.拦截非法内容,提示
        for (String word : wordList) { // 遍历非法词库
            if(content.contains(word)){ // 判断是否包含非法词汇
                response.getWriter().write("你输入的词汇敏感,拦截了。。。");
                return;
            }
        }

        // 3.放行
        chain.doFilter(request, response);
    }

    public void destroy() {

    }

}

四 Servlet规范中的监听器-Listener

4.1概述

生活中的监听器

我们很多商场有摄像头,监视着客户的一举一动。如果客户有违法行为,商场可以采取相应的措施。
观察者设计模式
观察者设计模式。因为所有的监听器都是观察者设计模式的体现。

那什么是观察者设计模式呢?

它是事件驱动的一种体现形式。就好比在做什么事情的时候被人盯着。当对应做到某件事时,触发事件。

观察者模式通常由以下三部分组成:

​ 事件源:触发事件的对象。

​ 事件:触发的动作,里面封装了事件源。

​ 监听器:当事件源触发事件时,要做的事情。一般是一个接口,由使用者来实现。
WebDay15 EL&&JSTL&&Filter&&Listener_第16张图片

javaweb中的监听器

在我们的java程序中,有时也需要监视某些事情,一旦被监视的对象发生相应的变化,我们应该采取相应的操作。

监听web三大域对象:HttpServletRequest、HttpSession、ServletContext (创建和销毁)

场景

历史访问次数、统计在线人数、系统启动时初始化配置信息

监听器的接口分类

事件源 监听器接口 时机
ServletContext ServletContextListener 上下文域创建和销毁
ServletContext ServletContextAttributeListener 上下文域属性增删改的操作
**HttpSession ** HttpSessionListener 会话域创建和销毁
**HttpSession ** HttpSessionAttributeListener 会话域属性增删改的操作
HttpServletRequest ServletRequestListener 请求域创建和销毁
HttpServletRequest ServletRequestAttributeListener 请求域属性增删改的操作

4.2快速入门

​ 监听器在web开发中使用的比较少,见的机会就更少了,今天我们使用ServletContextListenner来带领大家学习下监听器,因为这个监听器是监听器中使用率最高的一个,且监听器的使用方式都差不多。

我们使用这个监听器可以在项目启动和销毁的时候做一些事情,例如,在项目启动的时候加载配置文件。

步骤分析

1. 创建一个普通类,实现ServletContextListenner

2. 重写抽象方法
	监听ServletContext创建
	监听ServletContext销毁
	
3. 配置
	web.xml
	注解

① xml版本

/*
* 1. 定义一个类实现 ServletContextListener 接口, 重写方法
*           (监听ServletContext的创建和销毁)
*
* 2. 配置web.xml(或注解)
*
* */
public class MyServletContextListener implements ServletContextListener {

    /*
    *   ServletContext创建时,执行一次
    * */
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("ServletContext创建了");
    }
    /*
     *   ServletContext销毁时,执行一次
     * */
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("ServletContext销毁了");
    }
}
<listener>
    <listener-class>com.itheima07.listener.MyServletContextListenerlistener-class>
listener>

② 注解版本

@WebListener
public class MyListener implements ServletContextListener {

    // 监听ServletContext创建
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("ServletContext已经创建了...");
    }

    // 监听ServletContext销毁
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("ServletContext已经销毁了....");
    }
}

4.3Servlet规范中的8个监听器简介

监听对象创建的

1)ServletContextListener

/**
 * 用于监听ServletContext对象创建和销毁的监听器
 * @since v 2.3
 */

public interface ServletContextListener extends EventListener {

    /**
     *	对象创建时执行此方法。该方法的参数是ServletContextEvent事件对象,事件是【创建对象】这个动作
     *  事件对象中封装着触发事件的来源,即事件源,就是ServletContext
     */
    public default void contextInitialized(ServletContextEvent sce) {
    }

    /**
     * 对象销毁执行此方法
     */
    public default void contextDestroyed(ServletContextEvent sce) {
    }
}

2)HttpSessionListener

/**
 * 用于监听HttpSession对象创建和销毁的监听器
 * @since v 2.3
 */
public interface HttpSessionListener extends EventListener {

    /**
     * 对象创建时执行此方法。
     */
    public default void sessionCreated(HttpSessionEvent se) {
    }

    /**
     *  对象销毁执行此方法
     */
    public default void sessionDestroyed(HttpSessionEvent se) {
    }
}

3)ServletRequestListener

/**
 * 用于监听ServletRequest对象创建和销毁的监听器
 * @since Servlet 2.4
 */
public interface ServletRequestListener extends EventListener {

   	/**
     *  对象创建时执行此方法。
     */
    public default void requestInitialized (ServletRequestEvent sre) {
    }
    
    /**
     * 对象销毁执行此方法
     */
    public default void requestDestroyed (ServletRequestEvent sre) {
    } 
}

监听域中属性发生变化的

1)ServletContextAttributeListener

/**
 * 用于监听ServletContext域(应用域)中属性发生变化的监听器
 * @since v 2.3
 */

public interface ServletContextAttributeListener extends EventListener {
    /**
     * 域中添加了属性触发此方法。参数是ServletContextAttributeEvent事件对象,事件是【添加属性】。
     * 事件对象中封装着事件源,即ServletContext。
     * 当ServletContext执行setAttribute方法时,此方法可以知道,并执行。
     */
    public default void attributeAdded(ServletContextAttributeEvent scae) {
    }

    /**
     * 域中删除了属性触发此方法
     */
    public default void attributeRemoved(ServletContextAttributeEvent scae) {
    }

    /**
     * 域中属性发生改变触发此方法
     */
    public default void attributeReplaced(ServletContextAttributeEvent scae) {
    }
}

2)HttpSessionAttributeListener

/**
 * 用于监听HttpSession域(会话域)中属性发生变化的监听器
 * @since v 2.3
 */
public interface HttpSessionAttributeListener extends EventListener {

    /**
     * 域中添加了属性触发此方法。
     */
    public default void attributeAdded(HttpSessionBindingEvent se) {
    }

    /**
     * 域中删除了属性触发此方法
     */
    public default void attributeRemoved(HttpSessionBindingEvent se) {
    }

    /**
     * 域中属性发生改变触发此方法
     */
    public default void attributeReplaced(HttpSessionBindingEvent se) {
    }
}

3)ServletRequestAttributeListener

/**
 * 用于监听ServletRequest域(请求域)中属性发生变化的监听器
 * @since Servlet 2.4
 */
public interface ServletRequestAttributeListener extends EventListener {
    /**
     * 域中添加了属性触发此方法。
     */
    public default void attributeAdded(ServletRequestAttributeEvent srae) {
    }

    /**
     * 域中删除了属性触发此方法
     */
    public default void attributeRemoved(ServletRequestAttributeEvent srae) {
    }

    /**
     * 域中属性发生改变触发此方法
     */
    public default void attributeReplaced(ServletRequestAttributeEvent srae) {
    }
}

和会话相关的两个感知型监听器

此处要跟同学们明确一下,和会话域相关的两个感知型监听器是无需配置的,直接编写代码即可。

1)HttpSessionBinderListener

/**
 * 用于感知对象和和会话域绑定的监听器
 * 当有数据加入会话域或从会话域中移除,此监听器的两个方法会执行。
 * 加入会话域即和会话域绑定
 * 从会话域移除即从会话域解绑
 */
public interface HttpSessionBindingListener extends EventListener {

    /**
     * 当数据加入会话域时,也就是绑定,此方法执行
     */
    public default void valueBound(HttpSessionBindingEvent event) {
    }

    /**
     * 当从会话域移除时,也就是解绑,此方法执行
     */
    public default void valueUnbound(HttpSessionBindingEvent event) {
    }
}

2)HttpSessionActivationListener

/**
 * 用于感知会话域中对象钝化和活化的监听器
 */
public interface HttpSessionActivationListener extends EventListener {

    /**
     * 当会话域中的数据钝化时,此方法执行
     */
    public default void sessionWillPassivate(HttpSessionEvent se) {
    }

    /**
     * 当会话域中的数据活化时(激活),此方法执行
     */
    public default void sessionDidActivate(HttpSessionEvent se) {
    }
}

4.4案例:模拟spring框架

  • 可以在项目启动时读取配置文件

<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_3_1.xsd"
         version="3.1">
    

    
    <context-param>
        <param-name>configLocationparam-name>
        <param-value>words.propertiesparam-value>
    context-param>
web-app>
@WebListener
public class MyListener implements ServletContextListener {

    // 监听ServletContext创建
    /*
        ServletContextEvent 上下文事件对象,获取ServletContext
     */
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("ServletContext已经创建了...");

        // 通过servletContextEvent获取上下文对象
        ServletContext servletContext = servletContextEvent.getServletContext();
        // 我可以去加载公司定义配置文件的名称....
        String configLocation = servletContext.getInitParameter("configLocation");
        System.out.println("动态获取配置文件名称:" + configLocation);
    }

    // 监听ServletContext销毁
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("ServletContext已经销毁了....");
    }
}

4.5 案例:统计在线人数

需求
有用户使用网站,在线人数就+1;用户退出网站,在线人数就-1

技术分析

使用 ServletContext域对象 存储在线总人数

使用 ServletContextListener监听器,在项目启动时,初始化总人数为0

使用 HttpSessionListener监听器,用户访问,人数+1,用户退出,人数-1

使用 LogoutServlet控制器,对当前会话的session销毁

需求分析
WebDay15 EL&&JSTL&&Filter&&Listener_第17张图片
代码实现

① InitNumberListener

@WebListener
public class InitNumberListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        // 获取上下文域对象
        ServletContext servletContext = servletContextEvent.getServletContext();
        // 初始化在线人数
        servletContext.setAttribute("number", 0);
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }
}

② NumberChangeListener

@WebListener
public class NumberChangeListener implements HttpSessionListener {

    // 会话建立,在线人数+1
    @Override
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {
        // 获取session域对象
        HttpSession session = httpSessionEvent.getSession();
        // 获取上下文域对象
        ServletContext servletContext = session.getServletContext();
        // 取出在线人数
        Integer number = (Integer) servletContext.getAttribute("number");
        // +1
        servletContext.setAttribute("number", number + 1);
    }

    // 会话销毁,在线人数-1
    @Override
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
        // 获取session域对象
        HttpSession session = httpSessionEvent.getSession();
        // 获取上下文域对象
        ServletContext servletContext = session.getServletContext();
        // 取出在线人数
        Integer number = (Integer) servletContext.getAttribute("number");
        // -1
        servletContext.setAttribute("number", number - 1);
    }
}

③ index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

  
    ${NAME}
  
  
  

listener知识学习

在线人数:${applicationScope.number}
用户退出

④ LogoutServlet

@WebServlet("/LogoutServlet")
public class LogoutServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 销毁session
        request.getSession().invalidate();

        response.getWriter().write("logout");
    }

}

五总结

el是JSP内置的主要用于简化jsp页面中Java代码的编写
语法 ${表达式内容}
从四大域对象中获取数据,从小到大获取
没有空指针,越界和拼接

JSTL是Apache提供的,需要导包,替换和简化页面中java代码的,本质是servlet
<c:if 标签>
语句体
c:if>

Filter过滤器,实现Filter接口,可以注解配置,也可以web.xml配置
登录权限效验,全站解决乱码,敏感字符过滤
多个过滤器形成链
1.用户发送请求
2.FilterA拦截,放行
3.FilterB拦截,放行
4.执行目标资源 show.jsp
5.FilterB增强响应
6.FilterA增强响应
7.封装响应消息格式,返回到浏览器
执行顺序:web.xml是由上往下执行,注解的话是字典顺序A-B-C ,配置优先

Listener监听器,实现对应的接口
历史访问次数,统计在线人户,初始化配置信息
ServletContextListener是创建和销毁
ServletContextAttributeListerner是增删覆盖的操作
@webListener注册

你可能感兴趣的:(WebDay15 EL&&JSTL&&Filter&&Listener)