22-05-31 西安 javaweb(12) 监听器listener和过滤器filter

服务器三大组件:

  1. servlet,处理请求和响应
  2. filter,过滤请求和响应
  3. 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,它不能直接向客户端生成响应,只是拦截已有的请求,对不需要或不符合的信息资源进行预处理。

  1. 创建一个类,实现Filter接口,并重写方法
  2. 在web.xml中进行注册
  3. 使用过滤器中的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资源有效果

  1.  <url-pattern>可以写多个
  2.  *要么出现在最前面,要么最后面
  3.  这三种语法必须单独使用,不能混合使用

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集合,以当前线程对象为键,以数据为值,可以将连接对象和当前线程进行绑定,这样在当前线程中,获取的连接对象都是同一个。

  1. void set(Object object):存储数据
  2. Object get():获取存储的数据
  3. 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() {

    }
}

未登录的情况下,去访问订单功能,会直接跳转到登录页面,并且提示也变为“订单功能清先登录”,比较友好!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值