Filter&Listener

Filter:过滤器

概念:

  • 生活中的过滤器:净水器,空气净化器,土匪
  • web 中的过滤器,当访问服务器的资源时,过滤器可以将请求拦截下来,完成一些特殊的功能.
  • 过滤器的作用:
    一般用于完成通用的操作.如:登陆验证,统一编码处理,敏感字符过滤...

快速入门

步骤

  1. 定义一个类,实现接口 filter
  2. 复写方法
  3. 配置拦截路径
    • web.xml
    • 注解
@WebFilter("/*") //访问所有资源之前,都会执行该过滤器
public class FilterDemo1 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("doFilter");
        //是否放行 --放行
        chain.doFilter(request, response);
    }
    @Override
    public void destroy() {
    }
}

过滤器细节

web.xml 配置

    <filter>
        <filter-name>demo2</filter-name>
        <filter-class>com.zhiyou100.filter.FilterDemo2</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>demo2</filter-name>
        <!--访问所有的资源都会执行 demo2的 filter-->
        <url-pattern>/*</url-pattern>
    </filter-mapping>

过滤器的执行流程

  1. 执行过滤器
  2. 执行放行后的资源
  3. 回来执行过滤器放行代码下边的代码

过滤器声明周期方法

  1. init(FilterConfig); 在服务器启动后,会创建 filter对象,然后调用 init方法。只执行一次。用于加载资源

  2. doFilter(SerlvetRequest, ServletResponse, FilterChain); 每个请求进来的时候这个方法都会被调用,并在Servlet的service方法执行之前。而FilterChain就代表当前的整个请求链,所以通过调用FilterChain的doFilter方法可以继续将请求传递下去。如果想拦截这个请求,可以不调用FilterChain的doFilter,那么这个请求就返回了,所以Filter是一种责任链模式。执行多次、

  3. destroy(); 在服务器关闭后,filter对象被销毁,如果服务器时正常关闭,则会执行 destroy方法。只执行一次。用于释放资源

  • Filter类的核心还是doFilter中传递的FilterChain对象,这个对象保存了到最终的Servlet对象的所有Filter对象,这些对象都保存在ApplicationFilterChain对象的filter数组中。在FilterChain链上每执行一个Filter对象,数组的当前计数都会加1,直到计数等于数组的长度,当FilterChain上的所有Filter对象都执行完之后,就会执行最终的Servlet。所以在ApplicationFilterChain对象中会持有Servlet对象的引用。

过滤器配置详解

  • 拦截路径配置:
    1. 具体资源路径:/index.jsp 只有访问 index.jsp资源时,过滤器才会被执行
    2. 拦截目录:/user/* 访问 /user下的所有资源时,过滤器都会被执行
    3. 后缀名拦截器: *.jsp 访问所有后缀名为 jsp资源时,过滤器都会被执行
    4. 拦截所有资源:/* 访问所有资源时,过滤器都会被执行
  • 拦截方式配置:资源被访问的方式
    • 注解配置:
      • 设置 dispatcherTypes 属性 (他是个数组)
        request:默认值.浏览器直接请去资源
        forward:转发访问资源
        include:包含访问资源
        error:错误跳转资源
        async:异步访问资源
    • web.xml 配置:
      在 file-mapping标签中配置
      <dispatcher></dispatcher>属性还是上面那五个

过滤器链(配置多个过滤器)

  • 执行顺序:如果有两个过滤器:过滤器1,和过滤器2
    过滤器1先执行
    过滤器2
    资源执行
    过滤器2
    过滤器1
  • 过滤器先后顺序问题
    1. 注解配置:按照类名的字符串比较规则比较,较小的先执行
      如:AFilter 和 BFilter,AFilter 先执行
    2. web.xml配置的:谁定义在上面,谁先执行

案例

登陆验证

需求

  1. 访问 该项目的资源时,验证是否登陆
  2. 如果登陆了,则直接放行
  3. 如果没有登陆,则跳转到登陆页面,提示"你尚未登陆,请先登录"

步骤

  1. 判断是否是登陆相关的资源
    是:直接放行
    不是:判断是否登陆
  2. 判断当前用户是否登陆,判断 Session中是否有 User
    有:已经登陆,放行
    没有,没有登陆,跳转到登陆页面
@WebFilter("/*")
public class FilterLogin implements Filter {

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        //强制转换
        HttpServletRequest request = (HttpServletRequest) req;
        //1. 获取资源请求路径
        String uri = request.getRequestURI();
        //2. 判断是否包含登陆相关资源路径,要注意排除掉 css、js、图片、验证码等资源
        if (uri.contains("/login.jsp") || uri.contains("/loginServlet")
                || uri.contains("/css/") || uri.contains("/js/")
                || uri.contains("/fonts") || uri.contains("/checkServlet")) {
            //包含,用户就是先登录,放行
            chain.doFilter(req, resp);
        } else {
            // 不包含,需要验证用户是否登陆
            //3. 从session中获取 user
            Object user = request.getSession().getAttribute("user");
            if (user != null) {
                //登陆了 放行
                chain.doFilter(req, resp);
            } else {
                //没有登陆,跳转登陆页面
                request.setAttribute("login_msg", "你尚未登陆,请登录");
                request.getRequestDispatcher("/login.jsp").forward(request, resp);
            }
        }
    }

    @Override
    public void init(FilterConfig config) throws ServletException {
    }
}

敏感词汇过滤

需求

  1. 对项目录入的数据进行敏感词汇过滤
  2. 敏感词汇:笨蛋 坏蛋
  3. 如果是敏感词汇,替换 **

步骤:

  1. filter 和 servlet 中的 request和response对象都是一样的
  2. 对 request对象的 getParameter方法进行增强.产生一个新的 request对象.
  3. 放行.将新的 request对象传入
    chain.doFilter(req,resp)
  4. 增强对象的功能:
    设计模式:一些通用的解决问题的方式
    • 装饰模式
    • 代理模式
      • 概念:
        真实对象:被代理的对象
        代理对象:
        代理模式:代理对象代理真实对象,达到增强真实对象功能的目的
      • 实现方式
        • 静态代理:

        • 动态代理
          实现步骤:

          1. 代理对象和真实对象实现相同的接口
          2. 代理对象 = newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h);
          3. 使用代理对象调用方法.
          4. 增强方法

          增强方法

          1. 增强参数列表
          2. 增强返回值类型
          3. 增强方法体执行逻辑
        public interface SaleComputer {
        	public String sale(double money);
        	public  void  show();
        }
        
        public class Lenovo implements SaleComputer {
        	@Override
        	public String sale(double money) {
        		System.out.println("花了" + money + "元,买了一台联想电脑");
        		return "联想电脑";
        	}
        
        	@Override
        	public void show() {
        		System.out.println("展示电脑");
        	}
        }
        
        public class ProxyTest {
        public static void main(String[] args) {
        	SaleComputer lenovo = new Lenovo();
        	System.out.println(lenovo.sale(800));
        	System.out.println("~~~~~~~~~~~~~~~~~~~~");
        	/*
        	 * Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
        	 * 三个参数:
        	 *  1. 类加载器,真实对象.getClass.getClassLoader()
        	 *  2. 接口数组:真实对象.getClass.getInterfaces()
        	 *  3. 处理器:new InvocationHandler() ==>重写 invoke(Object proxy,Method method,Object[] args)
        	 * */
        	SaleComputer proxyLenovo = (SaleComputer) Proxy.newProxyInstance(lenovo.getClass().getClassLoader()
        			, lenovo.getClass().getInterfaces(), (proxy, method, arguments) -> {
        				/*
        				 * 代理逻辑编写的方法:代理对象调用的所有都会触发该方法执行
        				 * invoke(Object proxy,Method method,Object[] args)
        				 * 参数
        				 *  1. proxy 代理对象
        				 *  2. method 代理对象调用的方法,被封装为的对象
        				 *  3. args 代理对象调用方法时,传递的实际参数
        				 * */
        				//判断是否是 sale方法
        				if (method.getName().equals("sale")) {
        					//1. 增强参数
        					double money = (double) arguments[0];
        					money = money * 0.85;
        					System.out.println("专车接你");
        					//使用真实对象调用该方法
        					//增强返回值类型 obj==字符串
        					Object obj = method.invoke(lenovo, money);
        					System.out.println("免费送货");
        					return obj + "_鼠标垫";
        				}
        				//使用真实对象调用该方法
        				return method.invoke(lenovo, arguments);
        			});
        	System.out.println(proxyLenovo.sale(800));
        }
        }
        

