尚硅谷JavaWeb笔记——书城项目(第九阶段:添加过滤器)
文章目录
- 第九阶段-使用Filter过滤器
- 使用Filter过滤器拦截
- ThreadLocal的使用
- 使用Filter和ThreadLocal组合管理事务
- 使用ThreadLocal来获取数据库连接
- 修改JDBC工具类
- **获取数据库连接**
- **数据库提交与关闭**
- **数据库事物回滚与关闭**
- 使用FIlter过滤器统一处理数据库事物
- 将所有异常都统一交给Tomcat
第九阶段-使用Filter过滤器
使用Filter过滤器拦截
需求:如果想查看后台数据,需要用户登陆后才能访问。
需要拦截/pages/manager/所有内容,实现权限检查
public class ManagerFilter implements Filter {
public void destroy() { }
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
HttpServletRequest httpServletRequest = (HttpServletRequest) req;
HttpSession session = httpServletRequest.getSession();
Object user = session.getAttribute("user");
if (user == null) {
req.getRequestDispatcher("/pages/user/login.jsp").forward(req, resp);
}else {
chain.doFilter(req, resp);
}
chain.doFilter(req, resp);
}
public void init(FilterConfig config) throws ServletException { }
}
需要在web.xml中追加过滤器的配置信息
<filter>
<filter-name>ManagerFilter</filter-name>
<filter-class>filter.ManagerFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ManagerFilter</filter-name>
<url-pattern>/pages/manager/*</url-pattern>
<url-pattern>/manager/bookServlet</url-pattern>
</filter-mapping>
这里为了防止用户使用Servlet程序进入后台,还需要追加一个拦截地址。
ThreadLocal的使用
ThreadLocal的作用:解决多线程数据安全问题
ThradLocal可以给当前线程关联一个数据(普通变量,对象,数组或集合等)
ThreadLocal的特点
ThreadLocal可以为当前线程关联一个数据(可以像Map一样存取数据,key为当前线程)Map关联线程名方法如下:
public static Map<String,Object> data = new Hashtable<String,Object>();
public static class Task implements Runnable {
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println("在线程[" +name + "]保存的值是:bbj" );
threadLocal.set("bbj");
// 在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();
}
new OrderService().createOrder(); // 在该线程下访问OrderService,模拟多线程
// 在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();
}
Map<String,Object> map = new HashMap<>();
System.out.println(map.get("key"));
System.out.println( threadLocal.get() ); // 不保存,也想取一个有效的值。怎么办,需要你事务准备这个值。
}
上述代码相当于模拟多线程操作,在主函数中创建了3个线程,每个线程都绑定了一个随机数。效果如图所示
- 每一个线程对应了一个随机数值,在该线程流运行的过程中,该线程途径的任何操作都有唯一的随机数值
每一个ThreadLocal对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用多个ThreadLocal对象实例。
使用
ThreadLocal
来替换上述代码public static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>(){
// 默认值
@Override
protected Object initialValue() {
return "童伟的默认值";
}
};
// 这里只有一个泛型——value值的类型
threadLocal.set("1");//向当前线程中设置唯一的的数据
Object o = threadLocal.get(); // 获取关联的唯一数据
- **小结:**刚刚使用了一个线程安全的静态变量的hashtable对象在多线程情况下存取数据,没有发生线程安全问题。使用共享的静态ThreadLocal对象在多线程情况下存取数据也不存在线程安全问题
- 每个ThreadLocal对象实例定义时,一般都是static类型
- ThreadLocal中保存的数据,在线程销毁后,会由JVM虚拟机释放
使用该方法最主要的原因是为了线程安全考虑。由于在分布式多线程环境下,如果希望处理数据库事务时能有回滚操作,就必须保证操作前后是针对于同一个数据库连接进程。因此,考虑到ThreadLocal
的特性,这里可以在/utils/中的的JDBCUtils
定义一个静态变量
private static ThreadLocal<Connection> conns = new ThreadLocal<Connection>();//value值类型时一个连接
使用Filter和ThreadLocal组合管理事务
回顾数据库事务:确保所有操作要么都成功,要么都失败。
Connection conn = JDBCUtils.getConnection();
try{
conn.setAutoCommit(false); //设置为手懂管理实务
执行一些列jdbc操作
conn.commit();//手动提交事务
}catch(Exception e){
conn.rollback(); // 如果有异常就会滚事务
}
⚠️ 如果要确保所有操作都在一个事务哪,就必须要确保,所有操作都在同一个Connection连接对象
问:如何确保所有操作都使用同一个Connection连接对象?
答:ThreadLocal线程绑定连接
使用ThreadLocal来获取数据库连接
可以使用ThreadLocal对象,来确保所有操作都适用同一个Connection对象。但所有操作都必须在同一个线程中完成,这个条件是满足的。由此可以得到如下分析示意图
说明:当创建了一个数据库连接后,就将其存储在当前线程的线程域中,之后设置事务,执行操作以及关闭时都从该线程域中获取对应的数据库连接,从而保证了前后都是同一个数据库连接(事务处理要求)
修改JDBC工具类
获取数据库连接
如果是第一次获取,则需要先从数据库连接池中获取,并设置提交方式。如果不是,则直接从线程域中得到连接。
private static final ThreadLocal<Connection> conns = new ThreadLocal<Connection>();//设置线程域对象
public static Connection getConnection3Druid() throws SQLException{
Connection conn = conns.get(); //先从当前线程域中获取,如果获取不了(说明是第一次获取)再创建
if (conn == null) {
// 将从数据库连接池中获取到连接保存到ThreadLocal对象中,供后面的jdbc操作使用 相对最后一次保存有效!
conns.set(source.getConnection());
conn = conns.get(); //以后所有的连接都获取唯一的
conn.setAutoCommit(false);// 设置为手动管理事务
}
return conn;
}
数据库提交与关闭
直接获取当前数据库连接,如果连戒值非空,则说明之前有过连接操作,直接从线程域中获取,然后再执行事务提交操作,并在最后关闭数据库连接(这里一定要把异常抛出去,让tomcat知道出现了异常)
/** * 提交事务,并关闭释放连接 */
public static void commitAndClose() {
Connection conn = conns.get();
if (conn != null) { // 如果不等于null,说明 之前使用过连接,操作过数据库
try {
conn.commit();// 提交 事务
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
try {
conn.close();// 关闭连接,资源资源
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
// 一定要执行remove操作,否则就会出错。(因为Tomcat服务器底层使用了线程池技术)
conns.remove();
}
数据库事物回滚与关闭
类似于事务提交,区别在于
/** * 回滚事务,并关闭释放连接 */
public static void rollbackAndClose() {
Connection conn = conns.get();
if (conn != null) { // 如果不等于null,说明 之前使用过连接,操作过数据库
try {
conn.rollback();// 回滚事务
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
try {
conn.close();// 关闭连接,资源资源
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
⚠️由于上述代码中包含有关闭数据库连接的操作,因此需要在baseDao
中删除数据库连接关闭的代码段,同时还要将捕获的异常外抛。Dao层的异常一定要外抛,这样外层就知道当前存在异常好执行回滚操作。
以update
方法为例
public int update(String sql, Object ... args) {
System.out.println(" BaseDao 程序在[" +Thread.currentThread().getName() + "]中");
Connection conn = null;
try {
conn = JDBCUtils.getConnection3Druid();
System.out.println(conn);
int i = queryRunner.update(conn, sql, args);
System.out.println("影响了" + i +"行");
return i;
} catch (SQLException throwables) { //外抛异常
throwables.printStackTrace();
throw new RuntimeException(throwables);
} // 删除数据库连接关闭操作
// finally{
// JdbcUtils.close(conn);
// }
}
使用FIlter过滤器统一处理数据库事物
由于每一个Servlet程序都需要添加try-catch
来进行提交与事务回滚,因此考虑使用Filter来对所有的Service服务进行事务管理。这是因为在Filter过滤器中,如果希望请求某个目标页面的资源,就会执行doFilter()
方法,一旦doFilter()
方法出现错误,就可以捕获该异常,从而实现事务回滚。
基于上述讨论,由于doFilter()
方法能够简介调用Servlet中的任何方法(对任意的资源进行过滤),因此可以捕获任意的servlet
程序,故可以构造一个TransactionFilter
过滤器,在其中增加try-catch
代码块,且该过滤器的过滤范围为工程内的全部资源,代码如下:
public class TransactionFilter implements Filter {
public void destroy() { }
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
try {
chain.doFilter(req, resp); //由于该处是最有一个filter过滤器,这里的功能是调用目标资源
JDBCUtils.commitAndClose();//提交事物
} catch (Exception e) {
JDBCUtils.rollbackAndClose();//回滚事物
e.printStackTrace();
throw new RuntimeException(e);//把异常抛给Tomcat管理展示友好的错误页面
}
}
public void init(FilterConfig config) throws ServletException { }
}
对应的xml配置信息如下:
<filter>
<filter-name>TransactionFilter</filter-name>
<filter-class>filter.TransactionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TransactionFilter</filter-name>
<!-- /* 表示当前工程下所有请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
此外,还需要注意:对于Service层出现的问题,抛出给上一级是是在BaseServlet
中,因此在对应位置还需要将异常抛给过滤器
将所有异常都统一交给Tomcat
让Tomcat展示友好的错误页面,在web.xml配置错误页面信息
<!--error-page标签配置,服务器出错之后,自动跳转的页面-->
<error-page>
<!--error-code是错误类型-->
<error-code>500</error-code>
<!--location标签表示。要跳转去的页面路径-->
<location>/pages/error/error500.jsp</location>
</error-page>
<!--error-page标签配置,服务器出错之后,自动跳转的页面-->
<error-page>
<!--error-code是错误类型-->
<error-code>404</error-code>
<!--location标签表示。要跳转去的页面路径-->
<location>/pages/error/error404.jsp</location>
</error-page>
还没有评论,来说两句吧...