Filter过滤器

Filter:过滤器,顾名思义就是用来过滤用户的请求,通过Filter可以拦截用户对Web资源的请求与响应操作

如何使用:
1.创建一个类实现Filter接口(javax.servlet包下的)
2.实现接口中的三个方法
3.在web.xml文件中配置过滤器信息

package com.itdream.filter;

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

/**
 * Created by Dream on 2017/12/8.
 */
public class MyFilter1 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("MyFilter1拦截开始");
        //放行,如果不执行该语句,那么对于Web资源的请求就到此结束,不再执行其他任何操作
        filterChain.doFilter(servletRequest,servletResponse);
        System.out.println("MyFilter1拦截结束");
    }
    @Override
    public void destroy() {
    }
}

web.xml

    <filter>
        <filter-name>myFilter1</filter-name>
        <filter-class>com.itdream.filter.MyFilter1</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>myFilter1</filter-name>
        <url-pattern>/*</url-pattern>   <!--对所有请求执行拦截操作-->
    </filter-mapping>

FilterChain:Servlet容器为开发人员提供的对象。可以这么理解,如果设置了多个过滤器,那么多个过滤器就会构成过滤器链,那么对于执行完某一个过滤器后,如何去执行下一个过滤操作或者执行下一个Web资源请求呢?就是通过FilterChain对象,它提供了对某一资源的已过滤请求调用链的视图。过滤器使用FilterChain 调用链中的下一个过滤器,如果调用的过滤器是链中的最后一个过滤器,则调用链末尾的资源,也就是去调用用户真正想请求的Web资源。

  • Q:怎样可以形成一个Filter链?

    只要多个Filter对同一个资源进行拦截就可以形成Filter链
    
  • Q:怎样确定Filter的执行顺序?

    由`<filter-mapping>`来确定
    

Filter与Servlet类似,也有生命周期
回忆一下Servlet的声明周期:
实例化–》初始化–》服务–》销毁
那么Servlet是何时被实例化初始化的呢?就是在用户进行Servlet请求的时候,Servlet开始其生命周期
而对于Filter来说,其生命周期与Servlet是完全相同的。
最大的不同的是创建时间不同。

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

FilterConfig:关于过滤器的配置信息
在Filter的init方法上有一个参数,类型就是FilterConfig.
FilterConfig它是Filter的配置对象,它可以完成下列功能
1.获取Filtr名称
2.获取Filter初始化参数
获取ServletContext对象。
这里写图片描述

对于过滤器的配置信息,在web.xml文件中如何配置呢?

    <filter>
        <filter-name>myFilter1</filter-name>
        <filter-class>com.itdream.filter.MyFilter1</filter-class>
        <!--可以配置多个参数-->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>otherConfig</param-name>
            <param-value>config</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>myFilter1</filter-name>
        <url-pattern>/*</url-pattern>   <!--对所有请求执行拦截操作-->
    </filter-mapping>

怎样在Filter中获取一个FilterConfig对象呢?
通过定义一个FilterConfig对象作为实例域,在init方法中对其进行赋值

public class MyFilter1 implements Filter {
    private FilterConfig filterConfig;
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("MyFilter1拦截开始");
        String encoding = filterConfig.getInitParameter("encoding");
        System.out.println(encoding);
        //放行,如果不执行该语句,那么对于Web资源的请求就到此结束,不再执行其他任何操作
        filterChain.doFilter(servletRequest,servletResponse);
        System.out.println("MyFilter1拦截结束");
    }
    @Override
    public void destroy() {
    }
}

Filter的其他配置:
基本配置与Servlet配置类似

<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>

关于其它配置:
<servlet-name>:它是对指定的servlet名称的servlet进行拦截的(filter-mapping标签内)
<dispatcher>:可以取的值有 REQUEST FORWARD ERROR INCLUDE
它的作用是:当以什么方式去访问web资源时,进行拦截操作.(filter-mapping标签内)
1.REQUEST 当是从浏览器直接访问资源,或是重定向到某个资源时进行拦截方式配置的(默认值)
2.FORWARD 它描述的是请求转发的拦截方式配置
3.ERROR 如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。
4.INCLUDE 如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用



下面使用过滤器实现自动登录:
分析下思路:在我们进行用户登录的时候,都会有一个可供用户选择的是否进行自动登录。那么何谓自动登录?即使用户关闭浏览器,如果勾选了自动登录,那么下一次用户访问主页的时候,仍然能读取到用户的登录信息,那么此时用户的信息应该被持久化保存而不应该存放在Session对象中,也就是需要将用户的信息保存在Cookie中。

在没有过滤器的时候我们是用户进行登录,然后LoginServlet根据用户是否选择自动登录将用户信息保存至Cookie中然后写回客户端;如果用户没有选择自动登录,则需要将Cookie的时间设置为0,即删除客户端保存的Cookie
LoginServlet代码:

public class LoginServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)throws IOException,ServletException{
        request.setCharacterEncoding("UTF-8");
        //1.获取表单数据
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String autoLogin = request.getParameter("autologin");
        //2.处理业务逻辑
        UserService us = new UserService();
        User u = us.findUser(username,password);
        if(u!=null){
            Cookie cookie = new Cookie("user",username+"&"+password);
            cookie.setPath("/");  //设置路径为应用目录下
            //判断用户是否勾选自动登录,如果勾选,则需要将信息保存到cookie中
            if(autoLogin != null){
                cookie.setMaxAge(60*60*24*7);
            }else{  //如果没有勾选,则清除cookie对象
                cookie.setMaxAge(0);
            }
            response.addCookie(cookie);  //把Cookie保存到客户端
             request.getSession().setAttribute("u",u);
            request.getRequestDispatcher("/home.jsp").forward(request,response);
        }else{
            request.setAttribute("msg","用户名或密码错误");
            request.getRequestDispatcher("/login.jsp").forward(request,response);
        }
    }
    public void doPost(HttpServletRequest request,HttpServletResponse response)throws IOException,ServletException{
        doGet(request,response);
    }
}

如果用户选择了自动登录,那么后来用户如果直接进入主页,即home.jsp,那么应该页面显示如下:
欢迎您,tom
那么此时就需要过滤器发挥作用了,过滤器通过去读取Cookie中的值,然后查找数据库是否有该用户的存在,如果存在,那么将该用户信息放到session域里,所以此时就算用户没有登录,那么访问主页还是可以显示当前的用户名
LoginFilter:

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //1.转换两个对象HttpServletRequest  HttpServletResponse
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        //2.处理业务
        //获取Cookie数组
        Cookie[] cookies = request.getCookies();
        String username = "";
        String password = "";
        if(cookies!=null){
            //从数组中找出想要的User对象信息
            for(Cookie c:cookies){
                if("user".equals(c.getName())){
                    String value = c.getValue();
                    String[] values = value.split("&");
                    username = values[0];
                    password = values[1];
                }
            }
        }
        //登录操作
        UserService us = new UserService();
        User u = us.findUser(username,password);
        //如果登录成功,则将用户信息保存在session中
        if(u!=null) {
            request.getSession().setAttribute("u", u);
        }
        //放行
        filterChain.doFilter(servletRequest,servletResponse);
    }

对于上述代码,可以实现用户选择自动登录,即使用户关闭了浏览器,只要在7天以内的时间内,不需要登录,直接访问home.jsp也可以出现用户信息。
但是,对于上述的自动登录,有一个问题,就是对于过滤器的配置如下:

<filter>
        <filter-name>loginFilter</filter-name>
        <filter-class>com.itdream.filter.LoginFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>loginFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

其中是对所有的请求进行了拦截操作,其实是没必要对所有的请求进行访问的,尤其该拦截操作牵扯到了对数据库的访问。
1.如果用户想要登陆操作,还需要自动登陆吗?
我们发现如果用户进行登录页面,那么其实是不需要拦截的,只有用户访问主页的时候才需要进行拦截。可能会想到那么我们把配置文件中的url-pattern写死就行了,但是如果对于一个更大的购物网站来说,可能不止一个主页需要拦截器,很多页面也需要。除了用户选择的登录页面,那么我们就想到可以在Filter中判断用户请求的uri来决定是否要进行拦截操作。
2.如果用户已经登陆了,还需要自动登陆吗?
如果用户又去访问了非登录页面以外的其他页面,也会进行拦截,如果此时在Session域中保存了用户信息,那么其实是完全没有必要拦截的。所以也需要在Filter中判断Session域中是否有用户信息存在,如果不存在则去拦截操作。
修改后代码如下:

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //1.转换两个对象HttpServletRequest  HttpServletResponse
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        String uri = request.getRequestURI();
        String path = request.getContextPath();
        path = path.substring(path.length());
        if(!(path.equals("/login.jsp")|path.equals("/servlet/loginServlet"))){  //如果用户不是进行登录操作,则拦截
            User user = (User)request.getSession().getAttribute("u");
            //如果用户没有登录过,则进行拦截操作
            if(user == null){
                //2.处理业务
                //获取Cookie数组
                Cookie[] cookies = request.getCookies();
                String username = "";
                String password = "";
                if(cookies!=null){
                    //从数组中找出想要的User对象信息
                    for(Cookie c:cookies){
                        if("user".equals(c.getName())){
                            String value = c.getValue();
                            String[] values = value.split("&");
                            username = values[0];
                            password = values[1];
                        }
                    }
                }
                //登录操作
                UserService us = new UserService();
                User u = us.findUser(username,password);
                //如果登录成功,则将用户信息保存在session中
                if(u!=null) {
                    request.getSession().setAttribute("u", u);
                }
            }
        }
        //放行
        filterChain.doFilter(servletRequest,servletResponse);
    }

MD5加密,对于数据库中存储的用户密码,为了保证安全性,我们可以使用MD5算法对数据进行加密,这是一种不可逆的算法,即不能通过加密后的数据得到原先的数据,只能对数据使用MD5算法进行加密,将加密后的数据与数据库中的数据进行比对从而验证用户输入的密码是否正确。

数据库加密:Md5(字段)
UPDATE USER SET PASSWORD=MD5(PASSWORD);
Java中也实现了MD5算法加密:

   /**
     * 使用md5的算法进行加密
     */
    public static String md5(String plainText) {
        byte[] secretBytes = null;
        try {
            secretBytes = MessageDigest.getInstance("md5").digest(plainText.getBytes());
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("没有md5这个算法!");
        }
        String md5code = new BigInteger(1, secretBytes).toString(16);
        for (int i = 0; i < 32 - md5code.length(); i++) {
            md5code = "0" + md5code;
        }
        return md5code;
    }

