持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第18天,点击查看活动详情
18、Zuul 功能进阶
本节的内容涉及 Zuul 的原理,如果读者只求掌握 Zuul 的使用,可以跳过本节
Zuul过滤器详解:www.jianshu.com/p/ff863d532… ****** 重要
1、过滤器优先级
Spring Cloud 为 HTTP 请求的各个阶段提供了多个过滤器,这些过滤器的执行顺序由它们各自提供的一个int值决定,提供的值越小,优先级越高。 下图展示了默认的过滤器,以及它们的优先级。
如图 所示,在 routing 阶段会优先执行 Ribbon 路由的过滤器,再执行简单路由过滤器。
springcloud zuul包含了对请求的路由和过滤两个功能,其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础;而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验,服务聚合等功能的基础。然而实际上,路由功能在真正运行时,它的路由映射和请求转发都是由几个不同的过滤器完成的。**其中,路由映射主要通过pre类型的过滤器完成,它将请求路径与配置的路由规则进行匹配,以找到需要转发的目标地址;而请求转发的部分则是由route类型的过滤器来完成,对pre类型过滤器获得的路由地址进行转发。所以说,过滤器可以说是zuul实现api网关功能最核心的部件,**每一个进入zuul的http请求都会经过一系列的过滤器处理链得到请求响应并返回给客户端。
链接:www.jianshu.com/p/ff863d532…
spring cloud zuul中实现的过滤器必须包含4个基本特征:过滤类型,执行顺序,执行条件,具体操作。这些步骤都是com.netflix.zuul.ZuulFilter接口中定义的4个抽象方法:
String filterType();
filterType:该函数需要返回一个字符串代表过滤器的类型,而这个类型就是在http请求过程中定义的各个阶段。在zuul中默认定义了4个不同的生命周期过程类型,具体如下
pre:可以在请求被路由之前调用 routing: 路由请求时被调用 post:在routing和error过滤器之后被调用 处理请求时发生错误时被调用
int filterOrder();
通过int值来定义过滤器的执行顺序,数值越小优先级越高。
boolean shouldFilter();
返回一个boolean值来判断该过滤器是否要执行。我们可以通过此方法来指定过滤器的有效范围。
Object run();
过滤器的具体逻辑。在该函数中,我们可以实现自定义的过滤逻辑,来确定是否要拦截当前的请求,不对其进行后续的路由,或是在请求路由返回结果之后,对处理结果做一些加工等。
2、自定义过滤器
了解过滤器的执行顺序后,我们编写一个自定义过滤器。新建过滤类 ,继承 ZuulFilter。
/**
* 自定义过滤器
*/
public class MyFilter extends ZuulFilter {
//过滤器执行条件
@Override
public boolean shouldFilter() {
return true;
}
// 执行方法
@Override
public Object run() {
System.out.println(" 执行自定义的 MyFilter 过滤器");
return null;
}
// 表示在路由阶段执行
@Override
public String filterType() {
return FilterConstants.ROUTE_TYPE;
}
// 返回 1 ,路由阶段,该过滤器将会最先执行
@Override
public int filterOrder() {
return 1;
}
}
新建的自定义过滤器将会在 routing 阶段执行,优先级为1 ,也就是在 routing 阶段,该过滤器最先执行。另外注意 shouldFilter 方法,过滤最终是否执行由该方法决定,本例返回 true,表示访问任何路由规则都会执行该过滤器。为了让 Spring 容器知道过滤器的存在,需要对该类进行配置
/**
* 自定义的过滤器
*/
@Bean
public MyFilter myFilter(){
return new MyFilter();
}
启动集群,访问网关 http://localhost:8080 test/1 ,会看到控制输出:执行 MyFilter 过滤器 。实际上,访问任何一个配置好的路由都会进行输出。
3、动态加载过滤器
相对于集群中的其他节点,网关更需要长期、稳定地提供服务。如果需要增加过滤器, 重启网关代价太大,为了解决该问题, Zuul 提供了过滤器的动态加载功能。可以使用 Groovy 来编写过滤器,然后添加到加载目录,让 Zuul 去动态加载。先为网关项目加入 Groovy 的依赖:
<!--动态加载过滤器 不知道为什么加 2.4.12版本的 依赖报错-->
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.15</version>
</dependency>
接下来,在网关项目的应用类中,调用 Zuul 的 API 来实现动态加载,请见代码清单
@SpringBootApplication
@EnableZuulProxy
public class SrlGatewayRouterApplication {
public static void main(String[] args) {
SpringApplication.run(SrlGatewayRouterApplication.class, args);
}
/**
* 动态加载过滤器
*/
@PostConstruct
public void zuulInit() {
FilterLoader.getInstance().setCompiler(new GroovyCompiler());
// 读取配置,获取脚本目录 -- 会到groovy/filters默认目录下读取配置zuul.filter.root,获取脚本根目录
String scriptRoot = System.getProperty("zuul.filter.root", "groovy/filters");
// 获取刷新间隔 ,5s 刷新一次
String refreshInterval = System.getProperty("zuul.filter.refreshInterval", "5");
if (scriptRoot.length() > 0) {
// 路径后 拼接 /
scriptRoot = scriptRoot + File.separator;
}
try {
// 通过FilterFileManager设置GroovyFileFilter过滤器
FilterFileManager.setFilenameFilter(new GroovyFileFilter());
// 通过FilterFileManager的init进行Groovy的文件管理,
// 参数说明:刷新时间、文件目录(因为我们有三个阶段,所以我们提供三个目录)
FilterFileManager.init(Integer.parseInt(refreshInterval),
"C:/Users/srl/Desktop/srl-cloud/srl-data-platform/srl-sys/srl-gateway-router/src/main/java/" + scriptRoot + "pre",
"C:/Users/srl/Desktop/srl-cloud/srl-data-platform/srl-sys/srl-gateway-router/src/main/java/" + scriptRoot + "route",
"C:/Users/srl/Desktop/srl-cloud/srl-data-platform/srl-sys/srl-gateway-router/src/main/java/" + scriptRoot + "post");
// 不知道为什么 scriptRoot + "pre" 写成这样,获取的下面的路径, 所以只好写绝对路径了。
// RuntimeException: C:\Users\srl\Desktop\srl-cloud\srl-data-platform\groovy\filters\pre is not a valid directory
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
在启动类中,增加了 zuuInit 方法,使用 @PostConstruct 进行修饰。 在该方法中,先读 zuul.filter.root 和 zuul.filter.refreshlnterval 两个属性,分别表示动态过滤器的根目录以及 刷新间隔,刷新间隔以秒为单位。这两个属性优先读取配置文件的值,如果没有则使用默认值。 在配置文件中,可使用下面的配置片断:
#动态加载过滤器,配置过滤器根目录以及时间间隔 -- 可在代码里配置,配置文件优先级高
#zuul.filter.root="groovy/filters"
#zuul.filter.refreshInterval=5
调用 FilterFileManager 的 init 方法,初始化3个过滤器目录: pre, route, post。 为了测试动态加载,使用 Groovy 编写一个最简单的过滤器。
文件名:DynamicFilter.groovy
package groovy.filters.route;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import com.netflix.zuul.ZuulFilter;
class DynamicFilter extends ZuulFilter {
public boolean shouldFilter() {
return true;
}
public Object run() {
System.out.println("================= 动态加载的过滤器: DynamicFilter ========= ");
return null;
}
public String filterType() {
return FilterConstants.ROUTE_TYPE;
}
public int filterOrder() {
return 3;
}
}
与前面的过滤器一致,同样继承自 ZuulFilter 。需要注意的是, 本例的过滤器并没有一开始就放到动态加载的过滤器目录中, 读者在测试时, 需要先启动网关项目,再将DynamicFilter.groovy 放到对应目录中。
完成以上工作后, 启动网关项目, 访问地址 http://localhost:8080/test/456 ,控制台中并没有输出 DynamicFilter 的信息。 将DynamicFilter.groovy复制到 src/main/iava/groovy/filters/route 目录,等待几秒后, 重新访问以上地址, 可以看到网关的 控制台输出如下:
=================动态加载的过滤器: DynamicFilter =========
项目目录结构如下:
4、禁用过滤器
如果想禁用其中一个过滤器,可以使用以下配置:
#禁用过滤器 -- 禁用处理跳转路由的过滤器 SendForwardFilter 再使用forward跳转路由,将失效
zuul.SendForwardFilter.route.disable=true
以上配置会将 SendForwardFilter (处理跳转路由的过滤器)禁用, 如果再为 url 属性使 用 forward:进行配置的话,将不会产生跳转效果。同样, 禁同其他过滤器也会导致失去相应的功能。