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

前面章节,我针对Netflix Eureka的核心源码进行了讲解。Spring Cloud在Netflix Eureka的基础上封装了一层,将Netflix Eureka整合到Spring技术体系中,也就是spring-cloud-netflix-eureka项目。这个项目仅仅是对Netflix Eureka的一个封装,提供了一些Spring Boot的注解,让Netflix Eureka更易于使用。

本章,我们就来看看,Spring Cloud是如何将Netflix Eureka整合到springboot技术栈中的。本章主要分为两部分: Eureka-Server注解式启动Eureka-Client注解式启动

一、Eureka-Server注解式启动

首先,我们来看下Spring Boot工程下,Eureka-Server是如何启动的。首先,我们引入spring-cloud-starter-eureka这个依赖,然后仅仅用了一个注解@EnableEurekaServer,最后直接启动一个main方法,就可以将Eureka-Server给启动起来了:

    @SpringBootApplication
    @EnableEurekaServer
    public class EurekaServer {
        public static void main(String[] args) {
            SpringApplication.run(EurekaServer.class, args);
        }
    }

1.1 EnableEurekaServer注解

@EnableEurekaServer注解,这个注解作用就是在Spring Boot启动一个内嵌的tomcat容器之后,将Eureka-Server在tomcat内部启动起来。这里的核心其实就是Spring Boot自身提供的 Auto Configuration机制

    /**
     * Annotation to activate Eureka Server related configuration {@link EurekaServerAutoConfiguration}
     *
     * @author Dave Syer
     * @author Biju Kunjummen
     *
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(EurekaServerMarkerConfiguration.class)
    public @interface EnableEurekaServer {
    
    }

依托Spring Boot的auto configuration机制,可以让我们使用一个注解@EnableEurekaServer,就触发EurekaServerAutoConfiguration的执行,从而完成Eureka-Server的初始化和启动。

1.2 EurekaServerAutoConfiguration自动装配

我们来看下EurekaServerAutoConfiguration,可以看到它自动帮我们组装好了Eureka-Server启动需要的各种配置和组件:

    @Configuration
    @Import(EurekaServerInitializerConfiguration.class)
    @ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
    @EnableConfigurationProperties({ EurekaDashboardProperties.class, InstanceRegistryProperties.class })
    @PropertySource("classpath:/eureka/server.properties")
    public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
        /**
         * List of packages containing Jersey resources required by the Eureka server
         */
        private static String[] EUREKA_PACKAGES = new String[] { "com.netflix.discovery",
                "com.netflix.eureka" };
    
        @Autowired
        private ApplicationInfoManager applicationInfoManager;
    
        @Autowired
        private EurekaServerConfig eurekaServerConfig;
    
        @Autowired
        private EurekaClientConfig eurekaClientConfig;
    
        @Autowired
        private EurekaClient eurekaClient;
    
        @Autowired
        private InstanceRegistryProperties instanceRegistryProperties;
    
        //...
    
        @Bean
        public PeerAwareInstanceRegistry peerAwareInstanceRegistry(
                ServerCodecs serverCodecs) {
            this.eurekaClient.getApplications(); // force initialization
            return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig,
                    serverCodecs, this.eurekaClient,
                    this.instanceRegistryProperties.getExpectedNumberOfRenewsPerMin(),
                    this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
        }
    
        @Bean
        @ConditionalOnMissingBean
        public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry,
                ServerCodecs serverCodecs) {
            return new PeerEurekaNodes(registry, this.eurekaServerConfig,
                    this.eurekaClientConfig, serverCodecs, this.applicationInfoManager);
        }
    
        @Bean
        public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,
                PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
            return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,
                    registry, peerEurekaNodes, this.applicationInfoManager);
        }
    
        @Bean
        public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
                EurekaServerContext serverContext) {
            return new EurekaServerBootstrap(this.applicationInfoManager,
                    this.eurekaClientConfig, this.eurekaServerConfig, registry,
                    serverContext);
        }
    
        //...
    }

1.3 EurekaServerInitializerConfiguration

最后,Spring Boot启动之后,就会执行EurekaServerInitializerConfiguration.start(),启动一个后台线程,完成剩余的Eureka-Srver初始化过程:

    // EurekaServerInitializerConfiguration.java
    
    @Autowired
    private EurekaServerBootstrap eurekaServerBootstrap;
    
    public void start() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try { 
                     //初始化
                    eurekaServerBootstrap
                         .contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
                    log.info("Started Eureka Server");
    
                    publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
                    EurekaServerInitializerConfiguration.this.running = true;
                    publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
                }
                catch (Exception ex) {
                    // Help!
                    log.error("Could not initialize Eureka servlet context", ex);
                }
            }
        }).start();
    }

EurekaServerInitializerConfiguration是Spring Boot自身的机制,我这里就不赘述,后续有时间我出个专栏讲解下Spring Boot的核心源码。

EurekaServerBootstrap是Spring Cloud自己封装的一个类,相当于把Eureka自身的EurekaBootStrap类的功能复制了一份:

    public void contextInitialized(ServletContext context) {
        try {
            initEurekaEnvironment();
            initEurekaServerContext();
    
            context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
        }
        catch (Throwable e) {
            log.error("Cannot bootstrap eureka server :", e);
            throw new RuntimeException("Cannot bootstrap eureka server :", e);
        }
    }

经过上述流程,最终就完成了Eureka-Server的启动。

二、Eureka-Client注解式启动

