Java三十五篇:初識Filter
Filter概述
Filter是JavaWeb三大組件之一。
Filter是JavaEE的規(guī)范。
Filter作用是攔截請求,過濾響應(yīng)。攔截請求常用的應(yīng)用場景
權(quán)限檢查
日志記錄
事務(wù)管理、等
Filter 的工作流程圖:

Filter開始之前的體驗
要求:
? ? ? 在web工程目錄下有一個admin文件夾,這個文件夾的資源必須在登陸之后才能允許訪問。
? 思考:
? ? ? 1.如何檢查用戶是否登陸?
? ? ? ? ?由于當(dāng)用于登陸后,信息會被保留到session中。所以查看sessino中是否有用戶登陸信息即可。
? 實現(xiàn):
? ? ? 在jsp文件中加入西面代碼。
? ? ? <%
? ? ? ? ? ? Object user = session.getAttribute("user");
? ? ? ? ? ? //判斷用戶是否為空
? ? ? ? ? ? if(user == null){
? ? ? ? ? ? ? request.getRequestDispatcher("/login.jsp").forward(request,response);
? ? ? ? ? ? ? return ;
? ? ? ? ? ? }
? ? ? ?%>
? 問題:
? ? ? 由于上面代碼是java代碼,html中不能寫,所以這種方法智能針對jsp文件進行設(shè)定。
? ? ? 那么html文件怎么辦呢?那么Filter就登場了。
? ? ?
Filter過濾器的使用
創(chuàng)建Filter的實現(xiàn)類去實現(xiàn)Filter接口
重寫doFilter的方法
到web.xml中配置攔截器的攔截路徑 使用Filter實現(xiàn)登錄頁面的功能。
Filter的生命周期
Filter 的生命周期包含幾個方法
1、 構(gòu)造器方法
2、 init 初始化方法 第 1, 2 步, 在 web 工程啟動的時候執(zhí)行(Filter 已經(jīng)創(chuàng)建)
3、 doFilter 過濾方法 第 3 步, 每次攔截到請求, 就會執(zhí)行
4、 destroy 銷毀 第 4 步, 停止 web 工程的時候, 就會執(zhí)行(停止 web 工程, 也會銷毀 Filter 過濾器)
FilterConfig類
FilterConfig類概述
FilterConfig就是Filter過濾器的的配置類.
? ?
Tomcat啟動,創(chuàng)建Filter的的時候就會創(chuàng)建一個FilterConfig類對象,
這里包含了Filter的配置信息。
FilterConfig類的作用
FilterConfig 類見名知義, 它是 Filter 過濾器的配置文件類。Tomcat 每次創(chuàng)建 Filter 的時候, 也會同時創(chuàng)建一個 FilterConfig 類, 這里包含了 Filter 配置文件的配置信息。
FilterConfig 類的作用是獲取 filter 過濾器的配置內(nèi)容
1、獲取過濾器的名稱即Filter-name標(biāo)簽的值。
2、獲取過濾器的初始參數(shù)即init-param標(biāo)簽的值。
3、獲取ServletContext對象。
FilterChain過濾鏈
概述
FilterChain過濾鏈就是多個過濾器的時候如何執(zhí)行。
如果不是最后一個過濾器,
則按照web.xml中filter-mapping的定義的先后順序往下執(zhí)行。
如果是最后一個過濾器了,則執(zhí)行目標(biāo)資源。

