写于: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 相关代码如下:
@Configurationpublic class ZuulProxyMarkerConfiguration {@Beanpublic 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 {@Beanpublic 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}@Overridepublic 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#servicereturn 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 AbstractControllerimplements BeanNameAware, InitializingBean, DisposableBean {@Overridepublic 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 {@Overridepublic 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 attachedRequestContext 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 priorityhashFiltersByType.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@Beanpublic ServletDetectionFilter servletDetectionFilter() {return new ServletDetectionFilter();}......// post filters@Beanpublic SendResponseFilter sendResponseFilter(ZuulProperties properties) {return new SendResponseFilter(zuulProperties);}......}
而我们在开发中能够通过 继承 ZuulFilter 重写相关方 进行自定义 ZuulFilter(操作应用这里不进行展开)
@Componentpublic class AccessFilter extends ZuulFilter {......}
这些在 Spring Bean 中的 ZuulFilter 如何被加载到 FilterRegistry 中。
聚焦 ZuulFilterConfiguration
public class ZuulServerAutoConfiguration {@Configurationprotected static class ZuulFilterConfiguration {@Autowiredprivate Map<String, ZuulFilter> filters;@Beanpublic 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;}@PostConstructpublic 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 过滤处理,最后响应处理结果。
