【JavaWeb】EL表示式&過濾器&監聽器

gonghr發表於2021-08-03

EL表示式和JSTL

EL表示式

EL表示式概述

基本概念

EL表示式,全稱是Expression Language。意為表示式語言。它是Servlet規範中的一部分,是JSP2.0規範加入的內容。其作用是用於在JSP頁面中獲取資料,從而讓我們的JSP脫離java程式碼塊和JSP表示式。

基本語法

EL表示式的語法格式非常簡單,寫為 ${表示式內容}

例如:在瀏覽器中輸出請求域中名稱為message的內容。

假定,我們在請求域中存入了一個名稱為message的資料(request.setAttribute("message","EL");),此時在jsp中獲取的方式,如下表顯示:

Java程式碼塊 JSP表示式 EL表示式
<%<br/> <br/> String message = (String)request.getAttribute("message");<br/> out.write(message);<br/>%> <%=request.getAttribute("message")%> ${message}

通過上面我們可以看出,都可以從請求域中獲取資料,但是EL表示式寫起來是最簡單的方式。這也是以後我們在實際開發中,當使用JSP作為檢視時,絕大多數都會採用的方式。

EL表示式的入門案例

第一步:建立JavaWeb工程

image

第二步:建立jsp頁面

image

第三步:在JSP頁面中編寫程式碼

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>EL表示式入門案例</title>
  </head>
  <body>
    <%--使用java程式碼在請求域中存入一個名稱為message的資料--%>
    <% request.setAttribute("message","Expression Language");%>

    Java程式碼塊獲取:<% out.print(request.getAttribute("message"));%>
    <br/>
    JSP表示式獲取:<%=request.getAttribute("message")%>
    <br/>
    EL表示式獲取:${message}
  </body>
</html>

第四步:部署工程

image

第五步:執行測試

image

EL表示式基本用法

在前面的概述介紹中,我們介紹了EL表示式的作用,它就是用於獲取資料的,那麼它是從哪獲取資料呢?

1)獲取四大域中的資料

它只能從四大域中獲取資料,呼叫的就是findAttribute(name,value);方法,根據名稱由小到大逐個域中查詢,找到就返回,找不到就什麼都不顯示。

它可以獲取物件,可以是物件中關聯其他物件,可以是一個List集合,也可以是一個Map集合。具體程式碼如下:

建立兩個實體類,User和Address

/**
 * 使用者的實體類
 */
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;
    }
}
/**
 * 地址的實體類
 */
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;
    }
}

JSP程式碼

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" %>
<%@ page import="com.cnblogs.gonghr.ELDemo.User" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
    <title>EL入門</title>
</head>
<body>
<%--EL表示式概念:
        它是Expression Language的縮寫。它是一種替換jsp表示式的語言。
    EL表示式的語法:
        ${表示式}
        表示式的特點:有明確的返回值。
        EL表示式就是把內容輸出到頁面上
    EL表示式的注意事項:
        1.EL表示式沒有空指標異常
        2.EL表示式沒有陣列下標越界
        3.EL表示式沒有字串拼接
    EL表示式的資料獲取:
        它只能在四大域物件中獲取資料,不在四大域物件中的資料它取不到。
        它的獲取方式就是findAttribute(String name)
 --%>
    <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>

執行結果如圖:

image

2)EL表示式的注意事項

在使用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>

執行結果圖:

image

3)EL表示式的使用細節

EL表示式除了能在四大域中獲取資料,同時它可以訪問其他隱式物件,並且訪問物件有返回值的方法.

4)EL表示式的運算子

EL表示式中運算子如下圖所示,它們都是一目瞭然的:

image

image

但是有兩個特殊的運算子,使用方式的程式碼如下:

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

執行結果圖:

image

EL表示式的11個隱式物件

1)隱式物件介紹

EL表示式也為我們提供隱式物件,可以讓我們不宣告直接來使用,十一個物件見下表,需要注意的是,它和JSP的隱式物件不是一回事:

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物件

JSTL

JSTL概述

1)簡介

JSTL的全稱是:JSP Standard Tag Library。它是JSP中標準的標籤庫。它是由Apache實現的。

它由以下5個部分組成:

