consul配置无法起效问题

当前项目pom文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.2.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.cloud</groupId>
	<artifactId>ms-class</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>ms-class</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<!-- 如果xxx是springboot出品 spring-boot-starter-xxx -->
		<!-- 如果xxx是第三方出品 xxx-spring-boot-starter -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!-- spring-cloud-starter-[springcloud子项目名称]-[子项目的模块名称] -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-consul-discovery</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-consul-config</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-zipkin</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-openfeign</artifactId>
		</dependency>

		<dependency>
			<groupId>io.github.openfeign</groupId>
			<artifactId>feign-httpclient</artifactId>
		</dependency>

		<dependency>
			<groupId>io.github.resilience4j</groupId>
			<artifactId>resilience4j-spring-cloud2</artifactId>
			<version>1.2.0</version>
		</dependency>


		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
		</dependency>

		<!-- <dependency> -->
		<!-- <groupId>io.github.openfeign</groupId> -->
		<!-- <artifactId>feign-okhttp</artifactId> -->
		<!-- </dependency> -->

		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-api</artifactId>
			<version>0.10.7</version>
		</dependency>
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-impl</artifactId>
			<version>0.10.7</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-jackson</artifactId>
			<version>0.10.7</version>
			<scope>runtime</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>

	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>Hoxton.SR1</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<repositories>
		<repository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/libs-snapshot</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/libs-milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-releases</id>
			<name>Spring Releases</name>
			<url>https://repo.spring.io/libs-release</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>


	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

配置文件如下:

server:
  port: 8010
  #tomcat优化参数
  tomcat:
    # tomcat最大连接数
    max-connections: 1000
    # 最大线程数
    max-threads: 300
    # 最小空闲线程数
    min-spare-threads: 20
    # 当tomcat启动的线程数达到最大时,接受排队的请求个数
    accept-count: 100
spring:
  rabbitmq:
    host: centos-001.com
    port: 5672
    username: admin
    password: admin
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/ms_class?serverTimezone=UTC
    hikari:
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: none  # 让hibernate不去操作表结构
    # 打印执行的sql
    show-sql: true 
  application:
    name: ms-class
  cloud:
    stream:
      bindings:
        output:
          destination: lesson-buy
    consul:
      # 配置的consul服务器地址
      host: centos-001.com
      port: 8500
      discovery:
        health-check-path: /actuator/health
        prefer-ip-address: true
        tags: JIFANG=NJ
  zipkin:
    # 指定zipkin server的地址
    base-url: http://centos-001.com:9411
    sender:
      # 指定用什么方式上报数据给zipkin server
      # web表示用http 还可以利用activemq rabbit kafka
      # 有一个小坑
      type: web
  sleuth:
    sampler:
      # 配置数据的采样率 默认值0.1(10%)
      probability: 1  
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always
#ms-user:
#  ribbon:
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
ribbon:
  eager-load:
    enabled: true
    clients: ms-user
ms-user:
  ribbon:
#    ServerListRefreshInterval: 5000
logging:
  level:
    com.cloud.msclass.feign.MsUserFeignClient: debug
    
feign:
  client:
    config:
      default:
        logger-level: full
        request-interceptors:
        - com.cloud.msclass.interceptor.MyHeaderRequestInterceptor
  # feign优化参数      
  httpclient:
    # 让feign使用apache httpclient 而不是默认的Client.Default
    enabled: true
    # feign的最大连接数
    max-connections: 200
    # feign单个路径的最大连接数
    max-connections-per-route: 50
    
jwt:
  # 密钥
    secret: aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrsssttt
    # 有效期,单位秒,默认2周
    expire-time-in-second: 1209600

resilience4j:
  ratelimiter:
    configs:
      default:
        # 在刷新周期内,请求的最大频次
        limit-for-period: 1
        # 刷新周期
        limit-refresh-period: 1s
        # 线程等待许可的时间 线程不等待 直接抛异常
        timeout-duration: 0
    rate-limiter-aspect-order: 3
  bulkhead:
    instances:
      buyById:
        # 最大并发请求数
        max-concurrent-calls: 3
        # 仓壁饱和时的最大等待时间 默认0
