2023-08-07  阅读(1)
原文作者:Ressmix 原文地址:https://www.tpvlog.com/article/273

本章,我将对Spring Cloud Netflix Zuul的初始化流程进行讲解。我们在使用Zuul时,基本都是引入spring-cloud-netflix-zuul依赖,然后在启动类上注解@EnableZuulProxy。那么,Spring Cloud底层到底做了什么事情呢?

一、初始化流程

Spring Boot应用启动后,会去两个地方加载配置:

  1. 扫描到@EnableZuulProxy注解,执行注解中的@Import操作;
  2. 加载spring-cloud-netflix-zuul源码包META-INF目录下的spring.factories配置。

我用下面这两张图表述Spring Cloud Netflix Zuul的初始化流程。第一张图很简单,就是注入了一个标记对象,后续自动装配时会根据是否存在该对象选择并注入合理的Bean;第二张图主要描述了Spring Cloud向容器中注入的核心Zuul组件:

202308072154530211.png

202308072154536932.png

我们先来看第一种情况。

1.1 @EnableZuulProxy

Spring Boot应用启动后,扫描到启动类上的@EnableZuulProxy注解:

    @EnableCircuitBreaker
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Import(ZuulProxyMarkerConfiguration.class)
    public @interface EnableZuulProxy {
    }

@EnableZuulProxy这个注解上标注了@EnableCircuitBreaker,说明Zuul和Hystrix是天然整合在一起的。我们继续看ZuulProxyMarkerConfiguration,它往Spring IoC容器中注入了一个标记对象Marker(啥功能都没有):

    @Configuration(proxyBeanMethods = false)
    public class ZuulServerMarkerConfiguration {
        @Bean
        public Marker zuulServerMarkerBean() {
            return new Marker();
        }
    
        class Marker {
        }
    }

后面的Zuul的自动装配类 ZuulProxyAutoConfiguration 会根据容器中是否有这个Maker对象而使对应的配置Bean生效。

注意:Spring Cloud还提供了一个@EnableZuulServer注册,功能和@EnableZuulProxy差不多,@EnableZuulServer所包含的功能,@EnableZuulProxy都具备,所以我们一般使用@EnableZuulProxy就可以了。

1.2 自动装配

我们再来看Spring Boot的自动装配机制所加载的类:

202308072154552463.png

    # spring.factories
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration,\
    org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration

可以看到,主要就是加载了两个自动装配类: ZuulServerAutoConfigurationZuulProxyAutoConfiguration 。ZuulProxyAutoConfiguration是ZuulServerAutoConfiguration的子类,它整合了Ribbon相关的功能。

ZuulServerAutoConfiguration

