服务器三大组件:
- servlet,处理请求和响应
- filter,过滤请求和响应
- listener,监听服务器中某个对象的状态
监听器listener
监听器用于监听服务器的状态或者服务器中某个对象的状态
监听器有3种:
1、ServletContextListener
因为ServletContext对象是在服务器启动的时候创建、 在服务器关闭的时候销毁。所以ServletContextListener也可以监听服务器的启动和关闭
ServletContextEvent对象代表从ServletContext对象身上捕获到的事件,通过这个事件对象我们可以获取到ServletContext对象。
public class MyListener implements ServletContextListener {
//1、ServletContextListener,监听ServletContext的状态
public void contextInitialized(ServletContextEvent sce) {
//监听ServletContext初始化
//获取ServletContext,ServeltContext是一个上下文对象
sce.getServletContext();
}
public void contextDestroyed(ServletContextEvent sce) {
//监听ServletContext销毁
}
}
在web.xml中Listener节点配置好
<web-app>
<listener>
<listener-class>com.chen.MyListener</listener-class>
</listener>
</web-app>
2、HttpSessionListener
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
1、过滤器的使用
Filter 并不是一个 Servlet,它不能直接向客户端生成响应,只是拦截已有的请求,对不需要或不符合的信息资源进行预处理。
- 创建一个类,实现Filter接口,并重写方法
- 在web.xml中进行注册
- 使用过滤器中的doFilter()过滤请求和响应
3大组件使用都要在web.xml中注册
<filter> <filter-name>FirstFilter</filter-name> <filter-class>com.atguigu.filter.FirstFilter</filter-class> </filter> <filter-mapping> <filter-name>FirstFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
filter注册的时候和servlet的注册是一模一样的,就是把单词换了一下
2、Filter路径
精确匹配 <url-pattern>/index.html</url-pattern> 只过滤某一个资源
模糊匹配
<url-pattern>/*</url-pattern> 所有请求
<url-pattern>/pages/*</url-pattern> 只对pages下的资源有效果后缀匹配
<url-pattern>*.html</url-pattern> 只对html资源有效果
- <url-pattern>可以写多个
- *要么出现在最前面,要么最后面
- 这三种语法必须单独使用,不能混合使用
servlet匹配
<servlet-name>OrderServlet</servlet-name> 对某个servlet(如整合后的servlet)的所有请求和响应过滤
3、Filter生命周期
初始化:过滤器在服务器启动时执行init()初始化。在整个服务器工作的整个过程中,只执行一次。
第一部分:需要设置初始化参数的时候,可以写到init()方法中。
执行过滤:只有访问符合过滤路径的资源才会执行doFilter(),执行多次。
第二部分:业务处理,拦截要执行的请求,对请求和响应进行处理,一般需要处理的业务操作都在这个方法中实现
销毁:在服务器关闭时销毁,destroy()。在整个服务器工作的整个过程中,只执行一次。
servletContext监听器先被加载,第二个是过滤器,第三个是servlet
4、过滤器链
过滤器可以定义多个,按照过滤器链【FilterChain对象】顺序调用:
多个过滤器同时过滤同一个资源,谁先过过滤,谁后过滤(doFilter)。只跟在web.xml 中 <filter-mapping> 的注册顺序有关
编码过滤器:
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
//处理完编码直接放行
chain.doFilter(req, resp);
}
chain.doFilter()调用过滤器链下一个过滤器的doFilter方法,没有下一个过滤器则直接放行。
在doFilter方法中(其实就是方法栈)
放行前的代码会按照在web.xml 中 <filter-mapping> 的顺序执行
放行后的内容会按照在web.xml 中 <filter-mapping> 的反序执行
6、事务过滤器 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 ThreadLocal<Connection> threadLocal = 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();
springboot整合
1、过滤器使用
定义一个过滤器,需要实现 javax.servlet.Filter 接口。
使用 @Component 将类声明为 Bean ,配合使用 @Order 注解可以设置过滤器执行顺序。
@Component
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//转换为HttpServletRequest类型方便调用某些方法
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
System.out.println("Filter已经截获到用户的请求的地址:" + request.getRequestURI());
// 业务处理...
//放行,调用下一个过滤器或者访问资源
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
这种方式默认拦截路径是/*,拦截所有。如果我们需要进一步拦截具体的则需要我们自己在代码里控制
2、FilterChain 接口
FilterChain 接口定义了 doFilter 方法
public interface FilterChain {
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException;
}
1
3、登录过滤器
值得注意的是,filter过滤路径这里用的是servlet匹配.表示在访问订单相关功能的时,都要先判断用户是否处于登录状态。
@Component
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//转换为HttpServletRequest类型方便调用某些方法
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
System.out.println("Filter已经截获到用户的请求的地址:" + request.getRequestURI());
//登录后才能进入下一步处理,否则直接进入错误提示页面
HttpSession session = request.getSession();
Object user = session.getAttribute("user");
if (user == null) {
//未登录,转发到登录页面,并给出提示
request.setAttribute("errorMsg", "订单功能请先登录");
request.getRequestDispatcher("/UserServlet?method=toLogin").forward(request, response);
} else {
//已登录,放行,调用下一个过滤器或者访问资源
filterChain.doFilter(servletRequest, servletResponse);
}
}
@Override
public void destroy() {
}
}
未登录的情况下,去访问订单功能,会直接跳转到登录页面,并且提示也变为“订单功能清先登录”,比较友好!