黑马就业班(02.JavaWeb+项目实战\day11_Filter&Listener):代理模式解析

本文详细讲解了JavaWeb中的过滤器Filter和监听器Listener。重点介绍了过滤器的配置、执行流程、生命周期方法、配置详解以及过滤器链的执行顺序。并通过登录验证和敏感词汇过滤两个案例,展示了过滤器的实际应用。此外,还探讨了监听器Listener,特别是ServletContextListener在监听ServletContext对象创建和销毁中的作用。
摘要由CSDN通过智能技术生成
  • 本文对应的自己项目:myday11

今日内容

1. Filter:过滤器
2. Listener:监听器
  • Servlet、Filter、Listener被称为Java Web的三大组件

1、Filter:过滤器
  概念:见视频2的解析

* 生活中的过滤器:净水器,空气净化器,土匪、
* web中的过滤器:当浏览器通过请求访问服务器的资源时,过滤器可以将请求拦截下来,添加完成一些特殊的功能。
* 过滤器的作用:
	* 一般用于完成通用的操作。如:登录验证、统一编码处理、敏感字符过滤...
1、比如我们之前写的用户登录案例,我们的用户不能直接访问index.jsp、list.jsp、update.jdp...等等,必须在login.jsp页面登录之后才能访问到这些页面的数据,那么这样我们在每一个.jsp文件都要判断是否登录(登录后Session会存储一个User对象)。如果我们在访问这些资源前面都加一个过滤器,在过滤器验证是否登录,就不需要在每一个.jsp文件都判断是否登录。
2、我们之前在服务器资源Servlet里面经常要获取表单数据(post请求),经常要设置编码,每一个Servlet都要设置很麻烦,如果直接在过滤器中设置编码就可以简化代码。

在这里插入图片描述

  快速入门:

步骤:
	1. 定义一个类,实现接口Filter(类似于Servlet)
	2. 复写方法
	3. 配置拦截路径(就是配置浏览器访问什么资源过滤器会生效)
		1. web.xml
		2. 注解

  我们设置一个过滤器,配置浏览器访问所有服务器资源都会执行该过滤器。相应代码与说明如下:

package lkj.web.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

//配置Filter,设置拦截路径。这里配置的是urlPatterns(value),而且只有一个值,可以省略urlPatterns与values与{}
@WebFilter("/*") //访问所有服务器资源之前,都会执行该过滤器。如果是"/demo.jsp",表示访问demo.jsp才会执行过滤器
//注意Servlet配置的是类的资源路径(既访问路径中类名的映射)
public class FilterTest1 implements Filter//注意,Filter有很多个,导入javax.servlet包下的Filter
{
    @Override
    public void init(FilterConfig filterConfig) throws ServletException
    {    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException
    {
        //比如我们访问index.jsp,就会执行该过滤器
        System.out.println("filterTest1被执行了....");

        //过滤器放行。
        filterChain.doFilter(servletRequest , servletResponse);
        //如果我们如果不放行,访问index.jsp会执行过滤器,但是不会执行index.jsp。只有放行后才会执行inde.jsp
    }

    @Override
    public void destroy()
    {    }
}

过滤器细节

细节1:web.xml配置方法

   web.xml配置方法的配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
        
    <filter>
        <filter-name>test1</filter-name>        <!--原来类名的映射名-->
        <filter-class>lkj.web.filter.FilterTest1</filter-class>     <!--原来的类名,包括src下的全部包名-->
    </filter>

    <filter-mapping>
        <filter-name>test1</filter-name>        <!--原来类名的映射名-->
        <url-pattern>/*</url-pattern>       <!--过滤器拦截的范围-->
    </filter-mapping>
    <!--
        Filter与之前Servlet的配置类似,不过url-pattern设置的是拦截路径,而Servlet设置的是类的资源访问路径
    -->
</web-app>
细节2:过滤器的执行流程

   过滤器执行流程如下。(见视频5的解析)

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

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter("/*")
public class FilterTest2 implements Filter
{
    public void destroy()
    {    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException
    {
        //对request对象请求消息增强
        System.out.println("filterDemo2执行了....");

        //放行
        chain.doFilter(req, resp);
        // -->Response回来的地方
        //request被放行去访问资源之后,资源会发送response回来,回到放行代码之后,继续执行doFilter之后的代码

        //对response对象的响应消息增强
        System.out.println("filterDemo2回来了...");
    }

    public void init(FilterConfig config) throws ServletException
    {    }
}
细节3:过滤器生命周期方法

  如下:

1. init:在服务器启动后,会创建Filter对象,然后调用init方法。只执行一次。用于加载资源
2. doFilter:每一次请求被拦截资源时,会执行。执行多次
3. destroy:在服务器关闭后,Filter对象被销毁。如果服务器是正常关闭,则会执行destroy方法。只执行一次。用于释放资源
package lkj.web.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter("/*") //注意将前面的过滤器注释。否则我们访问所有资源的时候也会访问其他过滤器
public class FilterTest3 implements Filter
{
    /**
     * 在服务器启动后,会创建Filter对象,然后调用init方法,只执行一次,用于加载资源。(由服务器完成)
     * @param config
     * @throws ServletException
     */
    public void init(FilterConfig config) throws ServletException
    {
        System.out.println("init....");
    }