我们先来看ZuulServerAutoConfiguration,它的主要作用如下:

  1. 向Spring IoC容器注入Zuul相关配置:ZuulProperties、ServerProperties;
  2. 注入Zuul的几个核心组件:ErrorController、ZuulController、ZuulHandlerMapping、ZuulServlet;
  3. 注入一系列路由加载器RouteLocator,用于根据请求URI匹配路由;
  4. 注入一系列的Filter。
    // ZuulServerAutoConfiguration.java
    
    @Configuration(proxyBeanMethods = false)
    @EnableConfigurationProperties({ ZuulProperties.class })
    @ConditionalOnClass({ ZuulServlet.class, ZuulServletFilter.class })
    @ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)    // 注意这里用到了Marker标记类
    public class ZuulServerAutoConfiguration {
    
        // yml配置,zuul开头相关配置在这注入
        @Autowired
        protected ZuulProperties zuulProperties;
    
        // yml配置,server开头相关配置在这注入,比如server.port
        @Autowired
        protected ServerProperties server;
    
        // 处理错误的Spring MVC Controller
        @Autowired(required = false)
        private ErrorController errorController;
    
        //...
    
        // 组合路由加载器
        @Bean
        @Primary
        public CompositeRouteLocator primaryRouteLocator(
                Collection<RouteLocator> routeLocators) {
            return new CompositeRouteLocator(routeLocators);
        }
    
        // 简单路由加载器
        @Bean
        @ConditionalOnMissingBean(SimpleRouteLocator.class)
        public SimpleRouteLocator simpleRouteLocator() {
            return new SimpleRouteLocator(this.server.getServlet().getContextPath(),
                    this.zuulProperties);
        }
    
        // ZuulController,Zuul请求入口Controller
        @Bean
        public ZuulController zuulController() {
            return new ZuulController();
        }
    
        // Zuul请求映射处理器
        @Bean
        public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes,
                ZuulController zuulController) {
            ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController);
            mapping.setErrorController(this.errorController);
            mapping.setCorsConfigurations(getCorsConfigurations());
            return mapping;
        }
    
        // 路由刷新监听器
        @Bean
        public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
            return new ZuulRefreshListener();
        }
    
        // ZuulServlet
        @Bean
        @ConditionalOnMissingBean(name = "zuulServlet")
        @ConditionalOnProperty(name = "zuul.use-filter", havingValue = "false",
                matchIfMissing = true)
        public ServletRegistrationBean zuulServlet() {
            ServletRegistrationBean<ZuulServlet> servlet = new ServletRegistrationBean<>(
                    new ZuulServlet(), this.zuulProperties.getServletPattern());
            servlet.addInitParameter("buffer-requests", "false");
            return servlet;
        }
    
        // 创建了ZuulServlet
        @Bean
        @ConditionalOnMissingBean(name = "zuulServlet")
        @ConditionalOnProperty(name = "zuul.use-filter", havingValue = "false",
                matchIfMissing = true)
        public ServletRegistrationBean zuulServlet() {
            ServletRegistrationBean<ZuulServlet> servlet = new ServletRegistrationBean<>(
                    new ZuulServlet(), this.zuulProperties.getServletPattern());
            servlet.addInitParameter("buffer-requests", "false");
            return servlet;
        }
    
        // 创建了ZuulServletFilter
        @Bean
        @ConditionalOnMissingBean(name = "zuulServletFilter")
        @ConditionalOnProperty(name = "zuul.use-filter", havingValue = "true", matchIfMissing = false)
        public FilterRegistrationBean zuulServletFilter() {
            final FilterRegistrationBean<ZuulServletFilter> filterRegistration = 
                new FilterRegistrationBean<>();
            filterRegistration.setUrlPatterns(
                    Collections.singleton(this.zuulProperties.getServletPattern()));
            filterRegistration.setFilter(new ZuulServletFilter());
            filterRegistration.setOrder(Ordered.LOWEST_PRECEDENCE);
            filterRegistration.addInitParameter("buffer-requests", "false");
            return filterRegistration;
        }
    
        // pre filters
    
        @Bean
        public ServletDetectionFilter servletDetectionFilter() {
            return new ServletDetectionFilter();
        }
    
        @Bean
        @ConditionalOnMissingBean
        public FormBodyWrapperFilter formBodyWrapperFilter() {
            return new FormBodyWrapperFilter();
        }
    
        @Bean
        @ConditionalOnMissingBean
        public DebugFilter debugFilter() {
            return new DebugFilter();
        }
    
        @Bean
        @ConditionalOnMissingBean
        public Servlet30WrapperFilter servlet30WrapperFilter() {
            return new Servlet30WrapperFilter();
        }
    
        // post filters
    
        @Bean
        public SendResponseFilter sendResponseFilter(ZuulProperties properties) {
            return new SendResponseFilter(zuulProperties);
        }
    
        @Bean
        public SendErrorFilter sendErrorFilter() {
            return new SendErrorFilter();
        }
    
        @Bean
        public SendForwardFilter sendForwardFilter() {
            return new SendForwardFilter();
        }
    
        // 用于Zuul的eager加载初始化类
        @Bean
        @ConditionalOnProperty("zuul.ribbon.eager-load.enabled")
        public ZuulRouteApplicationContextInitializer zuulRoutesApplicationContextInitiazer(
                SpringClientFactory springClientFactory) {
            return new ZuulRouteApplicationContextInitializer(springClientFactory,
                    zuulProperties);
        }
    
        // 该类的作用主要是把初始化的过滤器注册到zuul的FilterRegistry中
        @Configuration(proxyBeanMethods = false)
        protected static class ZuulFilterConfiguration {
    
            @Autowired
            private Map<String, ZuulFilter> filters;
    
            @Bean
            public ZuulFilterInitializer zuulFilterInitializer(CounterFactory counterFactory,
                    TracerFactory tracerFactory) {
                FilterLoader filterLoader = FilterLoader.getInstance();
                // FilterRegistry是一个单例用于初始化路由信息,在ZuulRunner中使用
                FilterRegistry filterRegistry = FilterRegistry.instance();
                return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory,
                        filterLoader, filterRegistry);
            }
    
        }
    
        // Zuul路由刷新监听器
        private static class ZuulRefreshListener
                implements ApplicationListener<ApplicationEvent> {
    
            @Autowired
            private ZuulHandlerMapping zuulHandlerMapping;
    
            private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor();
    
            @Override
            public void onApplicationEvent(ApplicationEvent event) {
                if (event instanceof ContextRefreshedEvent
                        || event instanceof RefreshScopeRefreshedEvent
                        || event instanceof RoutesRefreshedEvent
                        || event instanceof InstanceRegisteredEvent) {
                    // 刷新路由
                    reset();
                }
                else if (event instanceof ParentHeartbeatEvent) {
                    ParentHeartbeatEvent e = (ParentHeartbeatEvent) event;
                    resetIfNeeded(e.getValue());
                }
                else if (event instanceof HeartbeatEvent) {
                    HeartbeatEvent e = (HeartbeatEvent) event;
                    resetIfNeeded(e.getValue());
                }
            }
    
            private void resetIfNeeded(Object value) {
                if (this.heartbeatMonitor.update(value)) {
                    reset();
                }
            }
    
            private void reset() {
                // 设置dirty标识,下一次匹配到路径时,如果dirty==true,就会刷新路由信息
                this.zuulHandlerMapping.setDirty(true);
            }
    
        }
    
        //...
    }

