前面章节,我针对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的整体封装逻辑就是:
- Spring Boot的main方法启动,内嵌一个tomcat容器启动;
- 扫描到@EnableEurekaServer注解,就利用
EurekaServerAutoConfiguration
完成Eureka-Server依赖的各种组件的组件,然后通过EurekaServerInitializerConfiguration.start()
最终完成Eureka-Server的所有组件的初始化和启动; - 扫描到@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] ,回复【面试题】 即可免费领取。