    /**
     * 每一次请求被拦截资源时,会执行。执行多次
     * @param req
     * @param resp
     * @param chain
     * @throws ServletException
     * @throws IOException
     */
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException
    {
        System.out.println("doFilter");
        chain.doFilter(req, resp);
    }

    /**
     * 在服务器关闭后,Filter对象被销毁。如果服务器是正常关闭,则会执行destroy方法,只执行一次,用于释放资源。
     * 注意,destroy方法在Filter被销毁之前执行,一般用于释放资源。
     * destroy释放完资源之后,Filter 对象被标记为垃圾,被JVM回收。
     */
    public void destroy()
    {
        System.out.println("destroy....");
    }
}
细节4:过滤器配置详解

  拦截路径配置:

1. 具体资源路径: /index.jsp   只有访问index.jsp资源时,过滤器才会被执行
2. 拦截目录: /user/*	访问/user下的所有资源时,过滤器都会被执行
3. 后缀名拦截: *.jsp		访问所有后缀名为jsp资源时,过滤器都会被执行
4. 拦截所有资源:/*		访问所有资源时,过滤器都会被执行
//FilterTest4
package lkj.web.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

//注意将前面的过滤器注释。否则我们访问所有资源的时候也会访问其他过滤器

//1、@WebFilter("/*")   //访问所有资源时,过滤器都会被执行
//2、@WebFilter("/index.jsp") //具体资源路径: /index.jsp   只有访问index.jsp资源时,过滤器才会被执行

/*
3、我们在src下创建lkj.web.servlet.ServletTest1并将这个Servlet的资源路径配置为:
@WebServlet("/user/findAllServlet")

在src下创建lkj.web.servlet.ServletTest2并将这个Servlet的资源路径配置为:
@WebServlet("/user/updateServlet")

findAllServlet与updateServlet的访问路径在同一级路径/user下,访问的时候必须加/user
这是用来测试访问所有/user目录下的资源时,过滤器拦截路径设置为:/user/*,对user下的资源都会拦截
 */
//@WebFilter("/user/*")  //访问:http://localhost/user/updateServlet 与:http://localhost/user/findAllServlet 过滤器都会执行过滤器代码

@WebFilter("*.jsp") //访问所有后缀名为jsp资源时,过滤器都会被执行
public class FilterTest4 implements Filter
{

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException
    {
        System.out.println("doFilter .......");
        chain.doFilter(req, resp);
    }

    public void init(FilterConfig config) throws ServletException
    {    }

    public void destroy()
    {    }
}

  拦截方式配置:资源被访问的方式,比如浏览器直接访问,服务器内部的请求转发等访问方式。

1、注解配置:
	* 设置dispatcherTypes属性
			1. REQUEST:默认值。浏览器直接请求资源(注意REQUEST是默认值)
			2. FORWARD:转发访问资源
			3. INCLUDE:包含访问资源
			4. ERROR:错误跳转资源(我们之前设置JSP页面的errorPage:当前页面发生异常后,会自动跳转到指定的错误页面)
			5. ASYNC:异步访问资源
2、web.xml配置
	* 设置<dispatcher></dispatcher>标签即可
package lkj.web.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

//浏览器直接请求index.jsp资源时,该过滤器会被执行。这里注意有多个属性,因此我们必须加上value或者urlPatterns
//我们可以访问"/user/updateServlet",在这个Servlet下面转发请求index.jsp
//@WebFilter(value = "/index.jsp" , dispatcherTypes = DispatcherType.REQUEST)  //注意,这里dispatcherTypes的值不需要加"",直接写DispatcherType.什么什么

//只有转发访问index.jsp时,该过滤器才会被执行
//@WebFilter(value = "/index.jsp" , dispatcherTypes = DispatcherType.FORWARD)

//浏览器直接请求index.jsp或者转发访问index.jsp。该过滤器都会被执行
//dispatcherTypes属性可以配置多个值,注意同样适用{}括起来
//@WebFilter(value = "/index.jsp" , dispatcherTypes = {DispatcherType.FORWARD , DispatcherType.REQUEST})

/*
思考题:如下,我们在浏览器访问:http://localhost/user/updateServlet ,那么过滤器会被执行多少次?
我们发现过滤器被执行2次,首先访问updateServlet之前会执行过滤器,随后请求转发访问index.jsp,还要再访问一次过滤器才会去访问index.jsp
 */
@WebFilter(value = "/*" , dispatcherTypes = {DispatcherType.FORWARD , DispatcherType.REQUEST})
public class FilterTest5 implements Filter
{
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException
    {
        System.out.println("FilterTest5 run......");
        chain.doFilter(req, resp);
    }

    public void init(FilterConfig config) throws ServletException
    {    }

    public void destroy()
    {    }
}
细节5:过滤器链(配置多个过滤器)

  执行顺序:如果有两个过滤器:过滤器1和过滤器2(见视频8分析)

1. 过滤器1
2. 过滤器2
3. 资源执行
4. 过滤器2
5. 过滤器1 

  过滤器先后顺序问题:

1. 注解配置:按照类名的字符串比较规则比较,值小的先执行
	* 如: AFilter 和 BFilter,AFilter就先执行了。
2. web.xml配置: <filter-mapping>谁定义在上边,谁先执行

  这部分比较简单,自己试验过,见视频解析就可以。

案例

案例1_登录验证(权限控制)

  需求:

1. 访问day17_case案例的资源。验证其是否登录
2. 如果登录了,则直接放行。
3. 如果没有登录,则跳转到登录页面,提示"您尚未登录,请先登录"

  我们导入之前的day09_Case,里面所有的资源我们都可以通过浏览器直接访问到。 现在要求访问这些页面之前,验证其是否登录,如果登录了,则直接放行。如果没有登录,则跳转到登录页面,提示"您尚未登录,请先登录"。
  分析(见视频9分析)
在这里插入图片描述
  过滤器也应该是在三层架构的界面层,我们在web文件夹下创建一个filter文件夹,再创建一个登陆过滤器LoginFilter.java。
  相关的代码与流程如下:

package lkj.web.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * 登录验证的过滤器
 */
@WebFilter("/*") //首先,我们设置访问所有资源都要经过过滤器,下面再直接将登陆相关的类放行即可
public class LoginFilter implements Filter
{

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException
    {
        /*
        首先ServletRequest与ServletResponse是2个接口,他们下面有HttpServletRequest与HttpServletResponse,他们才有HTTP协议对应的方法
        我们想获取HTTP协议下的Request的请求路径,必须将ServletRequest与ServletResponse强制向下转换为HttpServletRequest与HttpServletResponse
         */
        //0.强制转换
        HttpServletRequest request = (HttpServletRequest)req;

        //1.获取资源请求路径(这里获取URI即可)
        String requestURI = request.getRequestURI();//
//        System.out.println(requestURI);//打印一下URI测试

        //2.判断是否包含登录相关资源路径,要注意排除掉 css/js/图片/验证码等资源
        /*
        首先是登录相关的JSP:login.jsp与处理JSP请求的Servlet:loginServlet
        其次,验证码:checkCodeServlet ,css/js/图片:/css/、/font/、/js/。这是将这几个目录下的资源全部排除
         */
        if(requestURI.contains("/login.jsp") || requestURI.contains("/loginServlet") || requestURI.contains("/checkCodeServlet") || requestURI.contains("/css/") || requestURI.contains("/fonts/") || requestURI.contains("/js/"))
        {
            //如果访问的是登录相关资源,放行
            chain.doFilter(req, resp);
        }
        else
        {
            //不包含,需要验证用户是否登录。我们在登录后会通过Session存储一个包含登录者信息的User对象,判断这个对象是否为空即可
            //3.从获取session中获取user
            Object user = request.getSession().getAttribute("user");
            if(user != null)
            {
                chain.doFilter(req, resp);//user不为空直接放行
            }
            else
            {
                //user为空,请求转发到login.jsp进行登录
                request.setAttribute("login_msg","您尚未登录,请登录");
                request.getRequestDispatcher("/login.jsp").forward(request , resp);
            }
        }
    }

    public void init(FilterConfig config) throws ServletException
    {    }

    public void destroy()
    {    }
}

  注意,我们在浏览器访问登陆后,再次访问index.jsp不需要再次访问,因为浏览器记住刚刚的登录状态。如果将浏览器关闭又开启,再次访问index.jsp则会再次要求登录。