ZuulProxyAutoConfiguration

我们再来看ZuulProxyAutoConfiguration这个自动装配类,可以看到,它导入了Ribbon相关的配置,这就是为什么Zuul可以通过服务名进行路由,因为天然整合了Ribbon。

ZuulProxyAutoConfiguration主要就是额外增加了Ribbon和服务发现的功能:

  1. 注入了Ribbon和Eureka(默认)相关的Bean;
  2. 注入了DiscoveryClientRouteLocator,用于根据服务名称路由;
  3. 注入了PreDecorationFilter、RibbonRoutingFilter、SimpleHostRoutingFilter这三个Filter。
    // ZuulProxyAutoConfiguration.java
    
    @Configuration(proxyBeanMethods = false)
    @Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
            RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
            RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
            HttpClientConfiguration.class })
    @ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)    // 注意这里用到了Marker标记对象
    public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
    
        @SuppressWarnings("rawtypes")
        @Autowired(required = false)
        private List<RibbonRequestCustomizer> requestCustomizers = Collections.emptyList();
    
        @Autowired(required = false)
        private Registration registration;
    
        @Autowired
        private DiscoveryClient discovery;
    
    
        // 路由加载器
        @Bean
        @ConditionalOnMissingBean(DiscoveryClientRouteLocator.class)
        public DiscoveryClientRouteLocator discoveryRouteLocator(
                ServiceRouteMapper serviceRouteMapper) {
            return new DiscoveryClientRouteLocator(this.server.getServlet().getContextPath(),
                    this.discovery, this.zuulProperties, serviceRouteMapper,
                    this.registration);
        }
    
        // pre filters
        @Bean
        @ConditionalOnMissingBean(PreDecorationFilter.class)
        public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator,
                ProxyRequestHelper proxyRequestHelper) {
            return new PreDecorationFilter(routeLocator,
                    this.server.getServlet().getContextPath(), this.zuulProperties,
                    proxyRequestHelper);
        }
    
        // route filters
        @Bean
        @ConditionalOnMissingBean(RibbonRoutingFilter.class)
        public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,
                RibbonCommandFactory<?> ribbonCommandFactory) {
            RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory,
                    this.requestCustomizers);
            return filter;
        }
    
        @Bean
        @ConditionalOnMissingBean({ SimpleHostRoutingFilter.class,
                CloseableHttpClient.class })
        public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper,
                ZuulProperties zuulProperties,
                ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
                ApacheHttpClientFactory httpClientFactory) {
            return new SimpleHostRoutingFilter(helper, zuulProperties,
                    connectionManagerFactory, httpClientFactory);
        }
    
        @Bean
        @ConditionalOnMissingBean({ SimpleHostRoutingFilter.class })
        public SimpleHostRoutingFilter simpleHostRoutingFilter2(ProxyRequestHelper helper,
                ZuulProperties zuulProperties, CloseableHttpClient httpClient) {
            return new SimpleHostRoutingFilter(helper, zuulProperties, httpClient);
        }
    
        // ServiceRouteMapper
        @Bean
        @ConditionalOnMissingBean(ServiceRouteMapper.class)
        public ServiceRouteMapper serviceRouteMapper() {
            return new SimpleServiceRouteMapper();
        }
    
        //...
    }

二、执行流程