#        max-wait-duration: 10ms
        # 事件缓冲区大小
#        event-consumer-buffer-size: 1

  retry:
    retry-aspect-order: 1
  circuitbreaker:
    circuit-breaker-aspect-order: 2

启动微服务项目的时候:会报如下的错误

Caused by: java.net.ConnectException: Connection refused: connect
	at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method) ~[na:1.8.0_121]
	at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85) ~[na:1.8.0_121]
	at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) ~[na:1.8.0_121]
	at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) ~[na:1.8.0_121]
	at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) ~[na:1.8.0_121]
	at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172) ~[na:1.8.0_121]
	at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) ~[na:1.8.0_121]
	at java.net.Socket.connect(Socket.java:589) ~[na:1.8.0_121]
	at org.apache.http.conn.socket.PlainConnectionSocketFactory.connectSocket(PlainConnectionSocketFactory.java:75) ~[httpclient-4.5.10.jar:4.5.10]
	at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142) ~[httpclient-4.5.10.jar:4.5.10]
	... 43 common frames omitted

因此在以下打上断点

DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142)

并bebug,利用异常栈信息,最后可以确定一切的源头在ConsulAutoConfiguration这个类中:

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnConsulEnabled
public class ConsulAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean
	// 此处会注入一个ConsulProperties类型的Bean
	public ConsulProperties consulProperties() {
		return new ConsulProperties();
	}

	@Bean
	@ConditionalOnMissingBean
	// 此处会注入一个ConsulClient类型的Bean 传入上面实例化的ConsulProperties实例
	// 但是很遗憾 直接通过默认构造的ConsulProperties中host是默认的localhost
	public ConsulClient consulClient(ConsulProperties consulProperties) {
		final int agentPort = consulProperties.getPort();
		final String agentHost = !StringUtils.isEmpty(consulProperties.getScheme())
				? consulProperties.getScheme() + "://" + consulProperties.getHost()
				: consulProperties.getHost();

		if (consulProperties.getTls() != null) {
			ConsulProperties.TLSConfig tls = consulProperties.getTls();
			TLSConfig tlsConfig = new TLSConfig(tls.getKeyStoreInstanceType(),
					tls.getCertificatePath(), tls.getCertificatePassword(),
					tls.getKeyStorePath(), tls.getKeyStorePassword());
			return new ConsulClient(agentHost, agentPort, tlsConfig);
		}
		return new ConsulClient(agentHost, agentPort);
	}
	...
}

从上面可以发现,默认情况下构造的ConsulProperties 采用的都是默认配置(如下),也就是此时根本不读取系统的配置。

@ConfigurationProperties("spring.cloud.consul")
@Validated
public class ConsulProperties {

	/** Consul agent hostname. Defaults to 'localhost'. */
	@NotNull
	private String host = "localhost";

	/**
	 * Consul agent scheme (HTTP/HTTPS). If there is no scheme in address - client will
	 * use HTTP.
	 */
	private String scheme;

	/** Consul agent port. Defaults to '8500'. */
	@NotNull
	private int port = 8500;

	/** Is spring cloud consul enabled. */
	private boolean enabled = true;

	/** configuration for TLS. */
	private TLSConfig tls;
}

跟踪栈信息:

// 当前类:org.springframework.boot.SpringApplication
// 当前方法:public ConfigurableApplicationContext run(String... args)
public ConfigurableApplicationContext run(String... args) {
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	ConfigurableApplicationContext context = null;
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
	configureHeadlessProperty();
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting();
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		// 此时容器还未进行初始化 
		// listeners中存在[org.springframework.boot.context.event.EventPublishingRunListener@6a43b3fb]这样一个实例
		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
		configureIgnoreBeanInfo(environment);
		Banner printedBanner = printBanner(environment);
		context = createApplicationContext();
		exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
				new Class[] { ConfigurableApplicationContext.class }, context);
		prepareContext(context, environment, listeners, applicationArguments, printedBanner);
		refreshContext(context);
		afterRefresh(context, applicationArguments);
		stopWatch.stop();
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
		}
		listeners.started(context);
		callRunners(context, applicationArguments);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, listeners);
		throw new IllegalStateException(ex);
	}

	try {
		listeners.running(context);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, null);
		throw new IllegalStateException(ex);
	}
	return context;
}
// 当前类:org.springframework.boot.SpringApplication
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments) {
	// Create and configure the environment
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	// Template method delegating to configurePropertySources and configureProfiles in that order. 
	// Override this method for complete control over Environment customization, or one of the above for fine-grained control over property sources or profiles, respectively.
	// 配置环境属性 设置默认的ConversionService 配置资源文件 程序版本等信息
	configureEnvironment(environment, applicationArguments.getSourceArgs());
	// 将系统环境属性放到ConfigurationPropertySources类中后面可以直接从这个类中获取属性
	ConfigurationPropertySources.attach(environment);
	// 本次重点:通过监听器准备环境
	// org.springframework.boot.SpringApplicationRunListeners@2131e0c1
	listeners.environmentPrepared(environment);
	bindToSpringApplication(environment);
	if (!this.isCustomEnvironment) {
		environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
				deduceEnvironmentClass());
	}
	ConfigurationPropertySources.attach(environment);
	return environment;
}

发布一个程序环境准备完毕的事件

// 当前类 org.springframework.boot.context.event.EventPublishingRunListener
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
	// this.initialMulticaster = new SimpleApplicationEventMulticaster();
	// 这个类实现了ApplicationEventMulticaster接口,是一个程序事件通知器(观察者模式)
	// Multicast the given application event to appropriate listeners. 
	// 发布了一个ApplicationEnvironmentPreparedEvent事件
	this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}

进行事件的监听

// 当前类 org.springframework.context.event.SimpleApplicationEventMulticaster
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
	ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
	Executor executor = getTaskExecutor();
	for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
		if (executor != null) {
			executor.execute(() -> invokeListener(listener, event));
		}
		// 此时executor == null
		else {
		// org.springframework.cloud.bootstrap.BootstrapApplicationListener@957ad27
		// ApplicationEnvironmentPreparedEvent[source=org.springframework.boot.SpringApplication@1e0a6316]
		// 通过BootstrapApplicationListener进行环境准备完毕事件的通知
			invokeListener(listener, event);
		}
	}
}
// 当前类 org.springframework.cloud.bootstrap.BootstrapApplicationListener
// 类说明 A listener that prepares a SpringApplication (e.g. populating its Environment) by delegating to ApplicationContextInitializer beans in a separate bootstrap context. The bootstrap context is a SpringApplication created from sources defined in spring.factories as BootstrapConfiguration, and initialized with external config taken from "bootstrap.properties" (or yml), instead of the normal "application.properties".
// 一个用于准备SpringApplication的监听器,在一个单独的引导上下文(bootstrap context)通过一个ApplicationContextInitializer类型的Bean来实现.这个引导上下文是在spring.factories中定义的BootstrapConfiguration所创建建,用于从外部bootstrap.properties(或yml)中读取配置,与常规的application.properties不同
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
	ConfigurableEnvironment environment = event.getEnvironment();
	if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
			true)) {
		return;
	}
	// don't listen to events in a bootstrap context
	if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
		return;
	}
	ConfigurableApplicationContext context = null;
	String configName = environment
			.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
	for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
			.getInitializers()) {
		if (initializer instanceof ParentContextApplicationContextInitializer) {
			context = findBootstrapContext(
					(ParentContextApplicationContextInitializer) initializer,
					configName);
		}
	}
	if (context == null) {
	    // 由于引导上下文为空 进入此处创建一个引导下上文
	    // 这个引导上下文其实也是一个Spring容器,类型为ConfigurableApplicationContext
	    // 从bootstrap引导文件中读取属性并进行刷新 创建引导上下文需要的Bean
	    // 此处不继续展开 具体可以查看如下两个类
	    // org.springframework.cloud.bootstrap.BootstrapImportSelectorConfiguration
	    // org.springframework.cloud.bootstrap.BootstrapImportSelector 
		context = bootstrapServiceContext(environment, event.getSpringApplication(),
				configName);
		event.getSpringApplication().addListeners(new CloseContextOnFailureApplicationListener(context));
	}
	apply(context, event.getSpringApplication(), environment);
}

初始化引导上下文并刷新

// 当前类 org.springframework.boot.builder.SpringApplicationBuilder
public ConfigurableApplicationContext run(String... args) {
	if (this.running.get()) {
		// If already created we just return the existing context
		return this.context;
	}
	configureAsChildIfNecessary(args);
	if (this.running.compareAndSet(false, true)) {
		synchronized (this.running) {
			// If not already running copy the sources over and then run.
			this.context = build().run(args);
		}
	}
	return this.context;
}

在这个引导上下文中包括如下这些bean:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor, 
org.springframework.context.annotation.internalAutowiredAnnotationProcessor, 
org.springframework.context.annotation.internalCommonAnnotationProcessor, 
org.springframework.context.annotation.internalPersistenceAnnotationProcessor, 
org.springframework.context.event.internalEventListenerProcessor, 
org.springframework.context.event.internalEventListenerFactory, 
bootstrapImportSelectorConfiguration, 
org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory, 
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration, 
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor, 
org.springframework.boot.context.internalConfigurationPropertiesBinderFactory, 
org.springframework.boot.context.internalConfigurationPropertiesBinder, 
org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator, 
org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata, 
spring.cloud.config-org.springframework.cloud.bootstrap.config.PropertySourceBootstrapProperties, 
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration, 
environmentDecryptApplicationListener, 
encrypt-org.springframework.cloud.bootstrap.encrypt.KeyProperties, 
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration, 
configurationPropertiesBeans, 
configurationPropertiesRebinder, 
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration, 
propertySourcesPlaceholderConfigurer, 
org.springframework.retry.annotation.RetryConfiguration, 
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration$CglibAutoProxyConfiguration, 
org.springframework.aop.config.internalAutoProxyCreator, 
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration, 
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration, 
org.springframework.cloud.consul.ConsulAutoConfiguration$RetryConfiguration, 
consulRetryInterceptor, 
spring.cloud.consul.retry-org.springframework.cloud.consul.RetryProperties, 
org.springframework.cloud.consul.ConsulAutoConfiguration$ConsulHealthConfig, consulEndpoint, 
consulHealthIndicator, 
org.springframework.cloud.consul.ConsulAutoConfiguration, 
consulProperties, 
consulClient, 
org.springframework.cloud.consul.config.ConsulConfigBootstrapConfiguration$ConsulPropertySourceConfiguration, 
consulConfigProperties, 
consulPropertySourceLocator, 
org.springframework.cloud.consul.config.ConsulConfigBootstrapConfiguration

其中关于consul的consulProperties、consulClient、consulConfigProperties等等赫然在列
随后初始化Bean以及依赖

PropertySourceBootstrapConfiguration # postProcessProperties
// injectedElements = [AutowiredFieldElement for private java.util.List org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration.propertySourceLocators, null, null, null, null, null, null, null, null, null]

consulPropertySourceLocator # instantiateUsingFactoryMethod

org.springframework.cloud.consul.config.ConsulConfigBootstrapConfiguration$ConsulPropertySourceConfiguration # postProcessProperties
// InjectionMetadata
// checkedElements = [AutowiredFieldElement for private com.ecwid.consul.v1.ConsulClient org.springframework.cloud.consul.config.ConsulConfigBootstrapConfiguration$ConsulPropertySourceConfiguration.consul]
// injectedElements = [AutowiredFieldElement for private com.ecwid.consul.v1.ConsulClient org.springframework.cloud.consul.config.ConsulConfigBootstrapConfiguration$ConsulPropertySourceConfiguration.consul]

consulClient # instantiateUsingFactoryMethod
consulProperties

// 从上面不难看出 之所以导致consul的一系列初始化 就是因为在PropertySourceBootstrapConfiguration进行了资源文件的处理逻辑
// 当前类 org.springframework.beans.factory.support.DefaultListableBeanFactory
// beanName = org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration
// requiredType = interface org.springframework.cloud.bootstrap.config.PropertySourceLocator
// 查找指定类型的Bean实例 而ConsulPropertySourceLocator就是实现了这个接口的类
protected Map<String, Object> findAutowireCandidates(
			@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {

		String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
				this, requiredType, true, descriptor.isEager());
		Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);
		for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
			Class<?> autowiringType = classObjectEntry.getKey();
			if (autowiringType.isAssignableFrom(requiredType)) {
				Object autowiringValue = classObjectEntry.getValue();
				autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
				if (requiredType.isInstance(autowiringValue)) {
					result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
					break;
				}
			}
		}
		for (String candidate : candidateNames) {
			// 此处就会导致consulPropertySourceLocator的初始化
			if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {
				addCandidateEntry(result, candidate, descriptor, requiredType);
			}
		}
		if (result.isEmpty()) {
			boolean multiple = indicatesMultipleBeans(requiredType);
			// Consider fallback matches if the first pass failed to find anything...
			DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
			for (String candidate : candidateNames) {
				if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) &&
						(!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) {
					addCandidateEntry(result, candidate, descriptor, requiredType);
				}
			}
			if (result.isEmpty() && !multiple) {
				// Consider self references as a final pass...
				// but in the case of a dependency collection, not the very same bean itself.
				for (String candidate : candidateNames) {
					if (isSelfReference(beanName, candidate) &&
							(!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) &&
							isAutowireCandidate(candidate, fallbackDescriptor)) {
						addCandidateEntry(result, candidate, descriptor, requiredType);
					}
				}
			}
		}
		return result;
	}

那么问题又来了,为啥PropertySourceBootstrapConfiguration会初始化呢?
查看spring-cloud-context-2.2.1.RELEASE.jar这个包下面的/META-INF/spring.factories文件:

# AutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.cloud.autoconfigure.LifecycleMvcEndpointAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration,\
org.springframework.cloud.autoconfigure.WritableEnvironmentEndpointAutoConfiguration
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener,\
org.springframework.cloud.bootstrap.LoggingSystemShutdownListener,\
org.springframework.cloud.context.restart.RestartListener
# Bootstrap components  引导组件 
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration

从其中可以看PropertySourceBootstrapConfiguration就在其中
查看类BootstrapImportSelector的逻辑

// 这个类是由注解@BootstrapImportSelectorConfiguration引入的
// 当前类 org.springframework.cloud.bootstrap.BootstrapImportSelector
// 在ConfigurationClassParser解析注解bootstrapImportSelectorConfiguration时引入的
// 
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
	ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
	// Use names and ensure unique to protect against duplicates
	// [org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration, org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration, org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration, org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration, org.springframework.cloud.consul.discovery.configclient.ConsulDiscoveryClientConfigServiceBootstrapConfiguration]
	// 此时会找到PropertySourceBootstrapConfiguration
	List<String> names = new ArrayList<>(SpringFactoriesLoader
			.loadFactoryNames(BootstrapConfiguration.class, classLoader));
	names.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(
			this.environment.getProperty("spring.cloud.bootstrap.sources", ""))));

	List<OrderedAnnotatedElement> elements = new ArrayList<>();
	for (String name : names) {
		try {
			elements.add(
					new OrderedAnnotatedElement(this.metadataReaderFactory, name));
		}
		catch (IOException e) {
			continue;
		}
	}
	AnnotationAwareOrderComparator.sort(elements);

	String[] classNames = elements.stream().map(e -> e.name).toArray(String[]::new);

	return classNames;
}

也就是说这个类是非启动不可了,那么ConsulPropertySourceLocator这个Bean呢?查找这个Bean引用的地方,发现如下类

package org.springframework.cloud.consul.config;

import com.ecwid.consul.v1.ConsulClient;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.consul.ConditionalOnConsulEnabled;
import org.springframework.cloud.consul.ConsulAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
 * @author Spencer Gibb
 * @author Edvin Eriksson
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnConsulEnabled
public class ConsulConfigBootstrapConfiguration {

	@Configuration(proxyBeanMethods = false)
	@EnableConfigurationProperties
	@Import(ConsulAutoConfiguration.class)
	@ConditionalOnProperty(name = "spring.cloud.consul.config.enabled",
			matchIfMissing = true)
	protected static class ConsulPropertySourceConfiguration {

		@Autowired
		private ConsulClient consul;

		@Bean
		@ConditionalOnMissingBean
		public ConsulConfigProperties consulConfigProperties() {
			return new ConsulConfigProperties();
		}

		@Bean
		public ConsulPropertySourceLocator consulPropertySourceLocator(
				ConsulConfigProperties consulConfigProperties) {
			return new ConsulPropertySourceLocator(this.consul, consulConfigProperties);
		}
	}
}

从上面可知,如果spring.cloud.consul.config.enabled参数不设置则默认为true,那么此时修改application.properties(或yaml)文件增加配置spring.cloud.consul.config.enabled=false有效吗?没有效果,因为此时在引导上下文中,只读取bootstrap.propertis(或yaml)配置文件。
添加一个bootstrap.yml文件

spring:
  cloud:
    consul:
      config:
        enabled: false

启动程序

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.netflix.client.config.IClientConfig]: Factory method 'ribbonClientConfig' threw exception; nested exception is java.lang.RuntimeException: Property ribbon is invalid
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:651) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	... 72 common frames omitted
Caused by: java.lang.RuntimeException: Property ribbon is invalid
	at com.netflix.client.config.DefaultClientConfigImpl.loadProperties(DefaultClientConfigImpl.java:589) ~[ribbon-core-2.3.0.jar:2.3.0]
	at org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration.ribbonClientConfig(RibbonClientConfiguration.java:102) ~[spring-cloud-netflix-ribbon-2.2.1.RELEASE.jar:2.2.1.RELEASE]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_121]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_121]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_121]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_121]
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	... 73 common frames omitted

在org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration添加断点

@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {
	DefaultClientConfigImpl config = new DefaultClientConfigImpl();
	// 此时this.name=ms-user 查找用户微服务
	config.loadProperties(this.name);
	config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
	config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
	config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
	return config;
}
// 当前类 com.netflix.client.config.DefaultClientConfigImpl 
 /**
   * Load properties for a given client. It first loads the default values for all properties,
   * and any properties already defined with Archaius ConfigurationManager.
   */
  @Override
public void loadProperties(String restClientName){
      enableDynamicProperties = true;
      setClientName(restClientName);
      loadDefaultValues();
      // getConfigInstance = com.netflix.config.ConcurrentCompositeConfiguration@70cda28d
      // 获取ms-user对应的配置子集合
      Configuration props = ConfigurationManager.getConfigInstance().subset(restClientName);
      for (Iterator<String> keys = props.getKeys(); keys.hasNext(); ){
      	  // key = ribbon 但是不存在ribbon这种类型的key
          String key = keys.next();
          String prop = key;
          try {
              if (prop.startsWith(getNameSpace())){
                  prop = prop.substring(getNameSpace().length() + 1);
              }
              setPropertyInternal(prop, getStringValue(props, key));
          } catch (Exception ex) {
              throw new RuntimeException(String.format("Property %s is invalid", prop));
          }
      }
  }

查看配置文件,发现存在如下的配置

ms-user:
  ribbon:
#    ServerListRefreshInterval: 5000

全部注释掉,再重启服务,查看consul控制台
在这里插入图片描述
通过以上的方式可以解决问题,另外还有第二种方法:
既然需要在引导上下文中启动consul,那么直接bootstrap.xml中如下配置:

spring:
  application:
    name: ms-class
  cloud:
    consul:
      host: 001.com
      port: 8500

启动网关服务,网关服务中关于consul的配置仍然放在application.properties配置文件中,但是启动并不会报错:
在bootstrap中初始化的bean如下:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor, 
org.springframework.context.annotation.internalAutowiredAnnotationProcessor, 
org.springframework.context.annotation.internalCommonAnnotationProcessor, 
org.springframework.context.event.internalEventListenerProcessor, 
org.springframework.context.event.internalEventListenerFactory, 
bootstrapImportSelectorConfiguration, 
org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory, 
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration, 
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor, 
org.springframework.boot.context.internalConfigurationPropertiesBinderFactory, org.springframework.boot.context.internalConfigurationPropertiesBinder, 
org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator, 
org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata, 
spring.cloud.config-org.springframework.cloud.bootstrap.config.PropertySourceBootstrapProperties, 
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration, 
environmentDecryptApplicationListener, 
encrypt-org.springframework.cloud.bootstrap.encrypt.KeyProperties, 
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration, 
configurationPropertiesBeans, 
configurationPropertiesRebinder, org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration, 
propertySourcesPlaceholderConfigurer

查看项目依赖,分析当前classpath目录,不存在org.springframework.cloud.consul.config.ConsulConfigBootstrapConfiguration这个类,这个类是存在于spring-cloud-consul-config这个依赖中的,但是当前的网关微服务不需要这个依赖。那么spring-cloud-consul-config这个依赖是干啥的呢?因为consul除了用于做服务注册与发现组件外,还可以用于做配置管理的,那么再回过头来在课程微服务中去掉spring-cloud-consul-config这个依赖会如何呢?仍然启动正常。
在这里插入图片描述从上面分析可以知道,如果在项目中加入了consul的配置管理依赖包spring-cloud-consul-config,默认情况下会在引导启动上下文中配置consul的信息,此时会查找consul服务器的信息,如果使用的是本地localhost的consul服务器,则不用添加任何配置,就能起作用。但是如何consul服务器的host和port不是默认的host或8500,有如下三种方法可以解决问题:

  1. 需要在bootstrap中添加consul的相关配置
spring:
  application:
    name: ms-class
  cloud:
    consul:
      host: centos-001.com
      port: 8500
  1. 直接在bootstrap配置文件中禁止consul,这样就可以在应用程序上下文中启动consul了
spring:
  cloud:
    consul:
      config:
        enabled: false
  1. 通过删掉spring-cloud-consul-config依赖包的方式,这样当前上下文就不存在ConsulConfigBootstrapConfiguration这个类了。
    最后查看一下官方文档:
    https://cloud.spring.io/spring-cloud-static/spring-cloud-consul/2.2.2.RELEASE/reference/html/#spring-cloud-consul-install
    在这里插入图片描述
    此处官方文档明确指出如果使用Spring Cloud Consul Config,这些配置都需要放到bootstrap.yml文件中,所以为了能使用Consul的配置功能,采用方案1.

为了更好理解Spring Cloud引导上下文,请参考官方文档:
https://cloud.spring.io/spring-cloud-static/Hoxton.SR3/reference/htmlsingle/#spring-cloud-context-application-context-services

A Spring Cloud application operates by creating a “bootstrap” context, which is a parent context for the main application. This context is responsible for loading configuration properties from the external sources and for decrypting properties in the local external configuration files. The two contexts share an Environment, which is the source of external properties for any Spring application.

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lang20150928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值