目錄
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>