了解了Spring Cloud Netflix Zuul的初始化流程,我们再来看下Zuul是如何处理请求的,本节我先讲解Zuul的整体处理流程,后续章节再深入细节,分析源码。

Zuul处理请求的整个流程可以用下面这张图表示:

202308072154559114.png

事实上,Spring Cloud Netflix Zuul集成了Spring MVC框架,所有http请求首先会由DispatcherServlet进行处理。DispatcherServlet会将请求交给映射处理器——ZuulHandlerMapping,最终转发给ZuulController进行处理:

    // DispatcherServlet.java
    
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;
            try {
                //...
    
                // 获取请求映射处理器
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }
    
                // 处理适配器
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
                //...
    
                // 处理请求
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
    
                applyDefaultViewName(processedRequest, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
       //...
    }

DispatcherServlet和HandlerMapping都属于Spring MVC的内部,我这里就不赘述了,想要深入了解的童鞋去看SpringMVC官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#spring-web。

2.1 ZuulHandlerMapping

ZuulHandlerMapping会根据请求URI,查找对应的处理器,其实是最终将请求转交给ZuulController进行处理:

    // ZuulHandlerMapping.java
    
    @Override
    protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
        if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) {
            return null;
        }
        if (isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) {
            return null;
        }
        // RequestContext是请求上下文信息,用ThreadLocal保证线程安全
        RequestContext ctx = RequestContext.getCurrentContext();
        if (ctx.containsKey("forward.to")) {
            return null;
        }
        // dirty==true时,会更新请求路由
        if (this.dirty) {
            synchronized (this) {
                if (this.dirty) {
                    registerHandlers();
                    this.dirty = false;
                }
            }
        }
        return super.lookupHandler(urlPath, request);
    }
    
    private void registerHandlers() {
        Collection<Route> routes = this.routeLocator.getRoutes();
        if (routes.isEmpty()) {
            this.logger.warn("No routes found from RouteLocator");
        }
        else {
            for (Route route : routes) {
                registerHandler(route.getFullPath(), this.zuul);
            }
        }
    }

2.2 ZuulController

ZuulController继承自ServletWrappingController,其实就是增加了清理请求上下文RequestContext的功能:

    // ZuulController.java
    
    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
                return super.handleRequestInternal(request, response);
            }
            finally {
                // 清理请求上下文信息
                RequestContext.getCurrentContext().unset();
            }
        }
    }

handleRequest方法其实是在DispatcherServlet中被调用的。

2.3 ZuulServlet

接下来,ZuulController会将请求交给ZuulServlet处理。ZuulServlet的执行逻辑还是很清晰的,就是依次去调用Pre、Route、Post过滤器:

    // ZuulServlet.java
    
    public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
        try {
            // 1.初始化ZuulRunner:内部封装了对Filter的调用逻辑
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();
    
            try {
                // 2.pre过滤器处理
                preRoute();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                // 3.Route过滤器处理
                route();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                // 4. Post过滤器处理
                postRoute();
            } catch (ZuulException e) {
                error(e);
                return;
            }
    
        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
        } finally {
            // 清除ThreadLocal中保存的上下文信息
            RequestContext.getCurrentContext().unset();
        }
    }
    
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
    
        String bufferReqsStr = config.getInitParameter("buffer-requests");
        boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false;
    
        zuulRunner = new ZuulRunner(bufferReqs);
    }

这里要注意ZuulRunner这个类,它的作用其实就是对HttpRequest和HttpResponse进行了封装,然后又封装了一层Filters的调用逻辑:

    public class ZuulRunner {
        private boolean bufferRequests;
    
        public ZuulRunner() {
            this.bufferRequests = true;
        }
    
        public ZuulRunner(boolean bufferRequests) {
            this.bufferRequests = bufferRequests;
        }
    
        public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
            // 封装HttpServletRequest和HttpServletResponse,并保存到请求上下文中
            RequestContext ctx = RequestContext.getCurrentContext();
            if (bufferRequests) {
                ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
            } else {
                ctx.setRequest(servletRequest);
            }
    
            ctx.setResponse(new HttpServletResponseWrapper(servletResponse));
        }
    
        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();
        }
    }

三、总结

本章,我对Spring Cloud Netflix Zuul的初始化流程和整体执行流程进行了讲解。Spring Cloud Zuul的初始化比较简单,重点是它的执行流程,而执行流程的重点又是一系列的Filter的执行逻辑。所以,下一章我将对各种内嵌的Filter的功能进行分析。


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文