JavaWeb三大组件之Filter学习详解

JavaWeb三大组件之Filter学习详解

Filter基本上能够说存在全部的JavaWeb项目中,好比最基本的一个请求参数的编码CharacterEncodingFilter,你们通常都会配置下,那么filter是干吗的呢?java

本篇将主要集中在fitler的如下几个知识点:web

  • 干吗的
  • 怎么用
  • 多个Filter执行的前后顺序
  • 注意事项

I. 基本知识

Filter称之为过滤器,是用来作一些拦截的任务, 在Servlet接受请求以前,作一些事情,若是不知足限定,能够拒绝进入Servletspring

arch

从上面的图,能够看出一个Filter的工做流程:安全

一个http请求过来以后mvc

  • 首先进入filter,执行相关业务逻辑
  • 若断定通行,则进入Servlet逻辑,Servlet执行完毕以后,又返回Filter,最后在返回给请求方
  • 断定失败,直接返回,不须要将请求发给Servlet

经过上面的流程,能够推算使用场景:app

  • 在filter层,来获取用户的身份
  • 能够考虑在filter层作一些常规的校验(如参数校验,referer校验等)
  • 能够在filter层作稳定性相关的工做(如全链路打点,能够在filter层分配一个traceId;也能够在这一层作限流等)

1. 基本使用姿式

要使用一个Filter,一半须要两步,实现Filter接口的自定义类,web.xml中对filter的定义curl

public interface Filter {
    public void init(FilterConfig filterConfig) throws ServletException;
	
	
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain)
            throws IOException, ServletException;


    public void destroy();
}

主要就三个方法,从命名来看,async

  • 也比较清晰,在建立Filter对象的时候,调用 init 方法
  • 销毁Filter对象的时候,调用 destroy 方法
  • 当请求过来以后,调用 doFilter,也就是主要的业务逻辑所在了

详细case后面再说ide

接下来就是xml的配置了,和Servlet相似,每自定义一个,都须要在xml中加上一个配置(挺繁琐的操做的)学习

<!-- 解决乱码的问题 -->
<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <async-supported>true</async-supported>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

配置也比较简单了,一个 <filter> 一个 <filter-mapping> 前者定义具体的Filter,后者表示这个Filter拦截的URL (看起来和Servlet的配置规则没什么两样)


II. 实例

咱们的实例,就拿大名鼎鼎的CharacterEncodingFilter来讲明,顺带膜拜下Spring的大神的优秀源码

public class CharacterEncodingFilter extends OncePerRequestFilter {

	private String encoding;

	private boolean forceEncoding = false;

	public CharacterEncodingFilter() {
	}

	public CharacterEncodingFilter(String encoding) {
		this(encoding, false);
	}

	public CharacterEncodingFilter(String encoding, boolean forceEncoding) {
		Assert.hasLength(encoding, "Encoding must not be empty");
		this.encoding = encoding;
		this.forceEncoding = forceEncoding;
	}

	public void setEncoding(String encoding) {
		this.encoding = encoding;
	}

	public void setForceEncoding(boolean forceEncoding) {
		this.forceEncoding = forceEncoding;
	}

	@Override
	protected void doFilterInternal(
			HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) {
			request.setCharacterEncoding(this.encoding);
			if (this.forceEncoding) {
				response.setCharacterEncoding(this.encoding);
			}
		}
		filterChain.doFilter(request, response);
		System.out.printl("servelt 执行完成,又返回filter");
	}
}

上面的实现比较简单,主要将视线集中在 doFilterInternal 方法内部,若是要设置编码参数,则直接修改 HttpServletRequest, HttpServletResponse 两个参数,操做完成以后,执行下面这一行

filterChain.doFilter(request, response);

注意

  • 上面这一行执行,表示Filter层已经经过了,请求能够转发给下一个Filter或者直接传给Servlet
  • 而下一个Filter, Servlet执行完成以后,还会继续往下走,就是上面的那一行输出,也会被调用(那一行是我加的,源码中没有)

因此,若是你不但愿继续往下走,那么就简单了,不执行上面的那一行便可

疑问

问题一:看了上面的源码,一个很明显的问题就是,参数怎么设置的?

仔细看上面的源码,发现自定义Filter是继承 org.springframework.web.filter.OncePerRequestFilter 而不是直接实现的 Filter 接口,并且方法内也没有显示的实现 init()方法,全部很容易猜到是父类中实现了参数的初始化过程

具体的实现逻辑是在 org.springframework.web.filter.GenericFilterBean#init 中,一样是Spring实现的,主要代码捞出来

