写于:2019-07-09 22:52:37 参考资料: Spring Cloud 官网 Zuul wiki
相关版本:zuul 1.3.1,spring boot 2.1.5 ,spring cloud Greenwich.SR1
一、zuul 使用案例
二、源码追踪入口
2.1、@EnableZuulProxy
入口
@EnableZuulProxy
该注解开启了 Zuul 功能
2.1.1、@EnableZuulProxy
相关代码如下
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}
@EnableZuulProxy 引入了配置类 ZuulProxyMarkerConfiguration
聚焦 ZuulProxyMarkerConfiguration 相关代码如下:
@Configuration
public class ZuulProxyMarkerConfiguration {
@Bean
public Marker zuulProxyMarkerBean() {
return new Marker();
}
class Marker {
}
}
小贴士 借助
@Conditionxxx
注解,结合ZuulProxyMarkerConfiguration.Marker.class
实现 zuul 自动配置的触发开关。
通过 ZuulProxyMarkerConfiguration.Marker
追踪到 zuul的两个自动配置类:ZuulServerAutoConfiguration
、 ZuulProxyAutoConfiguration
二、zuul 工作流程
Zuul 工作流程图如下:
上图是 Zuul 处理请求的全过程。
根据上述流程图进行主线执行流程分析
2.1、ZuulController
2.1.1、 ZuulServerAutoConfiguration
中对 ZuulController
进行配置,代码如下:
public class ZuulServerAutoConfiguration {
@Bean
public ZuulController zuulController() {
return new ZuulController();
}
}
2.1.2、ZuulController
功能作用,直接看代码分析
public class ZuulController extends ServletWrappingController {
public ZuulController() {
setServletClass(ZuulServlet.class);
setServletName("zuul");
setSupportedMethods((String[]) null); // Allow all
}
@Override
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {
try {
// We don't care about the other features of the base class, just want to
// handle the request
// 调用的 ZuulServlet#service
return super.handleRequestInternal(request, response);
}
finally {
// @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter
// 清除 ThreadLocal 上下文信息
RequestContext.getCurrentContext().unset();
}
}
}
通过上述代码得到几个结论:
ZuulController 继承自 ServletWrappingController。
ServletWrappingController 作用:将应用中的某个 Servlet 封装成 Controller 用来处理 Servlet 中的所有请求。
ZuulController 代理了 zuulServlet 的所有请求。所有请求最终都调用的
ZuulServlet#service
猜测:使用
ZuulController
代理ZuulServlet
所有请求的原因,是为了最终 ThreadLocal 上下文的清除的实现。
2.3、ZuulServlet
2.3.1、ZuulServlet 在 ZuulController
中通过反射的方式实例化
public class ServletWrappingController extends AbstractController
implements BeanNameAware, InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
......
// 通过反射的方式实例化 ZuulServlet 对象
this.servletInstance = ReflectionUtils.accessibleConstructor(this.servletClass).newInstance();
this.servletInstance.init(new DelegatingServletConfig());
}
}
2.3.2、ZuulServlet 主要功能作用
ZuulServlet 本身是一个 Servlet ,通过 service() 方法进行分析,代码如下:
public class ZuulServlet extends HttpServlet {
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
// 通过该方法,能够知道 ZuulRunner 的生命周期为:一次请求
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
// Marks this request as having passed through the "Zuul engine", as opposed to servlets
// explicitly bound in web.xml, for which requests will not have the same data attached
RequestContext context = RequestContext.getCurrentContext();
// 标记请求为 zuul 请求
context.setZuulEngineRan();
// 进行 pre、route、post route 过滤处理
try {
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
}
实现功能
- a、标注请求为 zuul 请求
- b、进行 pre、route、post route 过滤处理,而真正执行 过滤处理功能的是
ZuulRunner
2.4、ZuulRunner
2.4.1、ZuulRunner 功能作用
public class ZuulRunner {
public void postRoute() throws ZuulException {
FilterProcessor.getInstance().postRoute();
}
public void route() throws ZuulException {
FilterProcessor.getInstance().route();
}
public void preRoute() throws ZuulException {
FilterProcessor.getInstance().preRoute();
}
public void error() {
FilterProcessor.getInstance().error();
}
}
通过代码能够发现 ZuulRunner 啥都没做,直接就把 pre、route、post route 的所有操作直接交给 FilterProcessor 来进行处理。
2.5、FilterProcessor
2.5.1、FilterProcessor 功能
public class FilterProcessor {
public void postRoute() throws ZuulException {
runFilters("post");
}
public void error() {
runFilters("error");
}
public void route() throws ZuulException {
runFilters("route");
}
public void preRoute() throws ZuulException {
runFilters("pre");
}
}
所有从 ZuulRunner 中过来过滤操作会被分为:post、error、route、pre 这四种类型。然后通过 FilterProcessor#runFilters 进行处理。
聚焦FilterProcessor#runFilters
public class FilterProcessor {
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
boolean bResult = false;
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
for (int i = 0; i < list.size(); i++) {
ZuulFilter zuulFilter = list.get(i);
Object result = processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= ((Boolean) result);
}
}
}
return bResult;
}
}
代码中,主要的两个逻辑方法
- FilterLoader.getInstance().getFiltersByType
根据 post、error、route、pre 类型,从 FilterLoader 中获取所有该类型的 ZuulFilter 过滤操作 - FilterProcessor#processZuulFilter
遍历查找到的 ZuulFilter ,并调用 ZuulFilter#runFilter 进行处理,获取成功或者失败的操作。
2.6、总结
zuul 由 servlet + filter 完成请求的处理。
请求交由 ZuulServlet 处理, ZuulServlet 根据 pre、post、route、error 过滤链进行请求过滤处理,最后得到处理结果。
三、扩展:ZuulFilter 过滤器如何被加载
3.1、ZuulFilter 存放容器
小贴士:FilterLoader 是一个饿汉模式的单例
FilterLoader 是存放 ZuulFilter 的容器,并提供有相关的 增删改查方法,其简单结构图如下:
ZuulFilter 真实存放在 FilterRegistry
中的 ConcurrentHashMap
中,
FilterLoader 有点装饰者模式的味道(类似于 Mybatis 的 二级 Cache),FilterLoader 在 FilterRegistry 的基础上提供了更丰富操作的增删改查。
3.1.1、FilterLoader#getFiltersByType
ZuulFilter 查询方法
代码如下:
public class FilterLoader {
private FilterRegistry filterRegistry = FilterRegistry.instance();
private final ConcurrentHashMap<String, List<ZuulFilter>> hashFiltersByType = new ConcurrentHashMap<String, List<ZuulFilter>>();
public List<ZuulFilter> getFiltersByType(String filterType) {
// 先从 FilterLoader 缓存中获取相关过滤类型的过滤链数据
List<ZuulFilter> list = hashFiltersByType.get(filterType);
if (list != null) return list;
list = new ArrayList<ZuulFilter>();
// 如果缓存中没有,从 FilterRegistry 中获取。
Collection<ZuulFilter> filters = filterRegistry.getAllFilters();
for (Iterator<ZuulFilter> iterator = filters.iterator(); iterator.hasNext(); ) {
ZuulFilter filter = iterator.next();
if (filter.filterType().equals(filterType)) {
list.add(filter);
}
}
Collections.sort(list); // sort by priority
hashFiltersByType.putIfAbsent(filterType, list);
return list;
}
}
FilterLoader 根据过滤链类型进行缓存,在进行调用获取过滤链时,先从缓存中获取,如果没有再从 FilterRegistry 中获取所有的过滤链,然后根据类型遍历获取对应类型的过滤链。
小贴士:上述代码存在代码:
Collections.sort(list);
。由此能够猜测 ZuulFilter 是存在执行顺序的。通过设定不同的优先级,来指定特定的 ZuulFilter 执行顺序。
3.1.2、FilterLoader#getInstance()#putFilter
File 类型的 ZuulFilter 存储
public class FilterLoader {
public boolean putFilter(File file) throws Exception {
String sName = file.getAbsolutePath() + file.getName();
if (filterClassLastModified.get(sName) != null && (file.lastModified() != filterClassLastModified.get(sName))) {
LOG.debug("reloading filter " + sName);
filterRegistry.remove(sName);
}
ZuulFilter filter = filterRegistry.get(sName);
if (filter == null) {
Class clazz = COMPILER.compile(file);
if (!Modifier.isAbstract(clazz.getModifiers())) {
filter = (ZuulFilter) FILTER_FACTORY.newInstance(clazz);
List<ZuulFilter> list = hashFiltersByType.get(filter.filterType());
if (list != null) {
hashFiltersByType.remove(filter.filterType()); //rebuild this list
}
filterRegistry.put(file.getAbsolutePath() + file.getName(), filter);
filterClassLastModified.put(sName, file.lastModified());
return true;
}
}
return false;
}
}
主线逻辑:
- 1、判定指定名称的 ZuulFilter 如果被修改过,清除 FilterRegistry 中对应的缓存数据
- 2、实例化 指定目录中加载的 ZuulFilter (groovy 文件 ) 并存入 FilterRegistry 缓存。
在 FilterLoader 中维护了一个列表 filterClassLastModified ,存放了 ZuulFilter 名称 和 文件最后修改时间的关联关系,通过维护一个修改时间来判定每次轮训加载的 ZuulFilter 是否是最新的,是否修改过,来对 ZuulFilter 缓存进行增删改查,依次来实现动态加载,更新 ZuulFilter 的功能。
在 FilterLoader#putFilter 执行逻辑:
- 逻辑1、根据关联关系 filterClassLastModified
1、加载的 ZuulFilter 如果之前未加载,则不处理
2、加载的 ZuulFilter 如果之前加载,判定指定 zuulFilter 是否修改过,如果修改过从 FilterRegistry 缓存中移除。 - 逻辑2、加载 ZuulFilter 到 FilterRegistry 中
1、加载的 ZuulFilter 如果之前未加载,加载到 FilterRegistry 缓存中
2、加载的 ZuulFilter 之前被加载过 - ZuulFilter 被修改了,重新加载到 FilterRegistry 缓存中
- ZuulFilter 未被修改,不处理。
3.2、加载 ZuulFilter
通过源码追踪, ZuulFilter 有两种加载渠道:
- Groovy File
- Bean config
3.2.1、ZuulFilter 获取渠道:Groovy File
Groovy Filter 能够动态加载 ZuulFilter。
执行 Groovy File 加载的类为 FilterFileManager
。
FilterFileManager 是实现动态加载 ZuulFilter 管理类。 Zuul 默认没有开启动态加载 ZuulFilter 的方式,需要自己实例化 FilterFileManager 并修改 FilterLoader DynamicCodeCompiler 的 Compile 为 GroovyCompiler。 更多相关配置参考 Hystrix 官方提供的 StartServer.java
聚焦 FilterFileManager 相关代码
public class FilterFileManager {
static FilterFileManager INSTANCE;
// 初始化方法
public static void init(......){
// 开启线程
INSTANCE.startPoller();
}
// 以守护进程的方式启动了一个线程,线程通过 while 循环,定时加载 GroovyFile 文件
void startPoller() {
poller = new Thread("GroovyFilterFileManagerPoller") {
public void run() {
while (bRunning) {
try {
sleep(pollingIntervalSeconds * 1000);
manageFiles();
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
poller.setDaemon(true);
poller.start();
}
// 加载 Groovy File 文件
void manageFiles() throws Exception, IllegalAccessException, InstantiationException {
List<File> aFiles = getFiles();
processGroovyFiles(aFiles);
}
// 调用 FilterLoader#putFile 将 Groovy File 文件加载到容器 FilterLoader(FilterRegistry)中
void processGroovyFiles(List<File> aFiles) throws Exception, InstantiationException, IllegalAccessException {
for (File file : aFiles) {
FilterLoader.getInstance().putFilter(file);
}
}
}
FilterFileManager 加载 ZuulFilter 文件的两个主要方法:
- FilterFileManager#getFiles
获取指定目录文件下的所有 ZuulFilter groovy 文件 - FilterFileManager#processGroovyFiles
调用 FilterLoader.getInstance().putFilter(file); 将 ZuulFilter 加载到 FilterRegistry 缓存中
且在上述代码中启动了一个守护线程,通过 死循环 + sleep 的方式,实现轮询某个文件路径实现,动态加载 ZuulFilter 的功能。
3.2.2、ZuulFilter 获取渠道:Bean config
通过 Spring Bean 的方式加载 ZuulFilter 。
查看 ZuulServerAutoConfiguration 自动配置类,看看 zuul 默认提供的部分 zuulFilter 的加载
public class ZuulServerAutoConfiguration {
// pre filters
@Bean
public ServletDetectionFilter servletDetectionFilter() {
return new ServletDetectionFilter();
}
......
// post filters
@Bean
public SendResponseFilter sendResponseFilter(ZuulProperties properties) {
return new SendResponseFilter(zuulProperties);
}
......
}
而我们在开发中能够通过 继承 ZuulFilter 重写相关方 进行自定义 ZuulFilter(操作应用这里不进行展开)
@Component
public class AccessFilter extends ZuulFilter {
......
}
这些在 Spring Bean 中的 ZuulFilter 如何被加载到 FilterRegistry 中。
聚焦 ZuulFilterConfiguration
public class ZuulServerAutoConfiguration {
@Configuration
protected static class ZuulFilterConfiguration {
@Autowired
private Map<String, ZuulFilter> filters;
@Bean
public ZuulFilterInitializer zuulFilterInitializer(CounterFactory counterFactory,
TracerFactory tracerFactory) {
FilterLoader filterLoader = FilterLoader.getInstance();
FilterRegistry filterRegistry = FilterRegistry.instance();
return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory,
filterLoader, filterRegistry);
}
}
}
通过查看代码,这里拿到了全局的 FilterLoader ,全局的 FilterRegistry 以及所有的 ZuulFilter 。将这些作为 ZuulFilterInitializer 的构造参数。
猜测 ZuulFilter 存入 FilterRegistry 的操作在 ZuulFilterInitializer 中执行。
聚焦 ZuulFilterInitializer
public class ZuulFilterInitializer {
// 构造方法
public ZuulFilterInitializer(Map<String, ZuulFilter> filters,
CounterFactory counterFactory, TracerFactory tracerFactory,
FilterLoader filterLoader, FilterRegistry filterRegistry) {
this.filters = filters;
this.counterFactory = counterFactory;
this.tracerFactory = tracerFactory;
this.filterLoader = filterLoader;
this.filterRegistry = filterRegistry;
}
@PostConstruct
public void contextInitialized() {
log.info("Starting filter initializer");
TracerFactory.initialize(tracerFactory);
CounterFactory.initialize(counterFactory);
for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
filterRegistry.put(entry.getKey(), entry.getValue());
}
}
}
在 ZuulFilterInitializer#contextInitialized 能够看到 @PostConstruct 。所以在 ZuulFilterInitializer 构造函数调用时该方法会被加载。
该方法会遍历所有的 filter ,并将 filter 放入 filterRegistry 的缓存 map 中。
四、总结
Zuul 通过 Servlet 和 Filter 完成了网关的功能。请求以 ZuulServlet 为入口,经由各种 ZuulFilter 过滤处理,最后响应处理结果。