在Spring Cloud Alibaba中使用nacos作为服务注册组件,对应客户端首先需要引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
然后在启动类上添加注解
@EnableDiscoveryClient
看一下这个注解的全称org.springframework.cloud.client.discovery.EnableDiscoveryClient
,是由Spring Cloud提供的注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {
/**
* If true, the ServiceRegistry will automatically register the local server.
* @return - {@code true} if you want to automatically register.
*/
boolean autoRegister() default true;
}
通过这个这个注解就会引入一个EnableDiscoveryClientImportSelector
类型的bean,在这个类中方法selectImports
第一行打上断点,以debug模式启动项目
从以上的调用栈不难看出,在Spring容器的启动刷新(refresh
)的过程中,会进行BeanFactoryPostProcessor
的实例化并调用,其中有一个重要的类ConfigurationClassPostProcessor
,就是用于处理注解注入的,比如@Configuration
,这个类是Spring的核心类,本文不详细探讨,如有兴趣可以参考博客:
ConfigurationClassPostProcessor:https://blog.csdn.net/m0_37607945/article/details/106676299
此处我们需要知道的是,通过EnableDiscoveryClientImportSelector
我们注册了一个BeanDefinition。
org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration
定义如下:
@Configuration
@EnableConfigurationProperties(AutoServiceRegistrationProperties.class)
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
public class AutoServiceRegistrationConfiguration {
}
这个类的目的主要是用于读取Spring Cloud的配置(默认情况下是开启的,可以通过spring.cloud.service-registry.auto-registration.enabled=false
关闭),通过以上的配置,最后会将AutoServiceRegistrationProperties
作为一个Bean注入到容器中。
那么这个Bean何时起效呢?在spring-cloud-commons
这个包中存在一个类org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration
,这个类是通过SPI机制导入的(spring.factories)。
同样按照相同的模式spring-cloud-starter-alibaba-nacos-discovery
也引入了一个自动注入的配置类com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration
,这个类上面注解中包含如下信息
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
AutoServiceRegistrationAutoConfiguration.class,
NacosDiscoveryAutoConfiguration.class })
不难看出,其中有两个类正是上面我们说到的类,而且在NacosServiceRegistryAutoConfiguration
自动配置之前,会首先处理AutoServiceRegistrationConfiguration
、AutoServiceRegistrationAutoConfiguration
和NacosDiscoveryAutoConfiguration
。
其中AutoServiceRegistrationConfiguration
、AutoServiceRegistrationAutoConfiguration
是由Spring Cloud官方提供的,而NacosDiscoveryAutoConfiguration
和NacosServiceRegistryAutoConfiguration
是由spring-cloud-alibaba
提供的。前者是针对服务与注册的公共逻辑,支持包括但不局限于nacos、consul、zookeeper以及eruka等这些服务与注册组件,而后者是专门针对nacos的。
以上这个类是在何时起作用的呢?主要是bean的实例化的问题,首先在web容器的初始化过程中onRefresh
会实例化一些Bean,然后在在Spring容器的刷新之后也会进行一些非懒加载bean的实例化finishBeanFactoryInitialization
.以下从这两个点来看看对nacos中这些bean的影响。
web容器的启动过程onRefresh
如果Spring Boot以web程序启动的话,那么代表容器的类型为org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext
,那么在容器刷新的过程中,会进入onRefresh
这个方法,这个方法做了一个扩展,就是启动一个web服务器,默认情况下为tomcat
.
web容器的初始化
package org.springframework.boot.web.embedded.tomcat;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.web.servlet.ServletContextInitializer;
/**
* {@link ServletContainerInitializer} used to trigger {@link ServletContextInitializer
* ServletContextInitializers} and track startup errors.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
class TomcatStarter implements ServletContainerInitializer {
private static final Log logger = LogFactory.getLog(TomcatStarter.class);
private final ServletContextInitializer[] initializers;
private volatile Exception startUpException;
TomcatStarter(ServletContextInitializer[] initializers) {
this.initializers = initializers;
}
@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
try {
for (ServletContextInitializer initializer : this.initializers) {
initializer.onStartup(servletContext);
}
}
catch (Exception ex) {
this.startUpException = ex;
// Prevent Tomcat from logging and re-throwing when we know we can
// deal with it in the main thread, but log for information here.
if (logger.isErrorEnabled()) {
logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: "
+ ex.getMessage());
}
}
}
public Exception getStartUpException() {
return this.startUpException;
}
}
最后一个为class org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext$Lambda@5048
,这不是ServletWebServerApplicationContext
类型对象,而是在这个类中定义一个匿名内部类,
这是定义在里面的匿名内部类,在创建服务器createWebServer
时传入的:
根据传入的ServletContextInitializer
创建WebServer
对象。
首先看一下这个接口的定义:
@FunctionalInterface
public interface ServletContextInitializer {
/**
* Configure the given {@link ServletContext} with any servlets, filters, listeners
* context-params and attributes necessary for initialization.
* @param servletContext the {@code ServletContext} to initialize
* @throws ServletException if any call against the given {@code ServletContext}
* throws a {@code ServletException}
*/
void onStartup(ServletContext servletContext) throws ServletException;
}
/**
* Returns the {@link ServletContextInitializer} that will be used to complete the
* setup of this {@link WebApplicationContext}.
* @return the self initializer
* @see #prepareWebApplicationContext(ServletContext)
*/
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
// 对应onStartup的实现
return this::selfInitialize;
}
// 此处是ServletContextInitializer的onStartup实现
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
额外两个实现类也是在创建tomcat服务器的时候创建的,依次调用方法和对应源码如下所示:
org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer
org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#prepareContext
org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory#mergeInitializers
/**
* Utility method that can be used by subclasses wishing to combine the specified
* {@link ServletContextInitializer} parameters with those defined in this instance.
* @param initializers the initializers to merge
* @return a complete set of merged initializers (with the specified parameters
* appearing first)
*/
protected final ServletContextInitializer[] mergeInitializers(ServletContextInitializer... initializers) {
List<ServletContextInitializer> mergedInitializers = new ArrayList<>();
mergedInitializers.add((servletContext) -> this.initParameters.forEach(servletContext::setInitParameter));
mergedInitializers.add(new SessionConfiguringInitializer(this.session));
mergedInitializers.addAll(Arrays.asList(initializers));
mergedInitializers.addAll(this.initializers);
return mergedInitializers.toArray(new ServletContextInitializer[0]);
}
当容器完全启动之后,就会执行以上三个ServletContextInitializer
接口实现类的 onStartup
方法。
前面两个分别是处理参数和session的,与本文关系不大,因此略过。
容器刷新操作
针对最后一个的分析
private void selfInitialize(ServletContext servletContext) throws ServletException {
// 准备一个WebApplicationContext对象
prepareWebApplicationContext(servletContext);
// 注册scope
registerApplicationScope(servletContext);
// 注册servletContext容器上下文相关信息
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
// 容器启动
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
准备WebApplicationContext对象
- 首先从servletContext中获取
org.springframework.web.context.WebApplicationContext.ROOT
属性,默认情况下为null - 将当前类设置为
org.springframework.web.context.WebApplicationContext.ROOT
属性值,当前类对象为org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@3e2943ab, started on Fri Jul 31 13:53:02 CST 2020, parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@2d0399f4
. - Set the ServletContext that this WebApplicationContext runs in.
// String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
/**
- Prepare the {@link WebApplicationContext} with the given fully loaded
- {@link ServletContext}. This method is usually called from
- {@link ServletContextInitializer#onStartup(ServletContext)} and is similar to the
- functionality usually provided by a {@link ContextLoaderListener}.
- @param servletContext the operational servlet context
*/
protected void prepareWebApplicationContext(ServletContext servletContext) {
Object rootContext = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (rootContext != null) {
if (rootContext == this) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - "
+ "check whether you have multiple ServletContextInitializers!");
}
return;
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring embedded WebApplicationContext");
try {
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this);
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name ["
+ WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
setServletContext(servletContext);
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - getStartupDate();
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
此时控制台打印一下日志
2020-07-31 14:15:15.639 INFO 13928 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-07-31 14:17:48.231 INFO 13928 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1484350 ms
**注册ServletContextScope **
- 首先创建一个
ServletContextScope
对象 - 在bean工厂中进行注册,名称为
application
的scope - 设置到容器上下文
servletContext
对象属性当中
private void registerApplicationScope(ServletContext servletContext) {
ServletContextScope appScope = new ServletContextScope(servletContext);
getBeanFactory().registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
// Register as ServletContext attribute, for ContextCleanupListener to detect it.
servletContext.setAttribute(ServletContextScope.class.getName(), appScope);
}
注册servletContext容器上下文相关信息
调用的方法如下:
org.springframework.web.context.support.WebApplicationContextUtils#registerEnvironmentBeans(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, javax.servlet.ServletContext)
- 将当前
servletContext
注册为名称为servletContext
的单例Bean - 如果参数
servletConfig
不为null(默认为null),则注册名称为servletConfig
的单例bean - 根据当前
servletContext
的初始化参数构建一个Map对象,并注册为名称为contextParameters
的单例bean - 根据当前
servletContext
的参数构建一个Map对象,并注册为名称为contextAttributes
的单例bean
对应源码:
/**
* Register web-specific environment beans ("contextParameters", "contextAttributes")
* with the given BeanFactory, as used by the WebApplicationContext.
* @param bf the BeanFactory to configure
* @param sc the ServletContext that we're running within
*/
public static void registerEnvironmentBeans(ConfigurableListableBeanFactory bf, @Nullable ServletContext sc) {
registerEnvironmentBeans(bf, sc, null);
}
/**
* Register web-specific environment beans ("contextParameters", "contextAttributes")
* with the given BeanFactory, as used by the WebApplicationContext.
* @param bf the BeanFactory to configure
* @param servletContext the ServletContext that we're running within
* @param servletConfig the ServletConfig
*/
public static void registerEnvironmentBeans(ConfigurableListableBeanFactory bf,
@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
if (servletContext != null && !bf.containsBean(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME)) {
bf.registerSingleton(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME, servletContext);
}
if (servletConfig != null && !bf.containsBean(ConfigurableWebApplicationContext.SERVLET_CONFIG_BEAN_NAME)) {
bf.registerSingleton(ConfigurableWebApplicationContext.SERVLET_CONFIG_BEAN_NAME, servletConfig);
}
if (!bf.containsBean(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME)) {
Map<String, String> parameterMap = new HashMap<>();
if (servletContext != null) {
Enumeration<?> paramNameEnum = servletContext.getInitParameterNames();
while (paramNameEnum.hasMoreElements()) {
String paramName = (String) paramNameEnum.nextElement();
parameterMap.put(paramName, servletContext.getInitParameter(paramName));
}
}
if (servletConfig != null) {
Enumeration<?> paramNameEnum = servletConfig.getInitParameterNames();
while (paramNameEnum.hasMoreElements()) {
String paramName = (String) paramNameEnum.nextElement();
parameterMap.put(paramName, servletConfig.getInitParameter(paramName));
}
}
bf.registerSingleton(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME,
Collections.unmodifiableMap(parameterMap));
}
if (!bf.containsBean(WebApplicationContext.CONTEXT_ATTRIBUTES_BEAN_NAME)) {
Map<String, Object> attributeMap = new HashMap<>();
if (servletContext != null) {
Enumeration<?> attrNameEnum = servletContext.getAttributeNames();
while (attrNameEnum.hasMoreElements()) {
String attrName = (String) attrNameEnum.nextElement();
attributeMap.put(attrName, servletContext.getAttribute(attrName));
}
}
bf.registerSingleton(WebApplicationContext.CONTEXT_ATTRIBUTES_BEAN_NAME,
Collections.unmodifiableMap(attributeMap));
}
}
onStartup
创建一个ServletContextInitializerBeans
对象,并传入bean工厂对象
/**
* Returns {@link ServletContextInitializer}s that should be used with the embedded
* web server. By default this method will first attempt to find
* {@link ServletContextInitializer}, {@link Servlet}, {@link Filter} and certain
* {@link EventListener} beans.
* @return the servlet initializer beans
*/
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
return new ServletContextInitializerBeans(getBeanFactory());
}
@SafeVarargs
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
Class<? extends ServletContextInitializer>... initializerTypes) {
this.initializers = new LinkedMultiValueMap<>();
// 默认情况下initializerTypes集合为空的 因此此处会返回包含ServletContextInitializer的集合
this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
: Collections.singletonList(ServletContextInitializer.class);
// 从容器中获取ServletContextInitializer类型的bean
addServletContextInitializerBeans(beanFactory);
addAdaptableBeans(beanFactory);
List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
.flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
.collect(Collectors.toList());
this.sortedList = Collections.unmodifiableList(sortedInitializers);
logMappings(this.initializers);
}
其中servletEndpointRegistrar
是在org.springframework.boot.actuate.autoconfigure.endpoint.web.ServletEndpointManagementContextConfiguration.WebMvcServletEndpointManagementContextConfiguration
类中定义的,只有在SERVLET环境下才会注入。
对应源码如下所示:
@Bean
public ServletEndpointRegistrar servletEndpointRegistrar(WebEndpointProperties properties,
ServletEndpointsSupplier servletEndpointsSupplier) {
DispatcherServletPath dispatcherServletPath = this.context.getBean(DispatcherServletPath.class);
return new ServletEndpointRegistrar(dispatcherServletPath.getRelativePath(properties.getBasePath()),
servletEndpointsSupplier.getEndpoints());
}
这个类会去构建EndpointBeans,获取Spring容器中包含有org.springframework.boot.actuate.endpoint.annotation.Endpoint
注解的bean名称,并用于构建EndpointBean
对象。是在WebEndpointAutoConfiguration
类中引入。
org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer#discoverEndpoints
private Collection<E> discoverEndpoints() {
Collection<EndpointBean> endpointBeans = createEndpointBeans();
addExtensionBeans(endpointBeans);
return convertToEndpoints(endpointBeans);
}
private Collection<EndpointBean> createEndpointBeans() {
Map<EndpointId, EndpointBean> byId = new LinkedHashMap<>();
String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.applicationContext,
Endpoint.class);
for (String beanName : beanNames) {
if (!ScopedProxyUtils.isScopedTarget(beanName)) {
EndpointBean endpointBean = createEndpointBean(beanName);
EndpointBean previous = byId.putIfAbsent(endpointBean.getId(), endpointBean);
Assert.state(previous == null, () -> "Found two endpoints with the id '" + endpointBean.getId() + "': '"
+ endpointBean.getBeanName() + "' and '" + previous.getBeanName() + "'");
}
}
return byId.values();
}
如下这些bean都包含了这个注解
而类NacosDiscoveryEndpoint
就包含这个注解(对应bean的注入是通过SPI机制完成的,可以参考NacosDiscoveryEndpointAutoConfiguration
类)
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public NacosDiscoveryEndpoint nacosDiscoveryEndpoint(
NacosDiscoveryProperties nacosDiscoveryProperties) {
return new NacosDiscoveryEndpoint(nacosDiscoveryProperties);
}
由于依赖了NacosDiscoveryProperties
,这里也会导致NacosDiscoveryProperties
的实例化。
@Bean
@ConditionalOnMissingBean
public NacosDiscoveryProperties nacosProperties() {
return new NacosDiscoveryProperties();
}
serviceRegistryEndpoint
Bean的初始化
@Configuration
public class ServiceRegistryAutoConfiguration {
@ConditionalOnBean(ServiceRegistry.class)
@ConditionalOnClass(Endpoint.class)
protected class ServiceRegistryEndpointConfiguration {
@Autowired(required = false)
private Registration registration;
@Bean
@ConditionalOnEnabledEndpoint
public ServiceRegistryEndpoint serviceRegistryEndpoint(
ServiceRegistry serviceRegistry) {
ServiceRegistryEndpoint endpoint = new ServiceRegistryEndpoint(
serviceRegistry);
endpoint.setRegistration(this.registration);
return endpoint;
}
}
}
实例化这个Bean会导致ServiceRegistry
类型和Registration
类型Bean的初始化操作。
然后又会在构造其中实例化NamingService
对象,而这个对象就是用于服务注册与发现的关键类,比如registerInstance
注册客户端实例、deregisterInstance
取消注册、getAllInstances
,selectInstances
,selectOneHealthyInstance
获取注册的实例、subscribe
订阅等。
private final NamingService namingService;
public NacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {
this.nacosDiscoveryProperties = nacosDiscoveryProperties;
this.namingService = nacosDiscoveryProperties.namingServiceInstance();
}
而Registration
类型在nacos中的实现为com.alibaba.cloud.nacos.registry.NacosRegistration
.
此处不继续探讨了,可以参考博客
https://blog.csdn.net/m0_37607945/article/details/106441940
返回到ServletContextInitializerBeans
类的构造中,通过addServletContextInitializerBeans
方法,将ServletContextInitializer
类型的bean进行了实例化,并在此过程中实例化了一些相关的bean,其中就包括了nacos的一些bean。然后后处理与Servlet
、Filter
和EventListener
,此处也不深入探讨了。
protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory);
addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(multipartConfig));
addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
for (Class<?> listenerType : ServletListenerRegistrationBean.getSupportedTypes()) {
addAsRegistrationBean(beanFactory, EventListener.class, (Class<EventListener>) listenerType,
new ServletListenerRegistrationBeanAdapter());
}
}
ServletContextInitializerBeans
实现了AbstractCollection<ServletContextInitializer>
,对其进行遍历,会遍历在构造中实例化的各个ServletContextInitializer
,依次执行onStartup
方法,完成web容器的启动过程。
注册过滤器
注册servlet
执行Spring容器的初始化操作finishBeanFactoryInitialization
实例化其他诸如nacosServiceDiscovery
、nacosAutoServiceRegistration
的初始化
com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration#nacosAutoServiceRegistration
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
public NacosAutoServiceRegistration nacosAutoServiceRegistration(
NacosServiceRegistry registry,
AutoServiceRegistrationProperties autoServiceRegistrationProperties,
NacosRegistration registration) {
// 此时完成了registry、registration、autoServiceRegistrationProperties的实例化
return new NacosAutoServiceRegistration(registry,
autoServiceRegistrationProperties, registration);
}
NacosAutoServiceRegistration
实现了AbstractAutoServiceRegistration
抽象类,而NacosServiceRegistry
实现了ServiceRegistry
接口,这样Spring Cloud Ailbaba
就和Spring Cloud
统一起来了,NacosAutoServiceRegistration
的实例化会将nacos的实现类设置到了AbstractAutoServiceRegistration
抽象类中。在AbstractAutoServiceRegistration
实现了公共逻辑(模板方式)。另外实现了ApplicationListener<WebServerInitializedEvent>
接口。在抽象类中会监听对应的WebServerInitializedEvent
事件,通过这个事件进行bind
->start
->register
,最后会调用到真实实现类的逻辑。此处有个大前提,就是在容器中非懒加载的bean都实例化之后才会发布WebServerInitializedEvent
事件。
发布容器事件并进行客户端的注册
@Override
@SuppressWarnings("deprecation")
public void onApplicationEvent(WebServerInitializedEvent event) {
bind(event);
}
@Deprecated
public void bind(WebServerInitializedEvent event) {
ApplicationContext context = event.getApplicationContext();
if (context instanceof ConfigurableWebServerApplicationContext) {
if ("management".equals(((ConfigurableWebServerApplicationContext) context)
.getServerNamespace())) {
return;
}
}
this.port.compareAndSet(0, event.getWebServer().getPort());
this.start();
}
public void start() {
// 此方法是一个扩展点 由子类去实现
if (!isEnabled()) {
if (logger.isDebugEnabled()) {
logger.debug("Discovery Lifecycle disabled. Not starting");
}
return;
}
// only initialize if nonSecurePort is greater than 0 and it isn't already running
// because of containerPortInitializer below
if (!this.running.get()) {
// 此处会发布一个InstancePreRegisteredEvent事件
this.context.publishEvent(
new InstancePreRegisteredEvent(this, getRegistration()));
// 此处会进行注册操作
register();
if (shouldRegisterManagement()) {
registerManagement();
}
// 此处会发布一个InstanceRegisteredEvent事件
this.context.publishEvent(
new InstanceRegisteredEvent<>(this, getConfiguration()));
this.running.compareAndSet(false, true);
}
}
对应nacos中的实现:
@Override
protected boolean isEnabled() {
return this.registration.getNacosDiscoveryProperties().isRegisterEnabled();
}
配置类中对应的实现com.alibaba.cloud.nacos.NacosDiscoveryProperties
/**
* if you just want to subscribe, but don't want to register your service, set it to
* false.
*/
private boolean registerEnabled = true;
子类执行注册
@Override
protected void register() {
if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) {
log.debug("Registration disabled.");
return;
}
if (this.registration.getPort() < 0) {
this.registration.setPort(getPort().get());
}
super.register();
}
抽象类中的实现
/**
* Register the local service with the {@link ServiceRegistry}.
*/
protected void register() {
this.serviceRegistry.register(getRegistration());
}
此处又涉及到一个抽象方法需要在子类中实现
@Override
protected NacosRegistration getRegistration() {
if (this.registration.getPort() < 0 && this.getPort().get() > 0) {
this.registration.setPort(this.getPort().get());
}
Assert.isTrue(this.registration.getPort() > 0, "service.port has not been set");
return this.registration;
}
Spring Cloud一开始并不知道具体的注册逻辑,也不知道注册的对象实例,但是提供了以下两个接口
public interface Registration extends ServiceInstance {
}
public interface ServiceRegistry<R extends Registration> {
/**
* Registers the registration. A registration typically has information about an
* instance, such as its hostname and port.
* @param registration registration meta data
*/
void register(R registration);
}
前者代表一个客户端实例,而后者则是进行这个客户端实例的注册实现,Spring Cloud在容器实例化之后通过发布事件然后执行对应的注册操作,但是不关心具体注册啥以及具体的注册逻辑,这些都由具体的提供商来实现(仅仅提供标准)。
而对于具体的服务注册与实现的实现这来说,必须实现这两个接口Registration
和ServiceRegistry
,另外还有抽象类AbstractAutoServiceRegistration
。如果没有实现AbstractAutoServiceRegistration
这个抽象类(其实是AutoServiceRegistration接口),容器在启动的时候会报错IllegalStateException
,对应源代码如下:
@Configuration
@Import(AutoServiceRegistrationConfiguration.class)
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
public class AutoServiceRegistrationAutoConfiguration {
@Autowired(required = false)
private AutoServiceRegistration autoServiceRegistration;
@Autowired
private AutoServiceRegistrationProperties properties;
@PostConstruct
protected void init() {
if (this.autoServiceRegistration == null && this.properties.isFailFast()) {
throw new IllegalStateException("Auto Service Registration has "
+ "been requested, but there is no AutoServiceRegistration bean");
}
}
}