案例2_敏感词汇过滤

  需求:

1. 对day17_case案例录入的数据进行敏感词汇过滤
2. 敏感词汇参考《敏感词汇.txt》
3. 如果是敏感词汇,替换为 *** 

  注意,我们在Filter与Servlet里面获取到的Request对象是同一个!
在这里插入图片描述
  分析:(见视频11分析)

1. 对request对象进行增强。增强获取参数相关方法
Request对象获取相关参数的方法getParameter("键"),但是Request没有setParameter()的方法,我们将敏感词取出后想把替换后的信息设置回Request对象比较麻烦。我们需要对Request对象的getParameter()方法进行增强,增强后会有一个新的Request对象,那么这个新的Request对象调用getParameter()获取出来的信息就是已经过滤的信息,我们可以直接将新的Request对象通过chain.doFilter(request,response)传递到要访问的Servlet。

2. 放行。传递代理对象

  增强对象的功能:

* 设计模式:一些通用的解决固定问题的方式
	1. 装饰模式(之前使用过,相对于代理模式要笨重)
	2. 代理模式(见视频12分析)
		* 概念:
			1. 真实对象:被代理的对象
			2. 代理对象:
			3. 代理模式:代理对象代理真实对象,达到增强真实对象功能的目的
	 	* 实现方式:
	 	1. 静态代理:有一个类文件描述代理模式
	 	2. 动态代理:在内存中形成代理类(用的比较多)
			* 实现步骤:
				1. 代理对象和真实对象实现相同的接口(invoke方法中实现)
				2. 代理对象 = Proxy.newProxyInstance();
				3. 使用代理对象调用真实对象的方法。
				4. 增强方法(invoke方法中实现)

				* 增强方式:
					1. 增强参数列表
					2. 增强返回值类型
					3. 增强方法体执行逻辑	

  我们以下面的卖电脑的例子为例,实现代理模式。在myday10项目的src下创建一个包lkj.proxy,在下面进行买电脑案例(代理模式测试)代码的编写。具体过程分析见下面代码以及视频分析。

package lkj.proxy;

public interface SaleComputer
{
    //创建一个卖电脑的抽象方法.
    //这个方法返回一个String类型的电脑,接收doblue类型的money
    public String sale(double money);

    public void show();//创建一个新的方法方便展示
}


package lkj.proxy;

/**
 * 创建一个联想公司的真实类,实现卖电脑的功能.
 * 将来会根据真实类创建真实对象,真实对象被代理对象所代理,来增强真实对象的功能。
 */
public class Lenovo implements SaleComputer
{
    @Override
    public String sale(double money)
    {
        System.out.println("花了"+money+"元买了一台联想电脑...");
        return "联想电脑";
    }

    @Override
    public void show()
    {
        System.out.println("展示电脑....");
    }
}

package lkj.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 代理类
 */
