Пакет java.nio.file предоставляет API уведомления об изменении файла, который называется API службы наблюдения . Это позволяет нам зарегистрировать папку в службе часов. При регистрации мы сообщаем сервису, какие типы событий нас интересуют: создание файла, изменение файла или удаление файла.
Вам также могут понравиться: Java IO и NIO
Когда служба обнаруживает интересующее событие, оно направляется зарегистрированному процессу и обрабатывается по мере необходимости. Вот как это работает:
- Первым шагом является создание нового
WatchServiceс использованиемnewWatchService()методаFileSystemкласса. - Затем мы регистрируем
Pathэкземпляр для отслеживаемой папки с типами интересующих нас событий. - И наконец, мы реализуем бесконечный цикл для ожидания входящих событий. Когда происходит событие, ключ сигнализируется и помещается в очередь наблюдателя. После обработки его событий нам нужно вернуть его в
readyсостояние, вызвав егоreset()метод. Если он возвращает false, ключ больше не действителен и цикл может завершиться.
Джава
1
WatchService watchService = Файловые системы . getDefault (). newWatchService ();
2
Путь Путь = Пути . get ( "c: \\ directory" );
3
путь . зарегистрироваться ( watchService , ENTRY_CREATE , ENTRY_MODIFY , ENTRY_DELETE );
4
логический опрос = true ;
5
while ( опрос ) {
6
WatchKey ключ = watchService . взять ();
7
для ( WatchEvent <?> событие : key . pollEvents ()) {
8
Система . из . println ( "Вид события:" + событие . вид () + "- Файл:" + событие . контекст ());
9
}
10
опрос = ключ . сброс ();
11
}
Это вывод консоли:
Джава
xxxxxxxxxx
1
Вид события : ENTRY_CREATE - Файл : файл . текст
2
Вид события : ENTRY_DELETE - Файл : файл . текст
3
Вид события : ENTRY_CREATE - Файл : тест . текст
4
Вид события : ENTRY_MODIFY - Файл : тест . текст
API WatchService находится на довольно низком уровне, что позволяет нам настраивать его. В этой статье и следуя шаблону Observer, мы собираемся разработать высокоуровневый API поверх этого механизма для прослушивания событий файла для данной папки. Мы начнем с создания FileEventкласса, расширяющего класс, java.util.EventObject из которого должны быть получены все объекты состояния события. FileEvent Экземпляр построен с ссылкой на источник, который является логически файл , на котором произошло событие на.
FileEvent.java
Джава
xxxxxxxxxx
1
импорт Java . -й . Файл ;
2
импорт Java . Util . EventObject ;
3
открытый класс FileEvent расширяет EventObject {
5
public FileEvent ( Файловый файл ) {
7
супер ( файл );
8
}
9
public File getFile () {
11
return ( File ) getSource ();
12
}
13
}
Затем мы создаем FileListenerинтерфейс, который должен быть реализован наблюдателем, чтобы получать уведомления о событиях файла. Он расширяет java.util.EventListenerинтерфейс, который является интерфейсом тегирования, который должны расширять все интерфейсы прослушивателя событий.
FileListener.java
Джава
xxxxxxxxxx
1
импорт Java . Util . EventListener ;
2
открытый интерфейс FileListener расширяет EventListener {
4
public void onCreated ( событие FileEvent );
6
public void onModified ( событие FileEvent );
8
public void onDeleted ( событие FileEvent );
10
}
Последняя часть головоломки состоит в создании субъекта, который ведет список наблюдателей и уведомляет их о любых изменениях состояния, вызывая один из их методов. Мы назовем его FileWatcherи дадим папку, вот как создается экземпляр этого класса.
Джава
xxxxxxxxxx
1
открытый класс FileWatcher {
2
защищенный список < FileListener > listeners = new ArrayList <> ();
4
защищенная итоговая папка File ;
5
public FileWatcher ( Папка файлов ) {
7
это . папка = папка ;
8
}
9
public List < FileListener > getListeners () {
11
вернуть слушателей ;
12
}
13
public FileWatcher setListeners ( Список < FileListener > слушателей ) {
15
это . слушатели = слушатели ;
16
верни это ;
17
}
18
}
Он может реализовать Runnableинтерфейс, поэтому мы можем запустить процесс наблюдения с потоком демона при вызове его watch()метода, если папка существует.
Джава
xxxxxxxxxx
1
открытый класс FileWatcher реализует Runnable {
2
public void watch () {
4
if ( папка . существует ()) {
5
Тема потока = новая тема ( это );
6
нить . setDaemon ( true );
7
нить . начало ();
8
}
9
}
10
12
public void run () {
13
// реализация еще не предоставлена
14
}
15
}
При реализации его run()метода создается WatchServiceэкземпляр для опроса событий в операторе try-with-resources. Мы будем следить за этим, используя статический финальный список в FileWatcherклассе, чтобы позже мы могли вызвать его close()метод, чтобы заставить любой поток, ожидающий получения ключей, выбросить непроверенный ClosedWatchServiceException, что будет прерывать процесс наблюдения чистым способом. Поэтому мы не получим предупреждений об утечке памяти, когда приложение будет корректно завершено.
Джава
xxxxxxxxxx
1
2
public void contextDestroyed ( событие ServletContextEvent ) {
3
для ( WatchService watchService : FileWatcher . getWatchServices ()) {
4
попытаться {
5
WatchService . закрыть ();
6
} catch ( IOException e ) {
7
}
8
}
9
}
Джава
xxxxxxxxxx
1
открытый класс FileWatcher реализует Runnable {
2
защищенный статический финал List < WatchService > watchServices = new ArrayList <> ();
4
6
public void run () {
7
try ( WatchService watchService = FileSystems . getDefault (). newWatchService ()) {
8
Путь Путь = Пути . get ( папка . getAbsolutePath ());
9
путь . зарегистрироваться ( watchService , ENTRY_CREATE , ENTRY_MODIFY , ENTRY_DELETE );
10
часыУслуги . добавить ( watchService );
11
логический опрос = true ;
12
while ( опрос ) {
13
poll = pollEvents ( watchService );
14
}
15
} catch ( IOException | InterruptedException | ClosedWatchServiceException e ) {
16
Автор . currentThread (). прерывание ();
17
}
18
}
19
защищенный логический pollEvents ( WatchService watchService ) выбрасывает InterruptedException {
21
WatchKey ключ = watchService . взять ();
22
Путь путь = ( путь ) ключ . смотрибельный ();
23
для ( WatchEvent <?> событие : key . pollEvents ()) {
24
notifyListeners ( событие . вид (), путь . разрешение (( путь ), событие . контекст ()). toFile ());
25
}
26
вернуться ключ . сброс ();
27
}
28
public static List < WatchService > getWatchServices () {
30
Вернуть Коллекции . unmodifiableList ( watchServices );
31
}
32
}
Всякий раз, когда происходит событие, путь к файлу разрешается, и слушатели уведомляются соответствующим образом. Если это создание новой папки, FileWatcherдля ее мониторинга будет создан другой экземпляр.
Джава
xxxxxxxxxx
1
открытый класс FileWatcher реализует Runnable {
2
protected void notifyListeners ( WatchEvent . Kind <?> kind , File file ) {
4
Событие FileEvent = новый FileEvent ( файл );
5
if ( kind == ENTRY_CREATE ) {
6
for ( слушатель FileListener : слушатели ) {
7
слушатель . onCreated ( событие );
8
}
9
if ( file . isDirectory ()) {
10
// создаем новый экземпляр FileWatcher для просмотра нового каталога
11
новый FileWatcher ( файл ). setListeners ( слушатели ). смотреть ();
12
}
13
}
14
иначе если ( kind == ENTRY_MODIFY ) {
15
for ( слушатель FileListener : слушатели ) {
16
слушатель . onModified ( событие );
17
}
18
}
19
иначе если ( kind == ENTRY_DELETE ) {
20
for ( слушатель FileListener : слушатели ) {
21
слушатель . onDeleted ( событие );
22
}
23
}
24
}
25
}
Вот полный список FileWatcherкласса.
FileWatcher.java
Джава
xxxxxxxxxx
1
импортировать статическую Java . Nio . файл . StandardWatchEventKinds . * ;
2
импорт Java . -й . Файл ;
3
импорт Java . -й . IOException ;
4
импорт Java . Nio . файл . ClosedWatchServiceException ;
5
импорт Java . Nio . файл . Файловые системы ;
6
импорт Java . Nio . файл . Путь ;
7
импорт Java . Nio . файл . Дорожки ;
8
импорт Java . Nio . файл . WatchEvent ;
9
импорт Java . Nio . файл . WatchKey ;
10
импорт Java . Nio . файл . WatchService ;
11
импорт Java . Util . ArrayList ;
12
импорт Java . Util . Коллекции ;
13
импорт Java . Util . Список ;
14
открытый класс FileWatcher реализует Runnable {
16
защищенный список < FileListener > listeners = new ArrayList <> ();
18
защищенная итоговая папка File ;
19
защищенный статический финал List < WatchService > watchServices = new ArrayList <> ();
20
21
public FileWatcher ( Папка файлов ) {
22
это . папка = папка ;
23
}
24
public void watch () {
26
if ( папка . существует ()) {
27
Тема потока = новая тема ( это );
28
нить . setDaemon ( true );
29
нить . начало ();
30
}
31
}
32
34
public void run () {
35
try ( WatchService watchService = FileSystems . getDefault (). newWatchService ()) {
36
Путь Путь = Пути . get ( папка . getAbsolutePath ());
37
путь . зарегистрироваться ( watchService , ENTRY_CREATE , ENTRY_MODIFY , ENTRY_DELETE );
38
часыУслуги . добавить ( watchService );
39
логический опрос = true ;
40
while ( опрос ) {
41
poll = pollEvents ( watchService );
42
}
43
} catch ( IOException | InterruptedException | ClosedWatchServiceException e ) {
44
Автор . currentThread (). прерывание ();
45
}
46
}
47
защищенный логический pollEvents ( WatchService watchService ) выбрасывает InterruptedException {
49
WatchKey ключ = watchService . взять ();
50
Путь путь = ( путь ) ключ . смотрибельный ();
51
для ( WatchEvent <?> событие : key . pollEvents ()) {
52
notifyListeners ( событие . вид (), путь . разрешение (( путь ), событие . контекст ()). toFile ());
53
}
54
вернуться ключ . сброс ();
55
}
56
protected void notifyListeners ( WatchEvent . Kind <?> kind , File file ) {
58
Событие FileEvent = новый FileEvent ( файл );
59
if ( kind == ENTRY_CREATE ) {
60
for ( слушатель FileListener : слушатели ) {
61
слушатель . onCreated ( событие );
62
}
63
if ( file . isDirectory ()) {
64
новый FileWatcher ( файл ). setListeners ( слушатели ). смотреть ();
65
}
66
}
67
иначе если ( kind == ENTRY_MODIFY ) {
68
for ( слушатель FileListener : слушатели ) {
69
слушатель . onModified ( событие );
70
}
71
}
72
иначе если ( kind == ENTRY_DELETE ) {
73
for ( слушатель FileListener : слушатели ) {
74
слушатель . onDeleted ( событие );
75
}
76
}
77
}
78
public FileWatcher addListener ( слушатель FileListener ) {
80
слушатели . добавить ( слушатель );
81
верни это ;
82
}
83
public FileWatcher removeListener ( слушатель FileListener ) {
85
слушатели . удалить ( слушатель );
86
верни это ;
87
}
88
public List < FileListener > getListeners () {
90
вернуть слушателей ;
91
}
92
public FileWatcher setListeners ( Список < FileListener > слушателей ) {
94
это . слушатели = слушатели ;
95
верни это ;
96
}
97
public static List < WatchService > getWatchServices () {
99
Вернуть Коллекции . unmodifiableList ( watchServices );
100
}
101
}
Последним штрихом нашего дизайна может быть создание FileAdapterкласса, который обеспечивает реализацию FileListener интерфейса по умолчанию, так что мы можем обработать только несколько событий для сохранения кода.
FileAdapter.java
Джава
xxxxxxxxxx
1
открытый абстрактный класс FileAdapter реализует FileListener {
2
4
public void onCreated ( событие FileEvent ) {
5
// реализация не предоставлена
6
}
7
9
public void onModified ( событие FileEvent ) {
10
// реализация не предоставлена
11
}
12
14
public void onDeleted ( событие FileEvent ) {
15
// реализация не предоставлена
16
}
17
}
FileAdapterКласс очень полезен в моем случае, чтобы перезагрузить сценарий Groovy при разработке приложения сервлета в моем IDE. Когда файл изменяется и переиздается в каталоге развертывания, он сначала удаляется перед повторным созданием. Поэтому событие модификации, которое запускается дважды на моей платформе Windows, можно игнорировать, а его аналог удаления нельзя использовать в моем контексте.
Это потому, что в настоящее время мы не можем отменить регистрацию сервлета, фильтра или прослушивателя из веб-контейнера. Таким образом, я не нашел оснований для включения такой функции в производство. Кроме того, в этом случае использования производительность даже не является проблемой, так как будет трудно иметь даже пять пакетов для наблюдения другим FileWatcherэкземпляром.
Джава
xxxxxxxxxx
1
Защищенный void loadScripts ( папка File ) {
2
if ( папка . существует ()) {
3
Файл [] файлы = папка . listFiles ();
4
if ( files ! = null ) {
5
для ( Файл файл : файлы ) {
6
if ( file . isFile ()) {
7
Объектный объект = scriptManager . loadScript ( файл );
8
регистр ( объект );
9
} еще {
10
loadScripts ( файл );
11
}
12
}
13
}
14
смотреть ( папка );
15
}
16
}
17
защищенные пустые часы ( папка с файлами )
19
новый FileWatcher ( папка ). addListener ( новый FileAdapter () {
20
21
public void onCreated ( событие FileEvent ) {
22
Файл файл = событие . getFile ();
23
if ( file . isFile ()) {
24
регистратор . информация ( «сценарий обработки» + файл . getName ());
25
процесс ( файл );
26
}
27
}
28
}). смотреть ();
29
}
30
защищенный процесс void ( файловый скрипт ) {
32
Объектный объект = scriptManager . loadScript ( скрипт );
33
// обновить приложение соответствующим образом
34
}
Хотя говорят, что использование Thread.sleep() метода в модульном тесте - плохая идея, мы собираемся использовать его для написания тестового примера для FileWatcherкласса, поскольку нам нужна задержка между операциями.
Джава
xxxxxxxxxx
1
импортировать статическую орг . Junit . Утверждать . * ;
2
импорт Java . -й . Файл ;
3
импорт Java . -й . FileWriter ;
4
импорт Java . -й . IOException ;
5
импорт Java . Util . HashMap ;
6
импорт Java . Util . Карта ;
7
импорт орг . Junit . Тест ;
8
открытый класс FileWatcherTest {
10
12
public void test () выдает IOException , InterruptedException {
13
Папка файла = новый файл ( "src / test / resources" );
14
final Map < String , String > map = new HashMap <> ();
15
FileWatcher watcher = новый FileWatcher ( папка );
16
наблюдающий . addListener ( новый FileAdapter () {
17
public void onCreated ( событие FileEvent ) {
18
карта . put ( "file.created" , event . getFile (). getName ());
19
}
20
public void onModified ( событие FileEvent ) {
21
карта . put ( "file.modified" , event . getFile (). getName ());
22
}
23
public void onDeleted ( событие FileEvent ) {
24
карта . put ( "file.deleted" , event . getFile (). getName ());
25
}
26
}). смотреть ();
27
assertEquals ( 1 , watcher . getListeners (). size ());
28
ждать ( 2000 );
29
Файл файл = новый файл ( папка + "/test.txt" );
30
try ( FileWriter writer = новый FileWriter ( файл )) {
31
писатель . написать ( "Some String" );
32
}
33
ждать ( 2000 );
34
файл . удалить ();
35
ждать ( 2000 );
36
assertEquals ( file . getName (), map . get ( "file.created" ));
37
assertEquals ( file . getName (), map . get ( "file.modified" ));
38
assertEquals ( file . getName (), map . get ( "file.deleted" ));
39
}
40
public void wait ( int time ) выдает InterruptedException {
42
Автор . сон ( время );
43
}
44
}
46
В моей предыдущей статье « Groovify ваших Java-сервлетов (часть 2): создание сценариев JVM » я показал, как создать экземпляр объекта из сценария с помощью Groovy Script Engine с помощью простого ScriptManager класса. Это может быть идеальной возможностью для меня исправить его реализацию, заменив устаревший Class.newInstance()метод на Class.getConstructor().newInstance()метод, чтобы сделать его правильным без исключений.
xxxxxxxxxx
1
импорт Java . -й . Файл ;
2
импорт Java . нетто . URL ;
3
импортная заводная . Util . GroovyScriptEngine ;
4
открытый класс ScriptManager {
6
защищенный финальный движок GroovyScriptEngine ;
8
public ScriptManager ( папка файлов ) {
10
engine = createScriptEngine ( папка );
11
}
12
защищенный GroovyScriptEngine createScriptEngine ( папка « Файл » ) {
14
URL [] urls = { папка . TOURI (). toURL ()};
15
вернуть новый GroovyScriptEngine ( URL , this . getClass (). getClassLoader ());
16
}
17
public Object loadScript ( String name ) {
19
вернуть двигатель . loadScriptByName ( имя ). getConstructor (). newInstance ()
20
}
21
}
23
Приведенный выше класс не может загружать сценарии, расположенные в подкаталогах данной папки, если вы не передадите относительный путь в аргументе имени сценария. Вот почему лучше написать это так:
xxxxxxxxxx
1
импорт Java . -й . Файл ;
2
импорт Java . нетто . URL ;
3
импортная заводная . Util . GroovyScriptEngine ;
4
открытый класс ScriptManager {
6
7
защищенная итоговая папка File ;
8
защищенный финальный движок GroovyScriptEngine ;
9
public ScriptManager ( папка файлов ) {
11
это . папка = папка ;
12
engine = createScriptEngine ();
13
}
14
защищенный GroovyScriptEngine createScriptEngine () {
16
URL [] urls = { папка . TOURI (). toURL ()};
17
вернуть новый GroovyScriptEngine ( URL , this . getClass (). getClassLoader ());
18
}
19
public Object loadScript ( File file ) {
21
Имя строки = файл . getAbsolutePath (). подстрока ( папка . getAbsolutePath (). length () + 1 );
22
вернуть двигатель . loadScriptByName ( имя ). getConstructor (). newInstance ()
23
}
24
}