【问题标题】:Reload a bean configuration xml file in a spring boot application without restarting在 Spring Boot 应用程序中重新加载 bean 配置 xml 文件而无需重新启动
【发布时间】:2018-07-23 22:14:19
【问题描述】:

应用程序类。

@SpringBootApplication
public class ServerBApplication {

public static void main(String[] args) {
    SpringApplication.run(ServerBApplication.class, args);
}

private Student stu;

public ServerBApplication() {
    FileSystemXmlApplicationContext cont = new FileSystemXmlApplicationContext("./config/student.xml");
    stu = cont.getBean(Student.class);
    cont.close();
    cont.destroy();
}

@Bean
Student stu() {
    return stu;
}
}

还有xml文件

<bean id="stu" name="stu" class="com.example.demo.Student">
    <property name="id" value="100"></property>
    <property name="name" value="summer"></property>
</bean>

重新加载控制器。

@RestController
public class ReloadController {

@Autowired
Student stu;

@RequestMapping(value = "/reload", method = RequestMethod.GET)
public String reload() {
    FileSystemXmlApplicationContext cont = new FileSystemXmlApplicationContext("./config/student.xml");
    stu = cont.getBean(Student.class);
    cont.close();
    cont.destroy();
    return "Reload success." + stu.toSting();
}
}

我想更改 ./config/student.xml 的一些值/属性,然后运行方法 /reload,但我仍然可以通过

获得学生价值

id:100,名字:夏天

谁能告诉我当我运行 /reload 方法而不重新启动应用程序时,可以更改 stu 的值/属性。非常感谢。

【问题讨论】:

  • 你想要的叫 Spring Cloud @RequestScope 。第一个任务,您必须将 Spring Cloud 依赖项添加到您的类路径中。你可以看到authoritative example。什么时候使用这种技术?当您想要为您的云应用程序进行动态配置时。出于琐碎的目的,您使用 Spring Boot devtools 进行开发过程。
  • Request Scope 不是 Spring Cloud 的一部分,它绑定到 Web 请求。 docs.spring.io/spring-framework/docs/current/javadoc-api/org/… 此示例不需要 Spring Cloud Config,因为您可以将该 bean 绑定到 RequestScoping。但是,重新加载配置很困难,因为创建的依赖项(可以)注入到云配置帮助的单例对象中,但您需要以不同的方式设计启动应用程序。

标签: xml spring spring-boot configuration reload


【解决方案1】:
package com.test;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.core.io.Resource;

import java.io.IOException;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;


public class RefreshableSqlSessionFactoryBean extends SqlSessionFactoryBean implements DisposableBean {
  private static final Logger logger = LoggerFactory.getLogger(RefreshableSqlSessionFactoryBean.class);
  private SqlSessionFactory proxy;
  private int interval = 500;
  private Timer timer;
  private TimerTask task;
  private Resource[] refreshableMapperLocations;
  private boolean running = false;
  private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
  private final Lock r = rwl.readLock();
  private final Lock w = rwl.writeLock();

  public RefreshableSqlSessionFactoryBean() {
    
  }

  @Override
  public void afterPropertiesSet() throws Exception {
    super.afterPropertiesSet();
    setRefreshable();
  }

  public void setRefreshableMapperLocations(Resource[] refreshableMapperLocations) {
    this.refreshableMapperLocations = refreshableMapperLocations;
  }

  public void setInterval(int ms) {
    this.interval = ms;
  }

  public void setCheckInterval(int ms) {
    interval = ms;
    if (timer != null) {
      resetInterval();
    }
  }

  private void refresh() throws Exception {
    if (logger.isInfoEnabled()) {
      logger.info("> Refresh SqlMapper...");
    }
    w.lock();
    try {
      super.afterPropertiesSet();
      logger.info("> Done.");
      logger.info("======================================================================================");
    } finally {
      w.unlock();
    }
  }

  @SuppressWarnings("java:S3776")
  private void setRefreshable() {
    proxy = (SqlSessionFactory) Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(),
                                                       new Class[]{SqlSessionFactory.class},
                                                       (aProxy, method, args) -> method.invoke(getParentObject(),
                                                                                               args));

    task = new TimerTask() {
      private final Map<Resource, Long> map = new HashMap<>();

      @Override
      public void run() {
        if (isModified()) {
          try {
            refresh();
          } catch (Exception e) {
            logger.error("caught exception", e);
          }
        }
      }

      private boolean isModified() {
        if (refreshableMapperLocations != null) {
          List<String> modifiedResources = new ArrayList<>();
          for (Resource mappingLocation : refreshableMapperLocations) {
            modifiedResources.addAll(findModifiedResource(mappingLocation));
          }
          if (!modifiedResources.isEmpty()) {
            if(logger.isInfoEnabled()) {
              logger.info("======================================================================================");
              logger.info("> Update File name : {}", modifiedResources);
            }
            return true;
          }
        }
        return false;
      }

      private List<String> findModifiedResource(Resource resource) {
        List<String> modifiedResources = new ArrayList<>();
        try {
          long modified = resource.lastModified();
          if (map.containsKey(resource)) {
            long lastModified = map.get(resource);
            if (lastModified != modified) {
              map.put(resource, modified);
              //modifiedResources.add(resource.getDescription()); // 전체경로
              modifiedResources.add(resource.getFilename()); // 파일명
            }
          } else {
            map.put(resource, modified);
          }
        } catch (IOException e) {
          logger.error("caught exception", e);
        }

        return modifiedResources;
      }
    };

