Created
September 14, 2021 07:18
-
-
Save composite/6ecf354d96cc24eafc977bb02bdad147 to your computer and use it in GitHub Desktop.
Refreshable YAML Properties Factory Bean (Spring boot 2 or later, Spring with SnakeYAML 1.18 or later)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| @Component | |
| public class ExampleComponent { | |
| @Autowired | |
| // @Qualifier("myProperties") | |
| // @Resource(name = "myProperties") | |
| private Properties myProperties; | |
| public String getMyConfig() { | |
| return myProperties.get("some.config.key", "alt value"); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; | |
| import org.springframework.cglib.proxy.Enhancer; | |
| import org.springframework.cglib.proxy.MethodInterceptor; | |
| import org.springframework.core.io.Resource; | |
| import java.io.IOException; | |
| import java.util.*; | |
| import java.util.concurrent.locks.Lock; | |
| import java.util.concurrent.locks.ReentrantReadWriteLock; | |
| /** | |
| * Refreshable YAML Properties Factory Bean | |
| * | |
| * @author Composite | |
| */ | |
| public class RefreshableYamlPropertiesFactoryBean extends YamlPropertiesFactoryBean { | |
| private static final Logger log = LoggerFactory.getLogger(RefreshableYamlPropertiesFactoryBean.class); | |
| private Properties proxy; | |
| private int interval = 10000; | |
| private Timer timer; | |
| private TimerTask task; | |
| private Resource[] resources; | |
| /** | |
| * File watcher running? | |
| */ | |
| private boolean running = false; | |
| private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); | |
| private final Lock r = rwl.readLock(); | |
| private final Lock w = rwl.writeLock(); | |
| @Override | |
| public void setResources(Resource... resources) { | |
| super.setResources(resources); | |
| this.resources = resources; | |
| } | |
| public void setInterval(int interval) { | |
| this.interval = interval; | |
| } | |
| /** | |
| * @throws Exception | |
| */ | |
| public void refresh() throws Exception { | |
| if (log.isInfoEnabled()) { | |
| log.info("refreshing YML Resources..."); | |
| } | |
| w.lock(); | |
| try { | |
| super.afterPropertiesSet(); | |
| } finally { | |
| w.unlock(); | |
| } | |
| } | |
| /** | |
| * Override YAML Properties with Refresh | |
| */ | |
| public void afterPropertiesSet() { | |
| super.afterPropertiesSet(); | |
| setRefreshable(); | |
| } | |
| private void setRefreshable() { | |
| Enhancer enhancer = new Enhancer(); | |
| enhancer.setSuperclass(Properties.class); | |
| enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> method.invoke(getParentObject(), args)); | |
| proxy = (Properties) enhancer.create(); | |
| task = new TimerTask() { | |
| private final Map<Resource, Long> map = new HashMap<>(); | |
| public void run() { | |
| if (isModified()) { | |
| try { | |
| refresh(); | |
| } catch (Exception e) { | |
| log.error("caught exception", e); | |
| } | |
| } | |
| } | |
| private boolean isModified() { | |
| boolean retVal = false; | |
| if (resources != null) { | |
| for (Resource mappingLocation : resources) { | |
| retVal |= findModifiedResource(mappingLocation); | |
| } | |
| } | |
| return retVal; | |
| } | |
| private boolean findModifiedResource(Resource resource) { | |
| boolean retVal = false; | |
| 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()); | |
| retVal = true; | |
| } | |
| } else { | |
| map.put(resource, modified); | |
| } | |
| } catch (IOException e) { | |
| log.error("caught exception", e); | |
| } | |
| if (retVal) { | |
| if (log.isInfoEnabled()) { | |
| log.info("modified files : " + modifiedResources); | |
| } | |
| } | |
| return retVal; | |
| } | |
| }; | |
| timer = new Timer(true); | |
| resetInterval(); | |
| } | |
| private Object getParentObject() throws Exception { | |
| r.lock(); | |
| try { | |
| return super.getObject(); | |
| } finally { | |
| r.unlock(); | |
| } | |
| } | |
| public Properties getObject() { | |
| return this.proxy; | |
| } | |
| public Class<? extends Properties> getObjectType() { | |
| return (this.proxy != null ? this.proxy.getClass() | |
| : Properties.class); | |
| } | |
| public boolean isSingleton() { | |
| return true; | |
| } | |
| public void setCheckInterval(int ms) { | |
| interval = ms; | |
| if (timer != null) { | |
| resetInterval(); | |
| } | |
| } | |
| private void resetInterval() { | |
| if (running) { | |
| timer.cancel(); | |
| running = false; | |
| } | |
| if (interval > 0) { | |
| timer.schedule(task, 0, interval); | |
| running = true; | |
| } | |
| } | |
| public void destroy() { | |
| timer.cancel(); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| @Configuration | |
| public class YamlExampleConfig { | |
| @Autowired | |
| private ResourceLoader resourceLoader; | |
| @Bean | |
| public RefreshableYamlPropertiesFactoryBean myProperties() { | |
| RefreshableYamlPropertiesFactoryBean bean = new RefreshableYamlPropertiesFactoryBean(); | |
| bean.setResources(resourceLoader.getResource("classpath:path/to/config.yml")); | |
| return bean; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment