Springbootjava学习Spring

九、Listener 监听器& Filter过滤器

2018-10-04  本文已影响48人  圣贤与无赖

一、 Listener监听器

Javaweb中的监听器是用于监听web常见对象HttpServletRequest,HttpSession,ServletContext
作用:
监听web对象的创建与销毁
监听web对象的属性变化
监听session绑定javaBean操作
监听机制的相关概念
事件----一件事情
事件源---产生这件事情的源头
注册监听---将监听器与事件绑定,当事件产生时,监听器可以知道,并进行处理。
监听器---对某件事情进行处理监听的一个对象

Servlet的监听器:

Servlet中的监听器:提供了8个监听器.
一类:监听三个域对象的创建和销毁的监听器.3个
二类:监听三个域对象的属性变更的监听器.(属性添加,属性移除,属性替换)3个.
三类:监听HttpSession对象中的JavaBean的状态的改变.(绑定,解除绑定,钝化和活化)2个

1. 一类监听器:监听三个域对象的创建和销毁的监听器

1.1 ServletContextListener:监听ServletContext对象的创建和销毁.

【方法】

ServletContextListener.png

【问题】
ServletContext对象何时创建和销毁:

【入门案例】
1.编写一个类实现监听器的接口.

public class MyServletContextListener implements ServletContextListener{

    @Override
    /**
     * 监听ServletContext对象的创建的方法:
     * @param sce
     */
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("ServletContext对象被创建了...");
    }

    @Override
    /**
     * 监听ServletContext对象的销毁的方法:
     * @param sce
     */
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("ServletContext对象被销毁了...");
    }

}
  1. 通过配置完成监听器和事件源的绑定.
 <!-- 配置监听器 -->
  <listener>
    <listener-class>com.itheima.weblistener.MyServletContextListener</listener-class>
  </listener>

【企业中应用】

1.2 HttpSessionListener:监听HttpSession对象的创建和销毁的监听器.

【方法】

HttpSessionListener.png

【问题】

【入门】

  1. 编写监听器:
public class MyHttpSessionListener implements HttpSessionListener {

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println("HttpSession对象被创建了...");
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("HttpSession对象被销毁了...");
    }

}

  1. 配置监听器:
<listener>
    <listener-class>com.itheima.weblistener.MyHttpSessionListener</listener-class>
 </listener>

【问题】
1.访问html是否创建session对象? 不会
2.访问一个Servlet是否创建session对象? 不会
3.访问一个jsp是否创建session对象? 会

1.3 ServletRequestListener:监听ServletRequest对象的创建和销毁的监听器

【方法】

ServletRequestListener.png

【问题】
ServletRequest对象何时创建和销毁?

【入门】
1.编写一个监听器

public class MyServletRequestListener implements ServletRequestListener {
    public void requestInitialized(ServletRequestEvent sre)  { 
        System.out.println("ServletRequest被创建了...");
    }
    public void requestDestroyed(ServletRequestEvent sre)  { 
        System.out.println("ServletRequest被销毁了...");
    }
    
}
  1. 配置监听器
 <listener>
    <listener-class>com.itheima.weblistener.MyServletRequestListener</listener-class>
  </listener>

【问题】
1.访问html是否创建request对象? 会
2.访问一个Servlet是否创建request对象? 会
3.访问一个jsp是否创建request对象? 会

2. 二类:监听三个域对象属性变更的监听器

2.1 ServletContextAttributeListener:监听ServletContext对象中的属性变更的监听器
ServletContextAttributeListener.png
2.2 HttpSessionAttributeListener:监听HttpSession对象中的属性变更的监听器
HttpSessionAttributeListener:监听HttpSession对象中的属性变更的监听器.png
2.3 ServletRequestAttributeListener:监听ServletRequest对象中的属性变更的监听器
ServletRequestAttributeListener.png

3. 三类:监听HttpSession中的JavaBean的状态改变的监听器.(绑定,解决绑定,钝化,活化)

三类监听器非常特殊
监听器作用在JavaBean上.JavaBean可以自己感知在session中状态.
这类监听器不用配置.

3.1 HttpSessionBindingListener:监听HttpSession中的JavaBean的绑定和解除绑定的状态.
HttpSessionBindingListener.png
3.2 HttpSessionActivationListener:监听HttpSession中的JavaBean的钝化和活化的状态.
HttpSessionActivationListener.png

sessionDidActivate(HttpSessionEvent se); -- 活化
SessionWillPassivate(HttpSessionEvent se); -- 钝化

通过配置序列化session:

context.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
    maxIdleSwap :1分钟 如果session不使用就会序列化到硬盘.
    directory   :itheima 序列化到硬盘的文件存放的位置.
 -->
<Context>
    <Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
        <Store className="org.apache.catalina.session.FileStore" directory="itheima"/>
    </Manager>
</Context>

4. 监听器的总结:

Servlet的监听器分成三类8个:

5. 案例-定时删除过时订单分析

功能描述:
若一个订单从下单开始超过30分钟未支付,则删除该订单。
分析:
为了这个操作我们需要拿到订单的下单时间和支付状态.然后判断订单是否超过30分钟未支付,若未支付则取消该订单,想实现此功能,还需要使用任务调度功能.要求在项目一启动的时候就可以扫描订单.比如每分钟查找一下,将满足条件的删除掉.

在java中有一个Timer定时器类 Timer.png

步骤分析:
创建一个ServletContext创建与销毁监听器,在ServletContext对象创建时,启动定时扫描器.
在定时器内部实现查询订单及删除订单操作

public void contextInitialized(ServletContextEvent sce) {
        //一旦服务器启动 该定时器就开始扫描
        Timer t=new Timer();
        t.schedule(new TimerTask() {
            @Override
            public void run() {
                //完成订单查询和删除操作即可
                System.out.println(".........");
            }
        }, 1000, 2000);//延迟1秒开始执行,每2秒执行一次
    }

二、Filter过滤器

1. filter介绍及其作用介绍

Filter是sun公司中servlet2.3后增加的一个新功能.
Servlet规范中三个技术 Servlet Listener Filter
在javaEE中定义了一个接口 javax.servlet.Filter来描述过滤器
作用:
通过Filter可以拦截访问web资源的请求与响应操作.
WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。

常用api:

常用api.png

filter入门案例
创建步骤:

  1. 编写filter
    a. 创建一个类实现javax.servlet.Filter接口
    b. 重写接口方法
  2. 编写配置文件
    a. 注册filter
    b. 绑定路径

Filter在web.xml文件中配置的目的:配置拦截什么样的资源。

Filter初始化

<filter>
    <filter-name>demo1Filter</filter-name>
    <filter-class>cn.itcast.web.filter.Demo1Filter</filter-class>
</filter>
<filter-mapping>
    <filter-name>demo1Filter</filter-name>
    <url-pattern>/demo1</url-pattern>
</filter-mapping>

拦截分析:

拦截分析.png

注意:
在Filter的doFilter方法内如果没有执行,那么资源是不会被访问到的。

FilterChain功能介绍
FilterChain 是 servlet 容器为开发人员提供的对象,它提供了对某一资源的已过滤请求调用链的视图。过滤器使用 FilterChain 调用链中的下一个过滤器,如果调用的过滤器是链中的最后一个过滤器,则调用链末尾的资源。

FilterChain.png

2. filter链与生命周期

filter链介绍
多个Filter对同一个资源进行了拦截,那么当我们在开始的Filter中执行 chain.doFilter(request,response)时,是访问下一下Filter,直到最后一个Filter执行时,它后面没有了Filter,才会访问web资源。

如果有多个Filter形成了Filter链,那么它们的执行顺序是怎样确定的?
它们的执行顺序取决于<filter-mapping>在web.xml文件中配置的先后顺序。

filter生命周期
当服务器启动,会创建Filter对象,并调用init方法,只调用一次.
当访问资源时,路径与Filter的拦截路径匹配,会执行Filter中的doFilter方法,这个方法是真正拦截操作的方法.
当服务器关闭时,会调用Filter的destroy方法来进行销毁操作.

4. FilterConfig介绍

Filter功能介绍
在Filter中的init方法上有一个参数叫FilterConfig,是Filter的配置对象
作用:
获取初始化参数
获取filter的名称
获取全局管理者(SerlvetContext对象)

常用api

FilterConfig.png

5. filter配置详解

Filter基本配置介绍

<filter>
    <filter-name>filter名称</filter-name>
    <filter-class>filter类全名</filter-class>
</filter>
<filter-mapping>
    <filter-name>filter名称</filter-name>
    <url-pattern>映射路径</url-pattern>
</filter-mapping>

url-pattern配置
完全匹配 :
要求必须以"/"开始.

目录匹配:
要求必须以"/"开始,以*结束.

扩展名匹配:
不能以"/"开始,以.xxx结束.例如:.jsp *.do

关于servlet-name配置
针对于servlet拦截的配置 <servlet-name>配置
在Filter中它的url-pattern配置项上有一个标签
<servlet-name>它用于设置当前Filter拦截哪一个servlet。
是通过servlet的name来确定的。

关于dispatcher配置
可以取的值有 REQUEST FORWARD ERROR INCLUDE
作用:
当以什么方式去访问web资源时,进行拦截操作.

REQUEST
当是从浏览器直接访问资源,或是重定向到某个资源时进行拦截方式配置的 它也是默认值
FORWARD
它描述的是请求转发的拦截方式配置
ERROR
如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。
INCLUDE
如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。