全局编码过滤器

数据从浏览器发送至服务器端,方式无非两种get和post两种方式

  • 对于post方式提交数据产生的乱码,我们一句话可以搞定request.setCharacterEncoding(“UTF-8”);

  • 对于get方式提交的请求,我们则无法通过一两句代码实现,因为对于不同的表单提交的数据方式是不尽相同的,也就是输入框的类型很多。那么如何一劳永逸的解决这个问题呢?过滤器就发挥其不可磨灭的作用了!

    对于乱码的处理方式,无非就是对获取到的输入框的值进行解码再编码的过程,也就是利用了String对象的编码解码能力。但是对于不同类型的输入框,其获取值的方式是不同的。对于request对象来说,一共有三种方式获取客户端发来的请求数据,那么我们只需要重写这三个方法就可以了。
    在java中怎样可以对一个方法进行功能增强?
    1.继承
    2.装饰设计模式
    1).创建一个类让它与被装饰类实现同一个接口或继承同一个父类
    2).在装饰类中持有一个被装饰类的引用
    3).重写要增强的方法
    3.适配器模式——装饰设计模式的一种变形
    对于继承,由于HttpServletRequest是接口,所以继承免去考虑
    对于装饰者设计模式,实现HttpServletRequest接口,除了重写的三个方法,其余的很多方法都需要调用其原来的方法即可,但是方法数量很大,写起来很麻烦。
    这时候,救世主出现了,可能开发Java的也考虑到这个问题了,就为我们实现了HttpServletRequest的适配器HttpServletRequestWrapper
    注意:
    a.由于HttpServletRequestWrapper中没有无参的构造方法,所以必须显示调用
    b.创建HttpServletRequest对象,将原始的该对象传入,进行包装
    c.对于重写的三个方法1.getParameter2.getPrameterValues3.getParameterMap,分析一下,我们发现getParameter方法依赖于getPrameterValues方法实现,而getPrameterValues方法依赖于getParameterMap方法的实现,所以方法重写为如下

package com.itdream.util;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.UnsupportedEncodingException;
import java.util.Map;

/**
 * Created by Dream on 2017/12/8.
 */
public class MyHttpServletRequest extends HttpServletRequestWrapper {
    private HttpServletRequest request;
    public MyHttpServletRequest(HttpServletRequest request) {
        super(request);
        this.request = request;
    }
    @Override
    public String getParameter(String name) {
        return getParameterValues(name)[0];
    }
    @Override
    public String[] getParameterValues(String name) {
        return getParameterMap().get(name);
    }
    @Override
    public Map<String, String[]> getParameterMap() {
        Map<String,String[]> map = request.getParameterMap();
            for(Map.Entry<String,String[]> m:map.entrySet()){
                String[] values = m.getValue();
                for(String s:values){
                    try {
                        s = new String(s.getBytes("iso-8859-1"),"utf-8");
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                }
            }
        return map;
    }
}

那么,对于包装后的对象我们如何使用呢?

package com.itdream.filter;

import com.itdream.util.MyHttpServletRequest;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * Created by Dream on 2017/12/8.
 */
public class EncodeFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //转换对象
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        //将原来的request对象包装,解决get方式提交数据的乱码问题
        HttpServletRequest req = new MyHttpServletRequest(request); 
        //解决post提交数据的乱码问题 
        req.setCharacterEncoding("UTF-8");  
        filterChain.doFilter(req,servletResponse);
    }
    @Override
    public void destroy() {
    }
}

至此,这个令人头疼的乱码问题就可以被一劳永逸的解决了~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值