Zuul 2沿用了Zuul 1的责任链模式的设计,其网关核心功能还是通过Filter链来实现的。要熟练使用和扩展Zuul 2的功能,必须要了解其Filter的加载和执行机制。另外,Zuul 2使用Guice作为依赖注入工具,因此在开始分析之前,我们需要大致了解Guice的基本原理和用法,传送门:Guide to Google Guice
为了了解Zuul 2的filter加载机制,我们从入口开始看起。在官方提供的zuul-sample项目中,启动类是Bootstrap,在其start方法中,可以看到这么几行:
ConfigurationManager.loadCascadedPropertiesFromResources("application");
Injector injector = InjectorBuilder.fromModule(new ZuulSampleModule()).createInjector();
BaseServerStartup serverStartup = injector.getInstance(BaseServerStartup.class);
server = serverStartup.server();
在上述代码中使用了ConfigurationManager去加载application.properties配置文件,然后通过Guice创建ZuulSampleModule,接着创建BaseServerStartup和Server实例。
在ZuulSampleModule的configure方法中,跟filter加载相关的是如下两行:
install(new ZuulFiltersModule());
bind(FilterFileManager.class).asEagerSingleton();
在ZuulFiltersModule的configure方法中,主要做的事情是指定了GroovyCompiler、GuiceFilterFactory和BasicFilterUsageNotifier的Guice注入绑定。而provideFilterFileManagerConfig方法,则是根据配置文件中zuul.filters.locations
属性按照路径加载filterLocations,以及根据zuul.filters.packages
和zuul.filters.classes
属性按照类名加载filterClassNames,然后据此创建FilterFileManagerConfig对象并返回。
FilterFileManagerConfig对象的属性如下:
private String[] directories; // filter文件夹路径
private String[] classNames; // filter类名
private int pollingIntervalSeconds; // 扫描时间间隔,秒数
private FilenameFilter filenameFilter; // 文件名过滤器
FilterFileManager会根据FilterFileManagerConfig指定的配置,使用FilterLoader执行filter文件加载,同样地这两个实例也是通过Guice注入到FilterFileManager的构造器中。
FilterLoader的类图如下:
FilterLoader其主要功能是按照类名、文件名、脚本内容等方式使用DynamicCodeCompiler去加载为ZuulFilter实例,为了只加载新增或者变更了的filter文件,其内部使用了一些ConcurrentMap记住已经加载过的filter及其上次修改时间戳。FilterRegistry是filter注册表,其类图如下:
在FilterFileManager的构造器中,除了设置相关属性以外,还启动了名为processFilesService的固定大小线程池,主要是为了能够异步地使用filterLoader去动态加载filter文件。
在FilterFileManager的@PostConstruct方法init中,主要做了三件事情:
- 加载config中classNames属性直接指定的filter;
- 加载config中directories属性指定的路径下的filter;
- 启动一个名为poller的Thread去定时检测config中directories属性指定的路径下的filter文件是否有更新;
其代码如下:
/*** Initialized the GroovyFileManager.** @throws Exception*/@PostConstructpublic void init() throws Exception{long startTime = System.currentTimeMillis();filterLoader.putFiltersForClasses(config.getClassNames());manageFiles();startPoller();LOG.warn("Finished loading all zuul filters. Duration = " + (System.currentTimeMillis() - startTime) + " ms.");}
在FilterFileManager的@PreDestroy方法shutdown中,主要是负责关闭poller Thread。
FilterFileManager启动关闭的流程如下图示:
根据类名加载filter的代码在FilterLoader.putFiltersForClasses()方法中:
/*** Load and cache filters by className** @param classNames The class names to load* @return List of the loaded filters* @throws Exception If any specified filter fails to load, this will abort. This is a safety mechanism so we can* prevent running in a partially loaded state.*/public List<ZuulFilter> putFiltersForClasses(String[] classNames) throws Exception{List<ZuulFilter> newFilters = new ArrayList<>();for (String className : classNames){newFilters.add(putFilterForClassName(className));}return newFilters;}public ZuulFilter putFilterForClassName(String className) throws Exception{Class clazz = Class.forName(className);if (! ZuulFilter.class.isAssignableFrom(clazz)) {throw new IllegalArgumentException("Specified filter class does not implement ZuulFilter interface!");}else {ZuulFilter filter = filterFactory.newInstance(clazz);putFilter(className, filter, System.currentTimeMillis());return filter;}}
实现动态加载变化的filter功能是在filterLoader.putFilter(file)中实现的,其代码如下:
/*** From a file this will read the ZuulFilter source code, compile it, and add it to the list of current filters* a true response means that it was successful.** @param file* @return true if the filter in file successfully read, compiled, verified and added to Zuul* @throws IllegalAccessException* @throws InstantiationException* @throws IOException*/
public boolean putFilter(File file) throws Exception
{try {String sName = file.getAbsolutePath();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 = filterFactory.newInstance(clazz);putFilter(sName, filter, file.lastModified());return true;}}}catch (Exception e) {LOG.error("Error loading filter! Continuing. file=" + String.valueOf(file), e);return false;}return false;
}
这段代码主要是通过比较传入的File的lastModified时间戳和map缓存中同名文件的时间戳来判定文件是否有变更,使用compiler去编译filter文件为Class对象,然后使用filterFactory去创建类实例。
至此,Zuul 2动态加载filter的机制已经介绍完毕,后续如果有时间会继续补充Zuul 2其他相关技术主题的分析文章。以两张UML图作为本篇文章的结语吧:
ZuulFilter相关的类后面的专题文章会具体展开介绍。
纵向的箭头表示F6,即step over;横向的箭头表示F5,即step in。需要注意的是,由于Guice容器托管的关系,有些箭头不是严格的表示调用的先后关系,对此大家意会就好了。