11_JavaWeb三大组件之Filter拦截器与Listener监听器

1. Filter:过滤器

1.1 什么是Filter

在JavaWeb中有着三大组件:Servlet,Filter过滤器与LIstener监听器

而Filter就是三大组件中的过滤器,他可以将资源的请求和响应拦截下来(也就是一次请求拦截俩次),从而实现一些特殊的功能

作用:
一般用于完成通用的操作(因为可以让在进行特定资源的请求直接先经过过滤器)

​ 如:登录验证、统一编码处理、敏感字符过滤…

1.2 Filter实现步骤:

JavaWeb的三大组件实现步骤基本都是一样的:

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

    示例:

public class FilterDemo1 implements Filter {
    public void init(FilterConfig config) throws ServletException {
    }

    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        // 放行前操作....
        
        // 放行代码:
        chain.doFilter(request, response);
        // 放行后回来的操作....
    }
}

其中的chain.doFilter(request, response);语句表示, 请求经过拦截器之后会被拦截器拦截,写了这一行语句表示从拦截器中放行请求

  1. 重写方法

    在一个过滤器中有三个方法,这三个方法就是过滤器的生命周期方法:

    init :在服务器启动后,会创建Filter对象,然后调用init方法, 只执行一次, 一般用于加载资源

    doFilter:每一次请求被拦截资源时,会执行, 执行多次

    destroy:在服务器关闭后,Filter对象被销毁。如果服务器是正常关闭,则会执行destroy方法。只执行一次。用于释放资源

  2. 配置拦截路径(注意不是访问路径)
    可以通过web.xml:

    配置示例:

<filter>
	<filter-name>demo1</filter-name>
	<filter-class>cn.itcast.web.filter.FilterDemo1</filter-class>