組成 作用 說明
Core 核心標籤庫。 通用邏輯處理
Fmt 國際化有關。 需要不同地域顯示不同語言時使用
Functions EL函式 EL表示式可以使用的方法
SQL 運算元據庫。 不用
XML 操作XML。 不用

2)使用要求

要想使用JSTL標籤庫,在javaweb工程中需要匯入座標。首先是在工程的WEB-INF目錄中建立一個lib目錄,接下來把jstl的jar拷貝到lib目錄中,最後在jar包上點選右鍵,然後選擇【Add as Libary】新增。如下圖所示:

image

核心標籤庫

在我們實際開發中,用到的jstl標籤庫主要以核心標籤庫為準,偶爾會用到國際化標籤庫的標籤。下表中把我們經常可能用到的標籤列在此處。

標籤名稱 功能分類 分類 作用
<c:if> 流程控制 核心標籤庫 用於判斷
<c:choose> ,<c:when>,<c:otherwise> 流程控制 核心標籤庫 用於多個條件判斷
<c:foreach> 迭代操作 核心標籤庫 用於迴圈遍歷

JSTL使用

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%--匯入jstl標籤庫 --%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>JSTL的常用標籤</title>
  </head>
  <body>
    <%-- c:if  c:choose   c:when c:otherwise --%>
    <% pageContext.setAttribute("score","F"); %>
    <c:if test="${pageScope.score eq 'A' }">
    	優秀
    </c:if>
    <c:if	test="${pageScope.score eq 'C' }">
    	一般
    </c:if>
    <hr/>
    <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>
    
    <%-- c:forEach 它是用來遍歷集合的
    	 屬性:
    	 	items:要遍歷的集合,它可以是EL表示式取出來的
    	 	var:把當前遍歷的元素放入指定的page域中。 var的取值就是key,當前遍歷的元素就是value
    	 		注意:它不能支援EL表示式,只能是字串常量
    	 	begin:開始遍歷的索引
    	 	end:結束遍歷的索引
    	 	step:步長。i+=step
    	 	varStatus:它是一個計數器物件。裡面有兩個屬性,一個是用於記錄索引。一個是用於計數。
    	 			   索引是從0開始。計數是從1開始
    --%>
    <hr/>
    <% List<String> list = new ArrayList<String>();
       list.add("AAA");
       list.add("BBB");
       list.add("CCC");
       list.add("DDD");
       list.add("EEE");
       list.add("FFF");
       list.add("GGG");
       list.add("HHH");
       list.add("III");
       list.add("JJJ");
       list.add("KKK");
       list.add("LLL");
       pageContext.setAttribute("list",list);
     %>
	<c:forEach items="${list}" var="s" begin="1" end="7" step="2">
    	${s}<br/>
    </c:forEach>
    <hr/>
    <c:forEach begin="1" end="9" var="num">
    	<a href="#">${num}</a>
    </c:forEach>
    <hr/>
    <table>
    	<tr>
    		<td>索引</td>
    		<td>序號</td>
    		<td>資訊</td>
    	</tr>
    <c:forEach items="${list}" var="s" varStatus="vs">
    	<tr>
    		<td>${vs.index}</td>
    		<td>${vs.count}</td>
    		<td>${s}</td>
    	</tr>
    </c:forEach>
    </table>
  </body>
</html>

Servlet規範中的過濾器-Filter

過濾器入門

過濾器概念及作用

過濾器——Filter,它是JavaWeb三大元件之一。另外兩個是Servlet和Listener。

它是在2000年釋出的Servlet2.3規範中加入的一個介面。是Servlet規範中非常實用的技術。

它可以對web應用中的所有資源進行攔截,並且在攔截之後進行一些特殊的操作。

常見應用場景:URL級別的許可權控制;過濾敏感詞彙;中文亂碼問題等等。

過濾器的入門案例

1)前期準備

建立JavaWeb工程

image

編寫和配置接收請求用的Servlet

/**
 * 用於接收和處理請求的Servlet
 */
public class ServletDemo1 extends HttpServlet {

    /**
     * 處理請求的方法
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("ServletDemo1接收到了請求");
        req.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       doGet(req,resp);
    }
}
<?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"
         metadata-complete="true">
    
    <!--配置Servlet-->
    <servlet>
        <servlet-name>ServletDemo1</servlet-name>
        <servlet-class>com.cnblogs.gonghr.servlet.ServletDemo1</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>ServletDemo1</servlet-name>
        <url-pattern>/ServletDemo1</url-pattern>
    </servlet-mapping>
</web-app>

編寫index.jsp

<%-- Created by IntelliJ IDEA. --%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>主頁面</title>
  </head>
  <body>
    <a href="${pageContext.request.contextPath}/ServletDemo1">訪問ServletDemo1</a>
  </body>
</html>

編寫success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>成功頁面</title>
</head>
<body>
<%System.out.println("success.jsp執行了");%>
執行成功!
</body>
</html>

image

2)過濾器的編寫步驟

編寫過濾器

/**
 * Filter的入門案例
 */
public class FilterDemo1 implements Filter {

    /**
     * 過濾器的核心方法
     * @param request
     * @param response
     * @param chain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        /**
         * 如果不寫此段程式碼,控制檯會輸出兩次:FilterDemo1攔截到了請求。
         */
        HttpServletRequest req = (HttpServletRequest) request;
        String requestURI = req.getRequestURI();
        if (requestURI.contains("favicon.ico")) {
            return;
        }
        System.out.println("FilterDemo1攔截到了請求");
    }
}

配置過濾器

<!--配置過濾器-->
<filter>
    <filter-name>FilterDemo1</filter-name>
    <filter-class>com.cnblogs.gonghr.filter.FilterDemo1</filter-class>
</filter>
<filter-mapping>
    <filter-name>FilterDemo1</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

3)測試部署

部署專案

image

測試結果

image

案例的問題分析及解決

當我們啟動服務,在位址列輸入訪問地址後,發現瀏覽器任何內容都沒有,控制檯卻輸出了【FilterDemo1攔截到了請求】,也就是說在訪問任何資源的時候,都先經過了過濾器。

這是因為:我們在配置過濾器的攔截規則時,使用了/*,表明訪問當前應用下任何資源,此過濾器都會起作用。除了這種全部過濾的規則之外,它還支援特定型別的過濾配置。我們可以稍作調整,就可以不用加上面那段過濾圖示的程式碼了。修改的方式如下:

image

現在的問題是,我們攔截下來了,點選連結傳送請求,執行結果是:

image

需要對過濾器執行放行操作,才能讓他繼續執行,那麼如何放行的?

我們需要使用FilterChain中的doFilter方法放行。

image

過濾器的細節

過濾器API介紹

1)Filter

image

image

2)FilterConfig

image

3)FilterChain

image

入門案例過程及生命週期

1)生命週期

出生——活著——死亡

出生:當應用載入的時候執行例項化和初始化方法。

活著:只要應用一直提供服務,物件就一直存在。

死亡:當應用解除安裝時,或者伺服器當機時,物件消亡。

Filter的例項物件在記憶體中也只有一份。所以也是單例的。

2)過濾器核心方法的細節

FilterDemo1doFilter方法新增一行程式碼,如下:

/**
     * 過濾器的核心方法
     * @param request
     * @param response
     * @param chain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        /**
         * 如果不寫此段程式碼,控制檯會輸出兩次:FilterDemo1攔截到了請求。

        HttpServletRequest req = (HttpServletRequest) request;
        String requestURI = req.getRequestURI();
        if (requestURI.contains("favicon.ico")) {
            return;
        }*/
        System.out.println("FilterDemo1攔截到了請求");
        //過濾器放行
        chain.doFilter(request,response);
        System.out.println("FilterDemo1放行之後,又回到了doFilter方法");
    }

測試執行結果,我們發現過濾器放行之後執行完目標資源,仍會回到過濾器中:

image

過濾器初始化引數配置

1)建立過濾器FilterDemo2

/**
 * Filter的初始化引數配置
 */
public class FilterDemo2 implements Filter {

    private FilterConfig filterConfig;