作用
往下繼續(xù)執(zhí)行過濾器
執(zhí)行目標(biāo)資源
當(dāng)FilterChain過濾器有多個Filter時的特點
多個Filter的執(zhí)行順序的先后是由在web.xml中定義的先后(從上到下)順序決定的。注意:由于Filter是根據(jù)URL進行過濾的,最先走到的是filter-mapping標(biāo)簽,所以上面所說的定義的先后順序是指filter-mapping標(biāo)簽的位置順序。
多個Filter執(zhí)行的時候默認是同一個線程目標(biāo)資源也是同一個線程
執(zhí)行的時候是同一個request對象。(因為同一次請求)
Filter攔截器路徑
精確匹配
例 /target.jsp 表示URL地址必須是:http://服務(wù)地址:端口/工程目錄/target.jsp
目錄匹配
例 /admin/* 表示URL地址必須是以:http://服務(wù)地址:端口/工程目錄/admin/ 開頭
后綴名匹配
● 例1
*.jsp
表示URL地址必須以.jsp結(jié)尾
● 例2
*.do
表示URL地址必須以.do結(jié)尾 注:不能是含有/否則錯誤
Filter 過濾器它只關(guān)心請求的地址是否匹配, 不關(guān)心請求的資源是否存在?。?!
1、 ThreadLocal 的使用
ThreadLocal 的作用, 它可以解決多線程的數(shù)據(jù)安全問題。
ThreadLocal 它可以給當(dāng)前線程關(guān)聯(lián)一個數(shù)據(jù)(可以是普通變量, 可以是對象, 也可以是數(shù)組, 集合)
ThreadLocal 的特點:
1、 ThreadLocal 可以為當(dāng)前線程關(guān)聯(lián)一個數(shù)據(jù)。(它可以像 Map 一樣存取數(shù)據(jù), key 為當(dāng)前線程)
2、 每一個 ThreadLocal 對象, 只能為當(dāng)前線程關(guān)聯(lián)一個數(shù)據(jù), 如果要為當(dāng)前線程關(guān)聯(lián)多個數(shù)據(jù), 就需要使用多個ThreadLocal 對象實例。
3、 每個 ThreadLocal 對象實例定義的時候, 一般都是 static 類型
4、 ThreadLocal 中保存數(shù)據(jù), 在線程銷毀后。會由 JVM 虛擬自動釋放。測試類:
publicclass OrderService {
? ? ? ? ? ?public void createOrder() {
? ? ? ? ? ? ? ?String name = Thread.currentThread().getName();
? ? ? ? ? ? ? ?System.out.println("OrderService 當(dāng)前線程[" + name + "]中保存的數(shù)據(jù)是:" +
? ? ? ? ? ? ? ? ? ? ? ?ThreadLocalTest.threadLocal.get());
? ? ? ? ? ? ? ?new OrderDao().saveOrder();
? ? ? ? ? ?}
? ? ? ?}
? ? ? ?publicclass OrderDao {
? ? ? ? ? ?public void saveOrder() {
? ? ? ? ? ? ? ?String name = Thread.currentThread().getName();
? ? ? ? ? ? ? ?System.out.println("OrderDao 當(dāng)前線程[" + name + "]中保存的數(shù)據(jù)是:" +
? ? ? ? ? ? ? ? ? ? ? ?ThreadLocalTest.threadLocal.get());
? ? ? ? ? ?}
? ? ? ?}
? ? ? ?publicclass ThreadLocalTest {
? ? ? ? ? ?// public static Map<String,Object> data = new Hashtable<String,Object>();
? ? ? ? ? ?publicstatic ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();
? ? ? ? ? ?privatestatic Random random = new Random();
? ? ? ? ? ?publicstaticclass Task implements Runnable {
? ? ? ? ? ? ? ?@Override
? ? ? ? ? ? ? ?public void run() {
// 在 Run 方法中, 隨機生成一個變量(線程要關(guān)聯(lián)的數(shù)據(jù)) , 然后以當(dāng)前線程名為 key 保存到 map 中
? ? ? ? ? ? ? ? ? ?Integer i = random.nextInt(1000);
// 獲取當(dāng)前線程名
? ? ? ? ? ? ? ? ? ?String name = Thread.currentThread().getName();
? ? ? ? ? ? ? ? ? ?System.out.println("線程[" + name + "]生成的隨機數(shù)是:" + i);
// data.put(name,i);
? ? ? ? ? ? ? ? ? ?threadLocal.set(i);
? ? ? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ? ? ?Thread.sleep(3000);
? ? ? ? ? ? ? ? ? ?} catch (InterruptedException e) {
? ? ? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ? ? ?new OrderService().createOrder();
// 在 Run 方法結(jié)束之前, 以當(dāng)前線程名獲取出數(shù)據(jù)并打印。查看是否可以取出操作
// Object o = data.get(name);
? ? ? ? ? ? ? ? ? ?Object o = threadLocal.get();
? ? ? ? ? ? ? ? ? ?System.out.println("在線程[" + name + "]快結(jié)束時取出關(guān)聯(lián)的數(shù)據(jù)是:" + o);
? ? ? ? ? ? ? ?}
? ? ? ? ? ?}
? ? ? ? ? ?public static void main(String[] args) {
? ? ? ? ? ? ? ?for (int i = 0; i < 3; i++) {
? ? ? ? ? ? ? ? ? ?new Thread(new Task()).start();
? ? ? ? ? ? ? ?}
? ? ? ? ? ?}
? ? ? ?}
? ?}
3、 使用 Filter 和 ThreadLocal 組合管理事務(wù)
3.1、 使用 ThreadLocal 來確保所有 dao 操作都:在同一個 Connection 連接對象中完 成
原理分析圖:

JdbcUtils 工具類的修改:修 改 BaseDao
publicclass JdbcUtils {
? ? ? ? ? ?privatestatic DruidDataSource dataSource;
? ? ? ? ? ?privatestatic ThreadLocal<Connection> conns = new ThreadLocal<Connection>();
? ? ? ? ? ?static {
? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ?Properties properties = new Properties();
// 讀取 jdbc.properties 屬性配置文件
? ? ? ? ? ? ? ? ? ?InputStream inputStream =
? ? ? ? ? ? ? ? ? ? ? ? ? ?JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
// 從流中加載數(shù)據(jù)
? ? ? ? ? ? ? ? ? ?properties.load(inputStream);
// 創(chuàng)建 數(shù)據(jù)庫連接 池
? ? ? ? ? ? ? ? ? ?dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
? ? ? ? ? ? ? ?} catch (Exception e) {
? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? ?}
? ? ? ? ? ?}
? ? ? ? ? ?/**
? ? ? ? ? ? *獲取數(shù)據(jù)庫連接池中的連接
? ? ? ? ? ?*@return 如果返回 null,說明獲取連接失敗<br/>有值就是獲取連接成功
? ? ? ? ? ?*/
? ? ? ? ? ?public static Connection getConnection() {
? ? ? ? ? ? ? ?Connection conn = conns.get();
? ? ? ? ? ? ? ?if (conn == null) {
? ? ? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ? ? ?conn = dataSource.getConnection();//從數(shù)據(jù)庫連接池中獲取連接
? ? ? ? ? ? ? ? ? ? ? ?conns.set(conn); // 保存到 ThreadLocal 對象中, 供后面的 jdbc 操作使用
? ? ? ? ? ? ? ? ? ? ? ?conn.setAutoCommit(false); // 設(shè)置為手動管理事務(wù)
? ? ? ? ? ? ? ? ? ?} catch (SQLException e) {
? ? ? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?return conn;
? ? ? ? ? ?} /*
? ? ? ? ? ? ? *提交事務(wù),并關(guān)閉釋放連接
? ? ? ? ? ? ?*/
? ? ? ? ? ?public static void commitAndClose() {
? ? ? ? ? ? ? ?Connection connection = conns.get();
? ? ? ? ? ? ? ?if (connection != null) { // 如果不等于 null, 說明 之前使用過連接, 操作過數(shù)據(jù)庫
? ? ? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ? ? ?connection.commit(); // 提交 事務(wù)
? ? ? ? ? ? ? ? ? ?} catch (SQLException e) {
? ? ? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? ? ? ?} finally {
? ? ? ? ? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ? ? ? ? ?connection.close(); // 關(guān)閉連接, 資源資源
? ? ? ? ? ? ? ? ? ? ? ?} catch (SQLException e) {
? ? ? ? ? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?//一定要執(zhí)行 remove 操作,否則就會出錯。(因為 Tomcat 服務(wù)器底層使用了線程池技術(shù))
? ? ? ? ? ? ? ?conns.remove();
? ? ? ? ? ?}
? ? ? ? ? ?/**
? ? ? ? ? ? *回滾事務(wù),并關(guān)閉釋放連接
? ? ? ? ? ? */
? ? ? ? ? ?public static void rollbackAndClose() {
? ? ? ? ? ? ? ?Connection connection = conns.get();
? ? ? ? ? ? ? ?if (connection != null) { // 如果不等于 null, 說明 之前使用過連接, 操作過數(shù)據(jù)庫
? ? ? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ? ? ?connection.rollback();//回滾事務(wù)
? ? ? ? ? ? ? ? ? ?} catch (SQLException e) {
? ? ? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? ? ? ?} finally {
? ? ? ? ? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ? ? ? ? ?connection.close(); // 關(guān)閉連接, 資源資源
? ? ? ? ? ? ? ? ? ? ? ?} catch (SQLException e) {
? ? ? ? ? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?//一定要執(zhí)行 remove 操作,否則就會出錯。(因為 Tomcat 服務(wù)器底層使用了線程池技術(shù))
? ? ? ? ? ? ? ?conns.remove();
? ? ? ? ? ?}
? ? ? ? ? ?/**
? ? ? ? ? ? *關(guān)閉連接,放回數(shù)據(jù)庫連接池
? ? ? ? ? ? *@param conn
? ? ? ? ? ? **/
? ? ? ? ? ?public static void close(Connection conn) {
? ? ? ? ? ? ? ?if (conn != null) {
? ? ? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ? ? ?conn.close();
? ? ? ? ? ? ? ? ? ?} catch (SQLException e) {
? ? ? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?}
? ? ? ? ? ?}
? ? ? ?}
修改 BaseDao
publicabstractclass BaseDao {
? ? ? ? ? ?//使用 DbUtils 操作數(shù)據(jù)庫
? ? ? ? ? ?private QueryRunner queryRunner = new QueryRunner();
? ? ? ? ? ?/**
? ? ? ? ? ? * update() 方法用來執(zhí)行:Insert\Update\Delete 語句
? ? ? ? ? ? * *
? ? ? ? ? ? @return 如果返回-1,說明執(zhí)行失敗<br/>返回其他表示影響的行數(shù)
? ? ? ? ? ? */
? ? ? ? ? ?public int update(String sql, Object... args) {
? ? ? ? ? ? ? ?System.out.println(" BaseDao 程序在[" + Thread.currentThread().getName() + "]中");
? ? ? ? ? ? ? ?Connection connection = JdbcUtils.getConnection();
? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ?return queryRunner.update(connection, sql, args);
? ? ? ? ? ? ? ?} catch (SQLException e) {
? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? ? ? ?thrownew RuntimeException(e);
? ? ? ? ? ? ? ?}
? ? ? ? ? ?}
? ? ? ? ? ?/**
? ? ? ? ? ? * 查詢返回一個 javaBean 的 sql 語句
? ? ? ? ? ? * *
? ? ? ? ? ? @param type 返回的對象類型
? ? ? ? ? ? ?* @param sql 執(zhí)行的 sql 語句
? ? ? ? ? ? * @param args sql 對應(yīng)的參數(shù)值
? ? ? ? ? ? * @param <T> 返回的類型的泛型
? ? ? ? ? ? * @return
? ? ? ? ? ? */
? ? ? ? ? ?public <T> T queryForOne(Class<T> type, String sql, Object... args) {
? ? ? ? ? ? ? ?Connection con = JdbcUtils.getConnection();
? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ?return queryRunner.query(con, sql, new BeanHandler<T>(type), args);
? ? ? ? ? ? ? ?} catch (SQLException e) {
? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? ? ? ?thrownew RuntimeException(e);
? ? ? ? ? ? ? ?}
? ? ? ? ? ?}
? ? ? ? ? ?/**
? ? ? ? ? ? * 查詢返回多個 javaBean 的 sql 語句
? ? ? ? ? ? * *
? ? ? ? ? ? @param type 返回的對象類型
? ? ? ? ? ? ?* @param sql 執(zhí)行的 sql 語句
? ? ? ? ? ? * @param args sql 對應(yīng)的參數(shù)值
? ? ? ? ? ? * @param <T> 返回的類型的泛型
? ? ? ? ? ? * @return
? ? ? ? ? ? */
? ? ? ? ? ?public <T> List<T> queryForList(Class<T> type, String sql, Object... args) {
? ? ? ? ? ? ? ?Connection con = JdbcUtils.getConnection();
? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ?return queryRunner.query(con, sql, new BeanListHandler<T>(type), args);
? ? ? ? ? ? ? ?} catch (SQLException e) {
? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? ? ? ?thrownew RuntimeException(e);
? ? ? ? ? ? ? ?}
? ? ? ? ? ?}
? ? ? ? ? ?/**
? ? ? ? ? ? * 執(zhí)行返回一行一列的 sql 語句
? ? ? ? ? ? * @param sql 執(zhí)行的 sql 語句
? ? ? ? ? ? * @param args sql 對應(yīng)的參數(shù)值
? ? ? ? ? ? * @return
? ? ? ? ? ? */
? ? ? ? ? ?public Object queryForSingleValue(String sql, Object... args) {
? ? ? ? ? ? ? ?Connection conn = JdbcUtils.getConnection();
? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ?return queryRunner.query(conn, sql, new ScalarHandler(), args);
? ? ? ? ? ? ? ?} catch (SQLException e) {
? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? ? ? ?thrownew RuntimeException(e);
? ? ? ? ? ? ? ?}
? ? ? ? ? ?}
? ? ? ?}
? ?}
3.2、 使用 Filter 過濾器統(tǒng)一給所有的 Service 方法都加上 try-catch。來進行實現(xiàn)的 管理。
原理分析圖 :

