spring cloud

spring boot 自动刷新配置导致的出错

2021-03-04  本文已影响0人  天草二十六_简村人

一、现象总结
运维人员只要一修改分布式配置,程序就报错,此时的健康检测也是出错。
但是,重启应用是能够成功的。
程序中有配置类使用注解@RefreshScope,用于实时更新,但又不想重启应用。

二、技术栈
分布式配置consul + spring boot 2.0 + mysql数据库连接池HicariCP

三、健康检测的错误详情

{
    "status":"DOWN",
    "details":{
        "diskSpace":{
            "status":"UP",
            "details":{
                "total":52710469632,
                "free":44208345088,
                "threshold":10485760
            }
        },
        "db":{
            "status":"UP",
            "details":{
                "database":"MySQL",
                "hello":1
            }
        },
        "elasticsearch":{
            "status":"UP",
            "details":{
                "clusterName":"es-cluster",
                "numberOfNodes":3,
                "numberOfDataNodes":3,
                "activePrimaryShards":94,
                "activeShards":193,
                "relocatingShards":0,
                "initializingShards":0,
                "unassignedShards":0
            }
        },
        "refreshScope":{
            "status":"DOWN",
            "details":{
                "error":"org.springframework.boot.context.properties.ConfigurationPropertiesBindException: Error creating bean with name 'dataSource': Could not bind properties to 'HikariDataSource' : prefix=spring.datasource.hikari, ignoreInvalidFields=false, ignoreUnknownFields=true; nested exception is org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'spring.datasource.hikari' to com.zaxxer.hikari.HikariDataSource"
            }
        },
        "discoveryComposite":{
            "status":"UP",
            "details":{
                "discoveryClient":{
                    "status":"UP",
                    "details":{
                        "services":[
                            "user-service"
                        ]
                    }
                }
            }
        },
        "hystrix":{
            "status":"UP"
        },
        "redis":{
            "status":"UP",
            "details":{
                "version":"3.2.11"
            }
        }
    }
}

四、java后端的日志打印出错详情

2021-03-04 10:51:01.651 ERROR 20017 --- [TaskScheduler-1] o.s.s.s.TaskUtils$LoggingErrorHandler    : Unexpected error occurred in scheduled task.

org.springframework.boot.context.properties.ConfigurationPropertiesBindException: Error creating bean with name 'dataSource': Could not bind properties to 'HikariDataSource' : prefix=spring.datasource.hikari, ignoreInvalidFields=false, ignoreUnknownFields=true; nested exception is org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'spring.datasource.hikari' to com.zaxxer.hikari.HikariDataSource
    at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.bind(ConfigurationPropertiesBindingPostProcessor.java:109) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:93) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:423) ~[spring-beans-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1702) ~[spring-beans-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:414) ~[spring-beans-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
    at org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder.rebind(ConfigurationPropertiesRebinder.java:102) ~[spring-cloud-context-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder.rebind(ConfigurationPropertiesRebinder.java:84) ~[spring-cloud-context-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder.onApplicationEvent(ConfigurationPropertiesRebinder.java:141) ~[spring-cloud-context-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder.onApplicationEvent(ConfigurationPropertiesRebinder.java:50) ~[spring-cloud-context-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172) ~[spring-context-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165) ~[spring-context-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139) ~[spring-context-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:399) ~[spring-context-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:353) ~[spring-context-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
    at org.springframework.cloud.context.refresh.ContextRefresher.refresh(ContextRefresher.java:65) ~[spring-cloud-context-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.cloud.endpoint.event.RefreshEventListener.handle(RefreshEventListener.java:36) ~[spring-cloud-context-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_181]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_181]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_181]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_181]
    at org.springframework.context.event.ApplicationListenerMethodAdapter.doInvoke(ApplicationListenerMethodAdapter.java:264) ~[spring-context-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
    at org.springframework.context.event.ApplicationListenerMethodAdapter.processEvent(ApplicationListenerMethodAdapter.java:182) ~[spring-context-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
    at org.springframework.context.event.ApplicationListenerMethodAdapter.onApplicationEvent(ApplicationListenerMethodAdapter.java:144) ~[spring-context-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172) ~[spring-context-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165) ~[spring-context-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139) ~[spring-context-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:399) ~[spring-context-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:353) ~[spring-context-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
    at org.springframework.cloud.consul.config.ConfigWatch.watchConfigKeyValues(ConfigWatch.java:158) ~[spring-cloud-consul-config-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) ~[spring-context-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_181]
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308) [na:1.8.0_181]
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_181]
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294) [na:1.8.0_181]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_181]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_181]
    at java.lang.Thread.run(Thread.java:748) [na:1.8.0_181]
Caused by: org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'spring.datasource.hikari' to com.zaxxer.hikari.HikariDataSource
    at org.springframework.boot.context.properties.bind.Binder.handleBindError(Binder.java:250) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:226) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:210) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:192) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.boot.context.properties.ConfigurationPropertiesBinder.bind(ConfigurationPropertiesBinder.java:78) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.bind(ConfigurationPropertiesBindingPostProcessor.java:106) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    ... 36 common frames omitted
