过滤器的工作时机介于浏览器和Servlet请求处理之间,可以拦截浏览器对Servlet的请求,也可以改变Servlet对浏览器的响应。

其工作模式大概是这样的:

一、Filter的原理

在Servlet API中,过滤器接口Filter会依赖于FilterChain和FilterConfig两个接口,都位于package javax.servlet;包中。

在Tomcat容器中,ApplicationFilterChain和ApplicationFilterConfig两个类分别实现了FilterChain和FilterConfig两个接口。

Filter接口和FilterChain接口都有一个叫doFilter()的方法,Filter的doFilter()在web应用中编写具体的过滤器时,由继承Filter的实现类来重写;FilterChain接口的doFilter()方法由Tomcat容器实现了。

相关接口和类的结构关系如下图:

以下简单探索Filter的原理。

ApplicationFilterChain类在Tomcat源码中位于包org.apache.catalina.core中。

用ApplicationFilterConfig类型的数组filters保存了web应用中设置的若干个ApplicationFilterConfig实例,由ApplicationFilterConfig实例可以获得web中定义的Filter实例。因此在这种意义上,ApplicationFilterConfig实例也就的映射成Filter实例。

pos指针记录filters数组中当前的ApplicationFilterConfig实例索引。

n指针存储所有的ApplicationFilterConfig实例数量。

在ApplicationFilterChain中,主要实现了FilterChain的doFilter()方法。

  1.    //package org.apache.catalina.core;
  2. //继承javax.servlet.FilterChain(request,response)方法
  3. @Override
  4. public void doFilter(ServletRequest request, ServletResponse response)
  5. throws IOException, ServletException {
  6. if( Globals.IS_SECURITY_ENABLED ) {//判断是否设置安全管理器
  7. final ServletRequest req = request;
  8. final ServletResponse res = response;
  9. try {
  10. java.security.AccessController.doPrivileged(
  11. new java.security.PrivilegedExceptionAction<Void>() {
  12. @Override
  13. public Void run()
  14. throws ServletException, IOException {
  15. internalDoFilter(req,res);//调用内部私有的internalDoFilter()方法
  16. return null;
  17. }
  18. }
  19. );
  20. } catch( PrivilegedActionException pe) {
  21. Exception e = pe.getException();
  22. if (e instanceof ServletException)
  23. throw (ServletException) e;
  24. else if (e instanceof IOException)
  25. throw (IOException) e;
  26. else if (e instanceof RuntimeException)
  27. throw (RuntimeException) e;
  28. else
  29. throw new ServletException(e.getMessage(), e);
  30. }
  31. } else {
  32. internalDoFilter(request,response);//调用内部私有的internalDoFilter()方法
  33. }
  34. }

内部私有的internalDoFilter()方法如下:

  1. private void internalDoFilter(ServletRequest request,ServletResponse response)
  2. throws IOException, ServletException {
  3. if (pos < n) {
  4. ApplicationFilterConfig filterConfig = filters[pos++];
  5. try {
  6. Filter filter = filterConfig.getFilter();
  7. //其他处理省略
  8. if( Globals.IS_SECURITY_ENABLED ) {
  9. //其他处理省略
  10. //通过invoke()调用filter对象的doFilter()方法
  11. SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
  12. } else {
  13. //直接调用filter对象的doFilter()方法
  14. filter.doFilter(request, response, this);
  15. }
  16. } catch (IOException | ServletException | RuntimeException e) {
  17. //异常处理省略
  18. }
  19. return;
  20. }
  21. try {
  22. //其他代码省略
  23. if ((request instanceof HttpServletRequest) &&
  24. (response instanceof HttpServletResponse) &&
  25. Globals.IS_SECURITY_ENABLED ) {
  26. //其他代码省略
  27. //通过invoke()调用servlet对象的servicer()方法
  28. SecurityUtil.doAsPrivilege("service",servlet,classTypeUsedInService,args,principal);
  29. } else {
  30. //直接调用servlet对象的service()方法
  31. servlet.service(request, response);
  32. }
  33. } catch (IOException | ServletException | RuntimeException e) {
  34. //异常处理省略
  35. }
  36. }

逻辑已经很清楚,当web应用设置了多了过滤器时(比如同时设置字符过滤器、请求参数编码过滤和静态资源过滤器三个过滤器)。会依次调用这三个Filter实例的doFilter方法,就相当于是一个任务链,完成之后,在调用servlet的service()方法正式处理请求逻辑。

