十 过滤器10.1 过滤器的概念

滤器就是一个用于在请求之前处理资源的组件

生命周期
	随着服务器启动而初始化
  	随着请求的发出而过滤
  	随着服务器关闭而销毁

执行流程
  	浏览器发起请求
  	服务器会根据这个请求,创建request对象及response对象
  	过滤器会持有request对象及response对象
  	只有当过滤器放行之后,request对象及response对象才会传给Serlvet

过滤器链
  	根据配置顺序,遵从"先过滤,后放行"的原则

------------------------------

10.2 过滤器的基本使用
开发步骤
  	自定义类实现Filter接口
  	重写init、doFilter、destroy方法
  	在web.xml中配置过滤器
    	声明过滤器
    	过滤器配置过滤路径

过滤器

public class Demo01Filter implements Filter {
      @Override
      public void init(FilterConfig filterConfig) throws ServletException {
          //Demo01Filter过滤器的初始化
          System.out.println("Demo01Filter初始化");
      }
  
      @Override
      public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
          //Demo01Filter过滤器处理请求
          System.out.println("Demo01Filter放行之前");
          filterChain.doFilter(servletRequest,servletResponse);
          System.out.println("Demo01Filter放行之后");
      }
  
      @Override
      public void destroy() {
          //Demo01Filter过滤器的销毁
          System.out.println("Demo01Filter销毁");
      }
  }
  • web.xml