public class ProxyTest
{
    public static void main(String[] args)
    {
        /*
        //演示用户向真实对象买电脑
        Lenovo lv = new Lenovo();//创建真实类对象
        //调用方法“买电脑”
        String com = lv.sale(8000);
        System.out.println(com);
        */

        //我们使用动态代理来增强Lenovo对象

        //1、创建真实类对象
        Lenovo lv = new Lenovo();

        //2.动态代理增强lenovo对象。
        /*
            三个参数:
                1. 类加载器:真实对象.getClass().getClassLoader() 代理对象与真实对象使用相同的类加载器
                2. 接口数组:真实对象.getClass().getInterfaces()  保证代理对象与真实对象实现相同的接口
                3. 处理器:new InvocationHandler()  核心业务逻辑
         */
        //这个方法执行完会返回一个增强的代理对象。
        // 这个代理对象与真实对象实现相同的接口,我们可以将返回的代理对象强制转换为接口类型
        SaleComputer proxy_lv = (SaleComputer)Proxy.newProxyInstance(lv.getClass().getClassLoader(), lv.getClass().getInterfaces(), new InvocationHandler()
        {
            /*
                代理逻辑编写的方法:代理对象调用的所有方法都会触发该方法执行
                    参数:
                        1. proxy:代理对象(一般不会使用到 )
                        2. method:代理对象调用的方法,被封装为的对象
                        3. args:代理对象调用的方法时,传递的实际参数
             说明:我们使用代理对象调用真实对象的方法,这些方法就会被封装为Method对象传递到invoke方法的Method中,
                其次,代理对象调用方法时传递的参数也会被封装到数组,传递给invoke方法的args数组。
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
            {
//                System.out.println("该方法执行了....");
//                System.out.println(method.getName());
//                System.out.println(args[0]);


                //相当于使用真实对象调用sale方法
//                Object obj = method.invoke(lv, args);//传递进入真实对象以及代理对象调用真实对象方法的参数,相当于真实对象执行自己的方法。
//                return obj;//invoke方法的返回值就是我们代理对象调用真实对象方法后所接收的返回值

                /**
                 * 分析:代理对象实际上并没有对应的功能(方法),代理对象只是通过invoke方法来增强真实对象的功能(方法)。
                 * 我们在使用代理对象调用真实对象的方法时,会将方法的参数与名字传递给invoke方法,另一方面,
                 * invoke方法的返回值就是我们代理对象调用真实对象方法后所接收的返回值,那么我们就可以在invoke方法中,
                 * 对真实对象的方法进行增强:参数、返回值、方法体 进行增强,最后在invoke调用这个增强后的真实方法。
                 * 由于代理对象调用真实对象方法后invoke方法也会执行,invoke方法中的增强后的真实对象的方法也执行。
                 * 这就实现了真实对象方法的增强
                 */
                //方法执行的三要素:参数、返回值、方法的具体实现:方法体

                //通过方法名判断是否是sale方法,是sale方法才有必要增强
                if(method.getName().equals("sale"))
                {
                    //1、增强参数:将用户的付费克扣15%
                    double money = (double)args[0];//明确知道参数只有一个且是double类型,直接获取数组第0个且强转即可
                    money = money*0.85;//对参数进行增强(修改)

                    //3、增强方法体
                    System.out.println("买电脑之前专车接送");

                    String obj = (String) method.invoke(lv, money);//使用真实对象调用方法,此时参数已经增强(修改)。
                    // 明确知道返回值就是字符串,将其强转为字符串

                    //3、增强方法体
                    System.out.println("买电脑之后免费送货上门");

                    //2、增强返回值。
                    return obj+"_鼠标垫";//此时返回值增强
                }
                else
                {
                    //如果不是sale,不增强参数,该怎么执行就怎么执行
                    Object obj = method.invoke(lv, args);//使用真实对象调用方法
                    return obj;
                }

            }
        });

        //使用代理对象调用真实对象实现SaleComputer接口的方法
        //这里代理对象实际上只是将真实对象方法的参数与方法体传递到invoke方法,
        // invoke方法对象真实对象方法的参数、返回值、方法体增强后,在invoke执行增强后的真实对象方法,
        //由于代理对象调用真实对象方法后invoke方法也会执行,invoke方法中的增强后的真实对象的方法也执行。
        String com = proxy_lv.sale(8000);
        System.out.println(com);
/*
        该方法执行了....
        null
         */
//        proxy_lv.show();//同样invoke方法会执行

    }
}

在这里插入图片描述


  根据上面买电脑案例的分析,我们总结代理设计模式的运行具体运行规则:

1、首先创建真实对象(如果有真实对象则无需创建)
2、使用Proxy.newProxyInstance方法,返回一个代理对象(与真实对象使用相同的接口类型),并在invoke中实现真实与代理对象类加载器相同、接口相同,并在invoke方法中对真实方法进行增强。
3、使用代理对象调用真实对象的方法。

说明:
	使用代理对象调用真实对象的方法时,代理对象实际上只是将真实对象方法的参数与方法体传递到invoke方法。代理对象实际上并没有对应的功能(方法),代理对象只是通过invoke方法来增强真实对象的功能(方法)。
	我们在使用代理对象调用真实对象的方法时,会将方法的参数与名字传递给invoke方法,另一方面,invoke方法的返回值就是我们代理对象调用真实对象方法后所接收的返回值。那么我们就可以在invoke方法中,对真实对象的方法的:参数、返回值、方法体 进行增强,最后在invoke调用这个增强后的真实方法。
     由于代理对象调用真实对象方法后invoke方法也会执行,invoke方法中的增强后的真实对象的方法也执行, 这就实现了真实对象方法的增强

  下面对上面敏感词替换的案例进行代码实现,过滤器

package lkj.web.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

/**
 * 敏感词汇过滤器
 */
