If you use logback as a logout component in your application, most of them will configure the `logback.xml` file. In addition, in the production environment, you can directly modify the log level in the logback.xml file, and it can take effect without restarting the application. So, how is this function implemented?
If you use logback as a logout component in your application, most of them will configure the logback.xml file. In addition, in the production environment, you can directly modify the log level in the logback.xml file, and it can take effect without restarting the application.
So, how is this function implemented?
I. Problem description and analysis
In response to the above problem, first throw an actual case. In my personal website Z+, all gadgets are dynamically added and hidden through configuration files. Because there is only one server, the configuration files are simplified and placed directly in a directory on the server.
Now when I have a problem, I need to realize the change when the content of this file changes, the application can sense the change, reload the file content, and update the internal cache of the application
One of the easiest ways to think of is polling to determine whether the file has been modified. If it is modified, it will be reloaded and refreshed. Therefore, the main issues that need to be concerned about are as follows:
II. Design and Implementation
After the problem is abstracted, the corresponding solution is clearer
Then a very simple implementation is easier:
public class FileUpTest { private long lastTime; @Test public void testFileUpdate() { File file = new File("/tmp/alarmConfig"); // First, the last modified timestamp of the file lastTime = file.lastModified(); // Timed task, determines whether the file has changed every second, that is, determines whether lastModified changes ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { if (file.lastModified() > lastTime) { System.out.println("file update! time : " + file.lastModified()); lastTime = file.lastModified(); } } } },0, 1, TimeUnit.SECONDS); try { Thread.sleep(1000 * 60); } catch (InterruptedException e) { e.printStackTrace(); } }}The above is a very simple and very basic implementation, which can basically meet our needs. So what is the problem with this implementation?
What happens if an exception occurs during execution of a timing task?
Make some modifications to the above code
public class FileUpTest { private long lastTime; private void tt() { throw new NullPointerException(); } @Test public void testFileUpdate() { File file = new File("/tmp/alarmConfig"); lastTime = file.lastModified(); ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { if (file.lastModified() > lastTime) { System.out.println("file update! time : " + file.lastModified()); lastTime = file.lastModified(); ttt(); } } } }, 0, 1, TimeUnit.SECONDS); try { Thread.sleep(1000 * 60 * 10); } catch (InterruptedException e) { e.printStackTrace(); } }}In actual test, I found that the above code was triggered only when the first modification was modified, but the modification again would be useless. That is, when an exception was thrown, the timing task would no longer be executed. The main reason for this problem is that the ScheduledExecutorService is
Check out the source code comment instructions of ScheduledExecutorService directly
If any execution of the task encounters an exception, subsequent executions are suppressed.Otherwise, the task will only terminate via cancellation or termination of the executor.
Therefore, when using this posture, you must make sure that your task does not throw exceptions, otherwise you will not be able to play later
The corresponding solution is relatively simple, just catch it
III. Advanced Edition
The previous one is a basic implementation version. Of course, in the Java circle, there are basically many common needs that can be found to use corresponding open source tools. Of course, this is no exception, and it should be the apache series that everyone has more attributes.
First of all, maven dependencies
<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version></dependency>
Mainly, we use the FileAlterationObserver, FileAlterationListener, and FileAlterationMonitor in this tool to implement related requirements scenarios. Of course, it is very simple to use, so I don’t know how to explain it. Just look at the code copied from one of my open source project quick-alarm.
public class PropertiesConfListenerHelper { public static boolean registerConfChangeListener(File file, Function<File, Map<String, AlarmConfig>> func) { try { // Polling interval 5 seconds long interval = TimeUnit.SECONDS.toMillis(5); // Because listening is performed in directories, the root directory of the file is directly obtained here File dir = file.getParentFile(); // Create a file observer to filter FileAlterationObserver observer = new FileAlterationObserver(dir, FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.nameFileFilter(file.getName()))); //Set file change listener observer.addListener(new MyFileListener(func)); FileAlterationMonitor monitor = new FileAlterationMonitor(interval, observer); monitor.start(); return true; } catch (Exception e) { log.error("register properties change listener error! e:{}", e); return false; } } static final class MyFileListener extends FileAlterationListenerAdaptor { private Function<File, Map<String, AlarmConfig>> func; public MyFileListener(Function<File, Map<String, AlarmConfig>> func) { this.func = func; } @Override public void onFileChange(File file) { Map<String, AlarmConfig> ans = func.apply(file); // If loading fails, print a log log.warn("PropertiesConfig changed! reload ans: {}", ans); } }}For the above implementation, a few brief explanations:
A question, what happens if an exception is thrown when the func method is executed?
The actual test results are the same as above. After throwing an exception, you still have to be careful and don't run the exception.
So, let’s take a look at the above implementation logic and directly deduct the core module.
public void run() { while(true) { if(this.running) { Iterator var1 = this.observers.iterator(); while(var1.hasNext()) { FileAlterationObserver observer = (FileAlterationObserver)var1.next(); observer.checkAndNotify(); } if(this.running) { try { Thread.sleep(this.interval); } catch (InterruptedException var3) { ; } continue; } } return; }}It is basically clear from the above that the entire implementation logic is different from our first timed task method. Here we use threads and dead loops directly, and sleep methods are used to pause internally. Therefore, when an exception occurs, it is equivalent to throwing it directly, and the thread will kneel down.
Supplementary JDK version
jdk1.7 provides a WatchService, which can also be used to monitor file changes. I haven't been exposed to it before, so I found out that there is this thing. Then I searched for the use related information and found that it is quite simple. I saw a blog post that explains it is event-driven and has higher efficiency. Here is a simple example demo
@Testpublic void testFileUpWather() throws IOException { // Instructions, the listener here must also be the directory Path path = Paths.get("/tmp"); WatchService watcher = FileSystems.getDefault().newWatchService(); path.register(watcher, ENTRY_MODIFY); new Thread(() -> { try { while (true) { WatchKey key = watcher.take(); for (WatchEvent<?> event : key.pollEvents()) { if (event.kind() == OVERFLOW) { //Event may be lost or discarded continue; } Path fileName = (Path) event.context(); System.out.println("File Update: " + fileName); } if (!key.reset()) { //Reset WatchKey break; } } } } catch (Exception e) { e.printStackTrace(); } }).start(); try { Thread.sleep(1000 * 60 * 10); } catch (InterruptedException e) { e.printStackTrace(); }}IV. Summary
Using Java to implement monitoring of configuration file changes mainly involves two points
Overall, this implementation is relatively simple. Whether it is a custom implementation or relying on commos-io, it does not have much technical cost, but one thing to note is:
In order to avoid the above situation, an implementation that can be done is to use the asynchronous message notification of EventBus. After the file changes, send a message, and then add a @Subscribe annotation to the specific method of reloading the file contents. This not only realizes decoupling, but also avoids service exceptions caused by exceptions (if you are interested in this implementation, you can comment and explain)
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.