Spring Cloud源码解析:一个注解加载Eureka client

原文  http://www.idouba.net/spring-cloud-source-load-eureka-client-by-annotation/


一般我们的都会被告知使用spring cloud app 如果 需要服务服务注册发现功能 ,只需简单的一个 @EnableEurekaClient 的annotation,就可以搞定。服务启动时会自动注册到eureka服务,服务消费者只需要使用该名字加方法名就可以调用到服务。代码基本不用加任何东西,很神奇的样子!于是有点好奇一个本文重点关注在spring cloud中Eureka client是如何被注入,来供服务使用的。

1 Eureka client简介

Eureka client 负责与Eureka Server 配合向外提供注册与发现服务接口。首先看下eureka client是怎么定义,Netflix的 eureka client的行为在LookupService中定义,Lookup service for finding active instances,定义了,从outline中能看到起“规定”了如下几个最基本的方法。服务发现必须实现的基本行为:

从继承图上能看到两点:

第一, discoverclient需要实现该接口,用户app需要用该接口来实现服务发现。

第二, discover 的各个peer node实现该接口,来同步注册服务

com.netflix.discovery.DiscoveryClient是netflix使用的客户端,从其class的注释可以看到他主要做这几件事情:

a) Registering the instance with Eureka Server

b) Renewalof the lease with Eureka Server

c) Cancellation of the lease from Eureka Server during shutdown

2 Eureka Client 在Spring cloud中

看到spring cloud自己又搞了个org.springframework.cloud.client.discovery.

接口,其接口定义如下

其实现类是 org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient。看上去spring cloud是自己做了个新client,来和Eureka server通信来实现服务注册和发现吗?里面代码稍微多看一点会看到其实里面还是使用了netflix原生的 com.netflix.discovery.EurekaClient。

Java

@RequiredArgsConstructor
public class EurekaDiscoveryClient implements DiscoveryClient {
public static final String DESCRIPTION = “Spring Cloud Eureka Discovery Client”;
private final EurekaInstanceConfig config;
private final EurekaClient eurekaClient;
@RequiredArgsConstructor
public class EurekaDiscoveryClient implements DiscoveryClient {
public static final String DESCRIPTION = “SpringCloudEurekaDiscoveryClient”;
private final EurekaInstanceConfigconfig;
private final EurekaClienteurekaClient;

其实这种方式在spring cloud中运用很多。spring cloud里面挺多对象,都是自己定义了一个接口,定义了其在spring cloud上下文中赋予的新的行为要求,但是其实现类其实都是使用netflix原生的对应对象,包括里面的ServiceInstance等,都是有自己的定义 但是实现类EurekaServiceInstance 其实也是对netflix原生的com.netflix.appinfo.InstanceInfo 的封装。因此springcloud中的对象更像是一个wrapper。有点像那种被改造过的decorator pattern 。

因此要看下spring cloud中怎样加载一个eureka client,其实,先看下如何加载一个EurekaDiscoveryClient

3 EurekaDiscoveryClient在spring cloud的注入过程

如下的是一般写一个服务,在服务的启动类中,用户要做的所有事情就是在自己的app中使用@EnableEurekaClient

Java

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

这样一个annotation。我们试着看下这个annotation后面怎么把这个EurekaDiscoveryClient 注入的。可以看到这个自定义的annotation @EnableEurekaClient里面没有内容。只是个属于那种标记类型 的注释,他的作用就是开启Eureka discovery的配置,正是通过这个标记,autoconfiguration就可以加载相关的Eureka类。那我们看下他是怎么做到的。

Java

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

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

看到EnableEurekaClient这个自定义anntotaion的定义,用到了另一个自定义的注解:@EnableDiscoveryClient。看看这个自定义注解

Java

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {
}

这个注解import了EnableDiscoveryClientImportSelector.class这样一个类,其实就是通过这个类来加载需求要的bean。Import的用法和我们原来用xml的方式来聚合两个类似的配置类。

XHTML

<beans >
<import resource=”config/bean1config.xml”/>
<import resource=”config/bean2config.xml”/>
</beans>
<beans >
<importresource=”config/bean1config.xml”/>
<importresource=”config/bean2config.xml”/>
</beans>

看到这里有覆盖了父类SpringFactoryImportSelector的一个方法isEnabled,其实标记体现的也就是这里,在配置的环境中找这个key是否被配置,注意,默认是TRUE,也就是只要import了这个配置,就会enable。

Java

@Order(Ordered.LOWEST_PRECEDENCE – 100)
public class EnableDiscoveryClientImportSelector
extends SpringFactoryImportSelector<EnableDiscoveryClient> {

@Override
protected boolean isEnabled() {
return new RelaxedPropertyResolver(getEnvironment()).getProperty(
“spring.cloud.discovery.enabled”, Boolean.class, Boolean.TRUE);
}
@Order(Ordered.LOWEST_PRECEDENCE – 100)
public class EnableDiscoveryClientImportSelector
extends SpringFactoryImportSelector<EnableDiscoveryClient> {
 
@Override
protected boolean isEnabled() {
return new RelaxedPropertyResolver(getEnvironment()).getProperty(
“spring.cloud.discovery.enabled”, Boolean.class, Boolean.TRUE);
}

在其父类org.springframework.cloud.commons.util.SpringFactoryImportSelector的String[] selectImports(AnnotationMetadata metadata)方法中正是根据这个标记类判定是否加载如下定义的类。

TODOODO selectImports 代码快

调用loadFactoryNames其实加载META-INF/spring.factories下的class。

org.springframework.core.io.support.SpringFactoriesLoader.loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader)

TODOTODO loadFactoryNames

spring-cloud-netflix-eureka-client\src\main\resources\META-INF\spring.factories中配置,可以看到EnableAutoConfiguration的包含了EurekaClientConfigServerAutoConfiguration。

Shell

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration

EurekaClientAutoConfiguration分别会依次注入配置EurekaClientConfigBean、 EurekaInstanceConfigBean和DiscoveryClient

Java

@Bean
	@ConditionalOnMissingBean(value = EurekaClientConfig.class, search = SearchStrategy.CURRENT)
	public EurekaClientConfigBean eurekaClientConfigBean() {
		EurekaClientConfigBean client = new EurekaClientConfigBean();
		if ("bootstrap".equals(this.env.getProperty("spring.config.name"))) {
			// We don't register during bootstrap by default, but there will be another
			// chance later.
			client.setRegisterWithEureka(false);
		}
		return client;
	}

	@Bean
	public DiscoveryClient discoveryClient(EurekaInstanceConfig config,
			EurekaClient client) {
		return new EurekaDiscoveryClient(config, client);
	}
@Bean
 @ConditionalOnMissingBean(value = EurekaClientConfig.class, search = SearchStrategy.CURRENT)
 public EurekaClientConfigBeaneurekaClientConfigBean() {
 EurekaClientConfigBeanclient = new EurekaClientConfigBean();
 if ("bootstrap".equals(this.env.getProperty("spring.config.name"))) {
 // We don't register during bootstrap by default, but there will be another
 // chance later.
 client.setRegisterWithEureka(false);
 }
 return client;
 }
 
 @Bean
 public DiscoveryClientdiscoveryClient(EurekaInstanceConfigconfig,
 EurekaClientclient) {
 return new EurekaDiscoveryClient(config, client);
 }

4.  调用过程

至此就找到了Eureka相关类是在哪里定义的,怎样通过@EnableEurekaClient 这个注解来控制加载的。但是在服务的congtext中这部分又是如何被调用到的呢?如果要分析这部分,就有点想我没试图从原来的spring的application.xml去试图追寻其中定义的bean是如何在context中创建,又如何被被根据配置赋予属性,建立bean之间的关系。

其实在spring cloud中使用的annotation就是原来大家属性的xml配置的另外一种so called alternative。

这个调用关系真的是非常长,非常繁琐的,明显不是本文讨论的重点,这是springIOC的核心实现,详细可以参照spring core相关的东西。但是为了完整期间,还是最粗糙的把这个调用关系贴出来。能看到在自己写的一个服务的main函数开始如果使用spring 上下文来加载那几个eureka的类。只是简单看下这个过程,简化了很多,所以这里的调用过程也是一个简单的链路。

Java

bookmarks.BookmarkServiceApplication.main
bookmarks.BookmarkServiceApplication.main

Java

ConfigurableApplicationContext org.springframework.boot.SpringApplication.run
ConfigurableApplicationContextorg.springframework.boot.SpringApplication.run

Java

org.springframework.boot.SpringApplication.SpringApplication(Objectsources)
org.springframework.boot.SpringApplication.SpringApplication(Objectsources)

Java

org.springframework.boot.SpringApplication.initialize(Object[] sources)
org.springframework.boot.SpringApplication.initialize(Object[] sources)

Java

springframework.boot.SpringApplication.createAndRefreshContext()
springframework.boot.SpringApplication.createAndRefreshContext()

Java

org.springframework.context.support.AbstractApplicationContext.refresh()
@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

		}
	}
org.springframework.context.support.AbstractApplicationContext.refresh()
@Override
 public void refresh() throws BeansException, IllegalStateException {
 synchronized (this.startupShutdownMonitor) {
 // Prepare this context for refreshing.
 prepareRefresh();
 
 // Tell the subclass to refresh the internal bean factory.
 ConfigurableListableBeanFactorybeanFactory = obtainFreshBeanFactory();
 
 // Prepare the bean factory for use in this context.
 prepareBeanFactory(beanFactory);
 
 // Allows post-processing of the bean factory in context subclasses.
 postProcessBeanFactory(beanFactory);
 
 // Invoke factory processors registered as beans in the context.
 invokeBeanFactoryPostProcessors(beanFactory);
 
 // Register bean processors that intercept bean creation.
 registerBeanPostProcessors(beanFactory);
 
 // Initialize message source for this context.
 initMessageSource();
 
 // Initialize event multicaster for this context.
 initApplicationEventMulticaster();
 
 // Initialize other special beans in specific context subclasses.
 onRefresh();
 
 // Check for listener beans and register them.
 registerListeners();
 
 // Instantiate all remaining (non-lazy-init) singletons.
 finishBeanFactoryInitialization(beanFactory);
 
 // Last step: publish corresponding event.
 finishRefresh();
 }
 
 }
 }

TODOTO AbstractApplicationContext.refresh()

可以看到不管是原来的xml的方式还是现在这种annotation的方式,abstract的context还是同样的逻辑。进一步验证了annotation于xml也都只是一种配置的方式,是给开发人员的接口,spring IOC的东西没用变。其实从调用链路上各个类的package从属也能看出端倪。 org.springframework.context一般都是spring 这个包的,org.springframework.context.annotation就是annotation这个包的

Java

org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors()
org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors()

Java

org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()
org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()

Java

org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors()
org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors()

Java

org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(BeanDefinitionRegistryregistry)

Java

org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(BeanDefinitionRegistry registry)
org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(BeanDefinitionRegistryregistry)

Java

org.springframework.context.annotation.ConfigurationClassParser.parse()
org.springframework.context.annotation.ConfigurationClassParser.parse()

Java

org.springframework.context.annotation.ConfigurationClassParser.processDeferredImportSelectors()
org.springframework.context.annotation.ConfigurationClassParser.processDeferredImportSelectors()

Java

org.springframework.cloud.commons.util.SpringFactoryImportSelector.selectImports()
org.springframework.cloud.commons.util.SpringFactoryImportSelector.selectImports()

Java

org.springframework.context.annotation.ConfigurationClassParser.processImports()
org.springframework.context.annotation.ConfigurationClassParser.processImports()

这个就是我们前面看到的import 类并实例化的地方。看到在processImports中会对这里import的类进行实例化。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值