接着,我们再来看下Eureka-Client的注解式启动,原理几乎和Eureka-Server注解式启动一模一样。Spring Cloud就是做了一层简单的封装而已。

    @SpringBootApplication
    @EnableEurekaClient
    public class EurekaClient {
        public static void main(String[] args) {
            SpringApplication.run(EurekaServer.class, args);
        }
    }

2.1 EnableEurekaClient注解

@EnableEurekaClient注解,这个注解作用就是在Spring Boot启动一个内嵌的tomcat容器之后,将Eureka-Client在tomcat内部启动起来。这里的核心其实就是Spring Boot自身提供的 Auto Configuration机制

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface EnableEurekaClient {
    
    }

依托Spring Boot的auto configuration机制,可以让我们使用一个注解@EnableEurekaClient,就触发EurekaClientAutoConfiguration的执行,从而完成Eureka-Client的初始化和启动。

2.2 EurekaClientAutoConfiguration自动装配

我们来看下EurekaClientAutoConfiguration,可以看到它自动帮我们组装好了Eureka-Client启动需要的各种配置和组件:

    @Configuration
    @EnableConfigurationProperties
    @ConditionalOnClass(EurekaClientConfig.class)
    @Import(DiscoveryClientOptionalArgsConfiguration.class)
    @ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class)
    @ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
    @AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
            CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
    @AutoConfigureAfter(name = {"org.springframework.cloud.autoconfigure.RefreshAutoConfiguration",
            "org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration",
            "org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration"})
    public class EurekaClientAutoConfiguration {
    
        //...
    
        @Bean
        @ConditionalOnMissingBean(value = EurekaClientConfig.class, search = SearchStrategy.CURRENT)
        public EurekaClientConfigBean eurekaClientConfigBean(ConfigurableEnvironment env) {
            EurekaClientConfigBean client = new EurekaClientConfigBean();
            if ("bootstrap".equals(new RelaxedPropertyResolver(env).getProperty("spring.config.name"))) {
                client.setRegisterWithEureka(false);
            }
            return client;
        }
    
        @Bean
        @ConditionalOnMissingBean(value = EurekaInstanceConfig.class, search = SearchStrategy.CURRENT)
        public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils,
                                                                 ManagementMetadataProvider managementMetadataProvider, ConfigurableEnvironment env) throws MalformedURLException {
            //...
            EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils);
            //...
            return instance;
        }
    
        @Bean
        public DiscoveryClient discoveryClient(EurekaInstanceConfig config, EurekaClient client) {
            return new EurekaDiscoveryClient(config, client);
        }
    
        @Bean
        public EurekaServiceRegistry eurekaServiceRegistry() {
            return new EurekaServiceRegistry();
        }
    
        @Configuration
        @ConditionalOnMissingRefreshScope
        protected static class EurekaClientConfiguration {
    
            @Autowired
            private ApplicationContext context;
    
            @Autowired
            private AbstractDiscoveryClientOptionalArgs<?> optionalArgs;
    
            @Bean(destroyMethod = "shutdown")
            @ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)
            public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config) {
                return new CloudEurekaClient(manager, config, this.optionalArgs,
                        this.context);
            }
    
            @Bean
            @ConditionalOnMissingBean(value = ApplicationInfoManager.class, search = SearchStrategy.CURRENT)
            public ApplicationInfoManager eurekaApplicationInfoManager(
                    EurekaInstanceConfig config) {
                InstanceInfo instanceInfo = new InstanceInfoFactory().create(config);
                return new ApplicationInfoManager(config, instanceInfo);
            }
        }
    }

2.3 EurekaAutoServiceRegistration

最后,Spring Boot启动之后,就会执行EurekaAutoServiceRegistration.start(),启动一个后台线程,完成剩余的Eureka-Client初始化过程:

    //EurekaAutoServiceRegistration.java
    
    public void start() {
        // only set the port if the nonSecurePort or securePort is 0 and this.port != 0
        if (this.port.get() != 0) {
            if (this.registration.getNonSecurePort() == 0) {
                this.registration.setNonSecurePort(this.port.get());
            }
    
            if (this.registration.getSecurePort() == 0 && this.registration.isSecure()) {
                this.registration.setSecurePort(this.port.get());
            }
        }
    
        // only initialize if nonSecurePort is greater than 0 and it isn't already running
        // because of containerPortInitializer below
        if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
    
            this.serviceRegistry.register(this.registration);
    
            this.context.publishEvent(
                new InstanceRegisteredEvent<>(this, this.registration.getInstanceConfig()));
            this.running.set(true);
        }
    }

三、总结

本章,我讲解了Spring Cloud Eureka启动的核心原理,看懂了Netflix Eureka的源码后,再看Spring Cloud其实就一目了然了。Spring Cloud的整体封装逻辑就是:

  1. Spring Boot的main方法启动,内嵌一个tomcat容器启动;
  2. 扫描到@EnableEurekaServer注解,就利用EurekaServerAutoConfiguration完成Eureka-Server依赖的各种组件的组件,然后通过EurekaServerInitializerConfiguration.start()最终完成Eureka-Server的所有组件的初始化和启动;
  3. 扫描到@EnableEurekaClient注解,就利用EurekaClientAutoConfiguration完成Eureka-Client依赖的各种组件的组件,然后通过EurekaAutoServiceRegistration.start()最终完成Eureka-Client的所有组件的初始化和启动;

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] ,回复【面试题】 即可免费领取。

阅读全文