    timer = new Timer(true);
    resetInterval();
  }

  private SqlSessionFactory getParentObject() throws Exception {
    r.lock();
    try {
      return super.getObject();
    } finally {
      r.unlock();
    }
  }

  private void resetInterval() {
    if (running) {
      timer.cancel();
      running = false;
    }
    if (interval > 0) {
      timer.schedule(task, 0, interval);
      running = true;
    }
  }

  @Override
  public SqlSessionFactory getObject() {
    try {
      getParentObject();  // build factory if necessary
    } catch (Exception e) {
      e.printStackTrace();
    }
    return this.proxy;
  }

  @Override
  public Class<? extends SqlSessionFactory> getObjectType() {
    return (this.proxy != null ? this.proxy.getClass() : SqlSessionFactory.class);
  }

  @Override
  public boolean isSingleton() {
    return true;
  }

  @Override
  public void destroy() {
    timer.cancel();
  }
}
package com.test;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;

import javax.sql.DataSource;
import java.io.IOException;


public class NodeConfigurationHelper {
  private NodeConfigurationHelper() {
  }

  static SqlSessionFactory createRefreshableSqlSessionFactory(ApplicationContext applicationContext,
                                                              DataSource dataSource) throws IOException {
    RefreshableSqlSessionFactoryBean sqlSessionFactoryBean = new RefreshableSqlSessionFactoryBean();
    setSqlSessionFactoryBeanCommonSettings(sqlSessionFactoryBean, applicationContext, dataSource);

    // for refresh query
    sqlSessionFactoryBean.setInterval(1000);
    Resource[] mapper1 = applicationContext.getResources("classpath:/mapper/tibero/**/*.xml");
    Resource[] mapper2 = applicationContext.getResources("classpath:/com/daou/sabangnet/dao/**/*.xml");
    Resource[] mappers = ArrayUtils.addAll(mapper1, mapper2);
    sqlSessionFactoryBean.setRefreshableMapperLocations(mappers);

    return sqlSessionFactoryBean.getObject();
  }

  static void setSqlSessionFactoryBeanCommonSettings(SqlSessionFactoryBean sqlSessionFactoryBean,
                                                     ApplicationContext applicationContext,
                                                     DataSource dataSource) throws IOException {
    sqlSessionFactoryBean.setDataSource(dataSource);

    sqlSessionFactoryBean.setConfigLocation(applicationContext.getResource("classpath:/mapper/mybatis-config.xml"));
    sqlSessionFactoryBean.setMapperLocations(applicationContext.getResources("classpath:/mapper/tibero/*.xml"));
  }
}
package com.test;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.io.IOException;

@Slf4j
@Configuration
@PropertySource("classpath:/application.properties")
public class Node01DatabaseConfiguration {

    @Autowired
    private ApplicationContext applicationContext;

    @Value("${spring.datasource.node01.connection-pool-size}")
    private Integer maxConnectionPool;

    @Bean(name="node01Configuration")
    @ConfigurationProperties(prefix="spring.datasource.node01")
    public HikariConfig node01Config() {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setMaximumPoolSize(maxConnectionPool);
        return hikariConfig;
    }

    @Bean(name="node01DataSource")
    public DataSource node01DataSource() {
        HikariConfig firstConfig = node01Config();
        log.info("Max. Hikari Connections for Node01 DB: {}", firstConfig.getMaximumPoolSize());
        DataSource dataSource = new HikariDataSource(node01Config());
        log.info("datasource : {}", dataSource);
        return dataSource;
    }

    @Bean(name="node01SessionFactory")
    @Profile("local")
    public SqlSessionFactory refreshableSqlSessionFactory(@Qualifier("node01DataSource") DataSource dataSource)
            throws IOException {
        return NodeConfigurationHelper.createRefreshableSqlSessionFactory(applicationContext, dataSource);
    }

    @Bean(name="node01SessionFactory")
    @Profile("!local")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("node01DataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        NodeConfigurationHelper.setSqlSessionFactoryBeanCommonSettings(sqlSessionFactoryBean, applicationContext, dataSource);

        return sqlSessionFactoryBean.getObject();
    }

    @Bean(name="01TrManager")
    public DataSourceTransactionManager returnTransactionManager(@Qualifier("node01DataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name="node01SessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("node01SessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

【讨论】:

  • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-09-15
  • 2019-05-29
  • 1970-01-01
  • 1970-01-01
  • 2014-08-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多