Zuul 2是如何动态加载Filter的?

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.packageszuul.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
     */
    @PostConstruct
    public 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容器托管的关系,有些箭头不是严格的表示调用的先后关系,对此大家意会就好了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值