一般我们的都会被告知使用spring cloud app 如果 需要服务服务注册发现功能 ,只需简单的一个 @EnableEurekaClient 的annotation,就可以搞定。服务启动时会自动注册到eureka服务,服务消费者只需要使用该名字加方法名就可以调用到服务。代码基本不用加任何东西,很神奇的样子!于是有点好奇一个本文重点关注在spring cloud中Eureka client是如何被注入,来供服务使用的。
本文主要了解下在spring cloud下用户开发一个自己的服务,用一个annotation怎么就能自动的完成服务的注册和发现。怎样偷偷的集成了一个eureka的client进去的。说实话对于这个annotation虽然看着对使用者是很简单,但是可能对一般使用惯了API的人,封装太多了倒有点不踏实。
1 Eureka client简介
Eureka client 负责与Eureka Server 配合向外提供注册与发现服务接口。首先看下eureka client是怎么定义,Netflix的 eureka client的行为在LookupService中定义,Lookup service for finding active instances,定义了,从outline中能看到起“规定”了如下几个最基本的方法。
服务发现必须实现的基本行为:
lookupservice_outline
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。
@RequiredArgsConstructor
public class EurekaDiscoveryClient implements DiscoveryClient {
public static final String DESCRIPTION = “Spring Cloud Eureka Discovery Client”;
private final EurekaInstanceConfig config;
private final EurekaClient eurekaClient;
其实这种方式在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
@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类。那我们看下他是怎么做到的。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableDiscoveryClient
public @interface EnableEurekaClient {
}
看到EnableEurekaClient这个自定义anntotaion的定义,用到了另一个自定义的注解:@EnableDiscoveryClient。看看这个自定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {
}
这个注解import了EnableDiscoveryClientImportSelector.class这样一个类,其实就是通过这个类来加载需求要的bean。Import的用法和我们原来用xml的方式来聚合两个类似的配置类。
@Order(Ordered.LOWEST_PRECEDENCE – 100)
public class EnableDiscoveryClientImportSelector
extends SpringFactoryImportSelector {
@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)
/**
* Load the fully qualified class names of factory implementations of the
* given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
* class loader.
* @param factoryClass the interface or abstract class representing the factory
*/
public static List loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
Enumeration urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List result = new ArrayList();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
spring-cloud-netflix-eureka-client\src\main\resources\META-INF\spring.factories中配置,可以看到EnableAutoConfiguration的包含了EurekaClientConfigServerAutoConfiguration。
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
@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);
}
- 调用过程
至此就找到了Eureka相关类是在哪里定义的,怎样通过@EnableEurekaClient 这个注解来控制加载的。但是在服务的congtext中这部分又是如何被调用到的呢?如果要分析这部分,就有点想我没试图从原来的spring的application.xml去试图追寻其中定义的bean是如何在context中创建,又如何被被根据配置赋予属性,建立bean之间的关系。
其实在spring cloud中使用的annotation就是原来大家属性的 xml配置 的另外一种so called alternative。
这个调用关系真的是非常长,非常繁琐的,明显不是本文讨论的重点,这是springIOC的核心实现,详细可以参照spring core相关的东西。但是为了完整期间,还是最粗糙的把这个调用关系贴出来。能看到在自己写的一个服务的main函数开始如果使用spring 上下文来加载那几个eureka的类。只是简单看下这个过程,简化了很多,所以这里的调用过程也是一个简单的链路。
bookmarks.BookmarkServiceApplication.main
ConfigurableApplicationContext org.springframework.boot.SpringApplication.run
org.springframework.boot.SpringApplication.SpringApplication(Object… sources)
org.springframework.boot.SpringApplication.initialize(Object[] sources)
springframework.boot.SpringApplication.createAndRefreshContext()
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();
}
}
可以看到不管是原来的xml的方式还是现在这种annotation的方式,abstract的context还是同样的逻辑,这个方法是最典型的模板方法,调用的各个方法都是在子类中定义。进一步验证了annotation于xml也都只是一种配置的方式,是给开发人员的接口,spring IOC的东西没用变。其实从调用链路上各个类的package从属也能看出端倪。
org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors()
org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()
org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors()
org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(BeanDefinitionRegistry registry)
org.springframework.context.annotation.ConfigurationClassParser.parse()
org.springframework.context.annotation.ConfigurationClassParser.processDeferredImportSelectors()
org.springframework.cloud.commons.util.SpringFactoryImportSelector.selectImports()
org.springframework.context.annotation.ConfigurationClassParser.processImports()
这个就是我们前面看到的import 类并实例化的地方。看到在processImports中会对这里import的类进行实例化。
原创文章。为了维护文章的版本一致、最新、可追溯,转载请注明: 转载自 idouba
本文链接地址: Spring-cloud & Netflix 源码解析:一个注解加载Eureka client
转载自http://www.idouba.net/