@WebFilter("/*") //首先,我们设置访问所有资源都要经过过滤器,下面再直接将登陆相关的类放行即可
public class SensitiveWordsFilter implements Filter
{

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException
    {
        //1、要增强的真实对象ServletRequest已经有了

        //2、调用Proxy的newProxyInstance方法返回代理对象,增强ServletRequest对象的getParameter方法
        //真实对象req与代理对象都是ServletRequest类型,直接强转即可
        ServletRequest proxy_req = (ServletRequest)Proxy.newProxyInstance(req.getClass().getClassLoader(), req.getClass().getInterfaces(), new InvocationHandler()
        {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
            {
                //增强ServletRequest对象的getParameter方法
                //先判断是否是getParameter方法
                if(method.getName().equals("getParameter"))
                {
                    /*
                    我们getParameter方法获取用户输入数据,数据里面可能有敏感词汇,那么我们只需要增强这个方法的返回值,
                    将这个方法获取的值中的敏感词替换即可。后面使用代理对象调用getParameter方法获取返回值时,
                    就会将方法名与参数携带到invoke方法,来执行这个invoke增强的方法。
                    */

                    //1、首先,我们执行真正的getParameter方法,获取对应的值(注意执行原来的getParameter方法得格式)
                    String value = (String)method.invoke(req , args);

                    //2、对敏感词汇进行替换
                    /**
                     * 首先,我们将敏感词汇文件复制到src下,出现乱码,我们将文件字符集换为GBK,乱码消失。
                     * 当然我们可以在这个文件中手动添加我们想要的敏感词。
                     * 其次,我们必须将这个敏感词集合导到一个集合或者数组中,那么我们需要使用IO的方法读取这个敏感词汇表。
                     * 我们应该在初始化方法init()就加载这个敏感词表,将其数据取到集合中。(init方法只加载一次)
                     * 加载这个敏感词汇表到List集合后,就可以对集合进行遍历判断
                     */

                    if(value != null)//判断有相应的值,我们有可能没有输入相应的值,就不需要替换
                    {
                        for (String str : list)
                        {
                            if(value.contains(str))
                            {
                                //将value中的敏感词替换。使用replaceAll,将value中所有str都替换
                                value = value.replaceAll(str , "***");
                                /*
                                注意最后替换后的值重新赋值给value,因为对于replaceAll()方法,
                                注意:执行了替换操作后,返回一个新的对象,源字符串的内容是没有发生改变的。
                                因此我们要将替换后产生的新的字符串赋值给原来的字符串,这样原理的字符串value才是替换后的值
                                 */
                            }
                        }
                    }
                    return value;//注意将value返回
                }
                else
                {
                    //如果不是getParameter方法,我们原样执行即可
                    //我们在后面使用代理Request对象调用Request对象的其他方法的时候,同样会将方法名与参数传递到这个invoke方法执行!
                    return method.invoke(req , args);//注意原样执行的格式
                }
            }
        });


        //放行。放行的时候注意传递的时候传递Request的代理对象proxy_req
        chain.doFilter(proxy_req , resp);
    }

    //创建一个成员集合,使得init方法与doFilter方法都能访问到这个集合
    private List<String> list = new ArrayList<String>();
    public void init(FilterConfig config) throws ServletException
    {
        //我们将敏感词汇存储到集合中

        //1.获取敏感词汇文件真实路径
        ServletContext sc = config.getServletContext();//获取代表整个web项目的ServletContext对象,可以通过config获取
        String realPath = sc.getRealPath("/WEB-INF/classes/敏感词汇.txt");//获取src下文件的真实路径。注意参数的格式

        try
        {
            //2.读取文件
            //注意,我们在“敏感词汇.txt”中将其设置为GBK编码,而本地创建的流也是GBK编码。将来如果出现乱码要注意编码问题。
            BufferedReader bur = new BufferedReader(new FileReader(realPath));
            //3.将文件的每一行数据添加到list中
            String line = null;
            while((line = bur.readLine()) != null)
            {
                list.add(line);
            }
            bur.close();
            System.out.println(list);//测试集合有没有成功获取
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }


    }

    public void destroy()
    {    }
}

  完成之后,我们先写个测试类TestServlet进行测试getParameter()方法,这个测试类中会使用到getParameter()方法。

package lkj.web.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/testServlet")
public class TestServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String name = request.getParameter("name");
        String msg = request.getParameter("msg");

        System.out.println(name+":"+msg);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

  我们先在login.jsp进行登录,否则访问其他资源会被登录过滤器拦截。登录后我们再带上参数访问TestServlet:http://localhost/day09case/testServlet?name=张三&msg=你个笨蛋,你个大坏蛋,你个王八蛋 (其中笨蛋,坏蛋、王八蛋 是敏感词)。首先,启动服务器发现控制台打印

[笨蛋, 坏蛋, 王八蛋]  //说明,敏感词汇.txt遍历成功

  访问:http://localhost/day09case/testServlet?name=张三&msg=你个笨蛋,你个大坏蛋,你个王八蛋 后,控制台打印如下:

张三:你个***,你个大***,你个***  //替换成功

  实际的调用流程如下:

1、首先,在init()初始化方法中对“敏感词汇.txt”文件进行加载,并将其中的敏感词读取到List集合中;

2、其次,我们在doFilter()方法中使用Proxy.newProxyInstance()方法创建代理对象proxy_req,将这个对象强制装换为ServletRequest,同时,在invoke()方法中,判断代理对象proxy_req调用getParameter(“键”)传递进来的方法名是不是getParameter。如果不是getParameter方法,我们使用真实的request对象执行这个方法并返回对应的值:
return method.invoke(req , args);//不是getParameter方法原样执行,注意返回值
如果是,我们执行真正的request对象的getParameter()方法,获取相应的真正的值:
String value = (String)method.invoke(req , args);//获取真实对象调用getParameter()方法返回的真正的值
然后我们对象这个值进行判断,如果它非空且包含敏感词,我们对其进行替换,并将替换后的值返回。

3、放行,注意将代理对象proxy_req放入doFilter()放行方法。这样我们在访问其他Servlet的时候,如果这个Servlet使用request.getParameter(“键”)获取参数,其实这个request是代理对象proxy_req。
这个proxy_req调用getParameter(“键”)方法的时候,会将参数传递到它对应的invoke方法中 (实际上代理对象调用getParameter()并没有执行,执行将参数传递到invoke,并执行invoke方法)。
这个invoke方法对getParameter方法的返回值进行增强并返回。我们使用代理对象proxy_req调用这个getParameter方法,对应的invoke方法也会执行(代理对象调用原来真实的request对象的任意方法invoke都会执行),这样在invoke方法中使用真实对象调用getParameter()方法获取相应的值,并对这个值进行增强并返回。这样就相当于调用了增强的getParameter方法。
注意,如果代理对象调用的不是getParameter方法,我们在invoke中原样执行该方法并返回,这样使用调用代理对象这个方法的时候invoke方法执行并原样执行这个方法。

  获取参数还有getParameterValues()与getParameterMap(),getParameterNames(),我们想过滤敏感词都得实现。(方法大同小异,这里不赘述)

**2、Listener:监听器 **
  Listener:web的三大组件之一。我们之前讲BOM与DOM的时候就讲过监听器,参考自己文章:《黑马就业班(02.JavaWeb+项目实战\02.JavaScript)——part2:BOM&DOM》——4、事件监听机制。

 事件监听机制
	* 事件	:一件事情(比如点击按钮)
	* 事件源 :事件发生的地方(按钮)
	* 监听器 :一个对象(或者是一段代码)
	* 注册监听:将事件、事件源、监听器绑定在一起。 当事件源上发生某个事件后,执行监听器代码

  我们今天学习:ServletContextListener接口:监听ServletContext对象的创建和销毁。这个ServletContextListener接口没有对应的实现类,我们需要自己创建它的实现类。

* 方法:
	* void contextDestroyed(ServletContextEvent sce) :ServletContext对象被销毁之前会调用该方法
	* void contextInitialized(ServletContextEvent sce) :ServletContext对象创建后会调用该方法

* 步骤:(我们以后写监听器都是这个步骤,我们将这些步骤记下即可)
	1. 定义一个类,实现ServletContextListener接口
	2. 复写方法
	3. 配置(注意,配置后监听器才会执行)
		1. web.xml配置
			<listener>
 				<listener-class>cn.itcast.web.listener.ContextLoaderListener</listener-class>
			</listener>

		2. 注解:
			* @WebListener  (不需要加参数)
!!!这里注意通过web.xml动态加载配置文件的方法,通过指定初始化参数<context-param>进行文件的动态加载。

  测试代码如下,在lkj.web.listener包下创建一个ContextLoaderListener类,实现ServletContextListener接口。

package lkj.web.listener;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.io.FileInputStream;

//监听器的注解配置
@WebListener //监听器不需要指定路径,我们直接写即可,没有参数
public class ContextLoaderListener implements ServletContextListener
{
    //这个类(监听器)的2个方法相当于ServletContext对象的生命周期方法

