在 Spring Boot 应用程序中重新加载 bean 配置 xml 文件而无需重新启动

Posted

技术标签:

【中文标题】在 Spring Boot 应用程序中重新加载 bean 配置 xml 文件而无需重新启动【英文标题】:Reload a bean configuration xml file in a spring boot application without restarting 【发布时间】: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。但是,重新加载配置很困难,因为创建的依赖项(可以)注入到云配置帮助的单例对象中,但您需要以不同的方式设计启动应用程序。 【参考方案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。

以上是关于在 Spring Boot 应用程序中重新加载 bean 配置 xml 文件而无需重新启动的主要内容,如果未能解决你的问题,请参考以下文章

JUnit 4 & Spring Boot - 在测试前有选择地重新加载上下文/重新加载 Spring Security 配置

如何在运行时更新 Spring Boot 应用程序的配置而不重新加载整个 ApplicationContext

Spring Boot devtools - 静态内容重新加载在 IntelliJ 中不起作用

如何在 Angular、spring-boot、maven 项目中配置项目以自动重新加载浏览器

使用 Intellij IDEA 重新加载远程 Spring Boot 应用程序时出现异常

Spring Boot 热部署