    /**
     * 初始化方法
     * @param filterConfig
     * @throws ServletException
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("FilterDemo2的初始化方法執行了");
        //給過濾器配置物件賦值
        this.filterConfig = filterConfig;
    }

    /**
     * 過濾器的核心方法
     * @param request
     * @param response
     * @param chain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        System.out.println("FilterDemo2攔截到了請求");
        //過濾器放行
        chain.doFilter(request,response);
    }
    
    /**
     * 銷燬方法
     */
    @Override
    public void destroy() {
        System.out.println("FilterDemo2的銷燬方法執行了");
    }
}

2)配置FilterDemo2

<filter>
    <filter-name>FilterDemo2</filter-name>
    <filter-class>com.cnblogs.gonghr.filter.FilterDemo2</filter-class>
    <!--配置過濾器的初始化引數-->
    <init-param>
        <param-name>filterInitParamName</param-name>
        <param-value>filterInitParamValue</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>FilterDemo2</filter-name>
    <url-pattern>/ServletDemo1</url-pattern>
</filter-mapping>

3)在FilterDemo2的doFilter方法中新增下面的程式碼

//根據名稱獲取過濾器的初始化引數
String paramValue = filterConfig.getInitParameter("filterInitParamName");
System.out.println(paramValue);

//獲取過濾器初始化引數名稱的列舉
Enumeration<String> initNames = filterConfig.getInitParameterNames();
while(initNames.hasMoreElements()){
    String initName = initNames.nextElement();
    String initValue = filterConfig.getInitParameter(initName);
    System.out.println(initName+","+initValue);
}

//獲取ServletContext物件
ServletContext servletContext = filterConfig.getServletContext();
System.out.println(servletContext);

//獲取過濾器名稱
String filterName = filterConfig.getFilterName();
System.out.println(filterName);

4)測試執行結果

image

我們通過這個測試,看到了過濾器的初始化引數配置和獲取的使用。但是同學們也肯定發現了,在我們的工程中兩個過濾器都起作用了,這就是我們在API中說的鏈式呼叫,那麼當有多個過濾器,它的執行順序是什麼樣的呢?

我們來看下一小節。

多個過濾器的執行順序

1)修改FilterDemo1和FilterDemo2兩個過濾器的程式碼,刪掉多餘的程式碼

/**
 * Filter的入門案例
 */
public class FilterDemo1 implements Filter {
    /**
     * 過濾器的核心方法
     * @param request
     * @param response
     * @param chain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        
        System.out.println("FilterDemo1攔截到了請求");
        //過濾器放行
        chain.doFilter(request,response);
    }

    /**
     * 初始化方法
     * @param filterConfig
     * @throws ServletException
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("FilterDemo1的初始化方法執行了");
    }

    /**
     * 銷燬方法
     */
    @Override
    public void destroy() {
        System.out.println("FilterDemo1的銷燬方法執行了");
    }
}
/**
 * Filter的初始化引數配置
 */
public class FilterDemo2 implements Filter {

    /**
     * 初始化方法
     * @param filterConfig
     * @throws ServletException
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("FilterDemo2的初始化方法執行了");

    }

    /**
     * 過濾器的核心方法
     * @param request
     * @param response
     * @param chain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        System.out.println("FilterDemo2攔截到了請求");
        //過濾器放行
        chain.doFilter(request,response);
    }

    /**
     * 銷燬方法
     */
    @Override
    public void destroy() {
        System.out.println("FilterDemo2的銷燬方法執行了");
    }
}

2)修改兩個過濾器的配置,刪掉多餘的配置

<!--配置過濾器-->
<filter>
    <filter-name>FilterDemo1</filter-name>
    <filter-class>com.cnblogs.gonghr.filter.FilterDemo1</filter-class>
</filter>
<filter-mapping>
    <filter-name>FilterDemo1</filter-name>
    <url-pattern>/ServletDemo1</url-pattern>
</filter-mapping>


<filter>
    <filter-name>FilterDemo2</filter-name>
    <filter-class>com.cnblogs.gonghr.filter.FilterDemo2</filter-class>
</filter>
<filter-mapping>
    <filter-name>FilterDemo2</filter-name>
    <url-pattern>/ServletDemo1</url-pattern>
</filter-mapping>

3)測試執行結果

image

此處我們看到了多個過濾器的執行順序,它正好和我們在web.xml中的配置順序一致,如下圖:

image

在過濾器的配置中,有過濾器的宣告和過濾器的對映兩部分,到底是宣告決定順序,還是對映決定順序呢?

答案是:<filter-mapping>的配置前後順序決定過濾器的呼叫順序,也就是由對映配置順序決定。

過濾器的五種攔截行為

我們的過濾器目前攔截的是請求,但是在實際開發中,我們還有請求轉發和請求包含,以及由伺服器觸發呼叫的全域性錯誤頁面。預設情況下過濾器是不參與過濾的,要想使用,需要我們配置。配置的方式如下:

<!--配置過濾器-->
<filter>
    <filter-name>FilterDemo1</filter-name>
    <filter-class>com.cnblogs.gonghr.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>

過濾器與Servlet的區別

方法/型別 Servlet Filter 備註
初始化 方法 void init(ServletConfig); void init(FilterConfig); 幾乎一樣,都是在web.xml中配置引數,用該物件的方法可以獲取到。
提供服務方法 void service(request,response); void dofilter(request,response,FilterChain); Filter比Servlet多了一個FilterChain,它不僅能完成Servlet的功能,而且還可以決定程式是否能繼續執行。所以過濾器比Servlet更為強大。 在Struts2中,核心控制器就是一個過濾器。
銷燬方法 void destroy(); void destroy();

過濾器的使用案例

靜態資源設定快取時間過濾器

1) 需求說明

在我們訪問html,js,image時,不需要每次都重新傳送請求讀取資源,就可以通過設定響應訊息頭的方式,設定快取時間。但是如果每個Servlet都編寫相同的程式碼,顯然不符合我們統一呼叫和維護的理念。(此處有個非常重要的程式設計思想:AOP思想,在錄製視訊時提不提都可以)

因此,我們要採用過濾器來實現功能。

2) 編寫步驟

第一步:建立JavaWeb工程

image

第二步:匯入靜態資源

image

第三步:編寫過濾器

/**
 * 靜態資源設定快取時間
 * 	html設定為1小時
 *  js設定為2小時
 *  css設定為3小時
 */
public class StaticResourceNeedCacheFilter implements Filter {

    private FilterConfig filterConfig;

    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
    }


    public void doFilter(ServletRequest req, ServletResponse res,
                         FilterChain chain) throws IOException, ServletException {
        //1.把doFilter的請求和響應物件轉換成跟http協議有關的物件
        HttpServletRequest  request;
        HttpServletResponse response;
        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException("non-HTTP request or response");
        }
        //2.獲取請求資源URI
        String uri = request.getRequestURI();
        //3.得到請求資源到底是什麼型別
        String extend = uri.substring(uri.lastIndexOf(".")+1);//我們只需要判斷它是不是html,css,js。其他的不管
        //4.判斷到底是什麼型別的資源
        long time = 60*60*1000;
        if("html".equals(extend)){
            //html 快取1小時
            String html = filterConfig.getInitParameter("html");
            time = time*Long.parseLong(html);
        }else if("js".equals(extend)){
            //js 快取2小時
            String js = filterConfig.getInitParameter("js");
            time = time*Long.parseLong(js);
        }else if("css".equals(extend)){
            //css 快取3小時
            String css = filterConfig.getInitParameter("css");
            time = time*Long.parseLong(css);

        }
        //5.設定響應訊息頭
        response.setDateHeader("Expires", System.currentTimeMillis()+time);
        //6.放行
        chain.doFilter(request, response);
    }


    public void destroy() {

    }

}

第四步:配置過濾器

<filter>
    <filter-name>StaticResourceNeedCacheFilter</filter-name>
    <filter-class>com.cnblogs.gonghr.filter.StaticResourceNeedCacheFilter</filter-class>
    <init-param>
        <param-name>html</param-name>
        <param-value>3</param-value>
    </init-param>
    <init-param>
        <param-name>js</param-name>
        <param-value>4</param-value>
    </init-param>
    <init-param>
        <param-name>css</param-name>
        <param-value>5</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>StaticResourceNeedCacheFilter</filter-name>
    <url-pattern>*.html</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>StaticResourceNeedCacheFilter</filter-name>
    <url-pattern>*.js</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>StaticResourceNeedCacheFilter</filter-name>
    <url-pattern>*.css</url-pattern>