    /**
     * 监听ServletContext对象创建的。ServletContext对象代表整个web应用。
     * ServletContext对象服务器启动后自动创建,然后我们要使用这个对象使用Request对象或者HTTPServlet获取ServletContext对象即可。
     * @param servletContextEvent
     */
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent)
    {
        //服务器启动后自动创建ServletContext对象,监听器ServletContextListener监听到ServletContext对象被创建,
        // 就会自动调用contextInitialized方法
        System.out.println("ServletContext对象被创建了。。。");

        /**
         * 那么我们通过ServletContextListener对象来监听ServletContext对象的创建与销毁有什么用?
         * 监听ServletContext的创建,用于加载资源文件。
         * 下面是演示
         */
        //1、通过servletContextEvent获取ServletContext对象
        ServletContext sc = servletContextEvent.getServletContext();

        /*
        //2.加载资源文件
        如果将要加载的资源文件写死,这样扩展性比较差。我们希望用户通过配置,告诉我们他想要加载哪一个文件,我们就加载哪一个
        用户在web.xml配置,告知程序它想加载的文件,然后为其设置映射名。
        我们在监听器中使用getInitParameter方法获取加载文件映射名对应的工作空间路径,再通过这个路径获取加载文件在Tomcat项目中的真实路径。
         */
        String contextConfigLocation = sc.getInitParameter("contextConfigLocation");//通过加载文件映射名获取对应文件的工作空间路径。以后我们的映射名不变,要加载的文件改变,但是代码不需要改变
        String realPath = sc.getRealPath(contextConfigLocation);

        //4.加载进内存
        try{
            FileInputStream fis = new FileInputStream(realPath);
            System.out.println(fis);
        }catch (Exception e){
            e.printStackTrace();
        }
        /**
         * 我们一启动服务器,ServletContextListener监听器监听到服务器启动,调用contextInitialized方法,这个方法就会动态加载资源文件。
         * 一般这种资源文件是全局的资源文件,我们会在ServletContext的监听器的初始化方法中使用ServletContext来加载。
         * !注意这种通过web.xml配置来动态加载资源文件的方法。
         */

        //将来信息框架的时候,框架已经将监听器设置好,我们只需要在web.xml文件配置后就可以使用。
    }

    /**
     * 在服务器关闭后,ServletContext对象被销毁。当服务器正常关闭后该方法被调用
     * @param servletContextEvent
     */
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent)
    {
        //服务器正常关闭后自动销毁ServletContext对象,监听器ServletContextListener就会自动调用contextDestroyed方法
        //同样,服务器关闭前,监听器ServletContextListener监听到ServletContext对象要被销毁,先自动调用contextDestroyed方法释放资源,
        //释放完资源之后,ServletContext对象被标记为垃圾,被JVM回收
        System.out.println("ServletContext对象被销毁了。。。");
    }
}
/*
事件监听机制4部分的分析:
我们在web.xml里面注册监听,ServletContext对象被创建——事件,事件源——Tomcat,ServletContext对象在Tomcat下被创建,
我们自定义的ContextLoaderListener类就是监听器。
 */

  配置文件web.xml(注意如果使用注解配置,将web.xml的配置注释)

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <filter>
        <filter-name>test1</filter-name>        <!--原来类名的映射名-->
        <filter-class>lkj.web.filter.FilterTest1</filter-class>     <!--原来的类名,包括src下的全部包名-->
    </filter>

    <filter-mapping>
        <filter-name>test1</filter-name>        <!--原来类名的映射名-->
        <url-pattern>/*</url-pattern>       <!--过滤器拦截的范围-->

         <dispatcher>REQUEST</dispatcher>   <!--在这里设置资源被以什么方式访问时会执行拦截-->
    </filter-mapping>


    <!--
        Filter与之前Servlet的配置类似,不过Filter的url-pattern设置的是拦截路径,而Servlet设置的是类的资源访问路径
    -->


    <!--
      配置监听器:这里相当于注册监听
   -->
    <listener>
        <listener-class>lkj.web.listener.ContextLoaderListener</listener-class>   <!--实现ServletContextListener接口的类的全名(包括src下的包名)-->
    </listener>

    <!--
       我们为想加载的文件设置映射参数名。
       我们在src文件夹下创建一个applicationContext.xml文件,我们想在监听器中加载这个文件。
       后期我们想加载的文件可能改变,因此我们给这些文件创建一个映射名,我们在java代码中使用ServletContext的getInitParameter("加载文件映射名")获取加载文件在工作空间的路径,然后就可以通过工作空间路径,sc.getRealPath("工作空间路径")获取真正的Tomcat项目路径。而且就算要加载的文件改变,只要映射名不变,代码就不需要变。
    -->

    <context-param>
        <param-name>contextConfigLocation</param-name>      <!--想加载文件的映射名-->
        <param-value>/WEB-INF/classes/applicationContext.xml</param-value>      <!--想加载文件在项目空间的路径-->
        <!--
        如果将来我们想加载其他文件,只需要修改param-value的值即可,映射名不该,代码也不需要修改,直接加载。
        如果我们想加载多个文件就多配置几个context-param
        -->
    </context-param>
</web-app>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值