Filter 過濾器以及項目中事物的管理

目錄

1.什麼是 Filter 過濾器

2.Filter 的初體驗

3.Filter 的生命週期

4.FilterConfig 類

5.FilterChain 過濾器鏈

6.Filter 的攔截路徑

7.補充:ThreadLocal 的使用

8.項目中如何通過 Filter 和 ThreadLocal 組合管理事務


1.什麼是 Filter 過濾器

1、 Filter 過濾器它是 JavaWeb 的三大組件之一 三大組件分別是: Servlet 程序、 Listener 監聽器、 Filter 過濾器

2、 Filter 過濾器它是 JavaEE 的規範。 也就是接口

3、 Filter 過濾器它的作用是: 攔截請求, 過濾響應。

攔截請求常見的應用場景有:

1、 權限檢查

2、 日記操作

3、 事務管理

……等等
 

 

2.Filter 的初體驗

要求:web 工程下, 有一個 admin 目錄。 這個 admin 目錄下的所有資源(html 頁面、 jpg 圖片、 jsp 文件、 等等) 都必
須是用戶登錄之後才允許訪問

Filter工作流程圖

代碼示例:

public class AdminFilter implements Filter {
        /**
         * doFilter 方法, 專門用於攔截請求。 可以做權限檢查
         */
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
            HttpSession session = httpServletRequest.getSession();
            Object user = session.getAttribute("user");
            // 如果等於 null, 說明還沒有登錄
            if (user == null) {
                servletRequest.getRequestDispatcher("/login.jsp").forward(servletRequest,servletResponse);
                return;
            } else {
            // 讓程序繼續往下訪問用戶的目標資源
                filterChain.doFilter(servletRequest,servletResponse);
            }
        }
    }

web.xml中的配置

<!--filter 標籤用於配置一個 Filter 過濾器-->
<filter>
    <!--給 filter 起一個別名-->
    <filter-name>AdminFilter</filter-name>
    <!--配置 filter 的全類名-->
    <filter-class>com.atguigu.filter.AdminFilter</filter-class>