总结起来,过滤器的工作流程大致如下:

请求到来,陆续调用各个Filter实例的doFilter()方法,然后在调用servlet的service()处理请求,之后在执行位于doFilter()方法后的代码处理逻辑,最后才返回给浏览器。

  1. //service()前的处理逻辑
  2. chain.doFilter(req, res);
  3. //service()后的处理逻辑

二、简单应用

常见的设置过滤器的场景为:

1、特殊字符过滤器(如html页面元素:<,>等);

2、请求参数编码过滤器(GET请求参数和POST请求参数编码设置);

3、登录检测过滤器(判定用户是否登录)等等。

如下针对2和3的场景实现一个过滤器:

  1. package com.tms.web;
  2.  
  3. import java.io.IOException;
  4.  
  5. import javax.servlet.Filter;
  6. import javax.servlet.FilterChain;
  7. import javax.servlet.FilterConfig;
  8. import javax.servlet.ServletException;
  9. import javax.servlet.ServletRequest;
  10. import javax.servlet.ServletResponse;
  11. import javax.servlet.http.HttpServletRequest;
  12. import javax.servlet.http.HttpServletResponse;
  13.  
  14. import org.apache.log4j.Logger;
  15.  
  16. public class TMSFilter implements Filter {
  17. private static Logger logger = Logger.getLogger(TMSFilter.class);
  18. public void init(FilterConfig arg0) throws ServletException {
  19. // TODO Auto-generated method stub
  20. }
  21. public void destroy() {
  22. // TODO Auto-generated method stub
  23. }
  24. public void doFilter(ServletRequest resquest, ServletResponse response,
  25. FilterChain chain) throws IOException, ServletException {
  26. // TODO Auto-generated method stub
  27. HttpServletRequest req = (HttpServletRequest)resquest;
  28. HttpServletResponse res = (HttpServletResponse)response;
  29. logger.info(req.getRequestURL());
  30. //用户登录检测
  31. if (req.getSession().getAttribute("hasLoginedUsername") != null) {
  32. //请求参数编码设置
  33. HttpServletRequest wreq = new TMSRequestWrapper(req,"UTF-8");
  34. chain.doFilter(wreq, res);
  35. //service()后的处理逻辑
  36. }else {
  37. res.sendRedirect("/tms/login");
  38. }
  39. }
  40. }

通过继承HttpServletRequestWrapper类,覆盖其getParameter(String name)方法,将请求对象包装处理,使其针对GET和POST请求都能够正确的处理编码问题,这样的好处是集中在一个地方处理编码问题和登录检测,避免了在每个Servlet的doXXX()方法中去写这部分重复的代码,利于重构和维护,体现了DRY原则。

  1. package com.tms.web;
  2.  
  3. import javax.servlet.http.HttpServletRequest;
  4. import javax.servlet.http.HttpServletRequestWrapper;
  5.  
  6. public class TMSRequestWrapper extends HttpServletRequestWrapper {
  7. private String encoding = "";
  8. public TMSRequestWrapper(HttpServletRequest req,String encoding){
  9. super(req);
  10. this.encoding = encoding;
  11. }
  12. public String getParameter(String name){
  13. HttpServletRequest req = (HttpServletRequest)getRequest();
  14. String value = "";
  15.  
  16. try {
  17. //POST请求编码设置,直接通过setCharacterEncoding()来设置编码方式
  18. req.setCharacterEncoding(encoding);
  19. value = req.getParameter(name);
  20. //如果为GET请求,通过获取字节数据再转化为String对象来设置编码方式
  21. if("GET".equals(req.getMethod())){
  22. if (value != null) {
  23. byte[] b = value.getBytes("ISO-8859-1");//ISO-8859-1
  24. value = new String(b, encoding);
  25. }
  26. }
  27. } catch (Exception e) {
  28. // TODO: handle exception
  29. new RuntimeException(e);
  30. }
  31. return value;
  32. }
  33. }

参考资料:

1、《深入分析java web技术内幕》

2、《Servlet和JSP笔记》

3、apache-tomcat-9.0.1-src

