Tomcat过滤器

过滤器的功能

tomcat作为一个基于servlet的web服务器,主要职责在于封装请求(ServletRequest)和响应(ServletResponse)对象并分发请求和作出响应
一个java web程序按照tomcat的约定处理请求和响应的具体业务逻辑,本质上,这是一个在应用层基于servlet的程序,即使到了spring框架时代,底层依然如此
一个基于servlet的网络程序不必考虑应用层以及之下的网络通信细节,这些困难复杂繁琐通用而又容易出错的环节,已被封装为各类servlet接口

tomcat在分发请求以及作出响应之前,可以切入一段代码执行切入的业务逻辑,这正是通过其所谓的过滤器完成的

面向servlet编程时代,开发者只需要了解servlet编程中的请求、响应、过滤器等主要对象,就可以专注于自身业务逻辑的开发,完成绝大多数业务的处理
在spring框架编程时代,深度封装了servlet,请求和响应对象高度封装、spring容器内部请求路径映射、切面代码、依赖注入、配置文件以及注解等特性,使得web编程更加简化
在spring2.0即springboot时代,约定大于配置的思想,更多注解的引入,模块化的maven依赖配置等,结合众多插件如lombok的引入,进一步降低了代码量
在spring3.0即springcloud时代,一个集大成的架构层次的分布式的业务群模版的出现,使得架构解决方案成为每个入门开发者都可触及的能力

springfuture时代,只要web程序仍然部署在servlet容器中,框架底层就离不开servlet接口编程
tomcat过滤器本身,就是servlet的一个接口
过滤器的功能就是在分发请求以及作出响应之前切入代码

过滤器的设计原理和配置使用建议

过滤器Filter是定义于tomcat的servlet-api.jar中的一个接口,接口路径为javax.servlet.Filter
实现了该接口的类将被tomcat加载用以在请求处理前后拦截请求,拦截后,用户可对请求的ServletRequest和ServletResponse对象进行各种业务处理
常见的需求如,请求计数、请求地址等信息日志记录、消息加解密和编解码、消息内容检查以及转换和重写、登陆token校验等,都可以在拦截时处理

顾名思义,tomcat过滤器采用了典型的过滤器设计模式,过滤器链FilterChain由tomcat维持,链条是可以支持多个过滤器的
另外,为了分层简化代码,通常一个过滤器只需要完成一项任务,有多种功能需求时,应该配置多个过滤器
当然,根据需要,一个过滤器可以配置为拦截一个或多个资源,这些资源在同一个过滤器中完成同一项过滤任务

除非一个过滤器在web.xml中注册了多次,tomcat只会为每一个过滤器类注册一个对象,即过滤器对象是单例对象
过滤器可能同时处理来自多个用户的网络请求,因此需要注意多线程安全问题
通常,也不需要在过滤器中定义共享的可变变量

当定义了多个过滤器时,顺序通常是很重要的,比如先进行ip白名单过滤,然后再进行token校验,然后再进行请求地址的计数
过滤器由tomcat读取web.xml文件或扫描@WebFilter注解类注册,另外,还可以通过spring的FilterRegistrationBean注册
注解过滤器不能[tomcat8.5及以下,9及以上未验证]指定过滤器顺序
springboot项目约定大于配置,默认是不配置web.xml的,当然,也可以根据需要添加web.xml,web.xml按照上下顺序注册过滤器
FilterRegistrationBean可以指定过滤器顺序
那么应该使用哪种注册方式呢?
web.xml的优点是可以直接修改而不需要重新编译项目代码,缺点是需要配置web.xml文件
FilterRegistrationBean的优点是可以在代码中注册过滤器和指定其顺序,缺点是修改时需要重新编译,当然,如果在一个@Configuration配置类中注册frb,也只是需要重新编译一个java文件而已
能简就简,推荐在前后端分离的项目中使用frb注册过滤器,而在必须或已经存在web.xml的项目中使用web.xml文件注册过滤器

javax.servlet.Filter接口定义了3个方法:init、doFilter、destroy
①void init(FilterConfig filterConfig)
应用程序启动时,servlet容器就会调用init方法,该方法也只会在启动时调用一次
FilterConfig可以从@WebFilter注解中,或者web.xml中读取多个String类型参数进行过滤器对象全局变量的初始化
读取的参数如,免过滤的路径列表,是否启用过滤器等

web.xml配置:

    <filter>
        <filter-name>filterA</filter-name>
        <filter-class>com.liuwei.filter.FilterA</filter-class>
        <init-param>
            <param-name>freeFilterSet</param-name>
            <param-value>/login,/api,/sso</param-value>
        </init-param>
        <init-param>
            <param-name>startFilter</param-name>
            <param-value>false</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>filterA</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

类头@WebFilter注解配置:

    @WebFilter(filterName = "filterA", urlPatterns = { "/*" }, initParams = {
            @WebInitParam(name = "freeFilterSet", value = "/login,/api,/sso"),
            @WebInitParam(name = "startFilter", value = "false") })

过滤器类


public class FilterA implements Filter {

	// 免过滤的路径集合
	private final Set<String> freeFilterSet = new HashSet<String>();
	// 是否开启过滤器
	private boolean startFilter = true;

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		System.out.println("FilterA init:" + TimeUtil.getNow());
		String startFilterString = filterConfig.getInitParameter("startFilter");
		if (!Boolean.valueOf(startFilterString)) {
			startFilter = false;
			return;
		}
		String[] freeFilterList = filterConfig.getInitParameter("freeFilterList").split(",");
		for (String freeFilter : freeFilterList) {
			freeFilterSet.add(freeFilter);
		}
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		//过滤器业务代码
	}

	@Override
	public void destroy() {
		System.out.println("FilterA destroy:" + TimeUtil.getNow());
	}

}