</filter>
<filter-mapping>
	<filter-name>demo1</filter-name>
	<!-- 拦截路径,不是访问路径 -->
	<url-pattern>/*</url-pattern>
</filter-mapping>

也可以通过注解的方式配置:

​ 只需要在Filter类上加上一个@WebFilter注解即可,其中的value属性与urlPatterns属性代表含义是一样的,都是拦截路径,都是数组

​ 示例:

// "/*"表示所有资源都会被拦截
@WebFilter("/*")

1.3 Filter的使用细节

1.3.1 过滤器执行流程

  1. 执行过滤器(请求前)
  2. 执行放行后的资源
  3. 回来执行过滤器放行代码下边的代码(响应后)

因此我们可以在过滤器中对请求数据和响应数据做一些通用的操作

1.3.2 Filter的配置细节

1.3.2.1 拦截路径配置

拦截路径不管是使用注解还是xml配置,路径写法都有以下四种:

  1. 具体资源路径: /index.jsp 表示只有访问index.jsp资源时,过滤器才会被执行
  2. 拦截目录: /user/* 访问/user下的所有资源时,过滤器都会被执行
  3. 后缀名拦截: *.jsp 访问所有后缀名为jsp资源时,过滤器都会被执行,注意此时不要写/
  4. 拦截所有资源:/* 访问所有资源时,过滤器都会被执行
1.3.2.2 拦截方式配置

拦截方式配置,即配置资源在什么时候会别拦截下来,同样可以通过注解配置与xml配置:

注解配置:

设置注解的dispatcherTypes属性即可,dispatcherTypes也是一个数组,有五个可选的值:

  1. REQUEST:默认值。浏览器直接请求的资源会被拦截下来
  2. FORWARD:拦截转发访问资源
  3. INCLUDE:包含访问资源
  4. ERROR:拦截错误跳转资源
  5. ASYNC:拦截异步的访问资源

示例:

@WebFilter(value = "/*", dispatcherTypes = {DispatcherType.REQUEST,DispatcherType.FORWARD})

xml配置:

在<Fflter-mapping>标签中配置<dispatcher></dispatcher>标签即可,标签体的值为以上五个可选值

1.3.3 过滤器链(多个过滤器)

1). 如果配置了俩个过滤器,则此时的执行顺序为:

过滤器1->过滤器2->执行对应资源->过滤器2->过滤器1

2). 过滤器执行的先后顺序:

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

1.4 过滤器实例

1.4.1 验证是否登录

验证用户是否登录,如果登录了就直接放行,如果未登录则跳转到登录页面并提升用户登录(因为用户在访问很多资源时都需要验证是否已经登录,因此这种通用的操作我们放在过滤器中实现)

一般我们登录成功了会将用户存入到session中,因此我们查询session数据即可知道用户有没有成功登录了

注意:
在默认创建的Filter类中,俩个和请求与响应的参数是ServletRequest与ServletResponse,这俩个参数不包含与HTTP相关的方法,只有他们的俩个儿子HTTPServletRequest与HTTPServletResponse才会有与HTTP协议相关的方法,;例如获取Session,获取请求路径,请求参数等等…

​ 因此此时我们需要在方法体中做的第一件事就是对这俩个参数进行强制转换

验证登录代码:

package com.ahua.web.filter;

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

@WebFilter("/*")
public class LoginFilter implements Filter {
    public void init(FilterConfig config) throws ServletException {
    }

    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        // 第一步强转
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        // 先判断是否是登录相关的资源,如果则直接放行
        String requestURI = httpServletRequest.getRequestURI();
        // 注意:排除是否是登录相关资源的时候一定要排除干净
        // css文件,js文件,图片,验证码判断的Servlet,与登录相关Servlet都需要排除
        if (requestURI.contains("/login.jsp") || requestURI.contains("/loginServlet") || requestURI.contains("/checkServlet") ||
                requestURI.contains("/js/") || requestURI.contains("/lcss/") || requestURI.contains("/img/")) {
            // 如果是与登录相关的资源则直接放行
            chain.doFilter(request, response);
        }else {
            HttpSession session = httpServletRequest.getSession();
            // 如果已经登录,则放行
            if (session.getAttribute("User") != null){
                chain.doFilter(request, response);
            } else {
                // 如果还没登录, 则跳转页面,跳转前存储提示信息:
                httpServletRequest.setAttribute("login-msg", "您尚未登录,请先登录");
                httpServletRequest.getRequestDispatcher("/view/login.jsp").forward(httpServletRequest,response);
            }
        }
    }
}

1.4.2 乱码问题

可以参考spring解决乱码问题的Filter:

package org.springframework.web.filter;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

public class CharacterEncodingFilter extends OncePerRequestFilter {
    @Nullable
    private String encoding;
    private boolean forceRequestEncoding;
    private boolean forceResponseEncoding;

    public CharacterEncodingFilter() {
        this.forceRequestEncoding = false;
        this.forceResponseEncoding = false;
    }

    public CharacterEncodingFilter(String encoding) {
        this(encoding, false);
    }

    public CharacterEncodingFilter(String encoding, boolean forceEncoding) {
        this(encoding, forceEncoding, forceEncoding);
    }

    public CharacterEncodingFilter(String encoding, boolean forceRequestEncoding, boolean forceResponseEncoding) {
        this.forceRequestEncoding = false;
        this.forceResponseEncoding = false;
        Assert.hasLength(encoding, "Encoding must not be empty");
        this.encoding = encoding;
        this.forceRequestEncoding = forceRequestEncoding;
        this.forceResponseEncoding = forceResponseEncoding;
    }

    public void setEncoding(@Nullable String encoding) {
        this.encoding = encoding;
    }

    @Nullable
    public String getEncoding() {
        return this.encoding;
    }

    public void setForceEncoding(boolean forceEncoding) {
        this.forceRequestEncoding = forceEncoding;
        this.forceResponseEncoding = forceEncoding;
    }

    public void setForceRequestEncoding(boolean forceRequestEncoding) {
        this.forceRequestEncoding = forceRequestEncoding;
    }

    public boolean isForceRequestEncoding() {
        return this.forceRequestEncoding;
    }

    public void setForceResponseEncoding(boolean forceResponseEncoding) {
        this.forceResponseEncoding = forceResponseEncoding;
    }

    public boolean isForceResponseEncoding() {
        return this.forceResponseEncoding;
    }

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String encoding = this.getEncoding();
        if (encoding != null) {
            if (this.isForceRequestEncoding() || request.getCharacterEncoding() == null) {
                request.setCharacterEncoding(encoding);
            }

            if (this.isForceResponseEncoding()) {
                response.setCharacterEncoding(encoding);
            }
        }

        filterChain.doFilter(request, response);
    }
}

1.4.3 过滤敏感词汇

需求:如果出现敏感词汇表中的敏感词汇,就替换为***(因为在很多资源中都需要进行敏感词汇过滤,因此使用过滤器进行通用操作的过滤)

注意:

​ 由于request对象只有getParameter()方法,而没有setParameter方法来设置请求参数的值,因此我们需要对request对象进行增强,再使用这个增强后的新的request对象来完成需求

​ 对于request对象的增强,我们只需要使用一种设计模式:装饰模式或者代理模式即可,这里我们使用动态代理

示例:

package com.ahua.web.filter;

import javax.servlet.*;
import javax.servlet.annotation.*;
import java.io.BufferedReader;
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 {
    // 存放敏感词汇的集合:
    List<String> list = new ArrayList<>();

    /**
     * 在init()方法中读取文件,避免多次读取浪费内存
     * @param config
     * @throws ServletException
     */
    public void init(FilterConfig config) throws ServletException {
        BufferedReader bufferedReader = null;
        try {
            // 加载存放敏感词汇的文件:
            // 先获取真实路径
            ServletContext context = config.getServletContext();
            // 此时的txt文件放在src下
            String realPath = context.getRealPath("/WEB-INF/SensitiveWords.txt");
            // 加载文件进字符输入流
            System.out.println(realPath);
            bufferedReader = new BufferedReader(new FileReader(realPath));
            // 将文件中的信息加载进集合中
            String line;
            while ((line = bufferedReader.readLine()) != null){
                list.add(line);
            }
            for (String s : list) {
                System.out.println(s);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 获取代理对象并增强getParameter方法
        ServletRequest proxyRequest = (ServletRequest) Proxy.newProxyInstance(request.getClass().getClassLoader(), request.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (method.getName().equals("getParameter")){
                    // 增强getParameter方法的返回值
                    // 先获取getParameter方法的返回值
                    String parameter = (String) method.invoke(request, args);
                    // 检验返回值是否包含list中的敏感词
                    if (parameter != null) {
                        for (String s : list) {
                            if (parameter.contains(s)) {
                                // 包含则修改为***
                                parameter = parameter.replaceAll(s,"***");
                            }
                        }
                    }

                    return parameter;
                }
                // 除此之外还要增强所有可以获取值的方法
                // 例如getParameterMap, getParameterValue等
                // 不是我们需要增强的方法就直接调用返回即可
                return method.invoke(request, args);
            }
        });
        // 放行
        chain.doFilter(proxyRequest, response);
    }

    public void destroy() {
    }

}

