目录
Filter表示过滤器,是JavaWeb三大组件(Servlet、Filter、Listener)之一。
过滤器可以把资源的请求拦截下来,从而实现一些特殊的功能。
如下图所示,浏览器可以访问服务器上的所有的资源(Servlet、jsp、html等)

而在访问到这些资源之前可以使用过滤器拦截下来,也就是说在访问资源之前会先经过Filter,如下图
拦截器拦截到后可以做什么功能呢?
过滤器一般完成一些通用的操作
比如每个资源都要写一些代码完成某个功能,我们总不能在每个资源中写这样的代码吧,而此时我们可以将这些代码写在过滤器中,因为请求每一个资源都要经过过滤器。
我们之前做的品牌数据管理的案例中就已经做了登陆的功能,而如果我们不登录能不能访问到数据呢?我们可以在浏览器直接访问首页 ,可以看到 查询所有 的超链接

当我点击该按钮,居然可以看到品牌的数据

这显然是不合理的。我们希望实现的效果是如果用户登录过了就跳转到数据展示页面;如果用户没有登录就跳转到登陆页面让用户进行登陆,要实现这个效果需要在每一个资源中都写上这段逻辑,而像这种通用的操作,我们就可以放在过滤器中进行实现。这个就是权限控制,以后我们还会进行细粒度权限控制。过滤器还可以做 统一编码处理、 敏感字符处理 等等…
进行Filter开发分成以下三步实现
定义类,实现Filter接口,并重写其所有方法
- import javax.servlet.*;
- import javax.servlet.annotation.*;
- import java.io.IOException;
-
- @WebFilter("/*")
- public class FilterDemo 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);
- }
- }
配置Filter拦截资源的路径:在类上定义@WebFilter注解。而注解的value属性值 /* 表示拦截所有的资源

在doFilter方法中输出一句话,并放行

注意:
上述代码中的chain.doFilter(request, response);就是放行,也就是让其访问本该访问的资源
创建一个项目,项目下有一个hello.jsp页面,项目结构如下:

pom.xml如下配置文件
- "1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0modelVersion>
-
- <groupId>org.examplegroupId>
- <artifactId>filter-demoartifactId>
- <version>1.0-SNAPSHOTversion>
-
- <packaging>warpackaging>
-
- <properties>
- <maven.compiler.source>8maven.compiler.source>
- <maven.compiler.target>8maven.compiler.target>
- properties>
-
- <dependencies>
-
- <dependency>
- <groupId>javax.servletgroupId>
- <artifactId>javax.servlet-apiartifactId>
- <version>3.1.0version>
- <scope>providedscope>
- dependency>
-
-
- <dependency>
- <groupId>javax.servlet.jspgroupId>
- <artifactId>jsp-apiartifactId>
- <version>2.2version>
- <scope>providedscope>
- dependency>
-
- dependencies>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.tomcat.mavengroupId>
- <artifactId>tomcat7-maven-pluginartifactId>
- <version>2.2version>
- <configuration>
- <port>80port>
- configuration>
- plugin>
- plugins>
- build>
-
- project>
hello.jsp页面如下
- <%@ page contentType="text/html;charset=UTF-8" language="java" %>
- <html>
- <head>
- <title>Titletitle>
- head>
- <body>
- <h1>hello JSPh1>
- body>
- html>
启动测试
在浏览器输入 http://localhost/filter-demo/hello.jsp 访问 hello.jsp 页面,这里是可以访问到 hello.jsp 页面内容的。

注意:
这里访问时没有输入8080端口是因为我们在pom.xml中修改了端口号,如下所示

接下来编写过滤器,过滤器是Web三大组件之一,所以我们将filter创建在com.clear.web.filter包下,起名 FilterDemo
-
- @WebFilter("/*")
- public class FilterDemo implements Filter {
-
- @Override
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
- System.out.println("FilterDemo....");
-
-
- }
-
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
-
- }
-
- @Override
- public void destroy() {
-
- }
- }
重启启动服务器,再次重新访问 hello.jsp 页面,这次发现页面没有任何效果,但是在 idea 的控制台可以看到如下内容

上述效果说明了 FilterDemo这个过滤器的 doFilter()方法执行了,但是为什么在浏览器上看不到hello.jsp页面的内容呢?
这是因为在doFilter()方法中添加放行的方法才能访问到hello.jsp页面的内容,如下所示


如上图是使用过滤器的流程,我们通过以下问题来研究过滤器的执行流程:
放行后访问对应资源,资源访问完成后,还会回到Filter中吗?
从上图就可以看出肯定 会回到Filter中
如果回到Filter中,是重头执行还是执行放行后的逻辑呢?
如果是重头执行的话,就意味着放行前的逻辑会被执行两次,肯定不会这样设计的;所以访问完资源后,会回到放行后的逻辑,执行该部分代码
通过上述说明,我们可以将Filter执行流程总结如下:

接下来我们通过代码验证一下,在doFilter()方法前后都加上输出语句,如下

同时在 hello.jsp 页面加上输出语句,如下

执行访问该资源打印的顺序是按照我们标记的标号进行打印的话,说明我们上边总结出来的流程是没有问题的。启动服务器访问 hello.jsp 页面,在控制台打印的内容如下:

以后我们可以将对请求进行处理的代码放在放行之前进行处理,而如果请求完资源后还要对响应的数据进行处理时可以在放行后进行逻辑处理。
拦截路径表示Filter会对请求的哪些资源进行拦截,使用 @WebFilter注解进行配置。如下
@WebFilter("拦截路径")
拦截路径有如下四种配置方式:
拦截具体的资源:如:/index.jsp:之一访问index.jsp时才会被拦截
目录拦截:如:/user/*:访问/user下的所有资源都会被拦截
后缀名拦截:如:*.jsp:访问后缀名为jsp的资源,都会被拦截
拦截所有:/*:访问所有的资源,都会被拦截
通过上述,可以发现拦截路径的配置方式和Servlet的请求资源路径配置方式一样,但是表示的含义不同。
过滤器链是指在一个Web应用,可以配置多个过滤器,这多个过滤器称为过滤器链。
如下图就是一个过滤器链,我们学习过滤器链主要是学习过滤器链执行的流程
上图中的过滤器链执行顺序如下:
1、执行Filter1的放行前逻辑
2、执行Filter1的放行代码
3、执行Filter2的放行前逻辑
4、执行Filter2的放行代码
5、访问到资源
6、执行Filter1的放行后逻辑
7、执行Filter2的放行后逻辑
以上流程串起来就像一条链子,故称之为过滤器链。
编写第一个过滤器FIlterDemo,配置成拦截所有资源
-
- @WebFilter("/*")
- public class FilterDemo implements Filter {
-
- @Override
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
- // 放行前,对 request数据进行处理
- System.out.println("1、FilterDemo....");
- // 放行
- filterChain.doFilter(servletRequest,servletResponse);
- // 放行后,对 response数据进行处理
- System.out.println("5、FilterDemo....");
-
- }
-
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
-
- }
-
- @Override
- public void destroy() {
-
- }
- }
编写第二个过滤器FilterDemo2,配置成拦截所有
- @WebFilter("/*")
- public class FilterDemo2 implements Filter {
-
- @Override
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
- System.out.println("2、FilterDemo....");
- // 放行
- filterChain.doFilter(servletRequest,servletResponse);
- // 放行后,对 response数据进行处理
- System.out.println("4、FilterDemo....");
-
- }
-
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
-
- }
-
- @Override
- public void destroy() {
-
- }
- }
修改hello.jsp页面中脚本的输出语句
- <%@ page contentType="text/html;charset=UTF-8" language="java" %>
- <html>
- <head>
- <title>Titletitle>
- head>
- <body>
- <h1>hello JSPh1>
- <%
- System.out.println("3、hello jsp");
- %>
- body>
- html>
启动,在浏览器输入 http://localhost/filter-demo/hello.jsp 进行测试,在控制台打印内容如下

从结果可以看到确实是按照我们之前说的执行流程进行执行的。
上述代码中为什么先执行 FIlterDemo,后执行FilterDemo2呢?
我们现在使用的是注解配置Filter,而这种配置方式的优先级是按照过滤器类名(字符串)的自然排序
比如有如下两个名称的过滤器 : BFilterDemo 和 AFilterDemo 。那一定是 AFilterDemo 过滤器先执行。
访问服务器资源时,需要先进行登录验证,如果没有登录,则自动跳转到登录页面
我们要实现该功能是在每一个资源里加入登陆状态校验的代码吗?显然是不需要的,只需要写一个 Filter ,在该过滤器中进行登陆状态校验即可。而在该 Filter 中逻辑如下:

在之前的brand-demo工程创建com.clear.web.filter包,在该包下创建LoginFilter的过滤器
-
- @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 {
- chain.doFilter(request, response);
- }
- }
在doFilter()方法中编写登录状态校验的逻辑代码
我们首先需要从 session 对象中获取用户信息,但是 ServletRequest 类型的 requset 对象没有获取 session 对象的方法,所以此时需要将 request对象强转成 HttpServletRequest 对象。
HttpServletRequest req = (HttpServletRequest) request;
然后完成以下逻辑:
获取Session对象
从Session对象中获取名为 user 的数据
判断获取的数据是否为null
如果不是,说明已经登录,放行
如果是,说明未登录,将提示信息存储到域对象,并跳转至登录页面
代码如下:
-
- @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 req = (HttpServletRequest) request;
-
- HttpSession session = req.getSession();
- Object user = session.getAttribute("user");
- if (user == null) {
- // 没有登陆,存储提示信息,跳转到登录页面
- req.setAttribute("login_msg","您尚未登录");
- req.getRequestDispatcher("/login.jsp").forward(request, response);
-
- }else {
- // 登录过,放行
- chain.doFilter(request, response);
- }
- }
- }
在浏览器上输入 http://localhost:8080/brand-demo/ ,可以看到如下页面效果

从上面效果可以看出登录页面确实是跳转了,但是登录页面为什么会变成这种效果呢?
因为登录页面需要 css/login.css这个文件进行样式渲染,下面是登录页面引入的css文件图解

而在请求这个css资源时被过滤器拦截,就相当于没有加载到样式文件导致的。解决这个问题,只需要对所以的登陆相关的资源进行放行即可。还有一种情况就是当我没有用户信息时需要进行注册,而注册时也希望被过滤器放行。
综上,我们需要在判断session中是否包含用户信息user之前,应该加上对登录及注册相关资源放行的逻辑处理
LoginFilter代码如下如下
-
- @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 req = (HttpServletRequest) request;
-
- //判断访问资源路径是否和登录注册相关
- //1、在数组中存储登陆和注册相关的资源路径
- String[] urls = {"/login.jsp", "/imgs","/css","/loginServlet",
- "/register.jsp","/registerServlet","/checkCodeServlet"};
- //2、获取当前访问的资源路径
- String url = req.getRequestURL().toString();
-
- //3、遍历数组,获取到每一个需要放行的资源路径
- for (String u: urls) {
- //4、判断当前访问的资源路径字符串是否包含要放行的的资源路径字符串
- /*
- 比如当前访问的资源路径是 /brand-demo/login.jsp
- 而字符串 /brand-demo/login.jsp 包含了 字符串 /login.jsp ,所以这个字符串就需要放行
- */
- if(url.contains(u)){
- // 放行
- chain.doFilter(request,response);
- return;
- }
-
- }
-
- HttpSession session = req.getSession();
- Object user = session.getAttribute("user");
- if (user == null) {
- // 没有登陆,存储提示信息,跳转到登录页面
- req.setAttribute("login_msg", "您尚未登录");
- req.getRequestDispatcher("/login.jsp").forward(request, response);
-
- } else {
- // 登录过,放行
- chain.doFilter(request, response);
- }
- }
- }