当前项目pom文件如下:
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.2.RELEASEversion>
<relativePath />
parent>
<groupId>com.cloudgroupId>
<artifactId>ms-classartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>ms-classname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-consul-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-consul-configartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zipkinartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>io.github.openfeigngroupId>
<artifactId>feign-httpclientartifactId>
dependency>
<dependency>
<groupId>io.github.resilience4jgroupId>
<artifactId>resilience4j-spring-cloud2artifactId>
<version>1.2.0version>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-stream-rabbitartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwt-apiartifactId>
<version>0.10.7version>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwt-implartifactId>
<version>0.10.7version>
<scope>runtimescope>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwt-jacksonartifactId>
<version>0.10.7version>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Hoxton.SR1version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<repositories>
<repository>
<id>spring-snapshotsid>
<name>Spring Snapshotsname>
<url>https://repo.spring.io/libs-snapshoturl>
<snapshots>
<enabled>trueenabled>
snapshots>
repository>
<repository>
<id>spring-milestonesid>
<name>Spring Milestonesname>
<url>https://repo.spring.io/libs-milestoneurl>
<snapshots>
<enabled>falseenabled>
snapshots>
repository>
<repository>
<id>spring-releasesid>
<name>Spring Releasesname>
<url>https://repo.spring.io/libs-releaseurl>
<snapshots>
<enabled>falseenabled>
snapshots>
repository>
repositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>1.8source>
<target>1.8target>
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,有如下三种方法可以解决问题:
spring:
application:
name: ms-class
cloud:
consul:
host: centos-001.com
port: 8500
spring:
cloud:
consul:
config:
enabled: false
为了更好理解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.