</filter><!--filter-mapping 配置 Filter 過濾器的攔截路徑-->
<filter-mapping>
    <!--filter-name 表示當前的攔截路徑給哪個 filter 使用-->
    <filter-name>AdminFilter</filter-name>
    <!--url-pattern 配置攔截路徑
    / 表示請求地址爲: http://ip:port/工程路徑/ 映射到 IDEA 的 web 目錄
    /admin/* 表示請求地址爲: http://ip:port/工程路徑/admin/*
    -->
    <url-pattern>/admin/*</url-pattern>
</filter-mapping>

Filter 過濾器的使用步驟:

1、 編寫一個類去實現 Filter 接口

2、 實現過濾方法 doFilter()

3、 到 web.xml 中去配置 Filter 的攔截路徑

 

3.Filter 的生命週期

Filter 的生命週期包含幾個方法

1、 構造器方法

2、 init 初始化方法

第 1, 2 步, 在 web 工程啓動的時候執行(Filter 已經創建)

3、 doFilter 過濾方法

第 3 步, 每次攔截到請求, 就會執行

4、 destroy 銷燬

第 4 步, 停止 web 工程的時候, 就會執行(停止 web 工程, 也會銷燬 Filter 過濾器)

 

4.FilterConfig 類

Tomcat 每次創建 Filter 的時候, 也會同時創建一個 FilterConfig 類, 這裏包含了 Filter 配置文件的配置信息。

FilterConfig 類的作用是獲取 filter 過濾器的配置內容

1、 獲取 Filter 的名稱 filter-name 的內容

2、 獲取在 Filter 中配置的 init-param 初始化參數

3、 獲取 ServletContext 對象
 

代碼示例:

 @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("2.Filter 的 init(FilterConfig filterConfig)初始化");
// 1、 獲取 Filter 的名稱 filter-name 的內容
        System.out.println("filter-name 的值是: " + filterConfig.getFilterName());
// 2、 獲取在 web.xml 中配置的 init-param 初始化參數
        System.out.println("初始化參數 username 的值是: " + filterConfig.getInitParameter("username"));
        System.out.println("初始化參數 url 的值是: " + filterConfig.getInitParameter("url"));
// 3、 獲取 ServletContext 對象
        System.out.println(filterConfig.getServletContext());
    }

web.xml

<!--filter 標籤用於配置一個 Filter 過濾器-->
<filter>
    <!--給 filter 起一個別名-->
    <filter-name>AdminFilter</filter-name>
    <!--配置 filter 的全類名--><filter-class>com.atguigu.filter.AdminFilter</filter-class>
    <init-param>
        <param-name>username</param-name>
        <param-value>root</param-value>
    </init-param>
    <init-param>
        <param-name>url</param-name>
        <param-value>jdbc:mysql://localhost3306/test</param-value>
    </init-param>
</filter>

 

5.FilterChain 過濾器鏈

FilterChain 就是過濾器鏈(多個過濾器如何一起工作)

 

6.Filter 的攔截路徑

--精確匹配
<url-pattern>/target.jsp</url-pattern>
以上配置的路徑, 表示請求地址必須爲: http://ip:port/工程路徑/target.jsp

--目錄匹配
<url-pattern>/admin/*</url-pattern>
以上配置的路徑, 表示請求地址必須爲: http://ip:port/工程路徑/admin/*

--後綴名匹配
<url-pattern>*.html</url-pattern>
以上配置的路徑, 表示請求地址必須以.html 結尾纔會攔截到

<url-pattern>*.do</url-pattern>
以上配置的路徑, 表示請求地址必須以.do 結尾纔會攔截到

<url-pattern>*.action</url-pattern>
以上配置的路徑, 表示請求地址必須以.action 結尾纔會攔截到

Filter 過濾器它只關心請求的地址是否匹配, 不關心請求的資源是否存在

 

7.補充:ThreadLocal 的使用

ThreadLocal 的作用, 它可以解決多線程的數據安全問題。

ThreadLocal 它可以給當前線程關聯一個數據(可以是普通變量, 可以是對象, 也可以是數組, 集合)

ThreadLocal 的特點:

1、 ThreadLocal 可以爲當前線程關聯一個數據。 (它可以像 Map 一樣存取數據, key 爲當前線程)

2、 每一個 ThreadLocal 對象, 只能爲當前線程關聯一個數據, 如果要爲當前線程關聯多個數據, 就需要使用多個ThreadLocal 對象實例。

3、 每個 ThreadLocal 對象實例定義的時候, 一般都是 static 類型

4、 ThreadLocal 中保存數據, 在線程銷燬後。 會由 JVM 虛擬自動釋放。

代碼示例:

ThreadLocalTest類

public class ThreadLocalTest {
    // public static Map<String,Object> data = new Hashtable<String,Object>();
    public static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();
    private static Random random = new Random();
    public static class Task implements Runnable {
        @Override
        public void run() {
// 在 Run 方法中, 隨機生成一個變量(線程要關聯的數據) , 然後以當前線程名爲 key 保存到 map 中
            Integer i = random.nextInt(1000);
// 獲取當前線程名
            String name = Thread.currentThread().getName();
            System.out.println("線程["+name+"]生成的隨機數是: " + i);
// data.put(name,i);
            threadLocal.set(i);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } n
            ew OrderService().createOrder();
// 在 Run 方法結束之前, 以當前線程名獲取出數據並打印。 查看是否可以取出操作
// Object o = data.get(name);
            Object o = threadLocal.get();
            System.out.println("在線程["+name+"]快結束時取出關聯的數據是: " + o);
        }
    } 
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++){
            new Thread(new Task()).start();
        }
    }
}

OrderService 和 OrderDao類

public class OrderService {
    public void createOrder(){
        String name = Thread.currentThread().getName();
        System.out.println("OrderService 當前線程[" + name + "]中保存的數據是: " +
                ThreadLocalTest.threadLocal.get());
        new OrderDao().saveOrder();
    }
} 
public class OrderDao {
    public void saveOrder(){
        String name = Thread.currentThread().getName();
        System.out.println("OrderDao 當前線程[" + name + "]中保存的數據是: " +
                ThreadLocalTest.threadLocal.get());
    }
}

 

8.項目中如何通過 Filter 和 ThreadLocal 組合管理事務

1.JdbcUtils 工具類的修改

public class JdbcUtils {

    private static DruidDataSource dataSource;
    private static ThreadLocal<Connection> conns = new ThreadLocal<Connection>();


    static {
        try {
            Properties properties = new Properties();
            //讀取 jdbc.properties 屬性配置文件
            InputStream inputStream = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
            //從流中加載數據
            properties.load(inputStream);
            //創建數據庫連接池
            dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    /**
     * 獲取數據庫連接池中的連接
     * 並實現事務管理
     *
     * @return 如果返回 null 說明獲取連接失敗
     */
    public static Connection getConnection() {

        Connection connection = conns.get();

        if (connection == null) {
            try {

                connection = dataSource.getConnection();

                //保存到ThreadLocal中,供後面的jdbc操作
                conns.set(connection);

                //設置爲手動管理
                connection.setAutoCommit(false);

            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        return connection;

    }

    /**
     * 提交事務,並關閉數據庫
     *
     */
    public static void commitAndClose() {
        Connection connection = conns.get();
        //如果不等於null,說明以前使用過連接
        if(connection != null){

            try {
                connection.commit();
            } catch (SQLException e) {
                e.printStackTrace();
            }finally {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

        }
        conns.remove();
    }

    /**
     * 回滾事務,並關閉數據庫
     *
     */
    public static void rollbackAndClose() {
        Connection connection = conns.get();
        //如果不等於null,說明以前使用過連接
        if(connection != null){

            try {
                connection.rollback();
            } catch (SQLException e) {
                e.printStackTrace();
            }finally {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

        }
        conns.remove();

    }


}

2.修改 BaseDao 將所有異常都拋出

Dao

/**
 * 訪問數據的 DAO 接口
 * 裏邊定義好訪問數據表的各種方法
 * @author LIXICHEN
 * @create 2020-04-20 17:43
 * @param T :DAO 處理的實體類的類型
 * @author LIXICHEN
 * @create 2020-04-13 15:59
 */
public interface DAO<T> {


    /**
     * 批量處理方法
     *
     * @param connection 數據庫連接
     * @param sql        SQL 語句
     * @param args       填充佔位符的Object [] 類型的可變參數
     */
    void batch(Connection connection, String sql, Object[]... args);

    /**
     * 返回具體的一個值 例如總人數,email
     *
     * @param connection 數據庫連接
     * @param sql        SQL 語句
     * @param args       填充佔位符的可變參數
     * @param <E>
     * @return
     * @throws SQLException
     */
    <E> E getForValue(Connection connection, String sql, Object... args);

    /**
     * 返回 T 的集合
     *
     * @param connection 數據庫連接
     * @param sql        SQL 語句
     * @param args       填充佔位符的可變參數
     * @return
     * @throws SQLException
     */
    List<T> getForList(Connection connection, String sql, Object... args);


    /**
     * 返回一個 T 的對象
     *
     * @param connection 數據庫連接
     * @param sql        SQL 語句
     * @param args       填充佔位符的可變參數
     * @return
     * @throws SQLException
     */
    T get(Connection connection, String sql, Object... args);


    /**
     * INSERT,UPDATE,DELETE
     *
     * @param connection : 數據庫連接
     * @param sql        :SQL 語句
     * @param args       :填充佔位符的可變參數
     * @return            如果返回-1,說明執行失敗,返回其他表示影響的行數
     */
    int update(Connection connection, String sql, Object... args);

}

BaseDaoImpl

public class BaseDaoImpl<T> implements DAO<T> {

    private QueryRunner queryRunner = null;
    private Class<T> type;

    public BaseDaoImpl() {
        queryRunner = new QueryRunner();
        type = ReflectionUtils.getSuperGenericType(getClass());
    }

    @Override
    public void batch(Connection connection, String sql, Object[]... args){

        try {
            queryRunner.batch(connection, sql, args);
        } catch (SQLException e) {
            e.printStackTrace();
            throw  new RuntimeException(e);
        }

    }

    @Override
    public <E> E getForValue(Connection connection, String sql, Object... args){

        try {
            return (E) queryRunner.query(connection, sql, new ScalarHandler(), args);
        } catch (SQLException e) {
            e.printStackTrace();
            throw  new RuntimeException(e);
        }
    }

    @Override
    public List<T> getForList(Connection connection, String sql, Object... args){
        try {

            return (List<T>) queryRunner.query(connection, sql, new BeanListHandler<>(type), args);
        } catch (SQLException e) {
            e.printStackTrace();
            throw  new RuntimeException(e);
        }
    }

    @Override
    public T get(Connection connection, String sql, Object... args){
        try {
            return queryRunner.query(connection, sql, new BeanHandler<>(type), args);
        } catch (SQLException e) {
            e.printStackTrace();
            throw  new RuntimeException(e);
        }
    }

    @Override
    public int update(Connection connection, String sql, Object... args){

        try {
            return queryRunner.update(connection, sql, args);
        } catch (SQLException e) {
            e.printStackTrace();
            throw  new RuntimeException(e);
        }
    }



}

3.使用 Filter 過濾器統一給所有的 Service 方法都加上 try-catch。 來進行實現事物的管理。

原理圖

Filter類代碼

public class TransactionFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        try{

            filterChain.doFilter(servletRequest,servletResponse);
            JdbcUtils.commitAndClose();//提交事務
        }catch(Exception e){
            JdbcUtils.rollbackAndClose();//回滾事務
            e.printStackTrace();
            //把異常拋出到Tomcat,顯示錯誤頁面
            throw new RuntimeException(e);
        }

    }

    @Override
    public void destroy() {

    }
}

web.xml代碼

     <filter>
        <filter-name>TransactionFilter</filter-name>
        <filter-class>com.atmusic.filter.TransactionFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>TransactionFilter</filter-name>
        <!-- /* 表示當前工程下所有請求 -->
        <url-pattern>/*</url-pattern>
    </filter-mapping>

把 BaseServlet 中的異常往外拋給 Filter 過濾器

public abstract class BaseServlet extends HttpServlet {

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

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //解決post請求額中文亂碼問題
        req.setCharacterEncoding("UTF-8");
        String action = req.getParameter("action");

        //利用反射優化if-else代碼
        try {
            //通過反射獲取函數名稱
            Method method = this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);

            //調用函數
            method.invoke(this,req,resp);

        } catch (Exception e) {
            e.printStackTrace();
            //把異常拋給Filter過濾器
            throw new RuntimeException(e);
        }

    }

}

4.將所有異常都統一交給 Tomcat, 讓 Tomcat 展示友好的錯誤信息頁面

web.xml的配置

    <!--服務器出錯後,跳轉的頁面-->
    <error-page>
        <!--錯誤類型-->
        <error-code>500</error-code>
        <!--跳轉的頁面-->
        <location>/pages/error/error500.jsp</location>
    </error-page>

    <!--服務器出錯後,跳轉的頁面-->
    <error-page>
        <!--錯誤類型-->
        <error-code>404</error-code>
        <!--跳轉的頁面-->
        <location>/pages/error/error404.jsp</location>
    </error-page>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章