众所周知,在Servlet2.3规范之前Filter会拦截包括内部转发和外部转发,也就是会拦截Forward方式的转发请求。
在2.4规范之后,Filter只会拦截外部请求,诸如以下方式转发的请求,Filter不会拦截
request.getRequestDispatcher(newUri).forward(req, response);
如果需要拦截Foward方式的转发则需要配置web.xml内filter的配置,需要指明拦截dispatcher为FORWARD的方式。
<filter-mapping> <filter-name>ActionFilter</filter-name> <url-pattern>*.do</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> </filter-mapping>
一般情况下,开发者都能控制这个流程,但是倘若做一些url的重写,下面举一个例子:
现有一个struts项目配置的filter拦截请求的地址为*.action没有配置dispacher为FORWARD方式的请求,在struts的filter前有一个filter做url重写,需求是将请求地址栏的比如http://localhost/a.html转发到http://localhost/a.action做伪静态化。
那么当url rewrite filter将请求改写为a.aciton时,由于请求进入tomcat时为a.html,struts拦截的是*.action请求,filterchain并没有将struts filter加入到其中,如果只是重写getRequestUri,getRequestUrl,请求还是无法到达struts。
另一种方式就是做FORWARD转发,结果也是可想而知,struts filter将不会拦截FORWARD方式的转发。
有的同学会说,那我把struts filter的dispacher加上FORWARD方式不就好了?
但是得考虑到,这可能是一个庞大的项目,不一定可以这样随意修改。
下面说下思路:
在跟踪了tomcat dispacher那一块的源码后发现,在请求进入tomcat后,tomcat 在request请求实现上会创建filterchain,然后根据路径是否匹配filter
public ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) { // get the dispatcher type DispatcherType dispatcher = null; if (request.getAttribute(DISPATCHER_TYPE_ATTR) != null) { dispatcher = (DispatcherType) request.getAttribute(DISPATCHER_TYPE_ATTR); } String requestPath = null; Object attribute = request.getAttribute(DISPATCHER_REQUEST_PATH_ATTR); if (attribute != null){ requestPath = attribute.toString(); } // If there is no servlet to execute, return null if (servlet == null) return (null); boolean comet = false; // Create and initialize a filter chain object ApplicationFilterChain filterChain = null; if (request instanceof Request) { Request req = (Request) request; comet = req.isComet(); if (Globals.IS_SECURITY_ENABLED) { // Security: Do not recycle filterChain = new ApplicationFilterChain(); if (comet) { req.setFilterChain(filterChain); } } else { filterChain = (ApplicationFilterChain) req.getFilterChain(); if (filterChain == null) { filterChain = new ApplicationFilterChain(); req.setFilterChain(filterChain); } } } else { // Request dispatcher in use filterChain = new ApplicationFilterChain(); } filterChain.setServlet(servlet); filterChain.setSupport (((StandardWrapper)wrapper).getInstanceSupport()); // Acquire the filter mappings for this Context StandardContext context = (StandardContext) wrapper.getParent(); FilterMap filterMaps[] = context.findFilterMaps(); // If there are no filter mappings, we are done if ((filterMaps == null) || (filterMaps.length == 0)) return (filterChain); // Acquire the information we will need to match filter mappings String servletName = wrapper.getName(); // Add the relevant path-mapped filters to this filter chain for (int i = 0; i < filterMaps.length; i++) { if (!matchDispatcher(filterMaps[i] ,dispatcher)) { continue; } if (!matchFiltersURL(filterMaps[i], requestPath)) continue; ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMaps[i].getFilterName()); if (filterConfig == null) { // FIXME - log configuration problem continue; } boolean isCometFilter = false; if (comet) { try { isCometFilter = filterConfig.getFilter() instanceof CometFilter; } catch (Exception e) { // Note: The try catch is there because getFilter has a lot of // declared exceptions. However, the filter is allocated much // earlier } if (isCometFilter) { filterChain.addFilter(filterConfig); } } else { filterChain.addFilter(filterConfig); } } // Add filters that match on servlet name second for (int i = 0; i < filterMaps.length; i++) { if (!matchDispatcher(filterMaps[i] ,dispatcher)) { continue; } if (!matchFiltersServlet(filterMaps[i], servletName)) continue; ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMaps[i].getFilterName()); if (filterConfig == null) { // FIXME - log configuration problem continue; } boolean isCometFilter = false; if (comet) { try { isCometFilter = filterConfig.getFilter() instanceof CometFilter; } catch (Exception e) { // Note: The try catch is there because getFilter has a lot of // declared exceptions. However, the filter is allocated much // earlier } if (isCometFilter) { filterChain.addFilter(filterConfig); } } else { filterChain.addFilter(filterConfig); } } // Return the completed filter chain return (filterChain); }
仔细观察如下代码
//注意这个地方 //在遍历所有的filter判断当前请求是否需要添加filter //matchDispatcher则是实现判断的地方 // Add filters that match on servlet name second for (int i = 0; i < filterMaps.length; i++) { if (!matchDispatcher(filterMaps[i] ,dispatcher)) { continue; } if (!matchFiltersServlet(filterMaps[i], servletName)) continue; ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMaps[i].getFilterName()); if (filterConfig == null) { // FIXME - log configuration problem continue; } boolean isCometFilter = false; if (comet) { try { isCometFilter = filterConfig.getFilter() instanceof CometFilter; } catch (Exception e) { // Note: The try catch is there because getFilter has a lot of // declared exceptions. However, the filter is allocated much // earlier } if (isCometFilter) { filterChain.addFilter(filterConfig); } } else { filterChain.addFilter(filterConfig); } }
接下来看下matchDispatcher方法的实现
//很明显这里判断了下filter的dispacher type和参数type是否匹配 //也就是刚说的在filter的filter-mapping里指明的Dispacher type //如果是在filter-mapping里有指明当前转发方式则返回true否则返回false private boolean matchDispatcher(FilterMap filterMap, DispatcherType type) { switch (type) { case FORWARD : { if ((filterMap.getDispatcherMapping() & FilterMap.FORWARD) > 0) { return true; } break; } case INCLUDE : { if ((filterMap.getDispatcherMapping() & FilterMap.INCLUDE) > 0) { return true; } break; } case REQUEST : { if ((filterMap.getDispatcherMapping() & FilterMap.REQUEST) > 0) { return true; } break; } case ERROR : { if ((filterMap.getDispatcherMapping() & FilterMap.ERROR) > 0) { return true; } break; } case ASYNC : { if ((filterMap.getDispatcherMapping() & FilterMap.ASYNC) > 0) { return true; } break; } } return false; }
这样子看来就很明显了,只要把type参数改为REQUEST就可以实现让转发的FORWARD请求伪装成REQUEST请求了,也就是这样请求就可以和一个新的请求一样再走一遍正常流程了。
那么这个dispatcher参数是从哪里获取的呢?
回到第一段代码的开始,可以看到
// get the dispatcher type DispatcherType dispatcher = null; if (request.getAttribute(DISPATCHER_TYPE_ATTR) != null) { dispatcher = (DispatcherType) request.getAttribute(DISPATCHER_TYPE_ATTR); }
/** * Request dispatcher state. */ public static final String DISPATCHER_TYPE_ATTR = "org.apache.catalina.core.DISPATCHER_TYPE";
这样子就明朗了
请求在被进行分发的时候,分发器会读取请求的类型,也就是DispacherType,具体就是读取请求的"org.apache.catalina.core.DISPATCHER_TYPE"的属性,然后再装载filterchain的时候根据这个请求类型来装载filter,没有指明拦截FORWARD的filter不会装载在FORWAD的请求上,基于此种方式,我们只要将request的"org.apache.catalina.core.DISPATCHER_TYPE"属性改为DispacherType.REQUEST就可以达到让FORWARD请求变成REQUEST请求了,从而让我们的内部转发可以达到外部转发的效果。
接下来放两个实现类
public class FitrstFilter implements Filter { private String servletPath; public FitrstFilter() { } public void destroy() { } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { DispatcherWrapper req = new DispatcherWrapper((HttpServletRequest) request); System.out.println("第一个filter"); // 表明是请求第一次进入filter if (req.getAttribute("com.zhibei.filter.FORWARD") == null) { // 设置参数 req.setAttribute("com.zhibei.filter.FORWARD", Boolean.TRUE); // 请求分发 String newUri = req.getRequestURI().replace(this.servletPath, "").replace("index", "index2"); request.getRequestDispatcher(newUri).forward(req, response); } else { // 设置为false防止请求进入死循环 req.setAttribute("com.zhibei.filter.FORWARD", Boolean.FALSE); chain.doFilter(request, response); } } public void init(FilterConfig fConfig) throws ServletException { this.servletPath = fConfig.getServletContext().getContextPath(); } private class DispatcherWrapper extends HttpServletRequestWrapper { public DispatcherWrapper(HttpServletRequest request) { super(request); } @Override public Object getAttribute(String name) { // TOMCAT内部的请求分发属性 if (name.equals("org.apache.catalina.core.DISPATCHER_TYPE")) { Object forward = this.getAttribute("com.zhibei.filter.FORWARD"); if (forward == Boolean.TRUE) { //这里返回变量有两种,在Tomcat6中并没有DispacherType这个类,与REQUEST相对应的是一个int类型的数值8 //这个属性在Tomcat7后才被抽象为一个类 //所以这里要判断下如果有这个类则返回DisPacherType.REQUEST否则返回8即可 try { Class.forName("javax.servlet.DispatcherType"); return javax.servlet.DispatcherType.REQUEST; } catch (Exception e) { return 8; } } } return super.getAttribute(name); } } }
DispacherWrapper重写getAttribute,当获取request的"org.apache.catalina.core.DISPATCHER_TYPE"属性时,返回DispacherType.REQUEST即可,为了防止请求被无限转发进入死循环,加一个标志来判断即可,实现非常轻松。
public class SecondFilter implements Filter { public SecondFilter() { } public void destroy() { } //在第二个filter中打印相关信息 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("第二个filter"); HttpServletRequest req = (HttpServletRequest) request; System.out.println("URI:" + req.getRequestURI()); System.out.println("URL" + req.getRequestURL()); System.out.println("QueryString:" + req.getQueryString()); Map<String, String[]> paramMap = req.getParameterMap(); for (String key : paramMap.keySet()) { for (String value : paramMap.get(key)) { System.out.println("key:" + key + ",value:" + value); } } chain.doFilter(request, response); } public void init(FilterConfig fConfig) throws ServletException { } }
请求地址:
http://localhost:8080/filter/index.html?sdsd45=dw48d
打印结果:
第一个filter 第一个filter 第二个filter URI:/filter/index2.html URLhttp://localhost:8080/filter/index2.html QueryString:sdsd45=dw48d key:sdsd45,value:dw48d
和预想的一样,FirstFilter进入了两次,第一次为原始请求,第二次为FORWARD的请求
工程下载:https://download.csdn.net/download/yy417168602/10445381