过滤器

@WebFilter("/*")
public class SensitiveWordsFilter implements Filter {
    //敏感词汇集合
    private List<String> list = new ArrayList<>();

    @Override
    public void init(FilterConfig config) throws ServletException {
        //1. 加载文件,->获取文件真实路径
        ServletContext servletContext = config.getServletContext();
        //敏感词汇.txt 在 idea中的是 src下的,但在 idea部署的是这个路径下
        String path = servletContext.getRealPath("/WEB-INF/classes/敏感词汇.txt");
        //2. 读取文件
        try (
                BufferedReader bfr = new BufferedReader(new FileReader(path));
        ) {
            //3. 将文件的每一行添加到list中

            String line = null;
            while ((line = bfr.readLine()) != null) {
                list.add(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println(list);

    }


    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        //1. 创建代理对象,增强 getParameter 方法
        ServletRequest proxyRequest = (ServletRequest) Proxy.newProxyInstance(req.getClass().getClassLoader(), req.getClass().getInterfaces(),
                (proxy, method, args) -> {
                    //增强 getParameter方法
                    //判断是否是 getParameter方法
                    if (method.getName().equals("getParameter")) {
                        //增强返回值
                        //获取返回值
                        String value = (String) method.invoke(req, args);
                        //替换敏感词汇
                        if (value != null) {
                            for (String str : list) {
                                if (str.contains(value)) {
                                    value = value.replaceAll(str, "**");
                                }
                            }
                        }

                        return value;
                    }
                    return method.invoke(req, args);
                });
        // 2. 放行
        chain.doFilter(proxyRequest, resp);
    }

    @Override
    public void destroy() {
    }
}

控制器

@WebServlet("/TestServlet")
public class TestServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String text = request.getParameter("text");
        System.out.println(text);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

Listener:监听器

概念

  • web的三大组件之一,
    • 事件监听机制
      事件:一件事情
      事件源:事件发生的地方
      监听器:一个对象
      注册监听器:将事件,事件源,监听器绑定在一起,当事件源上发生某个事件后,执行监听器代码

Listener的分类与使用

1. ServletContext监听器

  • ServletContextListener:用于对Servlet整个上下文进行监听(创建,销毁)
    public interface ServletContextListener extends EventListener {
    	//上下文初始化
    	public default void contextInitialized(ServletContextEvent sce) {
    	}
    	//上下文销毁
    	public default void contextDestroyed(ServletContextEvent sce) {
    	}
    }
    
    • ServletContextEvent事件主要 api
      public class ServletRequestEvent extends java.util.EventObject
      public ServletRequest getServletRequest()
      public ServletContext getServletContext()
      
  • ServletContextAttributeListener,对Servlet上下文属性的监听(增删改查)
    public interface ServletContextAttributeListener extends EventListener {
    	//增加属性
    	public default void attributeAdded(ServletContextAttributeEvent scae) {
    	}
    	//属性删除
    	public default void attributeRemoved(ServletContextAttributeEvent scae) {
    	}
    	//属性替换(第二次设置同一个属性)
    	public default void attributeReplaced(ServletContextAttributeEvent scae) {
    	}
    }
    
    • ServletContextAttributeEvent 常用方法
      public String getName() 得到属性名称
      public Object getValue() 取得属性的值
      

2. Session监听

  1. Session属于 http协议的内容,接口位于 javax.sevlet.http.*包下。
  2. HttpSessionListener 接口,对 Session的整体状态的监听。
    public interface HttpSessionListener extends EventListener {
    	//session创建
    	public default void sessionCreated(HttpSessionEvent se) {
    	}
    	//session 销毁
    	public default void sessionDestroyed(HttpSessionEvent se) {
    	}
    }
    
    • HttpSessionEvent
      public HttpSession getSession() 取得当前操作的 session
      
  3. HttpSessionAttributeListener 接口,对 session的属性监听
    public interface HttpSessionAttributeListener extends EventListener {
    	//增加属性
    	public default void attributeAdded(HttpSessionBindingEvent se) {
    	}
    	//删除属性
    	public default void attributeRemoved(HttpSessionBindingEvent se) {
    	}
    	//替换属性
    	public default void attributeReplaced(HttpSessionBindingEvent se) {
    	}
    }
    
    • HttpSessionBindingEvent
      public String getName();//取得属性的名称
      public Object getValue();//取得属性的值
      public HttpSession getSession();//取得当前的session
      
  4. session 的销毁有两种情况
    1. session超时,web.xml 配置
      <session-config>
      	<session-timeout>120</session-timeout><!--session120分钟后超时销毁-->
      </session-config>
      
    2. 手工使 session失效
      public void invalidate();//使session失效方法。session.invalidate();
      

3. Request 监听

  1. ServletRequestListener,用于对 Request请求进行监听(创建、销毁)
    public interface ServletRequestListener extends EventListener {
    	// request 初始化
    	public default void requestDestroyed (ServletRequestEvent sre) {
    	}
    	// request 销毁 
    	public default void requestInitialized (ServletRequestEvent sre) {
    	}
    }
    
    • ServletRequestEvent事件
      public ServletRequest getServletRequest();//取得一个ServletRequest对象
      public ServletContext getServletContext();//取得一个ServletContext(application)对象
      
  2. ServletRequestAttributeListener:对 request属性的监听(增删改查)
    public interface ServletRequestAttributeListener extends EventListener {
    	//增加属性
    	public default void attributeAdded(ServletRequestAttributeEvent srae) {
    	}
    	//属性删除
    	public default void attributeRemoved(ServletRequestAttributeEvent srae) {
    	}
    	属性替换(第二次设置统一属性)
    	public default void attributeReplaced(ServletRequestAttributeEvent srae) {
    	}
    }
    
    • ServletRequestAttributeEvent事件:能取得设置属性的名称与内容
      public String getName();//得到属性名称
      public Object getValue();//取得属性的值
      

4. 配置 监听器

  • 在 web.xml 中配置
  • Listener配置信息必须在 filter和 servlet 配置之前,listener的初始化(ServletContentListener初始化)比servlet和 filter都有限,而销毁比 servlet和 filter都慢
    <listener>
    	<listener-class>路径</listener-class>
    </listener>
    
  • 注解
    @WebListener()

案例HttpSessionAttributeListener 实现统计会员在线人数

@WebListener
public class Demo01AttributeListener implements HttpSessionAttributeListener {
    @Override
    //HttpSessionAttributeListener 实现 统计在线会员人数
    public void attributeAdded(HttpSessionBindingEvent se) {
        System.out.println("Demo01SessionAttributeListener:::attributeAdded");
        System.out.println("session对象:" + se.getSession().getId() + ":::添加属性:" + se.getName() + "=" + se.getValue());

        //统计人数:需要被整个项目共享  装在servletcontext域中:::要求会员登录  在seesion域中添加属性user
        if (se.getName().equals("user")) {//会员+1
            Object count = se.getSession().getServletContext().getAttribute("count");//获取servletcontext域中的count属性
            if (count == null) {//你是第一个登录者
                se.getSession().getServletContext().setAttribute("count", 1);
            } else {//原来的值+1
                se.getSession().getServletContext().setAttribute("count", ((Integer) count) + 1);
            }
        }
    }

    @Override
    public void attributeRemoved(HttpSessionBindingEvent se) {
        System.out.println("Demo01SessionAttributeListener:::attributeRemoved");
        System.out.println("session对象:" + se.getSession().getId() + ":::删除属性:" + se.getName() + "=" + se.getValue());
        //统计人数:需要被整个项目共享  装在servletcontext域中:::要求会员登出:删除session中的域属性user
        if (se.getName().equals("user")) {//会员+1
            Object count = se.getSession().getServletContext().getAttribute("count");//获取servletcontext域中的count属性
            //原来的值-1
            se.getSession().getServletContext().setAttribute("count", ((Integer) count) - 1);
        }
    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent se) {
        System.out.println("Demo01SessionAttributeListener:::attributeReplaced");
        System.out.println("session对象:" + se.getSession().getId() + ":::修改属性:" + se.getName());
        System.out.println("删除属性" + se.getName() + "旧值=" + se.getValue() + ",新值=" + se.getSession().getAttribute(se.getName()));
    }
}

案例 ServletContextListerner 初始化指定文件

  1. 定义一个类,实现 ServletContextListener 接口

  2. 复写方法

  3. 配置

    public class ListenerDemo2 implements ServletContextListener {
    	/**
    	 * 监听 ServletContextListener 对象创建的。
    	 * ServletContextListener 对象服务器启动后自动创建
    	 * 在服务器启动后自动调用
    	 *
    	 * @param sce
    	 */
    	@Override
    	public void contextInitialized(ServletContextEvent sce) {
    		//加载资源文件
    		//
    		//1. 获取 ServletContext对象
    		ServletContext servletContext = sce.getServletContext();
    		//2. 加载资源文件
    		String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation");
    		//3. 获取真实路径
    		String realPath = servletContext.getRealPath(contextConfigLocation);
    		//4. 加载进内存
    		try {
    			FileInputStream fis = new FileInputStream(realPath);
    			System.out.println("fis = " + fis);
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    
    		System.out.println("ServletContextListener 对象被创建了");
    
    	}
    
    	/**
    	 * 在服务器关闭后,ServletContextListener 对象被销毁。当服务器正常关闭后该方法被调用
    	 *
    	 * @param sce
    	 */
    	@Override
    	public void contextDestroyed(ServletContextEvent sce) {
    		//
    		System.out.println("ServletContextListener 被销毁了");
    	}
    }
    
    web.xml 下
    <listener>
    	<listener-class>com.cainiao.Listener.ListenerDemo2</listener-class>
    </listener>
    <!--指定初始化参数-->
    <context-param>
    	<param-name>contextConfigLocation</param-name>
    	<!--因为创建的是普通的 web项目,在idea中的位置是 src下的,
    	其实看的是idea的out下的目录 -->
    	<param-value>WEB-INF/classes/applicationContext.xml</param-value>
    </context-param>
    
原文地址:https://www.cnblogs.com/zk2020/p/15164508.html