Caused by: java.lang.IllegalStateException: Unable to set value for property pool-name
    at org.springframework.boot.context.properties.bind.JavaBeanBinder$BeanProperty.setValue(JavaBeanBinder.java:322) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:78) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:61) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:53) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.boot.context.properties.bind.Binder.lambda$null$5(Binder.java:339) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) ~[na:1.8.0_181]
    at java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1359) ~[na:1.8.0_181]
    at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126) ~[na:1.8.0_181]
    at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:498) ~[na:1.8.0_181]
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485) ~[na:1.8.0_181]
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) ~[na:1.8.0_181]
    at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152) ~[na:1.8.0_181]
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_181]
    at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:464) ~[na:1.8.0_181]
    at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$6(Binder.java:340) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:439) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.boot.context.properties.bind.Binder$Context.withBean(Binder.java:425) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.boot.context.properties.bind.Binder$Context.access$400(Binder.java:379) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.boot.context.properties.bind.Binder.bindBean(Binder.java:337) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:279) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:221) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    ... 40 common frames omitted
Caused by: java.lang.reflect.InvocationTargetException: null
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_181]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_181]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_181]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_181]
    at org.springframework.boot.context.properties.bind.JavaBeanBinder$BeanProperty.setValue(JavaBeanBinder.java:319) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    ... 60 common frames omitted
Caused by: java.lang.IllegalStateException: The configuration of the pool is sealed once started.  Use HikariConfigMXBean for runtime changes.
    at com.zaxxer.hikari.HikariConfig.setPoolName(HikariConfig.java:861) ~[HikariCP-2.7.8.jar!/:na]
    ... 65 common frames omitted

可以看出,主要的错误在于末尾!! 这也是仅从健康检测的错误提示,无法看到出错在哪的原因。

java.lang.IllegalStateException: The configuration of the pool is sealed once started.  Use HikariConfigMXBean for runtime changes.
    at com.zaxxer.hikari.HikariConfig.setPoolName(HikariConfig.java:861) ~[HikariCP-2.7.8.jar!/:na]

五、consul的主要配置

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://mysql2.xx.net:3306/xxx?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
    username: xxxx
    password: xxxx
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimum-idle: 10
      maximum-pool-size: 150
      auto-commit: true
      idle-timeout: 600000
      pool-name: DatebookHikariCP
      max-lifetime: 1800000
      connection-timeout: 30000

六、出错的原因
1、com.zaxxer.hikari.HikariConfig.setPoolName

public void setPoolName(String poolName)
   {
      checkIfSealed();
      this.poolName = poolName;
   }
//这里引入了一个重要的vovatile变量

private volatile boolean sealed;

private void checkIfSealed()
   {
      if (sealed) throw new IllegalStateException("The configuration of the pool is sealed once started. Use HikariConfigMXBean for runtime changes.");
   }

void seal()
   {
      this.sealed = true;
   }

在数据源初始化和连接建立的时候,调用seal()方法。
见类public class HikariConnectionProvider implements ConnectionProvider, Configurable, Stoppable


HikariConnectionProvider.png

这里有意思的是getConnection()源码实现:
在获取连接池的时候,采用了双重检测锁机制,做到pool的单例。