2. Listener:监听器

2.1概念:

web的三大组件之一

由于在项目开发中监听器应用比较少,因此只解析了ServletContextListener监听器

2.2 ServletContextListener

ServletContextListener:

​ 可以监听ServletContext对象的创建和销毁

​ 在JavaEE中ServletContextListener是一个接口,并且没有提供这个接口的实现类(也就是说需要我们自己写)

ServletContextListener接口提供的API:

  1. void contextDestroyed(ServletContextEvent sce) :ServletContext对象被销毁之前会调用该方法,一般用于释放资源
  2. void contextInitialized(ServletContextEvent sce) :ServletContext对象创建后会调用该方法,一般用于加载资源文件

实现步骤:

  1. 定义一个类,实现ServletContextListener接口并重写里面的方法

  2. 配置,有俩种方式:注解与web.xml中配置:

    web.xml配置:(参数以及servlet标签中的初始化参数(springMVC))

<listener>
 	<listener-class>cn.ahua.web.listener.ContextLoaderListener</listener-class>
</listener>

上面说了contextInitialized方法主要用于加载资源文件, 但我们如果直接在代码中写死不太好, 因此我们一般使用xml配置资源文件,需要在<context-param>中配置全局初始化参数, 再在contextInitialized方法中加载配置的参数的资源文件(spring集成web开发也是这么做的)

配置示例:

<!-- context-param是全局参数 -->
<context-param> 
    <!-- 全局参数名 -->
	<param-name>contextConfigLocation</param-name> 
    <!-- 全局参数对应的值,这里需要的是配置文件地址 -->
    <!-- 此时的xml文件在resources目录下 -->
	<param-value>classpath:applicationContext.xml</param-value>
</context-param>

而在<context-param>中配置的参数,可以通过ServletContext对象的getInitParameter(String name)获取,其中的name就是我们在配置文件中写的<param-name>

**注解:**在监听器实现类上加@WebListener注解即可:

@WebListener

ContextLoaderListener.java示例:

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 {

    /**
     * 监听ServletContext对象创建的方法。ServletContext对象会在服务器启动后自动创建。
     * 在服务器启动后自动调用
     * @param servletContextEvent
     */
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        //加载资源文件
        //1.获取ServletContext对象
        ServletContext servletContext = servletContextEvent.getServletContext();

        //2.加载资源文件
        String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation");

        //3.获取真实路径
        String realPath = servletContext.getRealPath(contextConfigLocation);

        //4.加载进内存
        try{
            FileInputStream fis = new FileInputStream(realPath);
            System.out.println(fis);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 在服务器关闭后,ServletContext对象被销毁。当服务器正常关闭后该方法被调用
     * @param servletContextEvent
     */
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("ServletContext对象被销毁了。。。");
    }
}

有关web.xml中的参数配置:

  1. <context-param>配置全局初始化参数

  2. <init-param>配置初始化参数,一般用于<servlet>标签与<filter>标签中,一般配置类中的成员变量,会根据配置的value给这个变量赋值,或者告诉容器初始化这个类的参数,初始化时会加载对应的值

    示例(配置Spring中解决乱码问题):

<filter> 
	<filter-name>CharacterEncodingFilter</filter-name> 
	<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> 
    <!-- 告诉他使用哪种方式进行编码,其中encoding是CharacterEncodingFilter中的一个成员变量 -->
	<init-param> 
		<param-name>encoding</param-name> 
		<param-value>UTF-8</param-value>
	</init-param>
</filter>
<filter-mapping> 
	<filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

spring-mvc.xml中配置前端控制器也使用了<init-param>:

<!-- 在web.xml配置前端控制器 -->
<!-- DispatcherServlet类不是我们自己写的,使用xml配置 -->
<servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 接着加载spring-mvc.xml进内存 -->
    <!-- 表示初始化时会加载这个参数对应的值(配置文件) -->
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
    <!-- 让他与tomcat一块启动 -->
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

在web.xml中各个元素的执行顺序是这样的:

context-param–>listener–>filter–>servlet

小剧透:

当在Spring MVC中配置了拦截器,则从整个项目中看,一个servlet请求的执行过程就变成了这样:

context-param–>listener–>filter–>servlet–>interceptor(指的是拦截器)

为什么拦截器是在servlet执行之后,因为拦截器本身就是在servlet内部的

拦截器:

​ 在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。拦截是AOP的一种实现策略。
在Webwork的中文文档的解释为——拦截器是动态拦截Action调用的对象。它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行。同时也是提供了一种可以提取action中可重用的部分的方式。
​ 谈到拦截器,还有一个词大家应该知道——拦截器链(Interceptor Chain,在Struts 2中称为拦截器栈 Interceptor Stack)。拦截器链就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用

拦截器的实现原理:

​ 大部分时候,拦截器方法都是通过代理的方式来调用的。Struts 2的拦截器实现相对简单。当请求到达Struts 2的ServletDispatcher时,Struts 2会查找配置文件,并根据其配置实例化相对的拦截器对象,然后串成一个列表(list),最后一个一个地调用列表中的拦截器。

拦截器的执行时机:

拦截器(interceptor):就是对请求和返回进行拦截,它作用在servlet的内部,具体来说有三个地方:

1)请求还没有到controller层时

2)请求走出controller层次,还没有到渲染时图层

3)结束视图渲染,但是还没有到servlet的结束

拦截器与过滤器的使用选择:

​ 当需要监听到项目中的一些信息,并且不需要对流程做更改时,用监听器

​ 当需要过滤掉其中的部分信息,只留一部分时,就用过滤器;当需要对其流程进行更改,做相关的记录时用拦截器

至此,原始的JavaWeb开发技术告一段落

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值