过滤器Filter:
啥是过滤器?
在客户端发请求的时候,会先经过过滤器,过滤器会有放行语句,才会向后执行,执行完毕响应的时候还会输出一句话
代码实现:
没有过滤器的版本:
此时输出demo01正常工作并且成功跳转
有过滤器的版本:
创建过滤器:
因为我们要建立过滤器,所以要遵守过滤器规范,实现过滤器的接口,并且重写方法
过滤器也有初始化,服务,销毁三个过程
我们主要的代码还是要写在服务里面的
正常情况应该是被拦截——>输出helloA(servlet执行结束后返回响应)——>输出helloA2
用注解配置拦截:
还可以用配置文件拦截:
测试:
过滤器拦截所有请求:
可以使用通配符注解来完成
访问01:
访问02:
过滤器链(过滤器可以干什么?):
可以设置多层过滤器,让每层过滤器完成自己的功能,完成后再给servlet进行处理
‘
输出结果:
注意:过滤器链的执行顺序是文件命名顺序从上到下
过滤器对于我们现在项目的实际应用:
之前我们设置编码是在核心控制器中完成的,因为请求最先被过滤器拦截,所以我们可以把设置编码这个工作交给第一层过滤器去完成
而且我们还可以把utf-8设置为默认值,如果用户想改其他的编码方式,可以在初始化的时候获取再次设置编码
所以现在的控制器的设置编码就可以去掉了
测试正常:
总结:
事务管理:
我们现在的事务管理模式(我们没有写,但是老师现在的是)
所以我们需要对事务管理结构进行重新编写
1.尝试在service层进行事务管理:
2.用过滤器进行事务管理:
为什么不在service进行管理呢?
因为我们的service可能会有很多个,如果这样子,service每个功能的代码都需要进行事务管理,会非常麻烦,最终决定在过滤器进行事务管理
如果使用过滤器会遇到什么问题呢?
因为我们事务管理需要进行connection.setAutoCommit,和 connection.commit操作,所以包保证三个DAO组件使用的都是一个connection
如何去解决?
比如,现在一个工厂里有三个员工(DAO123),但是老板只给发了一套工具(connection),那么这三个人如何去工作?
可以设置一个纽带(ThreadLocal),员工1看看溴代上有没有工具,如果没有工具,就去拿一个工具并且使用,使用完毕后放回纽带上(ThreadLocal.set(connection)),第二个人如果想使用,先看看纽带上有没有工具,如果有就拿工具使用(ThreadLocal.get(connection)),这样子就可以解决只有一个connection的问题了
代码实现:
我们需要在这边实现关闭自动提交,手动提交,是否回滚事务,所以我们可以自己写一个实现类用于存放操作的代码,而且我们还可以在这里放set.connection和get.connection
设置一个ThreadLocal用于存放connection
public class TransactionManager {
private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
//开启事务
public void beginTrans() throws Exception {
//先获取threadLocal里面有没有connection
Connection connection = threadLocal.get();
//如果获取的为空,说明当前还没有创建connection
if (connection == null){
//创建connection
connection = JDBCutil.getConnection();
//放到threadLocal里
threadLocal.set(connection);
}
//如果connection不为空,此时关闭自动提交,就是开启事务
connection.setAutoCommit(false);
}
//提交事务
public void commit() throws Exception {
//先获取threadLocal里面有没有connection
Connection connection = threadLocal.get();
//如果获取的为空,说明当前还没有创建connection
if (connection == null){
//创建connection
connection = JDBCutil.getConnection();
//放到threadLocal里
threadLocal.set(connection);
}
//如果connection不为空,进行事务提交
connection.commit();
}
//回滚数据
public void rollback() throws Exception {
//先获取threadLocal里面有没有connection
Connection connection = threadLocal.get();
//如果获取的为空,说明当前还没有创建connection
if (connection == null){
//创建connection
connection = JDBCutil.getConnection();
//放到threadLocal里
threadLocal.set(connection);
}
//如果connection不为空,回滚数据
connection.rollback();
}
}
但是我们现在发现,里面有很多代码都是重复的,可不可以把重复的集成起来
可以集成到JDBCutil里面
现在的JDBCutil:
我们可以把判断ThreadLocal里有没有connection放到这里
如果有,直接返回connection
如果没有就在这里面调用方法创造一个connection放到ThreadLocal里
1.改名字:
2.创建真正的获取连接方法
此时外层就需要更改:
帮助我们判断ThreadLocal里有没有connection已经在JDBCutil里做好了,此时只需要进行事务管理就可以了
public class TransactionManager {
//开启事务
public static void beginTrans() throws Exception {
//如果connection不为空,此时关闭自动提交,就是开启事务
JDBCutil.getConnection().setAutoCommit(false);
}
//提交事务
public static void commit() throws Exception {
//如果connection不为空,进行事务提交
JDBCutil.getConnection().commit();
}
//回滚数据
public static void rollback() throws Exception {
//如果connection不为空,回滚数据
JDBCutil.getConnection().rollback();
}
}
此时我们的过滤器就可以起作用了
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try{
//开启事务
TransactionManager.beginTrans();
//放行
filterChain.doFilter(servletRequest,servletResponse);
//提交数据
TransactionManager.commit();
}catch (Exception e){
e.printStackTrace();
try {
//如果有异常,需要回滚
TransactionManager.rollback();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
但是现在还有问题,我们ThreadLocal的connection还没有关闭
应该是先开启事务
放行进行执行代码
如果没有异常,就提交并且关闭事务
如果有异常,就回滚并且关闭事务
我们也可以在JDBCutil里集成
注意:这里关闭的connection只限于关闭ThreadLocal里的connection,其他的关闭result或者preparementStatement..........会用之前的方法在BaseDAO里进行关闭
JDBCutil:
TranscationManager(commit和rollback需要修改):
我们现在的流程是
过滤器捕获
事务开始(此时已经有connection在thredlocal里)
过滤器放行
进入FruitController执行index方法
之前我们需要在这边获取connection并且传给下一层,现在已经在过滤器被创建好了,所以不需要了,关闭也不需要了
进入FruitService传参去掉
进入FruitDAO传参去掉
进入baseDAO
此时需要使用JDBCutil里的getConnection方法,如果里面有,就正常进行使用,如果没有就会去创建并且使用
此时执行程序
可以执行,那我们对于所有的方法都进行修改尝试以下
这里修正一下之前的bug
测试的时候,del方法总是执行不出来,method.invoke的时候返回空,原因是之前写的index.html点击删除链接之后跳转的是del.do,但是现在已经没有del.do了,应该跳转fruit.do
修改后目前测试功能正常
看看这个时候访问index页面对数据库的两个操作是否是同一个connection
是同一个
现在的问题:
现在的功能都正常,获取的数据库连接也都是对的
但是,如果我们程序真的出错了,我们可以进行事务回滚吗?
不能,因为现在我们的BaseDAO里有trycatch,有问题的话在DAO层就被拦截下来了,过滤器肯定检测不到
那我们如何做?——自己定义一个异常,并且向外抛出,一直抛到过滤器层就可以了
对BaseDAO的修改:
新建一个异常类,如果BaseDAO出了问题,就抛这个异常
给BaseDAO里每个方法都加这个异常向外抛的语句
现在我们BaseDAO的异常都向上抛给service,service没有处理异常的语句向上抛给controller,controller现在也没有处理异常的语句,向上跑给控制器,控制器里有异常处理语句
对DispacterServlet的修改:
新建一个DispacterServlet异常类
现在DispacterServlet继续向上抛异常,会被过滤器捕捉到,进行回滚处理
测试:
现在我们进行添加的时候强行改变id为6的价格,故意写错updatesql语句,看看会不会回滚事务
结果:
成功进行了回滚,没有成功添加
问题:之前改basedao代码把抛出异常写在finally里面了,应该在catch里面
修改后程序正常运行
ThreadLocal到底是个啥?
监听器是干什么的?
用代码进行测试:
配置文件:
因为监听器是被动监听的,所以不用像之前一样配置的十分麻烦
此时在tomcat启动好之前就监听了
tomcat关闭时也在监听
监听器对于我们现在的项目有什么优化呢?
我们现在的DispatcherServlet里是中央控制器初始化的时候才会加载BeanFactory,这样子是非常不好的,作为控制注入描述类与类关系的bean工厂我们希望越早进行实现🈷好,那我们的监听器在tomcat还没有启动好的时候就会执行里面的代码,我们可以把创建bean容器的代码放在监听器中,需要的时候直接获取就好
创建监听器:
我们是在中央控制器初始化时要使用IOC容器
此时运行程序正常
注意:中央控制器应该是MVC的内容,而创建IOC容器是整个应用程序应该做的,所以把他们分开写比较好
关于配置文件写法的改动:
之前我们的配置文件名称是写在ClassPathXmlApplicationContext里的,是固定写死的,这样子比较不好,可以设置为从web.xml里进行设置
ClassPathXmlApplicationContext的修改:
如何获取参数——在监听器内获取:
在web.xml写出配置文件的名称(路径):
这样子获取配置文件路径是动态的,更好一点