7.Filter案例

7.1自动登录功能

之前的登录流程:
login.jsp-->loginServlet-->UserService-->UserDao
现在分析下自动登录的原理
a.当用户登录成功之后,判断一下用户是否勾选了自动登录,若勾选了,将用户名和密码通过cookie持久化到浏览器上.
b.做一个filter,filter的作用为:判断cookie中是否有用户名和密码,若有拿过来调用service完成登录操作
c.filter放行

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //1.强转
        HttpServletRequest req=(HttpServletRequest) request;
        HttpServletResponse resp=(HttpServletResponse) response;
        
        //2.逻辑
        //首先判断session中是否有user  若没有再继续操作
        User user = (User) req.getSession().getAttribute("user");
        
        if(user==null){//session中没有用户 需要继续操作
            //获取请求路径 若是login和regist的不需要自动登录
            String uri = req.getRequestURI();
            String contextPath = req.getContextPath();
            String path=uri.substring(contextPath.length());
            //System.out.println(path);
            if(!("/login.jsp".equals(path)||"/login".equals(path)||"/regist.jsp".equals(path)||"/regist".equals(path))){
                //查找是否有自动登录的cookie
                Cookie c = CookieUtils.getCookieByName("autoLogin", req.getCookies());
                if(c!=null){
                    
                    //若有 调用service 自动登录
                    //拿到用户名和密码
                    String username=c.getValue().split("-")[0];
                    String password=c.getValue().split("-")[1];
                    
                    //调用service登录
                    user = new UserService().login(username, password);
                    //若登录成功 将user放入session中
                    if(user!=null){
                        req.getSession().setAttribute("user", user);
                        System.out.println("自动登录..........");
                    }
                }
            }
            
        }
        
        //3.放行
        chain.doFilter(req, resp);
    }

注意:
cookie中不能存放中文,若出现中文还需编码解决

7.2编码过滤器

思路:在Filter中对request进行功能增强,让它处理了乱码问题,再将request传递到servlet中,这样在servlet中获取请求参数就不会乱码。
如何进行功能加强?

装饰模式实现步骤:
a.装饰类与被装饰类要继承同一个父类或实现同一个接口。
b.在装饰类中重写方法,进行功能增强
c.在装饰类中持有一个被装饰类的对象

编码过滤器分析.png

在EncodingFilter中已经创建了一个MyReqeust,它是一个HttpServletRequest的装饰类,而我们在chain.doFilter(MyRequest,response);也就是在servlet中使用的request其实是装饰类。

对于我们通过reqeust对象获取请求参数有三种方式:
getParameter getParameterValues getParameterMap

我们不需要将这三个都进行编码处理,只需要对getParameterMap进行乱码处理,而getParameter及getParameteValues可以依赖于getParametreMap的实现

class MyRequest extends HttpServletRequestWrapper{
    private HttpServletRequest request;
    private boolean flag=true;
    
    
    public MyRequest(HttpServletRequest request) {
        super(request);
        this.request=request;
    }
    
    @Override
    public String getParameter(String name) {  
        if(name==null || name.trim().length()==0){
            return null;
        }
        String[] values = getParameterValues(name);
        if(values==null || values.length==0){
            return null;
        }
        
        return values[0];
    }
    
    @Override
    /**
     * hobby=[eat,drink]
     */
    public String[] getParameterValues(String name) {
        if(name==null || name.trim().length()==0){
            return null;
        }
        Map<String, String[]> map = getParameterMap();
        if(map==null || map.size()==0){
            return null;
        }
        
        return map.get(name);
    }
    
    @Override
    /**
     * map{ username=[tom],password=[123],hobby=[eat,drink]}
     */
    public Map<String,String[]> getParameterMap() {  
        
        /**
         * 首先判断请求方式
         * 若为post  request.setchar...(utf-8)
         * 若为get 将map中的值遍历编码就可以了
         */
        String method = request.getMethod();
        if("post".equalsIgnoreCase(method)){
            try {
                request.setCharacterEncoding("utf-8");
                return request.getParameterMap();
            } catch (UnsupportedEncodingException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }else if("get".equalsIgnoreCase(method)){
            Map<String,String[]> map = request.getParameterMap();
            if(flag){
                for (String key:map.keySet()) {
                    String[] arr = map.get(key);
                    //继续遍历数组
                    for(int i=0;i<arr.length;i++){
                        //编码
                        try {
                            arr[i]=new String(arr[i].getBytes("iso8859-1"),"utf-8");
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        }
                    }
                }
                flag=false;
            }
            //需要遍历map 修改value的每一个数据的编码
            
            return map;
        }
        
        return super.getParameterMap();
    }
    
}

上一篇下一篇

猜你喜欢

热点阅读