不要相信客户端, 所以做后端的人都应该铭记的事情。因为前端传过来的数据并不总是合法和有效的,所以后端是对访问资源的最后一道保护伞。之前我们在Spring中说到过AOP编程,AOP基础知识,它就可以在执行我们的方法之前进行一些预处理和验证来保护后端的资源。不难想到她的实现方式和本篇要说的过滤器的实现原理应该是相同的,都是通过Java的动态代理实现的(自己的理解)。
在Java Web的开发中,过滤器用于拦截请求,并对ServletRequest对象进行处理,我们可以想到的,它可以用来验证权限、加密和解密、Session检查、数据转换、防盗链等等。可以看出,它在web应用开发中是十分重要的。
一、 Filter配置使用
1、 配置方式
Filter的使用有两种配置方式,和Servlet的使用类似,分别是使用注解和部署描述文件web.xml。
使用注解配置Filter,如下:
@WebFilter(filterName="myfilter", urlPatterns="/myHttpServlet", initParams={ @WebInitParam(name="server", value="www.baidu.com"), @WebInitParam(name="port", value="443") } ) public class MyServletFilter implements Filter {}
这里和Servlet类似,分别配置了过滤器的名称、应用的url,这里url是指如果客户端访问Servlet时,如果servlet的url和这里的url相匹配,则会先执行过滤器类的方法。
另外还可以配置初始化参数。
在web.xml文件中配置如下:
<filter> <filter-name>myServletFilter1</filter-name> <filter-class>com.javaservlet.servlet.filter.MyServletFilter1</filter-class> <init-param> <param-name>server</param-name> <param-value>www.baidu.com</param-value> </init-param> <init-param> <param-name>port</param-name> <param-value>443</param-value> </init-param> </filter> <filter-mapping> <filter-name>myServletFilter1</filter-name> <url-pattern>/myHttpServlet</url-pattern> </filter-mapping>
注意在文件中配置Filter的时候要将该过滤器的配置放到Servlet配置之前。
在配置的属性中还有其他的,可以参考javaDoc看一下。
2、 基本API
Filter涉及的类有如下几个:
- javax.servlet.Filter:包含有Filter的生命周期方法,在servlet容器初始化时或者销毁时被调用;
- javax.servlet.FilterConfig:包含有关于Filter的配置信息,里边最重要就是它的初始化参数;
- javax.servlet.FilterChain:是servlet容器提供的来实现多个过滤器之间的调用;
Filte类和Servlet差不多,只是由三个生命周期方法组成,那么调用时机应该也是相同的,容器启动时调用init()方法,容器销毁时调用destroy()方法。当调用与过滤器相关的资源时则会调用doFilter()方法。如下容器启动时:
本程序是接着上一篇Java Web基础知识之Listener:监控Servlet的每个动作而来,所以程序是相同的,可以看到容器启动时首先Servlet容器初始化被监听器发现调用监听器的方法,之后就是初始化过滤器,初始化过滤器之后才会初始化Servlet,最后才是添加一个属性。
这才是Servlet容器的一个完整的启动过程。
当容器销毁时,发生的事情如下:
容器销毁时正好的启动时是相反的,首先销毁Servlet实例,然后是销毁Filter,最后是监听器监听到容器销毁并调用相关方法。
在FilterConfig中最重要的就是获取初始化参数,这和Servlet是相似的,如下:
filterConfig.getInitParameter("server"); Enumeration<String> enums = filterConfig.getInitParameterNames(); while(enums.hasMoreElements()){ String paramName = enums.nextElement(); String paramValue = filterConfig.getInitParameter(paramName); System.out.println(paramName + "-" + paramValue); }
另外FilterConfig中还有两个方法,其中getServletContext()是比较重要的,它可以获取ServletContext中的很多信息:
filterConfig.getFilterName(); ServletContext context = filterConfig.getServletContext();
最后是FilterChain类,个人感觉这是Filter里面最重要的类,只有它是和用户要访问的资源是直接相关的。
doFilter()方法才是我们实现具体的拦截器逻辑的地方,它可以修改请求的内容和属性,或者在响应中添加一个Http标头,完成自己的逻辑之后要调用FilterChain的doFilter()方法,将Servlet容器提供的请求实例和响应实例转发出去,为什么需要这个方法呢?简单说一个资源对应的过滤器可能不止一个,一个过滤器也会拦截多个请求,基于这种机制,所以一个资源实际上是对应一个过滤器链,每当一个过滤器处理结束后,就将请求转发给其他的过滤器,直到最后一个过滤器处理完成后,就会将请求和响应发给对应的Servlet。
注意:和Servlet类似,默认情况下servlet容器也是为每个过滤器类创建一个实例,也即是单实例,那么不可避免的就会遇到多线程的问题,所以这点要注意。
下面是完整的Filter实例:
@WebFilter(filterName="myfilter", urlPatterns="/myHttpServlet", initParams={ @WebInitParam(name="server", value="www.baidu.com"), @WebInitParam(name="port", value="443") } ) public class MyServletFilter implements Filter { public void init(FilterConfig filterConfig) throws ServletException { System.out.println("myServletFiler init"); filterConfig.getInitParameter("server"); Enumeration<String> enums = filterConfig.getInitParameterNames(); while(enums.hasMoreElements()){ String paramName = enums.nextElement(); String paramValue = filterConfig.getInitParameter(paramName); System.out.println(paramName + "-" + paramValue); } filterConfig.getFilterName(); ServletContext context = filterConfig.getServletContext(); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("filter service"); chain.doFilter(request, response); } public void destroy() { System.out.println("myServletFiler destroy"); } }
二、 Filter作用顺序
前面我们主要是集中在一个过滤器上,但是实际情况下,可能(因该是大多是)不止一个过滤器,所以过滤器之间顺序就很重要,这样才不会使业务逻辑混乱。这里我们做一个测试,主要有4个过滤器,两个配置使用@WebFilter配置,两个使用web.xml配置。在每个过滤器都通过打印来实现标识它们的初始化、销毁和进行过滤,同时它们都对同一资源进行过滤,如下:
public class MyServletFilter1 implements Filter { public void init(FilterConfig filterConfig) throws ServletException { System.out.println("MyServletFilter1 init"); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("MyServletFilter1 do filter"); chain.doFilter(request, response); } public void destroy() { System.out.println("MyServletFilter1 destroy"); } }
public class MyServletFilter2 implements Filter { public void init(FilterConfig filterConfig) throws ServletException { System.out.println("MyServletFilter2 init"); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("MyServletFilter2 do filter"); chain.doFilter(request, response); } public void destroy() { System.out.println("MyServletFilter2 destroy"); } }
@WebFilter(filterName="myServletFilter3", urlPatterns="/myHttpServlet") public class MyServletFilter3 implements Filter { public void init(FilterConfig filterConfig) throws ServletException { System.out.println("MyServletFilter3 init"); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("MyServletFilter3 do filter"); chain.doFilter(request, response); } public void destroy() { System.out.println("MyServletFilter3 destroy"); } }
@WebFilter(filterName="myServletFilter4", urlPatterns="/myHttpServlet") public class MyServletFilter4 implements Filter { public void init(FilterConfig filterConfig) throws ServletException { System.out.println("MyServletFilter4 init"); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("MyServletFilter4 do filter"); chain.doFilter(request, response); } public void destroy() { System.out.println("MyServletFilter4 destroy"); } }
MyServletFilter1和2都是在web.xml中进行配置的,3和4都是使用注解配置的。
<filter> <filter-name>myServletFilter1</filter-name> <filter-class>com.javaservlet.servlet.filter.MyServletFilter1</filter-class> </filter> <filter> <filter-name>myServletFilter2</filter-name> <filter-class>com.javaservlet.servlet.filter.MyServletFilter2</filter-class> </filter> <filter-mapping> <filter-name>myServletFilter2</filter-name> <url-pattern>/myHttpServlet</url-pattern> </filter-mapping> <filter-mapping> <filter-name>myServletFilter1</filter-name> <url-pattern>/myHttpServlet</url-pattern> </filter-mapping>
Filter的启动顺序:
在测试过程中的filter的启动顺序一直是2143,好像没有什么规律,但是可以发现,如果一个web应用程序中的过滤器是固定不变的,哪个这些过滤器的启动顺序就是不变的,除非有新的过滤器加入进来。
Filter的销毁顺序:
在测试过程中的filter的销毁顺序也一直是2143,如果一个web应用程序中的过滤器是固定不变的,哪个这些过滤器的启动顺序就是不变的,除非有新的过滤器加入进来;而且发现filter的销毁顺序和它们的启动顺序是一致的。
Filter的作用顺序:
在web.xml中指定过滤器的调用顺序,是由filterMapping元素决定的,哪个filter的filterMapping元素在前面则哪个filter首先被调用,而不是由filter元素决定的。
三、 Filter的dispatcher
Filter是有作用范围的,我们平常都是使用Filter作用于Request,这也是Filter中dispatcherTypes属性的默认值,这个属性的意思是由该过滤器管理的资源是通过什么样的方式访问到的,可以是请求、转发、声明式错误、包含等,但是我们还可以配置使Filter作用于其他范围,这是由dispatcherTypes属性决定的,它有如下几个值:
- DispatcherType.REQUEST
- DispatcherType.FORWARD
- DispatcherType.INCLUDE
- DispatcherType.ERROR
- DispatcherType.ASYNC
Filter属性的默认值就是DispatcherType.REQUEST,由于它是一个数组属性,所以可以配置多个值。配置方法可以使用注解,也可以使用web.xml进行配置。
使用注解:
@WebFilter(filterName="myfilter", urlPatterns="/myHttpServlet", initParams={ @WebInitParam(name="server", value="www.baidu.com"), @WebInitParam(name="port", value="443") }, dispatcherTypes={DispatcherType.REQUEST, DispatcherType.FORWARD} ) public class MyServletFilter implements Filter {}
使用web.xml,如下:
<filter-mapping> <filter-name>myServletFilter2</filter-name> <url-pattern>/myHttpServlet</url-pattern> <dispatcher>ERROR</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>REQUEST</dispatcher> </filter-mapping>
注意在web.xml中配置的时候,使用的部署描述符(web.xml)的版本要在2.3以上才会有这个属性。
配置该过滤器管理的资源当被直接调用或者通过转发调用时起作用。我们可以在servlet中进行调用,这里有两个servlet供使用:
下面的Servlet是被过滤器管理的:
@WebServlet(name="myHttpServlet", urlPatterns="/myHttpServlet") public class MyHttpServlet extends HttpServlet {......}
下面的Servlet是用来调用上面的Servlet的:
@WebServlet(name="myHttpServlet1", urlPatterns="/myHttpServlet1", loadOnStartup=1) public class MyHttpServlet1 extends HttpServlet { private static final long serialVersionUID = 6446908355902952649L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("MyHttpServlet1 request work"); System.out.println("current thread :" + Thread.currentThread().getName()); resp.getWriter().write("myHttpServlet1"); req.getRequestDispatcher("/myHttpServlet").forward(req, resp); // req.getRequestDispatcher("/myHttpServlet").include(req, resp); }
分析如下:
- 当在上述的Filter中没有DispatcherType.FORWARD时,那么上边通过getRequestDispatcher()调用forward()来访问MyHttpServlet时不会调用过滤器方法,使用forward()方法只会返回最后一个被调用的资源;
- 当在上述的Filter中没有DispatcherType.INCLUDE时,那么上边通过getRequestDispatcher()调用include()来访问MyHttpServlet时不会调用过滤器方法,使用include()方法则会将所有经过的资源全部返回;
还有一个是对错误的声明式处理,是在web.xml中进行配置的,如下:
<error-page> <error-code>404</error-code> <!-- error-code or exception-type can be chosen only one --> <!-- <exception-type>java.lang.NullPointerException</exception-type> --> <location>/myHttpServlet</location> </error-page>
当在发生404错误时,则会访问到/myHttpServlet对应的Servlet,这时候如果Filter中没有配置DispatcherType.ERROR,则也不会经过这个过滤器。
相关文章: