RefreshScope
@RefreshScope
是 scopeName="refresh"的 @Scope
, 用來實現配置、實例熱加載 。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public @interface RefreshScope {
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
對應的實現類是GenericScope
的子類org.springframework.cloud.context.scope.refresh.RefreshScope
,類中註釋寫明:
- 所有
@RefreshScope
管轄的 Bean 都是延遲加載的,只有在第一次訪問時纔會初始化 -
刷新 Bean 的邏輯 也是同理,下次訪問時會創建一個新的對象。
件的自動裝配類是:org.springframework.cloud.autoconfigure.RefreshAutoConfiguration
@Configuration
@ConditionalOnClass(RefreshScope.class)
@ConditionalOnProperty(name = RefreshAutoConfiguration.REFRESH_SCOPE_ENABLED, matchIfMissing = true)
@AutoConfigureBefore(HibernateJpaAutoConfiguration.class)
public class RefreshAutoConfiguration {
/** Name of the refresh scope name. */
public static final String REFRESH_SCOPE_NAME = "refresh";
/** Name of the prefix for refresh scope. */
public static final String REFRESH_SCOPE_PREFIX = "spring.cloud.refresh";
/** Name of the enabled prefix for refresh scope. */
public static final String REFRESH_SCOPE_ENABLED = REFRESH_SCOPE_PREFIX + ".enabled";
@Bean
@ConditionalOnMissingBean(RefreshScope.class)
public static RefreshScope refreshScope() {
return new RefreshScope();
}
@Bean
@ConditionalOnMissingBean
public ContextRefresher contextRefresher(ConfigurableApplicationContext context,
RefreshScope scope) {
return new ContextRefresher(context, scope);
}
@Bean
public RefreshEventListener refreshEventListener(ContextRefresher contextRefresher) {
return new RefreshEventListener(contextRefresher);
}
... ...
}
由類定義可知,RefreshScope
實現了接口 ApplicationListener<ContextRefreshedEvent>
。
public class RefreshScope extends GenericScope implements ApplicationContextAware,
ApplicationListener<ContextRefreshedEvent>, Ordered {
private ApplicationContext context;
private BeanDefinitionRegistry registry;
private boolean eager = true;
private int order = Ordered.LOWEST_PRECEDENCE - 100;
/** Creates a scope instance and gives it the default name: "refresh". */
public RefreshScope() {
super.setName("refresh");
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
start(event);
}
public void start(ContextRefreshedEvent event) {
if (event.getApplicationContext() == this.context && this.eager
&& this.registry != null) {
eagerlyInitialize();
}
}
private void eagerlyInitialize() {
for (String name : this.context.getBeanDefinitionNames()) {
BeanDefinition definition = this.registry.getBeanDefinition(name);
if (this.getName().equals(definition.getScope())
&& !definition.isLazyInit()) {
Object bean = this.context.getBean(name);
if (bean != null) {
bean.getClass();
}
}
}
}
因爲ContextRefreshedEvent
事件是在容器啓動時末處AbstractApplicationContext.finishRefresh()
發佈的,所以:此時會給初始化創建並緩存由RefreshScope
管轄的Bean實例!
刷新
入口在ContextRefresher.refresh
public synchronized Set<String> refresh() {
Set<String> keys = refreshEnvironment();
⑥this.scope.refreshAll();
return keys;
}
public synchronized Set<String> refreshEnvironment() {
Map<String, Object> before = ①extract(
this.context.getEnvironment().getPropertySources());
②addConfigFilesToEnvironment();
Set<String> keys = ④changes(before,
③extract(this.context.getEnvironment().getPropertySources())).keySet();
⑤this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
return keys;
}
首先觸發執行ContextRefresher.refreshEnvironment()
:
- 提取標準參數(SYSTEM,JNDI,SERVLET)之外所有參數變量
- 把原來的Environment裏的參數放到一個新建的Spring Context容器下重新加載,完事之後關閉新容器
- 提取更新過的參數(排除標準參數)
- 比較出變更項
- 發佈環境變更事件,接收:EnvironmentChangeListener、LoggingRebinder
- RefreshScope用新的環境參數重新生成Bean,重新生成的過程很簡單,清除refreshscope緩存幷銷燬Bean,下次訪問就會重新從BeanFactory獲取一個新的實例(該實例使用新的配置)
接着觸發執行RefreshScope.refreshAll()
:
public void refreshAll() {
super.destroy();
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
ReadWriteLock.writeLock()
方式銷燬BeanLifecycleWrapperCache -> StandardScopeCache
中的bean實例,這裏執行的是上文提及在創建實例過程最後註冊的銷燬回調函數(registerDestructionCallback
);
// GenericScope
public void destroy() {
List<Throwable> errors = new ArrayList<Throwable>();
Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
for (BeanLifecycleWrapper wrapper : wrappers) {
try {
Lock lock = this.locks.get(wrapper.getName()).writeLock();
lock.lock();
try {
wrapper.destroy();
}
finally {
lock.unlock();
}
}
catch (RuntimeException e) {
errors.add(e);
}
}
if (!errors.isEmpty()) {
throw wrapIfNecessary(errors.get(0));
}
this.errors.clear();
}
如何觸發 Refresh
bus廣播刷新各節點服務
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> <version>2.1.1.RELEASE</version> </dependency>
# 失效管理端安全: management.security.enabled = false
# 默認事件跟蹤功能是失效,需要通過配置項激活: spring.cloud.bus.trace.enabled=true
這裏需要集成RabbitMq服務,否則報錯
2020-10-14 11:33:32.716 INFO 19348 --- [main] o.s.c.s.m.DirectWithAttributesChannel : Channel 'application-1.springCloudBusInput' has 1 subscriber(s).
2020-10-14 11:33:32.855 INFO 19348 --- [main] o.s.i.monitor.IntegrationMBeanExporter : Registering MessageChannel nullChannel
2020-10-14 11:33:32.874 INFO 19348 --- [main] o.s.i.monitor.IntegrationMBeanExporter : Registering MessageChannel springCloudBusOutput
2020-10-14 11:33:32.941 INFO 19348 --- [main] o.s.i.monitor.IntegrationMBeanExporter : Registering MessageChannel errorChannel
2020-10-14 11:33:32.982 INFO 19348 --- [main] o.s.i.monitor.IntegrationMBeanExporter : Registering MessageChannel springCloudBusInput
2020-10-14 11:33:32.997 INFO 19348 --- [main] o.s.i.monitor.IntegrationMBeanExporter : Registering MessageHandler org.springframework.cloud.stream.binding.StreamListenerMessageHandler@7772d266
2020-10-14 11:33:33.062 INFO 19348 --- [main] o.s.i.monitor.IntegrationMBeanExporter : Registering MessageHandler errorLogger
2020-10-14 11:33:33.098 INFO 19348 --- [main] o.s.i.endpoint.EventDrivenConsumer : Adding {logging-channel-adapter:_org.springframework.integration.errorLogger} as a subscriber to the 'errorChannel' channel
2020-10-14 11:33:33.098 INFO 19348 --- [main] o.s.i.channel.PublishSubscribeChannel : Channel 'application-1.errorChannel' has 1 subscriber(s).
2020-10-14 11:33:33.098 INFO 19348 --- [main] o.s.i.endpoint.EventDrivenConsumer : started _org.springframework.integration.errorLogger
2020-10-14 11:33:35.015 INFO 19348 --- [main] o.s.a.r.c.CachingConnectionFactory : Attempting to connect to: [localhost:5672]
2020-10-14 11:33:39.041 INFO 19348 --- [main] o.s.c.s.m.DirectWithAttributesChannel : Channel 'application-1.springCloudBusOutput' has 1 subscriber(s).
2020-10-14 11:33:39.067 INFO 19348 --- [main] c.s.b.r.p.RabbitExchangeQueueProvisioner : declaring queue for inbound: springCloudBus.anonymous.G8UactzcQg6VXoHSlF3U9g, bound to: springCloudBus
2020-10-14 11:33:39.067 INFO 19348 --- [main] o.s.a.r.c.CachingConnectionFactory : Attempting to connect to: [localhost:5672]
2020-10-14 11:33:43.085 INFO 19348 --- [main] o.s.a.r.c.CachingConnectionFactory : Attempting to connect to: [localhost:5672]
2020-10-14 11:33:47.095 INFO 19348 --- [main] o.s.a.r.c.CachingConnectionFactory : Attempting to connect to: [localhost:5672]
2020-10-14 11:33:51.119 INFO 19348 --- [main] o.s.i.monitor.IntegrationMBeanExporter : Registering MessageChannel springCloudBus.anonymous.G8UactzcQg6VXoHSlF3U9g.errors
2020-10-14 11:33:51.197 INFO 19348 --- [main] o.s.c.stream.binder.BinderErrorChannel : Channel 'application-1.springCloudBus.anonymous.G8UactzcQg6VXoHSlF3U9g.errors' has 1 subscriber(s).
2020-10-14 11:33:51.197 INFO 19348 --- [main] o.s.c.stream.binder.BinderErrorChannel : Channel 'application-1.springCloudBus.anonymous.G8UactzcQg6VXoHSlF3U9g.errors' has 2 subscriber(s).
2020-10-14 11:33:51.212 INFO 19348 --- [main] o.s.a.r.c.CachingConnectionFactory : Attempting to connect to: [localhost:5672]
2020-10-14 11:33:55.242 INFO 19348 --- [main] o.s.a.r.l.SimpleMessageListenerContainer : Broker not available; cannot force queue declarations during start
2020-10-14 11:33:55.262 INFO 19348 --- [g6VXoHSlF3U9g-1] o.s.a.r.c.CachingConnectionFactory : Attempting to connect to: [localhost:5672]
2020-10-14 11:33:59.290 INFO 19348 --- [main] o.s.i.a.i.AmqpInboundChannelAdapter : started inbound.springCloudBus.anonymous.G8UactzcQg6VXoHSlF3U9g
2020-10-14 11:33:59.363 INFO 19348 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-10-14 11:33:59.366 INFO 19348 --- [main] com.noob.BootstrapApplication : Started BootstrapApplication in 39.91 seconds (JVM running for 40.479)
2020-10-14 11:34:04.303 WARN 19348 --- [g6VXoHSlF3U9g-1] o.s.a.r.l.SimpleMessageListenerContainer : Consumer raised exception, processing can restart if the connection factory supports it. Exception summary: org.springframework.amqp.AmqpConnectException: java.net.ConnectException: Connection refused: connect
2020-10-14 11:34:04.305 INFO 19348 --- [g6VXoHSlF3U9g-1] o.s.a.r.l.SimpleMessageListenerContainer : Restarting Consumer@18e4551: tags=[[]], channel=null, acknowledgeMode=AUTO local queue size=0
2020-10-14 11:34:04.307 INFO 19348 --- [g6VXoHSlF3U9g-2] o.s.a.r.c.CachingConnectionFactory : Attempting to connect to: [localhost:5672]
BusAutoConfiguration#BusRefreshConfiguration
發佈一個RefreshBusEndpoint
, RefreshBusEndpoint
會從http端口觸發廣播RefreshRemoteApplicationEvent
事件;
BusAutoConfiguration#RefreshListener
負責接收事件(所有配置bus的節點服務都能收到)。
@Configuration
@ConditionalOnClass({ Endpoint.class, RefreshScope.class })
protected static class BusRefreshConfiguration {
@Configuration
@ConditionalOnBean(ContextRefresher.class)
@ConditionalOnProperty(value = "endpoints.spring.cloud.bus.refresh.enabled", matchIfMissing = true)
protected static class BusRefreshEndpointConfiguration {
@Bean
public RefreshBusEndpoint refreshBusEndpoint(ApplicationContext context,
BusProperties bus) {
return new RefreshBusEndpoint(context, bus.getId());
}
}
}
@Bean
@ConditionalOnProperty(value = "spring.cloud.bus.refresh.enabled", matchIfMissing = true)
@ConditionalOnBean(ContextRefresher.class)
public RefreshListener refreshListener(ContextRefresher contextRefresher) {
return new RefreshListener(contextRefresher);
}
@Endpoint(id = "bus-refresh")
public class RefreshBusEndpoint extends AbstractBusEndpoint {
public void busRefresh() {
publish(new RefreshRemoteApplicationEvent(this, getInstanceId(), null));
}
}
RefreshListener.onApplicationEvent
觸發執行方法ContextRefresher.refresh()
開啓刷新過程。
public class RefreshListener implements ApplicationListener<RefreshRemoteApplicationEvent> {
public void onApplicationEvent(RefreshRemoteApplicationEvent event) {
Set<String> keys = contextRefresher.refresh();
}
手動刷新
@Endpoint
相當於 @WebEndpoint
(生成web的方式的端點監控)和@JmxEndpoint
( 生成JMX的方式監控 )的整合。
@Endpoint(id = "refresh")
public class RefreshEndpoint {
private ContextRefresher contextRefresher;
public RefreshEndpoint(ContextRefresher contextRefresher) {
this.contextRefresher = contextRefresher;
}
@WriteOperation
public Collection<String> refresh() {
Set<String> keys = this.contextRefresher.refresh();
return keys;
}
}
org.springframework.cloud.endpoint.RefreshEndpoint
的類上註解@Endpoint
需要顯式引入該jar才生效。
<dependency>
<!-- http://localhost:8080/actuator/health -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>
因爲在spring-cloud-context
的pom.xml
文件中:對actuator
的依賴設置<optional>true</optional>
表示兩個項目之間依賴不傳遞。(默認是false -> 傳遞依賴 )
這些 Endpoint 在org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet.WebMvcEndpointManagementContextConfiguration
中被封裝爲WebMvcEndpointHandlerMapping
,提供Http的路徑訪問功能。
默認的基礎路徑是/actuator
。如果一個端點配置的路徑是sessions
,那麼它的全路徑就是/actuator/sessions
。@Selector
的含義是讓這個路徑變成/actuator/sessions/{name}
,能從路徑上獲取一個入參。
@Endpoint(id = "sessions")
public class MyHealthEndpoint {
@ReadOperation
public Info get(@Selector String name) {
return new Info(name);
}
}
bootstrax.yml
加入開啓監控:
# 默認端口和應用的端口是一致的,但是也可以通過配置的方式改變端口
management.server.port = 8081
management.server.address = 127.0.0.1
# 允許跨域的網址
management.endpoints.web.cors.allowed-origins=http://example.com
# 允許跨域的方法
management.endpoints.web.cors.allowed-methods=GET,POST
# 開啓監控接口
management:
endpoints:
web:
exposure:
include: "*" # 默認是兩個health|info
#include: refresh,health,info # 打開部分
#exclude: beans # 關閉部分
2020-10-14 11:43:44.443 INFO 19176 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-10-14 11:43:44.883 INFO 19176 --- [main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 16 endpoint(s) beneath base path '/actuator'
2020-10-14 11:43:45.023 INFO 19176 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-10-14 11:43:45.025 INFO 19176 --- [main] com.noob.BootstrapApplication : Started BootstrapApplication in 5.978 seconds (JVM running for 6.534)
健康檢測: 訪問 http://localhost:8080/actuator/health , 返回: {"status":"UP"} 查看所有對象信息: http://localhost:8080/actuator/beans 查看所有環境變量: http://http://localhost:8080/actuator/env 查看不同對象的日誌級別: http://localhost:8080/actuator/loggers 刷新配置: curl -X POST http://localhost:8080/actuator/refresh 返回變更的配置信息