</filter-mapping>

3) 測試結果

此案例演示時需要注意一下,chrome瀏覽器重新整理時,每次也都會傳送請求,所以看不到304狀態碼。建議用IE瀏覽器,因為它在重新整理時不會再次請求。

image

特殊字元過濾器

1)需求說明

在實際開發中,可能會面臨一個問題,就是很多輸入框都會遇到特殊字元。此時,我們也可以通過過濾器來解決。

例如:

​ 我們模擬一個論壇,有人發帖問:“在HTML中表示水平線的標籤是哪個?”。

如果我們在文字框中直接輸入<hr/>就會出現一條水平線,這個會讓發帖人一臉懵。

我們接下來就用過濾器來解決一下。

2)編寫步驟

第一步:建立JavaWeb工程

沿用第一個案例的工程

第二步:編寫Servlet和JSP

public class ServletDemo1 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String content = request.getParameter("content");
        response.getWriter().write(content);
    }

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

}
<servlet>
    <servlet-name>ServletDemo1</servlet-name>
    <servlet-class>com.cnblogs.gonghr.servlet.ServletDemo1</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>ServletDemo1</servlet-name>
    <url-pattern>/ServletDemo1</url-pattern>
</servlet-mapping>
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
    <title></title>
</head>
<body>
<form action="${pageContext.request.contextPath}/ServletDemo1" method="POST">
    回帖:<textarea rows="5" cols="25" name="content"></textarea><br/>
    <input type="submit" value="發言">
</form>
</body>
</html>

第三步:編寫過濾器


public class HTMLFilter implements Filter {

    public void init(FilterConfig filterConfig) throws ServletException {

    }


    public void doFilter(ServletRequest req, ServletResponse res,
                         FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request;
        HttpServletResponse response;
        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException("non-HTTP request or response");
        }
        //建立一個自己的Request類
        MyHttpServletRequest2 myrequest = new MyHttpServletRequest2(request);
        //放行:
        chain.doFilter(myrequest, response);
    }

    public void destroy() {
    }
}
class MyHttpServletRequest2 extends HttpServletRequestWrapper {
    //提供一個構造方法
    public MyHttpServletRequest2(HttpServletRequest request){
        super(request);
    }

    //重寫getParameter方法
    public String getParameter(String name) {
        //1.獲取出請求正文: 呼叫父類的獲取方法
        String value = super.getParameter(name);
        //2.判斷value是否有值
        if(value == null){
            return null;
        }
        return htmlfilter(value);
    }

    private String htmlfilter(String message){
        if (message == null)
            return (null);

        char content[] = new char[message.length()];
        message.getChars(0, message.length(), content, 0);
        StringBuilder result = new StringBuilder(content.length + 50);
        for (int i = 0; i < content.length; i++) {
            switch (content[i]) {
                case '<':
                    result.append("&lt;");
                    break;
                case '>':
                    result.append("&gt;");
                    break;
                case '&':
                    result.append("&amp;");
                    break;
                case '"':
                    result.append("&quot;");
                    break;
                default:
                    result.append(content[i]);
            }
        }
        return (result.toString());
    }

}

第四步:配置過濾器

<filter>
    <filter-name>HTMLFilter</filter-name>
    <filter-class>com.cnblogs.gonghr.filter.HTMLFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>HTMLFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

3)測試結果

image

Servlet規範中的監聽器-Listener

觀察者設計模式

那什麼是觀察者設計模式呢?

它是事件驅動的一種體現形式。就好比在做什麼事情的時候被人盯著。當對應做到某件事時,觸發事件。

觀察者模式通常由以下三部分組成:

​ 事件源:觸發事件的物件。

​ 事件:觸發的動作,裡面封裝了事件源。

​ 監聽器:當事件源觸發事件時,要做的事情。一般是一個介面,由使用者來實現。

下圖描述了觀察者設計模式組成:

image

Servlet規範中的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) {
    } 
}

3.1.2 監聽域中屬性發生變化的

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) {
    }
}

監聽器的使用

