WebDay15 EL&&JSTL&&Filter&&Listener

一 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>

在这里插入图片描述

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 执行运算

在这里插入图片描述
但是有两个特殊的运算符,使用方式的代码如下:

<%@ 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>

在这里插入图片描述

1.2.4 隐式对象

隐式对象介绍

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

	1. pageContext
		就是jsp九大内置对象之一,这哥们可以获得其他八个内置对象
	2. cookie
		可以获取浏览器指定cookie名称的值
EL中的隐式对象类型对应JSP隐式对象备注
PageContextJavax.serlvet.jsp.PageContextPageContext完全一样
ApplicationScopeJava.util.Map没有应用层范围
SessionScopeJava.util.Map没有会话范围
RequestScopeJava.util.Map没有请求范围
PageScopeJava.util.Map没有页面层范围
HeaderJava.util.Map没有请求消息头key,值是value(一个)
HeaderValuesJava.util.Map没有请求消息头key,值是数组(一个头多个值)
ParamJava.util.Map没有请求参数key,值是value(一个)
ParamValuesJava.util.Map没有请求参数key,值是数组(一个名称多个值)
InitParamJava.util.Map没有全局参数,key是参数名称,value是参数值
CookieJava.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前缀
Corehttp://java.sun.com/jsp/jstl/corec
国际化(几乎不用)http://java.sun.com/jsp/jstl/fmtfmt
SQL(过时)http://java.sun.com/jsp/jstl/sqlsql
XML(过时)http://java.sun.com/jsp/jstl/xmlx
Functions(几乎不用)http://java.sun.com/jsp/jstl/functionsfn

2.2 Core标签使用

2.2.1 使用要求

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

在这里插入图片描述
2.当前jsp页面tablib指令引入
在这里插入图片描述

2.2.2 核心标签库

| 标签名称                             | 功能分类 | 分类       | 作用             || ------------------------------------ | -------- | ---------- | ---------------- ||                              | 流程控制 | 核心标签库 | 用于判断         ||  | 流程控制 | 核心标签库 | 用于多个条件判断 ||                        | 迭代操作 | 核心标签库 | 用于循环遍历     |
① 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中的过滤器

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

应用场景

如:登录验证、统一编码处理、敏感字符过滤
在这里插入图片描述

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
在这里插入图片描述
在这里插入图片描述
2)FilterConfig
在这里插入图片描述
3)FilterChain
在这里插入图片描述

  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>MethodFilter</filter-name>
       <filter-class>com.itheima04.method.MethodFilter</filter-class>
   </filter>
    <filter-mapping>
        <filter-name>MethodFilter</filter-name>
        <url-pattern>/doc/hello.html</url-pattern>
        <!--
            dispatcher : 用来指定拦截方式的
            1. REQUEST(默认) : 浏览器直接发送过来的请求
            2. FORWARD: 请求转发过来的请求

            可以同时设置
        -->
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </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先执行...

在这里插入图片描述

//@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请求还存在中文乱码

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

需求分析
在这里插入图片描述
代码实现

  • 真实场景中,过滤器不会统一响应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非法字符拦截

需求

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

需求分析
在这里插入图片描述
代码实现
① 非法词库
在这里插入图片描述
注意修改读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概述

生活中的监听器

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

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

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

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

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

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

​ 监听器:当事件源触发事件时,要做的事情。一般是一个接口,由使用者来实现。
在这里插入图片描述

javaweb中的监听器

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

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

场景

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

监听器的接口分类

事件源监听器接口时机
ServletContextServletContextListener上下文域创建和销毁
ServletContextServletContextAttributeListener上下文域属性增删改的操作
**HttpSession **HttpSessionListener会话域创建和销毁
**HttpSession **HttpSessionAttributeListener会话域属性增删改的操作
HttpServletRequestServletRequestListener请求域创建和销毁
HttpServletRequestServletRequestAttributeListener请求域属性增删改的操作

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.MyServletContextListener</listener-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框架

  • 可以在项目启动时读取配置文件
<?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">
    

    <!--全局配置参数-->
    <context-param>
        <param-name>configLocation</param-name>
        <param-value>words.properties</param-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销毁

需求分析
在这里插入图片描述
代码实现

① 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" %>
<html>
  <head>
    <title>${NAME}</title>
  </head>
  <body>
  <h3>listener知识学习</h3>

  <h5>在线人数:${applicationScope.number}</h5>
  <a href="${pageContext.request.contextPath}/LogoutServlet">用户退出</a>
  </body>
</html>

④ 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注册

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值