Servlet过滤器简单探索的更多相关文章

  1. Servlet 过滤器

    一.过滤器介绍 在Servlet 2.3中定义了过滤器,它能够对Servlet容器的请求和响应进行检查和修改. Servlet过滤器能够在Servlet被调用之前检查Request对象,并修改Requ ...

  2. Servlet过滤器和监听器知识总结(转)

    Servlet过滤器和监听器知识总结(转)  Servlet过滤器是 Servlet 程序的一种特殊用法,主要用来完成一些通用的操作,如编码的过滤.判断用户的登录状态.过滤器使得Servlet开发者能 ...

  3. TODO java-web相关 servlet过滤器+监听器

    servlet过滤器 定义: 过滤器是小型的web组件,它负责拦截请求和响应,以便查看.提供或以某种方式操作正在客户机和服务器之间交换的数据. 与过滤器相关的servlet共包含3个简单接口:Filt ...

  4. Servlet过滤器实现访客人数统计

    第一. Servlet的创建和配置  1. 创建一个Servlet需要实现javax.servlet.Filter接口,同时实现Filter的3个方法.             第一个方法时过滤器中的 ...

  5. java基础篇---Servlet过滤器

    Servlet过滤器从字面上的字意理解为景观一层次的过滤处理才达到使用的要求,而其实Servlet过滤器就是服务器与客户端请求与响应的中间层组件,在实际项目开发中Servlet过滤器主要用于对浏览器的 ...

  6. Servlet过滤器介绍之原理分析

    zhangjunhd 的BLOG     写留言去学院学习发消息 加友情链接进家园 加好友 博客统计信息 51CTO博客之星 用户名:zhangjunhd 文章数:110 评论数:858 访问量:19 ...

  7. Servlet过滤器---简介

    过滤器的基本概念 Servlet过滤器从字面上的字意理解为经过一层次的过滤处理才达到使用的要求,而其实Servlet过滤器就是服务器与客户端请求与响应的中间层组件,在实际项目开发中Servlet过滤器 ...

  8. Servlet过滤器的使用

    Servlet过滤器的使用 制作人:全心全意 Servlet过滤器:Servlet过滤器与Servlet十分相似,但它具有拦截客户端请求的功能,Servlet过滤器可以改变请求中的内容,来满足实际开发 ...

  9. Servlet过滤器和监听器知识总结

    Servlet过滤器是 Servlet 程序的一种特殊用法,主要用来完成一些通用的操作,如编码的过滤.判断用户的登录状态.过滤器使得Servlet开发者能够在客户端请求到达 Servlet资源之前被截 ...

随机推荐

  1. FreeMarker 快速入门

    FreeMarker 快速入门 FreeMarker是一个很值得去学习的模版引擎.它是基于模板文件生成其他文本的通用工具.本章内容通过如何使用FreeMarker生成Html web 页面 和 代码自 ...

  2. Kaggle Challenge简要介绍

    https://en.wikipedia.org/wiki/Kaggle 以下内容,直接摘自维基百科,主要起到一个记录的作用,提醒自己有时间关注关注这个竞赛. Kaggle is a platform ...

  3. struts2对于处理JSON的配置

    由于最近几年日益流行前后端分离模式,JSON作为数据载体也变得不可或缺.几乎所有的web框架都需要支持JSON,下面咱就一起了解下struts2是如何支持JSON的. 对于JSON的发送 这里有两种方 ...

  4. JSP8

     一.EL表达式 JSP表达式语言(EL)使得访问存储在JavaBean中的数据变得非常简单.JSP EL既可以用来创建算术表达式也可以用来创建逻辑表达式.在JSP EL表达式内可以使用整型数,浮点数 ...

  5. Docker 三剑客之 Docker Swarm

    上一篇:Docker 三剑客之 Docker Compose 阅读目录: Docker Machine 创建 Docker 主机 Docker Swarm 配置集群节点 Docker Service ...

  6. BootStra相关脚本引用说明

    先看一个简单的模板(DOCTYPE是html5的文档类型) <!DOCTYPE html> <html lang="zh-CN"> <head> ...

  7. jquery获取iframe页面的元素

    $("#iframe_id").contents().find("#iframe_page_id").val(); 其中,iframe_id是页面引用的ifra ...

  8. SpringMVC集成Shiro、读取数据库操作权限

    1.Maven添加Shiro所需的jar包 <dependency> <groupId>org.apache.shiro</groupId> <artifac ...

  9. RabbitMQ安装步骤

    给centos安装epel yum 源 # rpm -ivh http://dl.fedoraproject.org/pub/epel/5/i386/epel-release-5-4.noarch.r ...

  10. ELK介绍

    为什么用到ELK: 一般我们需要进行日志分析场景:直接在日志文件中 grep.awk 就可以获得自己想要的信息.但在规模较大的场景中,此方法效率低下,需要集中化的日志管理,所有服务器上的日志收集汇总. ...