在實際開發中,我們可以根據具體情況來從這8個監聽器中選擇使用。感知型監聽器由於無需配置,只需要根據實際需求編寫程式碼,所以此處我們就不再演示了。我們在剩餘6箇中分別選擇一個監聽物件建立銷燬和物件域中屬性發生變化的監聽器演示一下。

ServletContextListener的使用

第一步:建立工程

image

第二步:編寫監聽器

/**
 * 用於監聽ServletContext物件建立和銷燬的監聽器
 */
public class ServletContextListenerDemo implements ServletContextListener {

    /**
     * 物件建立時,執行此方法
     * @param sce
     */
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("監聽到了物件的建立");
        //1.獲取事件源物件
        ServletContext servletContext = sce.getServletContext();
        System.out.println(servletContext);
    }

    /**
     * 物件銷燬時,執行此方法
     * @param sce
     */
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("監聽到了物件的銷燬");
    }
}

第三步:在web.xml中配置監聽器

<!--配置監聽器-->
<listener>
    <listener-class>com.cnblogs.gonghr.listener.ServletContextListenerDemo</listener-class>
</listener>

第四步:測試結果

image

ServletContextAttributeListener的使用

第一步:建立工程

沿用上一個案例的工程

第二步:編寫監聽器

/**
 * 監聽域中屬性發生變化的監聽器
 */
public class ServletContextAttributeListenerDemo implements ServletContextAttributeListener {

    /**
     * 域中新增了資料
     * @param scae
     */
    @Override
    public void attributeAdded(ServletContextAttributeEvent scae) {
        System.out.println("監聽到域中加入了屬性");
        /**
         * 由於除了我們往域中新增了資料外,應用在載入時還會自動往域中新增一些屬性。
         * 我們可以獲取域中所有名稱的列舉,從而看到域中都有哪些屬性
         */
        
        //1.獲取事件源物件ServletContext
        ServletContext servletContext = scae.getServletContext();
        //2.獲取域中所有名稱的列舉
        Enumeration<String> names = servletContext.getAttributeNames();
        //3.遍歷名稱的列舉
        while(names.hasMoreElements()){
            //4.獲取每個名稱
            String name = names.nextElement();
            //5.獲取值
            Object value = servletContext.getAttribute(name);
            //6.輸出名稱和值
            System.out.println("name is "+name+" and value is "+value);
        }
    }

    /**
     * 域中移除了資料
     * @param scae
     */
    @Override
    public void attributeRemoved(ServletContextAttributeEvent scae) {
        System.out.println("監聽到域中移除了屬性");
    }

    /**
     * 域中屬性發生了替換
     * @param scae
     */
    @Override
    public void attributeReplaced(ServletContextAttributeEvent scae) {
        System.out.println("監聽到域中屬性發生了替換");
    }
}

同時,我們還需要藉助第一個ServletContextListenerDemo監聽器,往域中存入資料,替換域中的資料以及從域中移除資料,程式碼如下:

/**
 * 用於監聽ServletContext物件建立和銷燬的監聽器
 */
public class ServletContextListenerDemo implements ServletContextListener {

    /**
     * 物件建立時,執行此方法
     * @param sce
     */
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("監聽到了物件的建立");
        //1.獲取事件源物件
        ServletContext servletContext = sce.getServletContext();
        //2.往域中加入屬性
        servletContext.setAttribute("servletContext","test");
    }

    /**
     * 物件銷燬時,執行此方法
     * @param sce
     */
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        //1.取出事件源物件
        ServletContext servletContext = sce.getServletContext();
        //2.往域中加入屬性,但是名稱仍採用servletContext,此時就是替換
        servletContext.setAttribute("servletContext","demo");
        System.out.println("監聽到了物件的銷燬");
        //3.移除屬性
        servletContext.removeAttribute("servletContext");
    }
}

第三步:在web.xml中配置監聽器

<!--配置監聽器-->
<listener>
    <listener-class>com.cnblogs.gonghr.listener.ServletContextListenerDemo</listener-class>
</listener>

<!--配置監聽器-->
<listener>
    <listener-class>com.cnblogs.gonghr.listener.ServletContextAttributeListenerDemo</listener-class>
</listener>

第四步:測試結果

image

相關文章