<!--声明Demo01Filter过滤器-->
  <filter>
      <filter-name>Demo01Filter</filter-name>
      <filter-class>com.qfedu.filter.Demo01Filter</filter-class>
  </filter>
  
  <!--配置Demo01Filter的过滤路径-->
  <filter-mapping>
      <filter-name>Demo01Filter</filter-name>
      <url-pattern>/*</url-pattern>
  </filter-mapping>

------------------------------

10.3 过滤器的相关配置
  • 初始化参数
  • Filter配置初始化参数
<filter>
          <filter-name>Demo03Filter</filter-name>
          <filter-class>com.qfedu.filter.Demo03Filter</filter-class>
          <!--初始化参数-->
          <init-param>
              <param-name>username</param-name>
              <param-value>root</param-value>
          </init-param>
          <init-param>
              <param-name>password</param-name>
              <param-value>root123</param-value>
          </init-param>
  
      </filter>
  • 初始化参数
  • Filter获取初始化参数
public class Demo03Filter implements Filter {
  
      public void init(FilterConfig config) throws ServletException {
          Enumeration<String> parameterNames = config.getInitParameterNames();
          while (parameterNames.hasMoreElements()) {
              //获取初始化参数名称
              String parameterName = parameterNames.nextElement();
              //获取初始化参数值
              String parameterValue = config.getInitParameter(parameterName);
              System.out.println("name : " + parameterName + " , value : " + parameterValue);
          }
      }
      
      ......
      
  }
  • Filter的过滤路径
  • 针对< servlet-name > ,以为Filter仅针对Demo01Servlet进行过滤
<filter-mapping>
        <filter-name>Demo03Filter</filter-name>
        <servlet-name>Demo01Servlet</servlet-name>
    </filter-mapping>

---------------

  • Filter的过滤路径
  • 针对< url-pattern >
完全匹配:必须以"/"开头
<filter-mapping>
          <filter-name>Demo03Filter</filter-name>
          <url-pattern>/aa</url-pattern>
      </filter-mapping>

过滤器只过滤访问路径完全匹配"/aa"的资源

目录匹配:必须以"/"开头,以"*"结尾
<filter-mapping>
          <filter-name>Demo03Filter</filter-name>
          <url-pattern>/aa/bb/*</url-pattern>
      </filter-mapping>

过滤器只过滤访问路径目录匹配到“/aa/bb“的资源

后缀名匹配:必须以"*"开头,以后缀名结尾
<filter-mapping>
          <filter-name>Demo03Filter</filter-name>
          <url-pattern>*.jsp</url-pattern>
      </filter-mapping>

过滤器只过滤后缀名为jsp的资源

------------------------------

10.4 过滤器的注解开发
@WebFilter注解

	WebInitParam[] initParams() default {};		配置初始化参数

	String filterName() default "";		配置过滤器名称

	String[] servletNames() default {};		配置过滤的Servlet

	String[] urlPatterns() default {};		配置过滤路径
  • 基本使用
@WebFilter(filterName = "Demo04Filter" ,
            urlPatterns = "/demo01",
            servletNames = "Demo01Servlet" ,
            initParams = {
                    @WebInitParam(name = "username",value = "root"),
                    @WebInitParam(name = "password",value = "root123")
            })
    public class Demo04Filter implements Filter {
    
    
        public void init(FilterConfig config) throws ServletException {
            Enumeration<String> parameterNames = config.getInitParameterNames();
            while (parameterNames.hasMoreElements()) {
                String parameterName = parameterNames.nextElement();
                String parameterValue = config.getInitParameter(parameterName);
                System.out.println(parameterName + " , " + parameterValue);
            }
        }
        
        ......
    
    }
执行顺序
    	按照过滤器的类名的字典顺序决定谁先过滤,谁先放行
    		比如AFilter、BFilter,那么AFilter就会先过滤,BFilter会先放行

------------------------------

10.5 过滤器案例
10.5.1 中文乱码
public class EncodingFilter implements Filter {
    
        private String encoding = null;
    
        public void init(FilterConfig config) throws ServletException {
            encoding = config.getInitParameter("encoding");
        }
    
        public void destroy() {
        }
    
        public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
            //处理响应中文乱码
            resp.setContentType("text/html;charset="+encoding);
            //处理请求中文乱码
            req.setCharacterEncoding(encoding);
            chain.doFilter(req, resp);//放行
        }
    }
  • web.xml
<filter>
        <filter-name>EncodingFilter</filter-name>
        <filter-class>com.qfedu.filter.EncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    
    </filter>
    
    <filter-mapping>
        <filter-name>EncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
10.5.2 自动登录
实现步骤
	登录账户后,根据是否勾选了自动登录选项框,
	判断是否访问和登录相关资源
		如果是,直接放行
		如果不是,判断是否已经在登录状态
			如果是,直接放行
			如果不是,需要从cookie中取出存储的用户信息,进行登录操作
				如果登录成功,直接放行
				如果登录失败,就跳转到登录页面
  • 登录功能
@WebServlet(name = "LoginServlet" ,urlPatterns = "/login")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        if ("root".equals(username) && "root".equals(password)) {

            String autoLogin = request.getParameter("autoLogin");
            System.out.println(autoLogin);
            if ("autoLogin".equals(autoLogin)) {
                //进行自动登录,无非就是将用户信息(账户和密码(加密))保存起来!!!
                //request、servletContext、cookie、session
                Cookie cookie = new Cookie("autoLogin",username+"-"+password);
                cookie.setMaxAge(7 * 24 * 60 * 60);
                response.addCookie(cookie);
            }


            //登录成功,转发到一个页面,用来显示用户信息
            User existUser = new User();
            existUser.setUsername(username);
            existUser.setPassword(password);
            request.getSession().setAttribute("existUser",existUser);
            request.getRequestDispatcher("/showIndex").forward(request,response);
        } else {
            //登录失败,转到登录页面
            request.getRequestDispatcher("/index.jsp").forward(request,response);
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}
  • LoginFilter自动登录
public class AutoLoginFilter implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest request = (HttpServletRequest) req;
        //获取请求路径
        String requestURI = request.getRequestURI();
        //1,判断访问的资源是否和登录相关
        if (requestURI.contains("login")) {
            //和登录相关的资源,直接放行
            chain.doFilter(request, resp);
        } else {
            //不是登录相关的资源
            //2,判断是否在登录状态
            User existUser = (User) request.getSession().getAttribute("existUser");
            if (null == existUser) {
                //不在登录状态 , 进行自动登录
                //获取Cookie
                Cookie cookie = CookieUtils.getCookie(request.getCookies(), "autoLogin");
                //判断cookie是否为空 , 存在浏览器清理缓存
                if (null == cookie) {
                    //浏览器清理缓存 , 相当于自动登录失败!! 跳转到登录页面,进行手动登录
                    request.getRequestDispatcher("/login.jsp").forward(request,resp);
                } else {
                    //还有缓存,进行自动登录
                    //获取用户信息 root-root
                    String infoStr = cookie.getValue();
                    String[] infos = infoStr.split("-");
                    String username = infos[0];
                    String password = infos[1];
                    if ("root".equals(username) && "root".equals(password)) {
                        //自动登录成功  ,修改登录状态, 直接放行  ,意味着,还不在登录状态!!!
                        existUser = new User();
                        existUser.setUsername(username);
                        existUser.setPassword(password);
                        request.getSession().setAttribute("existUser",existUser);
                        chain.doFilter(req,resp);
                    } else {
                        //自动登录失败 (修改了密码) ,跳转到登录页面,进行手动登录
                        request.getRequestDispatcher("/login.jsp").forward(request,resp);
                    }
                }
            } else {
                //在登录状态 , 直接放行
                chain.doFilter(request,resp);
            }
        }
    }

    public void init(FilterConfig config) throws ServletException {

    }

}
10.5.3 敏感词屏蔽
对request对象进行增强。增强获取参数相关方法
放行。传递增强的请求方法
@WebFilter(
          filterName = "SensitiveWordsFilter" ,
          urlPatterns = "/*",
          initParams = {
                  @WebInitParam(name = "word1",value = "笨蛋"),
                  @WebInitParam(name = "word2" ,value = "傻瓜"),
                  @WebInitParam(name = "word3" ,value = "尼玛"),
                  @WebInitParam(name = "word4",value = "靠")
          })
  public class SensitiveWordsFilter implements Filter {
  
      //敏感词
      List<String> sensitiveWords = new ArrayList<>();
  
      public void init(FilterConfig config) throws ServletException {
          Enumeration<String> parameterNames = config.getInitParameterNames();
          while (parameterNames.hasMoreElements()) {
              String sensitiveWord = config.getInitParameter(parameterNames.nextElement());
              sensitiveWords.add(sensitiveWord);
          }
      }
  
      public void destroy() {
      }
  
  
  
      public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
          System.out.println("SensitiveWordsFilter doFilter");
          HttpServletRequest request = (HttpServletRequest) req;
          //增强request下的getParameter方法
          HttpServletRequest requestPrxoy = (HttpServletRequest) Proxy.newProxyInstance(
                  request.getClass().getClassLoader(),
                  request.getClass().getInterfaces(),
                  new InvocationHandler() {
                      @Override
                      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                          //增强getParameter方法
                          Object returnValue = null;
                          String methodName = method.getName();
                          if ("getParameter".equals(methodName)) {
                              //returnValue就是getParameter方法的返回值,可能会存在敏感词
                              String returnValue1 = (String)method.invoke(request, args);
                              //开始处理敏感词
                              for (String sensitiveWord : sensitiveWords) {
                                  if (returnValue1.contains(sensitiveWord)) {
                                      //getParameter方法的返回值包含敏感词
                                      returnValue1 = returnValue1.replace(sensitiveWord,"***");
                                  }
                              }
                              return returnValue1;
                          } else {
                              returnValue = method.invoke(request, args);
                          }
                          return returnValue;
                      }
                  });
          chain.doFilter(requestPrxoy, resp);
      }  
  }

十一 监听器

11.1 监听器的介绍
监听器概念
	事件源:事件发生的源头
	监听器:监听事件发生
  	绑定:将监听器绑定到事件源
  	事件:能够触发监听器的事
Servlet监听器
	事件源:request域对象、session域对象、ServletContext域对象
	监听器:Servlet三种监听器
  	绑定:配置web.xml
  	事件:域对象发生改变

------------------------------

11.2 监听器的分类
一类监听器		监听域对象的创建、销毁

二类监听器		监听域对象中的属性变更(属性设置、属性替换、属性移除)

三类监听器		监听域对象的java对象的绑定
11.2.1 一类监听器
ServletRequestListener : 监听ServletRequest域对象的创建、销毁

HttpSessionListener :监听HttpSession域对象的创建、销毁

ServletContextListener : 监听ServletContext域对象的创建、销毁
开发步骤
	自定义类实现一类监听器
	重写监听器中的方法
	配置web.xml
  • 监听器
public class MyListener01 implements ServletContextListener {
  
      @Override
      public void contextInitialized(ServletContextEvent sce) {
          //监听ServletContext域的初始化,随着服务器的启动
          System.out.println("ServletContext初始化");
      }
  
      @Override
      public void contextDestroyed(ServletContextEvent sce) {
          //监听ServletContext域的销毁,随着服务器的关闭
          System.out.println("ServletContext销毁");
      }
  }
  • web.xml(绑定)
<listener>
      <listener-class>com.qfedu.listener.MyListener01</listener-class>
  </listener>
事件源: ServletContext域对象
监听器:ServletContextListener
绑定:  web.xml配置
事件 : ServletContext域对象发生了创建、发生了销毁
11.2.2 二类监听器
ServletRequestAttributeListener :监听ServletRequest域对象中属性变更

HttpSessionAttributeListener : 监听HttpSession域对象中属性变更

ServletContextAttributeListener : 监听ServletContext域对象中属性变更
  • 监听器
public class MyServletContextAttributeListener implements ServletContextAttributeListener {
  
      @Override
      public void attributeAdded(ServletContextAttributeEvent scae) {
          //监听ServletContext域对象中属性添加
          System.out.println("ServletContext added");
      }
  
      @Override
      public void attributeReplaced(ServletContextAttributeEvent scae) {
          //监听ServletContext域对象中属性值被替换
          System.out.println("ServletContext replaced");
  
      }
  
      @Override
      public void attributeRemoved(ServletContextAttributeEvent scae) {
          //监听ServletContext域对象中属性值移除
          System.out.println("ServletContext removed");
  
      }
  }
  • web.xml(绑定)
<listener>
      <listener-class>com.qfedu.listener.MyServletContextAttributeListener</listener-class>
  </listener>
11.2.3 三类监听器
HttpSessionBindingListener

	监听session域中的java对象的状态(绑定和解绑)
		绑定:将java对象存储到session域对象
		解绑:将java对象从session域对象移除
监听器组成
		事件源:java对象
		监听器:HttpSessionBindingListener
		绑定:java对象实现HttpSessionBindingListener接口
		事件:java对象在session中状态发生改变
public class User implements HttpSessionBindingListener {
  
  
      @Override
      public void valueBound(HttpSessionBindingEvent event) {
          System.out.println("User绑定");
      }
  
      @Override
      public void valueUnbound(HttpSessionBindingEvent event) {
          System.out.println("User解绑");
      }
      
      ......
      
  }

HttpSessionBindingListener监听不需要在web.xml配置

------------------------------

11.3 监听器注解开发

@WebListener 相当于在web.xml绑定了监听器

@WebListener
  public class MyServletContextLIstener implements ServletContextListener {
  
      @Override
      public void contextInitialized(ServletContextEvent sce) {
          System.out.println("ServletContext创建");
      }
  
  
      @Override
      public void contextDestroyed(ServletContextEvent sce) {
          System.out.println("ServletContext销毁");
      }
  }

------------------------------

11.4 监听器综合案例
11.4.1 记录登录人数
  • 开发步骤
登录功能
    	登录失败,转发到登录页面
    	登录成功,记录登录状态,重定向到首页(显示用户名、注销登录、在线人数)
  	
  	注销登录
    	注销登录成功;正常来说,应该转发到登录页面;转发到首页
  	
  	在线人数
    	使用HttpSessionBindingListener监听器
    	使用ServletContext域对象,存储在线人数count
  • login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<form action="/day59/login" method="post">
    账户:<input type="text" name="username"/><br>
    密码:<input type="text" name="password"/><br>
    <button type="submit">登录</button>
</form>
</body>
</html>
  • bean.User
public class User implements HttpSessionBindingListener {
    @Override
    public void valueBound(HttpSessionBindingEvent event) {
        //有人登录成功 , 在线人数(count)加1
        //判断是否是第一个登录成功的人
        //获取ServletContext
        ServletContext servletContext = event.getSession().getServletContext();
        Integer count = (Integer) servletContext.getAttribute("count");
        if (null == count) {
            //就是第一个登录成功的人
            count = 1;
        } else {
            //不是第一个登录成功的人
            count++;
        }
        servletContext.setAttribute("count", count);
    }
    //在同一个浏览器,意味着是同一个session
    //第一次登录 ,session.setAttribute("existUser","root") , valueBound  +1
    //第二次登录 ,session.setAttribute("existUser","root1") , valueBound +1  -> valueUnbound -1


    @Override
    public void valueUnbound(HttpSessionBindingEvent event) {
        //有人注销登录 ,在线人数(count)减1
        System.out.println("User解绑");
        ServletContext servletContext = event.getSession().getServletContext();
        Integer count = (Integer) servletContext.getAttribute("count");
        count--;
        servletContext.setAttribute("count", count);
    }

    private Integer id;
    private String username;
    private String password;

    public User() {
    }

    public User(Integer id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

在线人数:在同一个浏览器,操作多次登录,意味着是同一个session

  • LoginServlet
@WebServlet(name = "LoginServlet", urlPatterns = "/login")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        if ("root".equals(username) && "root".equals(password)) {
            //登录成功 , 修改登录状态 ,跳转到ShowIndexServlet
            User existUser = new User();
            existUser.setUsername(username);
            existUser.setPassword(password);
            request.getSession().setAttribute("existUser", existUser);
//            request.getRequestDispatcher("/showIndex").forward(request,response);
            response.sendRedirect("/day59/showIndex");
        } else {
            //登录失败,转发到登录页面,重新登录
            request.getRequestDispatcher("/login.html").forward(request, response);
        }

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}
  • LogoutServlet
@WebServlet(name = "LogoutServlet", urlPatterns = "/logout")
public class LogoutServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //注销登录 , 将existUser变量从session域中移除!
//        request.getSession().removeAttribute("existUser");
        //注销登录,将session销毁 -> 将existUser变量从session域中移除!
        request.getSession().invalidate();
        //注销成功
        request.getRequestDispatcher("/showIndex").forward(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}
  • ShowIndexServlet
@WebServlet(name = "ShowIndexServlet", urlPatterns = "/showIndex")
public class ShowIndexServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        User existUser = (User) request.getSession().getAttribute("existUser");
        StringBuffer responseBody = new StringBuffer();
        if (null == existUser) {
            //不在登录状态,提示
            responseBody.append("您还没有登录;<a href='/day59/login.html'>请登录</a><br>");
        } else {
            //在登录状态,显示信息
            responseBody.append("欢迎回来," + existUser.getUsername() + "   <a href='/day59/logout'>注销</a><br>");
        }
        ServletContext servletContext = getServletContext();
        //获取在线人数count
        Integer count = (Integer) servletContext.getAttribute("count");
        System.out.println("在线人数 " + count);
        //count变量为null,在没有任何人登录状态下,访问了ShowIndexServlet

        if (null == count) {
            //没有任何人在登录状态 ,在线人数处理成0人
            count = 0;
        } else {
            //有人在登录状态 ,直接输出在线人数count人
        }
        responseBody.append("在线人数 : " + count);
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().write(responseBody.toString());
    }

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