Skip to content

Instantly share code, notes, and snippets.

@composite
Created September 14, 2021 07:18
Show Gist options
  • Select an option

  • Save composite/6ecf354d96cc24eafc977bb02bdad147 to your computer and use it in GitHub Desktop.

Select an option

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)
@Component
public class ExampleComponent {
@Autowired
// @Qualifier("myProperties")
// @Resource(name = "myProperties")
private Properties myProperties;
public String getMyConfig() {
return myProperties.get("some.config.key", "alt value");
}
}
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();
}
}
@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