public Connection getConnection() throws SQLException
   {
      if (isClosed()) {
         throw new SQLException("HikariDataSource " + this + " has been closed.");
      }
// 如果fast连接池存在,则直接跳转至获取连接的方法内。
      if (fastPathPool != null) {
         return fastPathPool.getConnection();
      }

      // See http://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java
      HikariPool result = pool;
      if (result == null) {
         synchronized (this) {
            result = pool;
            if (result == null) {
               validate();
               LOGGER.info("{} - Starting...", getPoolName());
               try {
                  pool = result = new HikariPool(this);
                  this.seal();
               }
               catch (PoolInitializationException pie) {
                  if (pie.getCause() instanceof SQLException) {
                     throw (SQLException) pie.getCause();
                  }
                  else {
                     throw pie;
                  }
               }
               LOGGER.info("{} - Start completed.", getPoolName());
            }
         }
      }

      return result.getConnection();
   }

PS: getConnection()里的实现,也比较简单,附上源码:
代码行数不错,先是采用Semaphore信号量的同步机制,实时触发关闭连接,循环判断是否超时,由PoolEntry去创建连接代理。同时收集指标信息。

public Connection getConnection(final long hardTimeout) throws SQLException
   {
      suspendResumeLock.acquire();
      final long startTime = currentTime();

      try {
         long timeout = hardTimeout;
         do {
            PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
            if (poolEntry == null) {
               break; // We timed out... break and throw exception
            }

            final long now = currentTime();
            if (poolEntry.isMarkedEvicted() || (elapsedMillis(poolEntry.lastAccessed, now) > aliveBypassWindowMs && !isConnectionAlive(poolEntry.connection))) {
               closeConnection(poolEntry, poolEntry.isMarkedEvicted() ? EVICTED_CONNECTION_MESSAGE : DEAD_CONNECTION_MESSAGE);
               timeout = hardTimeout - elapsedMillis(startTime);
            }
            else {
               metricsTracker.recordBorrowStats(poolEntry, startTime);
               return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);
            }
         } while (timeout > 0L);

         metricsTracker.recordBorrowTimeoutStats(startTime);
         throw createTimeoutException(startTime);
      }
      catch (InterruptedException e) {
         Thread.currentThread().interrupt();
         throw new SQLException(poolName + " - Interrupted during connection acquisition", e);
      }
      finally {
         suspendResumeLock.release();
      }
   }

2、配置自动加载
分析到这里,我们需要解释的是,为什么没有加@RefreshScope注解的数据库配置项会重新加载?
关于更多有关配置热更新的可以参考网址http://www.scienjus.com/spring-cloud-refresh/

文章开头说了,程序中并没有手写对于HikariCP的配置项,而是简单的使用@RefreshScope注解。

怀疑是spring boot autoconfigure 在加载数据源的时候,有指定刷新类DataSourceProperties。

{
      "sourceType": "org.springframework.boot.autoconfigure.jdbc.DataSourceProperties",
      "name": "spring.datasource.type",
      "description": "Fully qualified name of the connection pool implementation to use. By default, it\n is auto-detected from the classpath.",
      "type": "java.lang.Class<? extends javax.sql.DataSource>"
    },

检索spring.datasource.type,找到它的java配置类是DataSourceProperties。

@ConfigurationProperties(
    prefix = "spring.datasource"
)
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {

这里也没有直接写类DataSourceProperties的scope=refresh.

几个主要类的关联.png

于是,断点跟踪spring-cloud-context-2.0.0.RELEASE.jar中的几个关键类:
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration

@Bean
    @ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
    public ConfigurationPropertiesBeans configurationPropertiesBeans() {
        // Since this is a BeanPostProcessor we have to be super careful not to
        // cause a cascade of bean instantiation. Knowing the *name* of the beans we
        // need is super optimal, but a little brittle (unfortunately we have no
        // choice).
        ConfigurationBeanFactoryMetadata metaData = this.context.getBean(
                ConfigurationBeanFactoryMetadata.BEAN_NAME,
                        ConfigurationBeanFactoryMetadata.class);
        ConfigurationPropertiesBeans beans = new ConfigurationPropertiesBeans();
        beans.setBeanMetaDataStore(metaData);
        return beans;
    }

    @Bean
    @ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
    public ConfigurationPropertiesRebinder configurationPropertiesRebinder(
            ConfigurationPropertiesBeans beans) {
        ConfigurationPropertiesRebinder rebinder = new ConfigurationPropertiesRebinder(
                beans);
        return rebinder;
    }

进一步看ConfigurationPropertiesBeans类的实现细节,

@Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
// 如果bean的scope不是refresh, 则返回。
// 而org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari的scope切实地等于refresh
        if (isRefreshScoped(beanName)) {
            return bean;
        }
        ConfigurationProperties annotation = AnnotationUtils.findAnnotation(
                bean.getClass(), ConfigurationProperties.class);
        if (annotation != null) {
            this.beans.put(beanName, bean);
        }
        else if (this.metaData != null) {
            annotation = this.metaData.findFactoryAnnotation(beanName,
                    ConfigurationProperties.class);
            if (annotation != null) {
                this.beans.put(beanName, bean);
            }
        }
        return bean;
    }

    private boolean isRefreshScoped(String beanName) {
        if (this.refreshScope == null && !this.refreshScopeInitialized) {
            this.refreshScopeInitialized = true;
            for (String scope : this.beanFactory.getRegisteredScopeNames()) {
                if (this.beanFactory.getRegisteredScope(scope) instanceof org.springframework.cloud.context.scope.refresh.RefreshScope) {
                    this.refreshScope = scope;
                    break;
                }
            }
        }
        if (beanName == null || this.refreshScope == null) {
            return false;
        }
        return this.beanFactory.containsBeanDefinition(beanName)
                && this.refreshScope.equals(this.beanFactory.getBeanDefinition(beanName)
                        .getScope());
    }

至于为什么org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari它的scope=refresh。可以看类org.springframework.cloud.autoconfigure.RefreshAutoConfiguration


    @Component
    protected static class RefreshScopeBeanDefinitionEnhancer
            implements BeanDefinitionRegistryPostProcessor {

        /**
         * Class names for beans to post process into refresh scope. Useful when you don't
         * control the bean definition (e.g. it came from auto-configuration).
         */
        private Set<String> refreshables = new HashSet<>(
                Arrays.asList("com.zaxxer.hikari.HikariDataSource"));

        private Environment environment;

        public Set<String> getRefreshable() {
            return this.refreshables;
        }

        public void setRefreshable(Set<String> refreshables) {
            if (this.refreshables != refreshables) {
                this.refreshables.clear();
                this.refreshables.addAll(refreshables);
            }
        }

        public void setExtraRefreshable(Set<String> refreshables) {
            this.refreshables.addAll(refreshables);
        }

        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
                throws BeansException {
        }

        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
                throws BeansException {
            for (String name : registry.getBeanDefinitionNames()) {
                BeanDefinition definition = registry.getBeanDefinition(name);
                if (isApplicable(registry, name, definition)) {
                    BeanDefinitionHolder holder = new BeanDefinitionHolder(definition,
                            name);
                    BeanDefinitionHolder proxy = ScopedProxyUtils
                            .createScopedProxy(holder, registry, true);
                    definition.setScope("refresh");
                    registry.registerBeanDefinition(proxy.getBeanName(),
                            proxy.getBeanDefinition());
                }
            }
        }

        private boolean isApplicable(BeanDefinitionRegistry registry, String name,
                BeanDefinition definition) {
            String scope = definition.getScope();
            if ("refresh".equals(scope)) {
                // Already refresh scoped
                return false;
            }
            String type = definition.getBeanClassName();
            if (registry instanceof BeanFactory) {
                Class<?> cls = ((BeanFactory) registry).getType(name);
                if (cls != null) {
                    type = cls.getName();
                }
            }
            if (type != null) {
                if (this.environment == null && registry instanceof BeanFactory) {
                    this.environment = ((BeanFactory) registry)
                            .getBean(Environment.class);
                }
                if (this.environment == null) {
                    this.environment = new StandardEnvironment();
                }
                Binder.get(environment).bind("spring.cloud.refresh",
                        Bindable.ofInstance(this));
                return this.refreshables.contains(type);
            }
            return false;
        }

    }

总结:到此,可以解释为什么consul每次配置变更,都重新加载了HikariCP的数据库配置项,而这里踩到了一个坑seal()异常。但是我们的运维同事在重启应用,却又是顺利启动。

上一篇 下一篇

猜你喜欢

热点阅读