Filter 類代碼:
publicclass TransactionFilter implements Filter {
? ? ? ? ? ?@Override
? ? ? ? ? ?public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain
? ? ? ? ? ? ? ? ? ?filterChain) throws IOException, ServletException {
? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ?filterChain.doFilter(servletRequest, servletResponse);
? ? ? ? ? ? ? ? ? ?JdbcUtils.commitAndClose();// 提交事務(wù)
? ? ? ? ? ? ? ?} catch (Exception e) {
? ? ? ? ? ? ? ? ? ?JdbcUtils.rollbackAndClose();//回滾事務(wù)
? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? ?}
? ? ? ? ? ?}
? ? ? ?}
在 web.xml 中的配置:
<filter>
<filter-name>TransactionFilter</filter-name>
<filter-class>com.atguigu.filter.TransactionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TransactionFilter</filter-name>
<!-- /* 表示當(dāng)前工程下所有請求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
一定要記得把 BaseServlet 中的異常往外拋給 Filter 過濾器
publicabstractclass BaseServlet extends HttpServlet {
? ? ? ? ? ?@Override
? ? ? ? ? ?protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
? ? ? ? ? ? ? ? ? ?IOException {
? ? ? ? ? ? ? ?doPost(req, resp);
? ? ? ? ? ?}
? ? ? ? ? ?protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
? ? ? ? ? ? ? ? ? ?IOException {
// 解決 post 請求中文亂碼問題
// 一定要在獲取請求參數(shù)之前調(diào)用才有效
? ? ? ? ? ? ? ?req.setCharacterEncoding("UTF-8");
? ? ? ? ? ? ? ?String action = req.getParameter("action");
? ? ? ? ? ? ? ?try {
// 獲取 action 業(yè)務(wù)鑒別字符串, 獲取相應(yīng)的業(yè)務(wù) 方法反射對象
? ? ? ? ? ? ? ? ? ?Method method = this.getClass().getDeclaredMethod(action, HttpServletRequest.class,
? ? ? ? ? ? ? ? ? ? ? ? ? ?HttpServletResponse.class);
// System.out.println(method);
// 調(diào)用目標(biāo)業(yè)務(wù) 方法
? ? ? ? ? ? ? ? ? ?method.invoke(this, req, resp);
? ? ? ? ? ? ? ?} catch (Exception e) {
? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? ? ? ?thrownew RuntimeException(e);// 把異常拋給 Filter 過濾器
? ? ? ? ? ? ? ?}
? ? ? ? ? ?}
? ? ? ?}
3.3、 將所有異常都統(tǒng)一交給 Tomcat, 讓 Tomcat 展示友好的錯誤信息頁面
在 web.xml 中我們可以通過錯誤頁面配置來進行管理。
<!--error-page 標(biāo)簽配置, 服務(wù)器出錯之后, 自動跳轉(zhuǎn)的頁面-->
<error-page>
<!--error-code 是錯誤類型-->
<error-code>500</error-code>
<!--location 標(biāo)簽表示。要跳轉(zhuǎn)去的頁面路徑-->
<location>/pages/error/error500.jsp</location>
</error-page>
<!--error-page 標(biāo)簽配置, 服務(wù)器出錯之后, 自動跳轉(zhuǎn)的頁面-->
<error-page>
<!--error-code 是錯誤類型-->
<error-code>404</error-code>
<!--location 標(biāo)簽表示。要跳轉(zhuǎn)去的頁面路徑-->
<location>/pages/error/error404.jsp</location>
</error-page>
? ? ? ? ? ?
? ? ? ? ?? ? ? ??? ? ?
