过滤器优先级
如图所示,数字越小,执行的优先级就越高:
图表展示出来就是:
过滤器 | order | 描述 | 类型 |
---|---|---|---|
ServletDetectionFilter | -3 | 检测请求是用 DispatcherServlet还是 ZuulServlet | pre |
Servlet30WrapperFilter | -2 | 在Servlet 3.0 下,包装 requests | pre |
FormBodyWrapperFilter | -1 | 解析表单数据 | pre |
SendErrorFilter | 0 | 如果中途出现错误 | error |
DebugFilter | 1 | 设置请求过程是否开启debug | pre |
PreDecorationFilter | 5 | 根据uri决定调用哪一个route过滤器 | pre |
RibbonRoutingFilter | 10 | 如果写配置的时候用ServiceId则用这个route过滤器,该过滤器可以用Ribbon 做负载均衡,用hystrix做熔断 |
route |
SimpleHostRoutingFilter | 100 | 如果写配置的时候用url则用这个route过滤 | route |
SendForwardFilter | 500 | 用RequestDispatcher请求转发 | route |
SendResponseFilter | 1000 | 用RequestDispatcher请求转发 | post |
为了能够更清晰地知道底层是怎么完成这一系列流程的,我们通过编写一个自定义的过滤器来测试。从我们上一篇博客Spring Cloud集群中使用Zuul(十七) 底部拿到源码。
依次运行四个项目的*App启动类,启动四个项目。
在eureka-zuul网关项目中,在com.init.springCloud包下创建filter包,新建SelfDefineFilter类,去继承Zuul框架的ZuulFilter类,实现内部方法。在图上的Route阶段,三种路由方式都继承了这个ZuulFilter:
package com.init.springCloud.filter; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import com.netflix.zuul.ZuulFilter; public class SelfDefineFilter extends ZuulFilter { @Override public Object run() { System.out.println("执行自定义过滤器"); return null; } @Override public boolean shouldFilter() { //是否执行当前过滤器 return true; } @Override public int filterOrder() { //执行顺序 return 5; } @Override public String filterType() { //过滤器执行阶段的类型 return FilterConstants.ROUTE_TYPE; } }
按照Route阶段的执行顺序,我们规定自定义的路由过滤器执行顺序,在Route阶段比优先级最高的RibbonRoutingFilter还高(RibbonRoutingFilter为10,自定义路由过滤器为5)。
之后,编写一个配置类SelfDefineConf.class,把这个自定义的路由过滤器当成Spring的Bean组件注入进去:
package com.init.springCloud.filter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class SelfDefineConf { @Bean public SelfDefineFilter getFilter(){ return new SelfDefineFilter(); } }
重新启动eureka-zuul项目,任意访问一个接口,譬如这里访问:http://localhost:9090/consumer/router或者http://localhost:9090/users/router,都可以看到Zuul内部执行了我们自定义的路由过滤器:
过滤器的动态加载
在一个大型项目中,由于网关接手了大部分的请求,所以它是一个不能随随便便就停止的项目,当我们涉及到有新的路由规则上线,又不能停止网关项目的时候,就可以使用过滤器的动态加载来完成这个操作。使用groovy可以实现在不停止网关项目的同时,动态加载我们自定义的过滤器,在eureka-zuul的pom.xml中加入groovy相关依赖:
<dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-all</artifactId> <version>2.4.15</version> </dependency>
接着在ZuulApp.class启动类里面配置zuul的动态加载过滤器规则:
@PostConstruct public void zuulInit(){ FilterLoader.getInstance().setCompiler(new GroovyCompiler()); //过滤器的路径 String scriptRoot = System.getProperty("zuul.filter.root","groovy/filters"); //定时器时间,5秒刷新一次 String refreshInterval = System.getProperty("zuul.filter.refreshInterval","5"); if(scriptRoot.length() > 0){ scriptRoot += File.separator; } try { FilterFileManager.setFilenameFilter(new GroovyFileFilter()); FilterFileManager.init(Integer.parseInt(refreshInterval), scriptRoot+"pre", scriptRoot+"route", scriptRoot+"post"); } catch (Exception e) { e.printStackTrace(); } }
我们设置了过滤器读取的位置是groovy.filters,所以我们在src/main/java目录下创建groovy.filters包,并在这个包下创建三个包:pre、route、post(groovy只会读取这三个包下面的内容,源代码中把文件放在了外面的包里,不被读取)。之后启动项目。
接下来编写一个文件DynamicFilter.groovy,内容和我们上面自定义的过滤器类似,没有大的修改:
package groovy.filters; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import com.netflix.zuul.ZuulFilter; class DynamicFilter extends ZuulFilter { @Override public Object run() { System.out.println("===>这里是用groovy实现的动态加载过滤器"); return null; } @Override public boolean shouldFilter() { //是否执行当前过滤器 return true; } @Override public int filterOrder() { //执行顺序 return 3; } @Override public String filterType() { //过滤器执行阶段的类型 return FilterConstants.ROUTE_TYPE; } }
在项目启动之后,我们依然访问一下:http://localhost:9090/users/router,可以看到控制台使用了我们自定义的路由规则。接着我们把这个DynamicFilter.groovy文件拖拽到groovy.filters.route包下,等待5秒左右,再次访问:http://localhost:9090/users/router,就可以看到zuul已经动态加载了我们配置的过滤器了:
@EnableZuulServer
和@EnableZuulProxy对应的还有一个@EnableZuulServer,功能相差不多,相当于是一个简化版本,缺失了灰色字体标注的项目。