服务器三大组件:
- servlet,处理请求和响应
- filter,过滤请求和响应
- listener,监听服务器中某个对象的状态
监听器listener
监听器用于监听服务器的状态或者服务器中某个对象的状态
监听器有3种:
1、ServletContextListener,监听ServletContext的状态
ServletContextEvent对象代表从ServletContext对象身上捕获到的事件,通过这个事件对象我们可以获取到ServletContext对象。
1、ServletContextListener,监听ServletContext的状态
public void contextInitialized(ServletContextEvent sce) {
//监听ServletContext初始化
//因为ServletContext对象是在服务器启动的时候创建、 在服务器关闭的时候销毁
//所以ServletContextListener也可以监听服务器的启动和关闭
}
public void contextDestroyed(ServletContextEvent sce) {
//监听ServletContext销毁
}
2、HttpSessionListener,监听HttpSession的状态
HttpSessionEvent对象代表从HttpSession对象身上捕获到的事件,通过这个事件对象我们可以获取到触发事件的HttpSession对象。
2、HttpSessionListener,监听HttpSession的状态
public void sessionCreated(HttpSessionEvent se) {
//监听HttpSession的创建
}
public void sessionDestroyed(HttpSessionEvent se) {
//监听HttpSession的销毁
}
3、HttpSessionAttributeListener,监听HttpSession中共享数据的变化情况
3、HttpSessionAttributeListener,监听HttpSession中共享数据的变化情况
public void attributeAdded(HttpSessionBindingEvent sbe) {
//监听HttpSession中添加共享的数据
}
public void attributeRemoved(HttpSessionBindingEvent sbe) {
//监听HttpSession中删除共享的数据
}
public void attributeReplaced(HttpSessionBindingEvent sbe) {
//监听HttpSession中替换共享的数据
}
过滤器Filter:
过滤器本质是一个Filter接口。广义上来说:所有实现了Filter接口的类都是过滤器
filter:作用于浏览器和目标资源之间。什么叫资源?服务器中一切皆资源!
过滤器结果要么拦截要么放行。
过滤器的使用步骤:
- 创建一个类,实现Filter接口,并重写方法
- 在web.xml中进行注册
- 使用过滤器中的doFilter()过滤请求和响应
3大组件使用都要在web.xml中注册,filter注册的时候和servlet的注册是一模一样的,就是把单词换了一下
FirstFilter com.atguigu.filter.FirstFilter FirstFilter /*
filter过滤路径的写法
精确匹配
/index.html 只过滤某一个资源模糊匹配
/* 所有请求
/pages/* 只对pages下的资源有效果后缀匹配
*.html 只对html资源有效果
可以写多个 - *要么出现在最前面,要么最后面
- 这三种语法必须单独使用,不能混合使用
servlet匹配
OrderServlet 对某个servlet(如整合后的servlet)的所有请求和响应过滤
filter的生命周期
初始化:过滤器在服务器启动时执行init()初始化。在整个服务器工作的整个过程中,只执行一次。
执行过滤:只有访问符合过滤路径的资源才会执行doFilter(),执行多次。
销毁:在服务器关闭时销毁,destroy()。在整个服务器工作的整个过程中,只执行一次。
servletContext监听器先被加载,第二个是过滤器,第三个是servlet
过滤器链
过滤器不是只有一个,会把所有过滤器放在一起称为过滤器链(FilterChain对象)。如下是我们的编码过滤器。
多个过滤器同时过滤同一个资源,谁先过过滤,谁后过滤(doFilter)。只跟在web.xml 中
的注册顺序有关
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
//处理完编码直接放行
chain.doFilter(req, resp);
}
doFilter调用过滤器链下一个过滤器的DoFilter方法,没有下一个过滤器则直接放行。
在doFilter方法中(其实就是方法栈)
放行前的代码会按照在web.xml 中的顺序执行
放行后的内容会按照在web.xml 中的反序执行
登录过滤器 LoginFilter
值得注意的是,filter过滤路径这里用的是servlet匹配.表示在访问订单相关功能的时,都要先判断用户是否处于登录状态。
LoginFilter com.atguigu.filter.LoginFilter LoginFilter OrderClientServlet
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
HttpSession session = request.getSession();
Object user = session.getAttribute("user");
if (user == null) {
//未登录,转发到登录页面,并给出提示
request.setAttribute("errorMsg", "订单功能请先登录");
// processTemplate("user/login", request, response);
request.getRequestDispatcher("/UserServlet?method=toLogin").forward(request,response);
} else {
//已登录,放行
chain.doFilter(req, resp);
}
}
未登录的情况下,去访问订单功能(结账),会跳转到登录页面,并且提示也变为“订单功能清先登录”,比较友好
事务过滤器 TransactionFilter
一般情况下,业务逻辑层作为事务处理层,但是每一个功能都需要处理事务,每一个service的方法都需要添加事务的代码。
过滤器作用于浏览器和目标资源之间,放行chain.doFilter()表示servlet的执行,即当前功能的执行因此只需要在过滤器中,对放行chain.doFilter()添加事务的代码,即对所有的功能添加了事务的代码
当然,这个代码改动起来还是比较麻烦的,
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
Connection connection = null;
try {
connection = JDBCUtil.getConnection();
//开启事务
connection.setAutoCommit(false);
chain.doFilter(req, resp);
//没有发生异常,对事务提交
connection.commit();
System.out.println("TransactionFilter-->提交事务");
} catch (Exception e) {
//执行功能过程中发生了异常,对事务进行回滚
try {
connection.rollback();
System.out.println("TransactionFilter-->回滚事务");
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
//重定向到错误页面
response.sendRedirect(request.getContextPath() + "/ErrorServlet");
} finally {
//把连接放回到连接池
JDBCUtil.closeConnection(connection);
}
}
去保证出现异常的时候,之后的代码不再执行,之前的代码回滚。
1、用到的技巧有:在catch子句中手动抛出运行时异常!
在catch里手动抛出异常。throw new RuntimeException(e),加了参数e就会把真实的异常打印出来。
try {
connection = JDBCUtil.getConnection();
o = queryRunner.query(connection, sql, new ScalarHandler(), params);
} catch (SQLException e) {
throw new RuntimeException(e);
}
2、 ThreadLocal 使得事务过滤器中操作的连接对象和BaseDao中操作的连接对象是同一个
在从Filter、Servlet、Service一直到Dao运行的过程中,我们始终都没有做类似new Thread().start()这样开启新线程的操作,所以整个过程在同一个线程中。
ThreadLocal的本质是一个map集合,以当前线程对象为键,以数据为值,可以将连接对象和当前线程进行绑定,这样在当前线程中,获取的连接对象都是同一个。
- void set(Object object):存储数据
- Object get():获取存储的数据
- void remove():删除存储的数据
private static ThreadLocalthreadLocal = new ThreadLocal<>(); public static Connection getConnection(){ Connection connection = null; try { // 一个ThreadLocal对象,在一个线程中只能存储一个数据, //在该线程的任何地方调用get()方法获取到的都是同一个数据 connection = threadLocal.get(); if(connection==null){ connection = dataSource.getConnection(); //跟当前线程绑定,往当前线程共享了这个数据 threadLocal.set(connection); } } catch (SQLException e) { e.printStackTrace(); } return connection; }
当然,这部分只是展示了核心代码。实际上还有好几步要改。
比如释放连接的时候,要删除存储在ThreadLocal的数据,不然下一次用的时候有connection ,但是这个connection已经放回连接池中了,不能再使用了。
connection.close(); threadLocal.remove();