②void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException
每次调用过滤器关注的资源前,servlet容器都会先调用Filter对象的doFilter方法
servlet容器会向该方法传入3个必需的参数:ServletRequest、ServletResponse和FilterChain
这3个参数有各自的用处
开发者可以从ServletRequest获取、新增、删除或者修改一个属性,可以根据一些属性值完成token校验、请求地址记录日志、请求转发等任务
可以在ServletResponse中设置一个响应头或者请求重定向(到另一个请求路径)等
当过滤任务执行完成,可以根据需要直接返回响应结果(如校验ip白名单或token失败),或者更多情况下,放行请求到过滤器链的下一个过滤器中
如果需要放行,必须调用FilterChain的doFilter(request, response)方法,反之,如果不调用该方法,就不会放行而是直接返回响应

对于前后端分离的项目,在过滤失败时,返回一个约定格式的json对象,告知前端请求的错误码和详细信息
对于不分离的项目或者接入三方单点登入校验的项目,在过滤失败时,转发或重定向到4xx或5xx等错误视图

特别需要注意的是,过滤器链由一组按照顺序注册的过滤器组成,并且doFilter方法是同步阻塞的
这意味着请求和响应会在链上按顺序且同步地传递,请求时依次从第一个过滤器到最后一个放行进入的过滤器,响应时正相反顺序
注意,在链上的某个过滤器可能因为过滤失败而不再放行到下一个过滤器,因此,此处的最后一个放行进入的过滤器不一定是链上的最后一个过滤器
放行代码即FilterChain.doFilter(request, response)一般是一个过滤器doFilter方法的最后一行代码,这意味着过滤器对返回的响应不作任何处理
但也是可以根据需要后处理ServletResponse对象的,后处理虽然非常少用,但可以据此更清楚地认识整个过滤器链代码执行的顺序


③void destroy()
在过滤器终止服务之前被调用,当应用程序停止时,servlet容器会自动调用
当然,开发者也可以自己调用,比如,根据某个参数判断已经不需要此过滤器了,可以自我调用注销
通常一个应用程序关闭时并不需要在过滤器注销代码中执行任务
Spring框架的一些监听器接口如ServletContextListener、JVM的钩子等都可以执行程序关闭前的任务逻辑

过滤器的注册方式

定义一个过滤器类只需要实现Filter接口即可,但想要被tomcat容器或spring容器识别,还需要进行注册
1.@WebFilter注解
javax.servlet.annotation.WebFilter注解注于Filter实现类头即可
这种方式由于不能指定过滤器顺序,基本不会使用,不详述

2.web.xml配置
范例如下

    <filter>
        <filter-name>filterA</filter-name>
        <filter-class>com.liuwei.filter.FilterA</filter-class>
        <init-param>
            <param-name>freeFilterSet</param-name>
            <param-value>/login,/api,/sso</param-value>
        </init-param>
        <init-param>
            <param-name>startFilter</param-name>
            <param-value>false</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>filterA</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

3.FilterRegistrationBean注册
范例如下
在frb中注册的过滤器由spring容器管理,而非servlet容器管理
特别地,一些过滤器中需要依赖注入Spring容器中的bean对象,此时,frb注册是最好的选择

    package com.liuwei.configure;

    import com.telecom.js.noc.hxtnms.operationplan.filter.AuthFilter;
    import com.telecom.js.noc.hxtnms.operationplan.filter.SessionForbiddenFilter;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    import javax.servlet.Filter;

    /**
     * @author liuwei
     * @date 2019-07-26 15:16
     * @desc 过滤器注册:解决多过滤器顺序以及依赖注入问题
     * 在配置多个过滤器时,Order注解并不能保证过滤器顺序,需要通过FilterRegistrationBean在配置类中注册过滤器
     */
    @Configuration
    public class FilterRegistrationConfig {

        private static final String AUTHOR_KEY = "author";
        private static final String AUTHOR_VAL = "liuwei";

        /**
         * 注册鉴权过滤器:优先级1
         */
        @Bean(name = "authFilterRegistration")
        @Autowired
        public FilterRegistrationBean authFilterRegistration(Filter authFilter) {
            FilterRegistrationBean registration = new FilterRegistrationBean(authFilter);
            registration.addUrlPatterns("/*");
            registration.addInitParameter(AUTHOR_KEY, AUTHOR_VAL);
            registration.setName("authFilter");
            registration.setOrder(1);
            return registration;
        }

        /**
         * 对鉴权过滤器创建Bean对象:因需要进行Spring注入
         * 可以使用任何普通Bean的注册方式
         * 本例使用配置类注册过滤器为一个普通Bean
         * 也可以在过滤器类头添加@Component注解来注册
         */
        @Bean(name = "authFilter")
        public Filter authFilter() {
            return new AuthFilter();
        }


        /**
         * 注册session禁用过滤器:优先级0
         */
        @Bean(name = "sessionForbiddenFilterRegistration")
        public FilterRegistrationBean sessionForbiddenFilterRegistration() {
            FilterRegistrationBean registration = new FilterRegistrationBean(new SessionForbiddenFilter());
            registration.addUrlPatterns("/*");
            registration.addInitParameter(AUTHOR_KEY, AUTHOR_VAL);
            registration.setName("sessionForbiddenFilter");
            registration.setOrder(0);
            return registration;
        }

    }

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值