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

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="//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="//xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="//xmlns.jcp.org/xml/ns/javaee
                      //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

Tags: