Filter本身和Listener一样,在使用上是非常简单的。就是配置xml(或者注解),然后实现接口即可。所以这里只挑我认为有价值的东西写写。本文不适合从未学习过Filter的朋友,请先去看:Filter-黑马程序员

主要内容:

  • 为什么需要Filter
  • 山寨FilterChain(责任链模式)
  • Filter、Interceptor、AOP的执行位置

为什么需要Filter

假设现在有一个需求:

我们做了一个web应用,希望用户登录后才能访问一些页面。

你打算怎么做?

如果你学过JSP,可能立马就想到了答案:在JSP中做登录判断。未登录用户重定向到login.jsp。

img

index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>index</title>
</head>
<body>

<%
    // 从Session中获取用户信息
    Object user = session.getAttribute("user");
    // 如果user==null,说明用户尚未登录,引导到login.jsp登录
    if (user == null) {
        response.sendRedirect(request.getContextPath() + "/login.jsp");
        return;
    }

%>

</body>
</html>

login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>login</title>
</head>
<body>
<h1>登录页面</h1>
</body>
</html>

结果

img

被成功拦截,跳转到login.jsp

但这是JSP,本质还是个Servlet。如果是HTML页面怎么办?我们是没法在HTML里写Java代码的。图片,css、js文件就更不行了。

我们冷静分析一下。不管这个问题最终要以什么形式解决,都肯定要写Java代码。因为我肯定要去查询Session或者数据库。既然要写Java代码,要和页面请求打交道的话,只能是Servlet或者Controller。我们在Tomcat外传已经了解过,类似HTML这样的静态资源是由DefaultServlet负责读取的。

img

DefaultServlet是Tomcat写的类,我们无法修改它的源码,然后把Session判断等代码加进去。

Servlet已经是目前我们所学技术中离用户请求最近的了,后面的Service、Dao都无法直接和请求打交道。怎么办?

我们只能把这样的代码单独摘出来,放在一个新的对象中,而且必须设定一种机制,如果需要,可以强制所有对Servlet/JSP等的请求,都要先经过它。这,就是Filter。

img


山寨FilterChain(责任链模式)

如果我们都能山寨一个Filter,那么看待问题的角度会更全面一些。很多初学者,其实就是不愿意去多想一步。

责任链模式我也不打算带大家一步步写了,马士兵老师讲得挺不错的:责任链模式-马士兵

但是马士兵老师点到即止,没有给出山寨FilterChain的代码。我自己写了一个,大家看一下,基本责任链模式也就算打个照面了(看不懂的朋友,记得先看上面的视频,2.0倍速)。

public class Test {

    // main方法模拟Tomcat,假设现在浏览器有一个请求过来
    public static void main(String[] args) {
        // Tomcat准备了Request、Response
        Request request = new Request();
        Response response = new Response();

        // 过滤器链
        FilterChain filterChain = new FilterChain();
        // 注册过滤器,可以看看下方FilterChain代码是如何实现链式调用的
        filterChain.addFilter(new HTMLFilter()).addFilter(new SensitiveFilter());
        // 开始执行过滤器
        filterChain.doFilter(request, response);
    }

}

class Request {
    public void doSomething(String job) {
        System.out.println(job);
    }
}

class Response {
    public void doSomething(String job) {
        System.out.println(job);
    }
}

// 过滤器接口
interface Filter {
    void doFilter(Request request, Response response, FilterChain chain);
}


class HTMLFilter implements Filter {
    public void doFilter(Request request, Response response, FilterChain chain) {
        request.doSomething("HTMLFilter Request");
        chain.doFilter(request, response);
        response.doSomething("HTMLFilter Response");
    }
}

class SensitiveFilter implements Filter {
    public void doFilter(Request request, Response response, FilterChain chain) {
        request.doSomething("SensitiveFilter Request");
        chain.doFilter(request, response);
        response.doSomething("Sensitive Response");
    }
}

// 过滤器链
class FilterChain {

    // 标识当前执行到第几个过滤器
    private int index = 0;

    // 所有已注册的过滤器
    private List<Filter> filters = new ArrayList<Filter>();

    public FilterChain addFilter(Filter filter) {
        filters.add(filter);
                // return this,返回当前对象,即可形成链式调用
        return this;
    }

    // 执行过滤器
    public void doFilter(Request request, Response response) {
        // 所有过滤器执行完毕,return
        if (index == filters.size()) {
            return;
        }
        // 得到过滤器
        Filter filter = filters.get(index);
        // 自增操作不能和下面doFilter互换
        index++;
        // 执行过滤器
        filter.doFilter(request, response, this);
    }
}

img

这个程序的难点就是递归的退出时机,以及index++为什么不能和下面那句代码互换。

img

看大家能不能看懂return的作用吧,我不解释了。能想明白,你也就懂了。

不知道大家有没有想过下面两个问题:

  • 为什么执行Servlet之前会经过过滤器?
  • 为什么不修改代码,只修改配置就能新增的Filter,而且仍然保证在Servlet前拦截?

很多初学者其实思维有些固化了,觉得既然是过滤器,肯定就是在Servlet之前执行啊。但我偏要问:为什么?

虽然下面这个解释可能不正确,但有助于大家打破僵化的思维,站在更高的角度,不要老低着头,多抬头看看:

Tomcat应该也有类似Spring的对象管理功能。Tomcat容器初始化时,会把所有Filter对象都注入到FilterChain中,所以再次执行过滤器链,就会经过新增的过滤器(猜测而已)。

img如果Tomcat自身程序已经设定好先后顺序(如上图),那么Filters确实会在Servlet之前调用。后期通过配置(xml或注解)配置filter或servlet,都只是被注入各自阵营

而我们自己写的代码,自然和Tomcat没法比,还是无法扩展。如果需要新加一个过滤器,需修改源码。

如果你真的想搞懂责任链模式,应该把代码拷贝到自己的IDEA里,调试一遍。我知道画图画得再多,懒的人照样看不懂。


Filter、Interceptor、AOP的执行位置

img

  • DispatcherServlet本质还是一个Servlet,而Filter会在Servlet前执行。至于SpringMVC的其他组件,更是在DispatcherServlet之后,所以Filter在整体都在SpringMVC之前执行
  • SpringMVC的拦截器是在SpringMVC各个组件之间起作用的
  • AOP则是在同一个组件中要执行某个方法时才起作用

所以,我的理解是:

Filter(框架外) > Interceptor(框架内组件间) > AOP(组件内方法间)

以下代码,请自己拷贝到IDEA里调试一下。

MyController

@Controller
public class MyController {

    @RequestMapping("/hello")
    @ResponseBody
    public String hello(String say, String name) {
        System.out.println(say + "," + name +"!");
        return say + "," + name +"!";
    }

}

MyFilter

@Component
public class MyFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("filter init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("filter start");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("filter end");
    }

    @Override
    public void destroy() {
        System.out.println("filter destroy");
    }
}

MyInterceptor

@Component
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        System.out.println("preHandler...");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        System.out.println("postHandler...");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        System.out.println("afterCompletion...");
    }
}

MyAspect

@Aspect
@Component
public class MyAspect {


    @Before("execution(* com.longxiu.share.security.MyController.*(..))")
    public void before() {
        System.out.println("Before");
    }

    @After("execution(* com.longxiu.share.security.MyController.*(..))")
    public void after() {
        System.out.println("after");
    }

    @AfterReturning("execution(* com.longxiu.share.security.MyController.*(..))")
    public void afterReturning() {
        System.out.println("afterReturning");
    }

    @AfterThrowing("execution(* com.longxiu.share.security.MyController.*(..))")
    public void afterThrowing(){
        System.out.println("afterThrowing");
    }


    @Around("execution(* com.longxiu.share.security.MyController.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {

        System.out.println("around前置");

        Object result = pjp.proceed();

        System.out.println("around后置");
        return result;
    }
}

WebConfig

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private MyInterceptor myInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //继承WebMvcConfigurerAdapter,重写addInterceptor方法,利用registry添加拦截器(拦截器@Component,本就是Spring容器的)
        registry.addInterceptor(myInterceptor);

    }
}

另外,Filter解决全站编码问题的案例去看崔老师的视频,打开IDE跟着敲一下。看完多想想SpringMVC是怎么解决乱码问题的。不要怕,点开源码看看(SpringMVC的编码过滤器很简单,只是解决了POST请求乱码)。

最后,提一下Filter的REQUEST、FORWARD、INCLUDE、ERROR,把下面这个视频看懂即可(7分钟):

https://www.bilibili.com/video/av9906367/?p=9