参考:Nacos服务注册源码分析
Spring Boot版本:2.2.9.RELEASE
Spring Cloud版本:Hoxton.SR6
Spring Cloud Alibaba版本:2.2.1.RELEASE
Nacos版本:1.3.1
1 @EnableDiscoveryClient
如果需要启用服务注册功能,需要在启动类上面添加@EnableDiscoveryClient注解。
查看@EnableDiscoveryClient的源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {
/**
* 默认返回值为true。
* 如果返回值为true,ServiceRegistry将会自动将本服务注册到服务注册中心。
*/
boolean autoRegister() default true;
}
@EnableDiscoveryClient注解的源码中有一个方法autoRegister(),默认返回true,表示希望自动注册服务。
@EnableDiscoveryClient还通过@Import注解引入了EnableDiscoveryClientImportSelector类,查看源码:
@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableDiscoveryClientImportSelector
extends SpringFactoryImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
String[] imports = super.selectImports(metadata);
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(getAnnotationClass().getName(), true));
// 获取@EnableDiscoveryClient注解的属性autoRegister的值
boolean autoRegister = attributes.getBoolean("autoRegister");
// 如果autoRegister == true,加载AutoServiceRegistrationConfiguration配置类
if (autoRegister) {
List importsList = new ArrayList<>(Arrays.asList(imports));
importsList.add(
"org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");
imports = importsList.toArray(new String[0]);
}
// 如果autoRegister == false,设置spring.cloud.service-registry.auto-registration.enabled=false,关闭服务自动注册功能
else {
Environment env = getEnvironment();
if (ConfigurableEnvironment.class.isInstance(env)) {
ConfigurableEnvironment configEnv = (ConfigurableEnvironment) env;
LinkedHashMap map = new LinkedHashMap<>();
map.put("spring.cloud.service-registry.auto-registration.enabled", false);
MapPropertySource propertySource = new MapPropertySource(
"springCloudDiscoveryClient", map);
configEnv.getPropertySources().addLast(propertySource);
}
}
return imports;
}
@Override
protected boolean isEnabled() {
return getEnvironment().getProperty("spring.cloud.discovery.enabled",
Boolean.class, Boolean.TRUE);
}
@Override
protected boolean hasDefaultFactory() {
return true;
}
}
主要关注selectImports(...)这个方法。首先获取@EnableDiscoveryClient注解的属性autoRegister的值。
如果autoRegister == true,加载org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration配置类的配置。
如果autoRegister == false,设置spring.cloud.service-registry.auto-registration.enabled=false,关闭自动服务注册功能。因为服务自动注册相关的类在注册到Spring容器的时候都有一个条件@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)。
查看AutoServiceRegistrationConfiguration配置类源码:
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(AutoServiceRegistrationProperties.class)
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled",
matchIfMissing = true)
public class AutoServiceRegistrationConfiguration {
}
可以看到,这个类加载了AutoServiceRegistrationProperties这个属性类,这个类里面维护了所有spring.cloud.service-registry.auto-registration开头的属性的配置值。
@ConfigurationProperties("spring.cloud.service-registry.auto-registration")
public class AutoServiceRegistrationProperties {
/** Whether service auto-registration is enabled. Defaults to true. */
private boolean enabled = true;
/** Whether to register the management as a service. Defaults to true. */
private boolean registerManagement = true;
/**
* Whether startup fails if there is no AutoServiceRegistration. Defaults to false.
*/
private boolean failFast = false;
...
}
可以看到,spring.cloud.service-registry.auto-registration.enabled默认值就是true。也就是说,如果没有使用@EnableDiscoveryClient注解,服务也会被自动注册,因为@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)这个条件默认情况下就是满足的。
但是如果不想自动注册服务,可以通过使用@EnableDiscoveryClient(autoRegister = false),或者直接在配置文件中设置spring.cloud.service-registry.auto-registration.enabled=false。
2 Spring Cloud定义的服务发现的统一规范
拥有服务注册的中间件有很多,比如Euerka、Consul、ZooKeeper、Nacos等,想要接入Spring Cloud就必须遵守一套统一的规范,Spring Cloud将这套规范定义在了Spring Cloud Common中:
spring-cloud-commons
-- org.springframework.cloud
-- client
-- discovery
-- loadbalancer
-- serviceregistry
discovery包下面定义了服务发现的规范,loadbalancer包下面定义了负载均衡的规范,serviceregistry包下面定义了服务注册的规范。
org.springframework.cloud.client.serviceregistry包下面有三个接口,这是服务注册的核心接口:
AutoServiceRegistration接口
Registration接口
ServiceRegistry接口
2.1 AutoServiceRegistration接口
AutoServiceRegistration用于服务自动注册。自动注册的意思就是,服务启动后自动把服务信息注册到注册中心。
public interface AutoServiceRegistration {
}
这个接口没有定义方法,它的存在就是要规范实现必须要有自动注册。
Spring Cloud中有一个抽象类AbstractAutoServiceRegistration实现了这个接口。
2.2 Registration接口
Registration存储服务信息,用于规范将什么信息注册到注册中心。
public interface Registration extends ServiceInstance {
}
Registration继承ServiceInstance接口,ServiceInstance接口定义了一个服务实例应该具有哪些服务信息,源码如下:
public interface ServiceInstance {
// 返回服务实例唯一ID
default String getInstanceId() {
return null;
}
// 返回服务ID,服务名称
String getServiceId();
// 返回服务实例所在的主机的host值
String getHost();
// 返回服务实例的port
int getPort();
// 返回服务的实例port是否启用了https协议
boolean isSecure();
// 返回服务的URI地址
URI getUri();
// 返回服务实力的元数据
Map getMetadata();
// 返回服务实例的scheme
default String getScheme() {
return null;
}
}
2.3 ServiceRegistry接口
ServiceRegistry是服务注册接口,用来向注册中心注册服务。
public interface ServiceRegistry {
// 注册服务,registration保存了服务的信息
void register(R registration);
// 反注册,也就是从注册中心移除注册的服务信息
void deregister(R registration);
// 关闭ServiceRegistry,这是一个生命周期函数
void close();
/**
* 设置服务的状态,status的值取决于具体的实现。
* 查看org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
*/
void setStatus(R registration, String status);
/**
* 获取服务状态值。
* 查看org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
*/
T getStatus(R registration);
}
3 Nacos服务注册的实现
Nacos服务注册模块按照Spring Cloud的规范,实现了AutoServiceRegistration、Registration、ServiceRegistry三个接口,具体的实现了分别是:
NacosAutoServiceRegistration类
NacosRegistration类
NacosServiceRegistry类
这三个类位于spring-cloud-starter-alibaba-nacos-discovery包中的com.alibaba.cloud.nacos.registry包路径下。
3.1 NacosRegistration
首先看看NacosRegistration,这个类管理了Nacos服务的基本信息,如服务名、服务地址和端口等信息。
它实现了Spring Cloud定义的规范接口Registration,同时也实现了ServiceInstance接口。
不过实际上Nacos服务的基本信息都是由NacosDiscoveryProperties这个类来保存的,NacosRegistration只是对NacosDiscoveryProperties进行了封装而已。
public class NacosRegistration implements Registration, ServiceInstance {
/**
* management port
*/
public static final String MANAGEMENT_PORT = "management.port";
/**
* management context-path.
*/
public static final String MANAGEMENT_CONTEXT_PATH = "management.context-path";
/**
* management address.
*/
public static final String MANAGEMENT_ADDRESS = "management.address";
/**
* management endpoints web base path.
*/
public static final String MANAGEMENT_ENDPOINT_BASE_PATH = "management.endpoints.web.base-path";
// 配置文件中spring.cloud.nacos.discovery开头的配置的映射类
private NacosDiscoveryProperties nacosDiscoveryProperties;
// 应用上下文
private ApplicationContext context;
// 构造函数注入
public NacosRegistration(NacosDiscoveryProperties nacosDiscoveryProperties,
ApplicationContext context) {
this.nacosDiscoveryProperties = nacosDiscoveryProperties;
this.context = context;
}
// 当构造函数执行之后执行的初始化
@PostConstruct
public void init() {
Map metadata = nacosDiscoveryProperties.getMetadata();
Environment env = context.getEnvironment();
String endpointBasePath = env.getProperty(MANAGEMENT_ENDPOINT_BASE_PATH);
if (!StringUtils.isEmpty(endpointBasePath)) {
metadata.put(MANAGEMENT_ENDPOINT_BASE_PATH, endpointBasePath);
}
Integer managementPort = ManagementServerPortUtils.getPort(context);
if (null != managementPort) {
metadata.put(MANAGEMENT_PORT, managementPort.toString());
String contextPath = env
.getProperty("management.server.servlet.context-path");
String address = env.getProperty("management.server.address");
if (!StringUtils.isEmpty(contextPath)) {
metadata.put(MANAGEMENT_CONTEXT_PATH, contextPath);
}
if (!StringUtils.isEmpty(address)) {
metadata.put(MANAGEMENT_ADDRESS, address);
}
}
if (null != nacosDiscoveryProperties.getHeartBeatInterval()) {
metadata.put(PreservedMetadataKeys.HEART_BEAT_INTERVAL,
nacosDiscoveryProperties.getHeartBeatInterval().toString());
}
if (null != nacosDiscoveryProperties.getHeartBeatTimeout()) {
metadata.put(PreservedMetadataKeys.HEART_BEAT_TIMEOUT,
nacosDiscoveryProperties.getHeartBeatTimeout().toString());
}
if (null != nacosDiscoveryProperties.getIpDeleteTimeout()) {
metadata.put(PreservedMetadataKeys.IP_DELETE_TIMEOUT,
nacosDiscoveryProperties.getIpDeleteTimeout().toString());
}
}
// getter、setter
...
}
3.2 NacosServiceRegistry
NacosServiceRegistry实现了Spring Cloud定义的ServiceRegistry接口,负责将服务实例注册到Nacos服务注册中心上面。
public class NacosServiceRegistry implements ServiceRegistry {
private static final Logger log = LoggerFactory.getLogger(NacosServiceRegistry.class);
private final NacosDiscoveryProperties nacosDiscoveryProperties;
private final NamingService namingService;
public NacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {
this.nacosDiscoveryProperties = nacosDiscoveryProperties;
this.namingService = nacosDiscoveryProperties.namingServiceInstance();
}
@Override
public void register(Registration registration) {
// 如果获取不到服务名,则打印日志并返回
if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No service to register for nacos client...");
return;
}
// 服务名
String serviceId = registration.getServiceId();
// 分组
String group = nacosDiscoveryProperties.getGroup();
// 创建服务实例对象
Instance instance = getNacosInstanceFromRegistration(registration);
try {
// 通过namingService将服务实例注册到注册中心
namingService.registerInstance(serviceId, group, instance);
log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
instance.getIp(), instance.getPort());
}
catch (Exception e) {
log.error("nacos registry, {} register failed...{},", serviceId,
registration.toString(), e);
// rethrow a RuntimeException if the registration is failed.
// issue : https://github.com/alibaba/spring-cloud-alibaba/issues/1132
rethrowRuntimeException(e);
}
}
@Override
public void deregister(Registration registration) {
log.info("De-registering from Nacos Server now...");
if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No dom to de-register for nacos client...");
return;
}
// 获取namingService
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
// 服务名
String serviceId = registration.getServiceId();
// 分组
String group = nacosDiscoveryProperties.getGroup();
try {
// 通过namingService反注册当前服务实例
namingService.deregisterInstance(serviceId, group, registration.getHost(),
registration.getPort(), nacosDiscoveryProperties.getClusterName());
}
catch (Exception e) {
log.error("ERR_NACOS_DEREGISTER, de-register failed...{},",
registration.toString(), e);
}
log.info("De-registration finished.");
}
......
private Instance getNacosInstanceFromRegistration(Registration registration) {
Instance instance = new Instance();
instance.setIp(registration.getHost());
instance.setPort(registration.getPort());
instance.setWeight(nacosDiscoveryProperties.getWeight());
instance.setClusterName(nacosDiscoveryProperties.getClusterName());
instance.setMetadata(registration.getMetadata());
return instance;
}
}
主要是关注register(...)和deregister(...)方法,分别负责服务的注册和反注册。
最终都是通过Nacos中定义的NamingService来完成这两个功能的,NamingService在启动的时候封装了Nacos注册中心的信息,并且封装了服务注册和反注册相关的http请求。
3.3 NacosAutoServiceRegistration
NacosAutoServiceRegistration这个类实现了服务自动注册到Nacos注册中心的功能。这个类继承了AbstractAutoServiceRegistration类,大部分操作都是在抽象类中完成的。
查看抽象类源码:
public abstract class AbstractAutoServiceRegistration
implements AutoServiceRegistration, ApplicationContextAware,
ApplicationListener {
...
}
可以看到,实现了ApplicationListener,说明会被事件回调。当容器启动之后,应用上下文被刷新并且WebServer准备就绪之后,会触发WebServerInitializedEvent事件,那么抽象类中的onApplicationEvent(WebServerInitializedEvent event)方法就会被调用。查看源码:
@Override
@SuppressWarnings("deprecation")
public void onApplicationEvent(WebServerInitializedEvent event) {
// 调用bind方法
bind(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());
// 调用start方法
this.start();
}
实际调用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()) {
this.context.publishEvent(
new InstancePreRegisteredEvent(this, getRegistration()));
// 调用register()方法
register();
if (shouldRegisterManagement()) {
registerManagement();
}
this.context.publishEvent(
new InstanceRegisteredEvent<>(this, getConfiguration()));
this.running.compareAndSet(false, true);
}
}
实际又调用的是register()方法:
protected void register() {
this.serviceRegistry.register(getRegistration());
}
实际调用的是serviceRegistry的register方法,而serviceRegistry是在构造NacosAutoServiceRegistration的时候传入的NacosServiceRegistry。所以实际上是调用的NacosServiceRegistry的register方法。
查看NacosServiceRegistry源码:
@Override
public void register(Registration registration) {
if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No service to register for nacos client...");
return;
}
String serviceId = registration.getServiceId();
String group = nacosDiscoveryProperties.getGroup();
Instance instance = getNacosInstanceFromRegistration(registration);
try {
// 最后由namingService实现服务注册
namingService.registerInstance(serviceId, group, instance);
log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
instance.getIp(), instance.getPort());
}
catch (Exception e) {
log.error("nacos registry, {} register failed...{},", serviceId,
registration.toString(), e);
// rethrow a RuntimeException if the registration is failed.
// issue : https://github.com/alibaba/spring-cloud-alibaba/issues/1132
rethrowRuntimeException(e);
}
}
最后由namingService实现了服务注册。
总结:服务的自动注册其实是利用了Spring的WebServerInitializedEvent事件,最终由namingService完成服务注册工作。
3.4 其他辅助类
上面三个类是Nacos按照规范实现了Spring Cloud定义的统一接口。但是只有这三个类是没法完成服务“自动”注册的,还需要一些辅助类来协助完成这项任务。
3.4.1 NacosDiscoveryProperties
前面说到NacosRegistration只是对NacosDiscoveryProperties进行了封装而已,服务的基本信息真正的是保存在NacosDiscoveryProperties里面的。查看源码:
@ConfigurationProperties("spring.cloud.nacos.discovery")
public class NacosDiscoveryProperties {
private static final Logger log = LoggerFactory
.getLogger(NacosDiscoveryProperties.class);
/**
* Prefix of {@link NacosDiscoveryProperties}.
*/
public static final String PREFIX = "spring.cloud.nacos.discovery";
private static final Pattern PATTERN = Pattern.compile("-(\\w)");
/**
* nacos discovery server address.
*/
private String serverAddr;
// 各种配置字段
......
@Autowired
private InetUtils inetUtils;
@Autowired
private Environment environment;
private static NamingService namingService;
private static NamingMaintainService namingMaintainService;
@PostConstruct
public void init() throws SocketException {
metadata.put(PreservedMetadataKeys.REGISTER_SOURCE, "SPRING_CLOUD");
if (secure) {
metadata.put("secure", "true");
}
serverAddr = Objects.toString(serverAddr, "");
if (serverAddr.endsWith("/")) {
serverAddr = serverAddr.substring(0, serverAddr.length() - 1);
}
endpoint = Objects.toString(endpoint, "");
namespace = Objects.toString(namespace, "");
logName = Objects.toString(logName, "");
if (StringUtils.isEmpty(ip)) {
// traversing network interfaces if didn't specify a interface
if (StringUtils.isEmpty(networkInterface)) {
ip = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
}
else {
NetworkInterface netInterface = NetworkInterface
.getByName(networkInterface);
if (null == netInterface) {
throw new IllegalArgumentException(
"no such interface " + networkInterface);
}
Enumeration inetAddress = netInterface.getInetAddresses();
while (inetAddress.hasMoreElements()) {
InetAddress currentAddress = inetAddress.nextElement();
if (currentAddress instanceof Inet4Address
&& !currentAddress.isLoopbackAddress()) {
ip = currentAddress.getHostAddress();
break;
}
}
if (StringUtils.isEmpty(ip)) {
throw new RuntimeException("cannot find available ip from"
+ " network interface " + networkInterface);
}
}
}
this.overrideFromEnv(environment);
}
// getter、setter
......
public void overrideFromEnv(Environment env) {
if (StringUtils.isEmpty(this.getServerAddr())) {
String serverAddr = env
.resolvePlaceholders("${spring.cloud.nacos.discovery.server-addr:}");
if (StringUtils.isEmpty(serverAddr)) {
serverAddr = env.resolvePlaceholders(
"${spring.cloud.nacos.server-addr:localhost:8848}");
}
this.setServerAddr(serverAddr);
}
if (StringUtils.isEmpty(this.getNamespace())) {
this.setNamespace(env
.resolvePlaceholders("${spring.cloud.nacos.discovery.namespace:}"));
}
if (StringUtils.isEmpty(this.getAccessKey())) {
this.setAccessKey(env
.resolvePlaceholders("${spring.cloud.nacos.discovery.access-key:}"));
}
if (StringUtils.isEmpty(this.getSecretKey())) {
this.setSecretKey(env
.resolvePlaceholders("${spring.cloud.nacos.discovery.secret-key:}"));
}
if (StringUtils.isEmpty(this.getLogName())) {
this.setLogName(
env.resolvePlaceholders("${spring.cloud.nacos.discovery.log-name:}"));
}
if (StringUtils.isEmpty(this.getClusterName())) {
this.setClusterName(env.resolvePlaceholders(
"${spring.cloud.nacos.discovery.cluster-name:}"));
}
if (StringUtils.isEmpty(this.getEndpoint())) {
this.setEndpoint(
env.resolvePlaceholders("${spring.cloud.nacos.discovery.endpoint:}"));
}
if (StringUtils.isEmpty(this.getGroup())) {
this.setGroup(
env.resolvePlaceholders("${spring.cloud.nacos.discovery.group:}"));
}
if (StringUtils.isEmpty(this.getUsername())) {
this.setUsername(env.resolvePlaceholders("${spring.cloud.nacos.username:}"));
}
if (StringUtils.isEmpty(this.getPassword())) {
this.setPassword(env.resolvePlaceholders("${spring.cloud.nacos.password:}"));
}
}
public NamingService namingServiceInstance() {
if (null != namingService) {
return namingService;
}
try {
namingService = NacosFactory.createNamingService(getNacosProperties());
}
catch (Exception e) {
log.error("create naming service error!properties={},e=,", this, e);
return null;
}
return namingService;
}
@Deprecated
public NamingMaintainService namingMaintainServiceInstance() {
if (null != namingMaintainService) {
return namingMaintainService;
}
try {
namingMaintainService = NamingMaintainFactory
.createMaintainService(getNacosProperties());
}
catch (Exception e) {
log.error("create naming service error!properties={},e=,", this, e);
return null;
}
return namingMaintainService;
}
private Properties getNacosProperties() {
Properties properties = new Properties();
properties.put(SERVER_ADDR, serverAddr);
properties.put(USERNAME, Objects.toString(username, ""));
properties.put(PASSWORD, Objects.toString(password, ""));
properties.put(NAMESPACE, namespace);
properties.put(UtilAndComs.NACOS_NAMING_LOG_NAME, logName);
if (endpoint.contains(":")) {
int index = endpoint.indexOf(":");
properties.put(ENDPOINT, endpoint.substring(0, index));
properties.put(ENDPOINT_PORT, endpoint.substring(index + 1));
}
else {
properties.put(ENDPOINT, endpoint);
}
properties.put(ACCESS_KEY, accessKey);
properties.put(SECRET_KEY, secretKey);
properties.put(CLUSTER_NAME, clusterName);
properties.put(NAMING_LOAD_CACHE_AT_START, namingLoadCacheAtStart);
enrichNacosDiscoveryProperties(properties);
return properties;
}
private void enrichNacosDiscoveryProperties(Properties nacosDiscoveryProperties) {
Map properties = PropertySourcesUtils
.getSubProperties((ConfigurableEnvironment) environment, PREFIX);
properties.forEach((k, v) -> nacosDiscoveryProperties.putIfAbsent(resolveKey(k),
String.valueOf(v)));
}
private String resolveKey(String key) {
Matcher matcher = PATTERN.matcher(key);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
}
matcher.appendTail(sb);
return sb.toString();
}
}
可以看到,这个类实际上是配置文件中以spring.cloud.nacos.discovery开头的配置的映射类。
除此之外,还有一个init(...)方法,被@PostConstruct注释。当构造函数被调用之后,执行init初始化。初始化主要工作是对一些配置项的值进行数据转换处理等等。
3.4.2 NacosServiceRegistryAutoConfiguration
显然,这个NacosServiceRegistryAutoConfiguration类是为了使用Spring Boot自动装配功能的一个自动配置类。源码如下:
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnNacosDiscoveryEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled",
matchIfMissing = true)
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
AutoServiceRegistrationAutoConfiguration.class,
NacosDiscoveryAutoConfiguration.class })
public class NacosServiceRegistryAutoConfiguration {
@Bean
public NacosServiceRegistry nacosServiceRegistry(
NacosDiscoveryProperties nacosDiscoveryProperties) {
return new NacosServiceRegistry(nacosDiscoveryProperties);
}
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
public NacosRegistration nacosRegistration(
NacosDiscoveryProperties nacosDiscoveryProperties,
ApplicationContext context) {
return new NacosRegistration(nacosDiscoveryProperties, context);
}
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
public NacosAutoServiceRegistration nacosAutoServiceRegistration(
NacosServiceRegistry registry,
AutoServiceRegistrationProperties autoServiceRegistrationProperties,
NacosRegistration registration) {
return new NacosAutoServiceRegistration(registry,
autoServiceRegistrationProperties, registration);
}
}
这个自动装配类首先使用了@EnableConfigurationProperties注解,这个注解能够使使用了@ConfigurationProperties注解但是没有使用@Component等注解实例化为bean的类生效。因为NacosDiscoveryProperties类使用了@ConfigurationProperties("spring.cloud.nacos.discovery")注解,但是没有@Component注解,Spring容器不会实例化这个类,所以需要通过@EnableConfigurationProperties注解让NacosDiscoveryProperties被实例化并注入到容器中。这样就能获取到配置文件中配置的Nacos相关的配置值。
然后使用了@ConditionalOnNacosDiscoveryEnabled注解。这个注解的意思是spring.cloud.nacos.discovery.enabled=true的时候(默认是true),NacosServiceRegistryAutoConfiguration这个配置类才会生效。
然后使用了@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)注解。这个注解的意思是当spring.cloud.service-registry.auto-registration.enabled=true的时候,NacosServiceRegistryAutoConfiguration这个配置类才会生效。默认情况下就是true。
然后使用了如下注解:
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
AutoServiceRegistrationAutoConfiguration.class,
NacosDiscoveryAutoConfiguration.class })
这个注解的意思是要在AutoServiceRegistrationConfiguration、AutoServiceRegistrationAutoConfiguration、NacosDiscoveryAutoConfiguration配置类生效后再使NacosServiceRegistryAutoConfiguration这个配置类生效。
查看NacosServiceRegistryAutoConfiguration配置类的内容,可以看出,这个配置类实例化了NacosServiceRegistry、NacosRegistration、NacosAutoServiceRegistration这三个bean。其中,NacosServiceRegistry负责服务注册和反注册,NacosRegistration负责维护服务实例的基本信息,NacosAutoServiceRegistration主要是实现服务的自动注册。
3.4.3 NamingService的实例化
从上面的源码分析可以看出,服务注册和反注册工作,最终都是交由NamingService来处理的。
NamingService是nacos-api包里面定义的接口,其实现类NacosNamingService也是nacos-api包中定义的类。
NamingService是对Nacos注册中心的封装,除了有Nacos注册中心的基本信息之外,还对服务注册相关的http请求进行了封装。
那么,NamingService的实例bean又是何时被创建的呢?
查看NacosServiceRegistry源码,其构造函数如下:
public NacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {
this.nacosDiscoveryProperties = nacosDiscoveryProperties;
// 实例化namingService
this.namingService = nacosDiscoveryProperties.namingServiceInstance();
}
也就是在实例化NacosServiceRegistry的时候,通过调用nacosDiscoveryProperties.namingServiceInstance()方法实例化了namingService。
继续跟踪源码:
public NamingService namingServiceInstance() {
if (null != namingService) {
return namingService;
}
try {
// 创建namingService
namingService = NacosFactory.createNamingService(getNacosProperties());
}
catch (Exception e) {
log.error("create naming service error!properties={},e=,", this, e);
return null;
}
return namingService;
}
如果namingService为null,则通过NacosFactory.createNamingService(getNacosProperties())创建它。其中,getNacosProperties()方法获取Nacos注册中心的基本配置信息。
继续跟踪源码:
public static NamingService createNamingService(Properties properties) throws NacosException {
return NamingFactory.createNamingService(properties);
}
调用NamingFactory.createNamingService(properties)创建namingService。
继续跟踪源码:
public static NamingService createNamingService(Properties properties) throws NacosException {
try {
Class> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");
Constructor constructor = driverImplClass.getConstructor(Properties.class);
// 通过反射机制创建NamingService的对象
NamingService vendorImpl = (NamingService)constructor.newInstance(properties);
return vendorImpl;
} catch (Throwable e) {
throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
}
}
可以看出,最终是通过反射机制,创建了NamingService的实例对象,也就是NacosNamingService的对象。
继续跟踪源码,看看创建NacosNamingService对象的时候都干了什么?
public NacosNamingService(Properties properties) {
init(properties);
}
调用的是init方法:
private void init(Properties properties) {
// 初始化命名空间
namespace = InitUtils.initNamespaceForNaming(properties);
// 初始化Nacos注册中心服务地址
initServerAddr(properties);
// 初始化应用上下文
InitUtils.initWebRootContext();
// 初始化缓存目录
initCacheDir();
// 初始化日志文件名字
initLogName(properties);
// 初始化事件分发器
eventDispatcher = new EventDispatcher();
// 初始化服务代理
serverProxy = new NamingProxy(namespace, endpoint, serverList, properties);
// 初始化客户端心跳机制,会定时向服务端发送心跳信息
beatReactor = new BeatReactor(serverProxy, initClientBeatThreadCount(properties));
// 初始化服务信息更新机制,会定时拉去客户端关心的服务信息
hostReactor = new HostReactor(eventDispatcher, serverProxy, cacheDir, isLoadCacheAtStart(properties),
initPollingThreadCount(properties));
}
3.4.5 NamingService#registerInstance方法
com.alibaba.nacos.client.naming.NacosNamingService#registerInstance(java.lang.String, java.lang.String, com.alibaba.nacos.api.naming.pojo.Instance)
从上面的源码分析可以看出,服务注册最终调用的是NamingService#registerInstance的方法,那具体的实现就是NacosNamingService#registerInstance。
跟踪源码:
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
if (instance.isEphemeral()) {
BeatInfo beatInfo = new BeatInfo();
beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));
beatInfo.setIp(instance.getIp());
beatInfo.setPort(instance.getPort());
beatInfo.setCluster(instance.getClusterName());
beatInfo.setWeight(instance.getWeight());
beatInfo.setMetadata(instance.getMetadata());
beatInfo.setScheduled(false);
beatInfo.setPeriod(instance.getInstanceHeartBeatInterval());
beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
}
serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
}
最终通过serverProxy.registerService(...)注册服务信息:
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}",
namespaceId, serviceName, instance);
final Map params = new HashMap(9);
params.put(CommonParams.NAMESPACE_ID, namespaceId);
params.put(CommonParams.SERVICE_NAME, serviceName);
params.put(CommonParams.GROUP_NAME, groupName);
params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
params.put("ip", instance.getIp());
params.put("port", String.valueOf(instance.getPort()));
params.put("weight", String.valueOf(instance.getWeight()));
params.put("enable", String.valueOf(instance.isEnabled()));
params.put("healthy", String.valueOf(instance.isHealthy()));
params.put("ephemeral", String.valueOf(instance.isEphemeral()));
params.put("metadata", JSON.toJSONString(instance.getMetadata()));
reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, HttpMethod.POST);
}
其中:
public static String WEB_CONTEXT = "/nacos";
public static String NACOS_URL_BASE = WEB_CONTEXT + "/v1/ns";
public static String NACOS_URL_INSTANCE = NACOS_URL_BASE + "/instance";
所以,会向/nacos/v1/ns/instance发送一个POST请求,将服务信息通过这个请求注册到Nacos上面。