网关Zuul/功能进阶
- 之前章节已经学过Zuul路由配置的知识,Zuul的请求会经过多个阶段,每个阶段都会有多个过滤器
- 其中负责路由的阶段就叫做“routing”过滤器
1、过滤器优先级
- 实际过程中,是routing过滤器中将请求转发至源服务
- 数字越小,优先级越高
- routing过滤器中,最终只会选择一个进行执行,那么为什么也会有优先级呢?会通过外部的一个值,来决定执行哪个过滤器,例如,RibbonRoutingFilter这个过滤器执行完成之后,就将外部值设置一下,告诉SimpleHostRoutingFilter/SendForwardFilter过滤器,这个请求已经进行转发了,每个过滤器中都有一个是否执行的判断方法,通过这个方法来判断是否执行下个过滤器
2、自定义过滤器
- shouldFilter方法
- 优先级
- 仍然使用上章节的项目进行测试
2.1、编写过滤器
package com.atm.cloud.filter;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import com.netflix.zuul.ZuulFilter;
public class MyFilter extends ZuulFilter {
/**
* 过滤器配置完成之后,还需要告知Spring容器这个过滤器的存在
*/
/**
* 返回false/true,判断是否执行MyFilter过滤器
* <p>
* 决定该过滤器是否要执行
*/
public boolean shouldFilter() {
return true;
}
public Object run() {
System.out.println("Come into MyFilter...");
return null;
}
/**
* 过滤器中有多个阶段,通过该方法,设置自定义过滤器所处阶段
*/
@Override
public String filterType() {
return FilterConstants.ROUTE_TYPE;
}
/**
* 设置优先级
*/
@Override
public int filterOrder() {
return 1;
}
}
2.2、配置过滤器
package com.atm.cloud.filter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 将过滤器配置进Spring容器,Spring将会注册MyFilter过滤器
*
*/
@Configuration
public class MyFilterConfig {
@Bean
public MyFilter myFilter() {
return new MyFilter();
}
}
3、动态加载过滤器
- 相对于服务提供者、服务调用者而言,网关更需要稳定地提供服务
- 如果需要增加过滤器,重启网关的代价过大(重启期间,整个集群不可用)
- Zuul提供了动态加载顾虑起的功能,可以使用groovy来编写过滤器,然后添加到加载目录中,让Zuul去动态加载
3.1、网关项目引入依赖
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.12</version>
</dependency>
- 在网关启动时,Zuul使用groovy编译器,需要随时读取过滤器(定时扫描是否新增过滤器)
3.2、网关项目启动类
package com.atm.cloud
import java.io.File
import javax.annotation.PostConstruct
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.cloud.netflix.eureka.EnableEurekaClient
import org.springframework.cloud.netflix.zuul.EnableZuulProxy
import com.netflix.zuul.FilterFileManager
import com.netflix.zuul.FilterLoader
import com.netflix.zuul.groovy.GroovyCompiler
import com.netflix.zuul.groovy.GroovyFileFilter
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class ZuulRouterApp {
// 动态加载过滤器 start,这是从Zuul官方源代码中Copy出来的
@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),
scriptRoot + "pre", scriptRoot + "route", scriptRoot
+ "post")
} catch (Exception e) {
throw new RuntimeException(e)
}
}
// 动态加载过滤器 end
public static void main(String[] args) {
SpringApplication.run(ZuulRouterApp.class, args)
}
}
3.3、网关项目创建目录
3.4、DynamicFilter.groovy
package groovy.filters;
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("Come into DynamicFilter...");
return null;
}
public String filterType() {
return FilterConstants.ROUTE_TYPE;
}
public int filterOrder() {
return 3;
}
}
- 将文件直接上传至指定目录下(如:此处上传至groovy/filters/route下,当访问时,程序会自动加载该过滤器)【注意,编写xx.groovy文件时,包名与编码格式】
4、请求上下文
package com.atm.cloud.filter;
import javax.servlet.http.HttpServletRequest;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.web.client.RestTemplate;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
/**
* 使用RestTemplate来调用集群服务
*
*/
public class RestTemplateFilter extends ZuulFilter {
private RestTemplate restTemplate;
public RestTemplateFilter(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String uri = request.getRequestURI();
if (uri.indexOf("rest-tpl-sale") != -1) {
return true;
} else {
return false;
}
}
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
String serviceId = (String) ctx.get("serviceId");
String uri = (String) ctx.get("requestURI");
String url = "http://" + serviceId + uri;
System.out.println("Come into RestTemplateFilter...,URL:" + url);
String result = this.restTemplate.getForObject(url, String.class);
ctx.setResponseBody(result);
ctx.sendZuulResponse();
return null;
}
@Override
public String filterType() {
return FilterConstants.ROUTE_TYPE;
}
@Override
public int filterOrder() {
return 2;
}
}
5、@EnableZuulServer注解
- 与@EnableZuulProxy的区别
- @EnableZuulProxy的过滤器范围
- @EnableZuulServer的过滤器范围(不具备调用集群服务能力,也不具备简单路由功能)