public final void init(FilterConfig filterConfig) throws ServletException {
		Assert.notNull(filterConfig, "FilterConfig must not be null");
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'");
		}

		this.filterConfig = filterConfig;

		// Set bean properties from init parameters.
		try {
			PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
			initBeanWrapper(bw);
			bw.setPropertyValues(pvs, true);
		}
		catch (BeansException ex) {
			String msg = "Failed to set bean properties on filter '" +
				filterConfig.getFilterName() + "': " + ex.getMessage();
			logger.error(msg, ex);
			throw new NestedServletException(msg, ex);
		}

		// Let subclasses do whatever initialization they like.
		initFilterBean();

		if (logger.isDebugEnabled()) {
			logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");
		}
}

看上面一大串的代码,到底干了嘛? 简单来说,就是获取xml中配置的参数,而后填充到Filter对象中(对Srping而言,CharacterEncodingFilter就是一个bean),这个具体的逻辑和本篇关系不大,就直接跳过了

问题二:在Filter层中能够获取参数么

从doFilter的方法签名中看,既然有Request参数,那么应该是能够获取到请求参数的,那么实际验证一下

先实现一个最最最简单的Filter

public class TestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        System.out.println("in filter");
        System.out.println("args: " + JSON.toJSONString(request.getParameterMap()));
        chain.doFilter(request, response);
        System.out.println("out filter");
    }

    @Override
    public void destroy() {
    }
}

开始测试

curl -d 'name=Hello&password=world' http://127.0.0.1:8088/123

输出以下

in filter
args: {"name":["Hello"],"password":["world"]}
out filter

注意

在Filter中获取参数时,最好不要直接使用获取请求流的方式,若是获取请求流,那么Servlet就获取不到请求参数了

问题三:多个filter的顺序怎么定

前面学习Servlet的时候,也有这个问题,一个URL被多个Servlet命中了,那么前后顺序是怎样的呢?

  • 精确匹配 > 最长匹配 > 其余模糊匹配 > 没有匹配的则是404

那么Filter呢,他们的区别仍是比较明显的,不少Filter都是拦截全部的请求,即不少Filter的命中规则都是同样的,那么怎么办?

  • 先执行带有url-pattern标签的filter,再执行带有servlet-name标签的filter
  • 若是同为url-pattern或servlet-name,则会按照在web.xml中的声明顺序执行

测试case以下,咱们定义三个Filter:

  • TestFilter: 匹配全部路径
  • ATestFilter: 匹配全部路径
  • ServletFilter: 匹配 mvc-servlet
// ATestFilter
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    System.out.println("in ATestFilter");
    chain.doFilter(request, response);
}


// TestFilter
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    System.out.println("in TestFilter");
    chain.doFilter(request, response);
}

// ServletFilter
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    System.out.println("in ServletFilter");
    chain.doFilter(request, response);
}

对应的xml配置以下

<filter>
    <filter-name>servletFilter</filter-name>
    <filter-class>com.test.ServletFilter</filter-class>
    <async-supported>true</async-supported>
</filter>
<filter-mapping>
    <filter-name>servletFilter</filter-name>
    <servlet-name>mvc-dispatcher</servlet-name>
</filter-mapping>

<filter>
    <filter-name>testFilter</filter-name>
    <filter-class>com.test.TestFilter</filter-class>
    <async-supported>true</async-supported>
</filter>
<filter-mapping>
    <filter-name>testFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>


<filter>
    <filter-name>atestFilter</filter-name>
    <filter-class>com.test.ATestFilter</filter-class>
    <async-supported>true</async-supported>
</filter>
<filter-mapping>
    <filter-name>atestFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

输出结果

in TestFilter
in ATestFilter
in ServletFilter

III. 小结

Filter 一般用于JavaWeb的过滤使用,经过doFilter方法中执行 chain.doFilter(request, response);,进入下一个Filter或者Servlet执行逻辑,当执行完成以后,依然会回到Filter这一层,继续走下去

针对上面的逻辑,Filter的常见应用场景有:

  • 用户信息获取,身份校验
  • 安全校验(referer校验失败,直接拒绝)
  • 稳定性相关(限流,监控埋点,全链路日志埋点)

Filter的执行顺序:

  • url-mapping 的优先执行,其次是 servlet-mapping
  • 同一个匹配方式(如都是url-mapping)中,根据在xml中定义的前后顺序来肯定

Filter的注意事项:

  • 正常业务,请记得必定执行 chain.doFilter(request, response), 最后把它放在finnal块中,防止你在Filter中的代码抛异常致使进入不到后续的逻辑
  • 在Filter中不要直接获取请求数据流(请求流被读取完以后,Servlet就get不到了!)

IV. 其余

声明

尽信书则不如,已上内容,纯属一家之言,因本人能力通常,看法不全,若有问题,欢迎批评指正

扫描关注,java分享

QrCode

相关文章
相关标签/搜索