-
Notifications
You must be signed in to change notification settings - Fork 56
Google guava
Существует набор библиотек от Google для разработки на Java, который используется самим Google в своих разработках - это Google Guava http://code.google.com/p/guava-libraries/ .
Данная библиотека содержит множество классов для повседневной работы от чтения файлов в строку с указанием кодировки, до реализации мониторов многопоточных приложений.
Рассмотрим часть интересных классов из состава Google Guava.
Репозиторий по проекту располагается по адресу https://github.com/wizardjedi/my-spring-learning/tree/pres_0_8
Примеры использования классов из Guava приведены в соответствующих тестах
todo!
todo!
Часто встречающийся метод toString()
, который приходиться писать руками или генерировать с помощью IDE. Но в составе Guava есть helper'ы для безопасного создания строки представляющей объект.
@Override
public String toString() {
return
MoreObjects
.toStringHelper(this)
.add("field1", getField1())
.add("field2", getField())
.toString();
}
todo!
todo!
Часто встречаются задачи по ограничению количества вызовов (например, ораничение частоты запросов к странице или например ограничение отправки пакетов по сети). В состав guava входит класс RateLimiter
, который реализует функциональность по ограничению вызовов.
Для начала необходимо создать объект ограничителя:
RateLimiter rateLimiter = RateLimiter.create(10);
создан объект ограничителя со ограничением на 10 вызовов в секунду. Параметр в контрукторе - это double, соответственно можно передавать и дробные числа.
Далее в коде можно использовать как блокирующий вызов
rateLimiter.acquire();
так и не блокирующий
if (rateLimiter.tryAcquire()) {
. . .
}
в методы можно передавать необходимое количество "разрешений"
rateLimiter.acquire(2);
Guava предлагает абстракции для синхронизации и наблюдения за "задачами" в многопоточных приложениях.
Краеугольным камнем является понятие сервиса(интерфейс com.google.common.util.concurrent.Service
) и менеджера сервисов(com.google.common.util.concurrent.ServiceManager
).
Сервис может находит в одном из следующих состояний:
-
NEW
- только созданный, но незапущенный (неактивный) сервис -
STARTING
- сервис в процессе запуска -
RUNNING
- сервис работает -
STOPPING
- сервис в процессе остановки -
TERMINATED
- сервис остановлен -
FAILED
- произошла ошибка, сервис не может быть ни запущен, ни остановлен
Можно реализовать методы интерфейса Service
самому, но в состав guava входит несколько уже реализованных абстрактных классов для построения сервисов, например, AbstractExecutionThreadService
, который позволяет реализовывать сервисы работающие в одном потоке.
Для реализации сервиса на основе класса AbstractExecutionThreadService
необходимо реализовать метод run
(void run() throws Exception
). который отвечает за работу сервиса.
Типичный шаблон для этого метода:
public void run() throws Exception {
while (isRunning()) {
// Выполнение действий сервиса
}
}
Реализуем простой сервис и напишем тестовый код для запуска и остановки сервиса.
public class MyService extends AbstractExecutionThreadService {
@Override
protected void run() throws Exception {
while (this.isRunning()) {
// HEAVY UNIT OF WORK
}
}
}
Рассмотрим код тестового метода:
public void testService(){
MyService s1 = new MyService(); // Создаём объект сервиса
State state = s1.startAndWait(); // запустим сервис и дождёмся окончания инициализации, state хранит состояние сервиса
assertEquals(State.RUNNING, state); // состояние должно быть RUNNING
State state2 = s1.stopAndWait(); // отправим сигнал на останов и дождёмся остановки
assertEquals(State.TERMINATED, state2); // статус сервиса должен быть остановлен
}
Если сервис реализует некоторую логику при инициализации или остановке, то нам необходимо реализовать методы startUp()
и/или shutDown()
, который и будут реализовывать логику запуска и остановки сервиса.
Например, так:
@Override
protected void startUp() throws Exception {
super.startUp();
// some heavy work
Thread.sleep(2000);
}
Реализуем тестовый сервис, который будет "падать" в процессе запуска и напишем соответствующий тест:
public class MyService extends AbstractExecutionThreadService {
protected ServiceTest test;
protected boolean failOnStartup = false;
public MyService(ServiceTest test) {
this.test = test;
}
public MyService(ServiceTest test, boolean failOnStartup) {
this.test = test;
this.failOnStartup = failOnStartup;
}
@Override
protected void startUp() throws Exception {
super.startUp();
// some heavy work
Thread.sleep(2000);
if (this.failOnStartup) {
throw new Exception("Starting up failed");
}
}
@Override
protected void run() throws Exception {
while (this.isRunning()) {
synchronized(test) {
test.count++;
}
}
}
}
и код теста, который показывает асинхронный запуск сервиса:
public void testFailedService(){
MyService s1 = new MyService(this, true);
s1.start();
try {
Thread.sleep(3000);
} catch (InterruptedException ex) {
//
}
assertEquals(State.FAILED, s1.state());
}
Кроме синхронных проверок работы сервисов и статусов есть механизм слушателей событий. События генерируются в ответ на переход сервиса из состояние в состояние.
Реализуем слушатель событий, который будет устанавливать поля в классе теста, отвечающие за статус сервиса , асинхронно.
public class ServiceListener implements Service.Listener {
private ServiceTest test;
public ServiceListener(ServiceTest test) {
this.test = test;
}
@Override
public void starting() {
test.setServiceStarting(true);
}
@Override
public void running() {
test.setServiceRunning(true);
}
@Override
public void stopping(State from) {
test.setServiceStopping(true);
}
@Override
public void terminated(State from) {
test.setServiceTerminated(true);
}
@Override
public void failed(State from, Throwable failure) {
test.setServiceFailure(true);
}
}
В класс теста добавим следующие поля и соответствующие методы проверки и установки:
public volatile boolean serviceStarting = false;
public volatile boolean serviceRunning = false;
public volatile boolean serviceStopping = false;
public volatile boolean serviceTerminated = false;
public volatile boolean serviceFailure = false;
и реализуем метод startUp()
теста.
public void startUp() {
this.serviceFailure = false;
this.serviceRunning = false;
this.serviceStarting = false;
this.serviceStopping = false;
this.serviceTerminated = false;
}
Тестовый метод для проверки слушателя событий будет таким:
public void testServiceListener(){
MyService s1 = new MyService(this);
s1.addListener(new ServiceListener(this), MoreExecutors.sameThreadExecutor());
assertFalse(serviceFailure);
assertFalse(serviceStarting);
assertFalse(serviceRunning);
assertFalse(serviceStopping);
assertFalse(serviceTerminated);
s1.start();
assertFalse(serviceFailure);
assertTrue(serviceStarting);
assertFalse(serviceRunning);
assertFalse(serviceStopping);
assertFalse(serviceTerminated);
try {
Thread.sleep(3000);
} catch (InterruptedException ex) {
//
}
assertFalse(serviceFailure);
assertTrue(serviceStarting);
assertTrue(serviceRunning);
assertFalse(serviceStopping);
assertFalse(serviceTerminated);
s1.stop();
assertFalse(serviceFailure);
assertTrue(serviceStarting);
assertTrue(serviceRunning);
assertTrue(serviceStopping);
assertFalse(serviceTerminated);
try {
Thread.sleep(3000);
} catch (InterruptedException ex) {
//
}
assertFalse(serviceFailure);
assertTrue(serviceStarting);
assertTrue(serviceRunning);
assertTrue(serviceStopping);
assertTrue(serviceTerminated);
}
Чтобы "поймать" все состояния сервиса добавим задержку в метод shutDown()
нашего сервиса.
Обработка события FAILED
аналоична обработки события FAILED
для ServiceManager
'а и будет рассмотрена далее.
Также в состав Guava входит класс менеджера сервисов ServiceManager
, который позволяет работать с коллекциями сервисов.
Создадим менеджер сервисов, который будет обрабатывать коллекцию сервисов в виде множества(HashSet).
Set<Service> services = new HashSet<Service>();
MyService s1 = new MyService();
MyService s2 = new MyService();
MyService s3 = new MyService();
services.add(s1);
services.add(s2);
services.add(s3);
ServiceManager sm = new ServiceManager(services);
Запуск всех сервисов из коллекции осуществляется методом startAsync()
.
Сам менеджер сервисов может находится в одном из следующих псевдосостояний:
-
healthy
- все сервисы запустились успешно (методisHealthy()
) -
stopped
- все сервисы успешно остановлены -
fail
- произошла ошибка
Дополнительно менеджер сервисов предоставляет 2 метода для ожидания завершения процесса запуска и остановки:
-
awaitHealthy()
- дождаться окончания процесса запуска всех сервисов -
awaitStopped()
- дождаться окончания процесса остановки всех сервисов
Ниже представлен простой тест для запуска менеджера сервисов.
public void testListenerServiceManager() {
Set<Service> services = new HashSet<Service>();
MyService s1 = new MyService(this);
services.add(s1);
ServiceManager sm = new ServiceManager(services);
// register listener to service manager
sm.addListener(new ServiceManagerListener(this), MoreExecutors.sameThreadExecutor());
// check preconditions
assertFalse(this.serviceManagerFailure);
assertFalse(this.serviceManagerStopped);
assertFalse(this.serviceManagerHealthy);
// start services and wait all services to be started
sm.startAsync().awaitHealthy();
// wait to call listener
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
// check conditions
assertFalse(this.serviceManagerFailure);
assertFalse(this.serviceManagerStopped);
assertTrue(this.serviceManagerHealthy);
// simulate some work
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
}
assertTrue(s1.isRunning());
// stop all services and wait all to be stopped
sm.stopAsync().awaitStopped();
// wait to run listener
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
// check conditions
assertFalse(s1.isRunning());
assertFalse(this.serviceManagerFailure);
assertTrue(this.serviceManagerStopped);
assertTrue(this.serviceManagerHealthy);
}
Дополнительно есть возможность задать слушатель событий менеджера сервисов.
sm.addListener(new ServiceManagerListener(this), MoreExecutors.sameThreadExecutor());
Сам слушатель событий выглядит так:
class ServiceManagerListener implements ServiceManager.Listener{
private ServiceTest test;
public ServiceManagerListener(ServiceTest test) {
this.test = test;
}
@Override
public void healthy() {
test.setServiceManagerHealthy(true);
}
@Override
public void stopped() {
test.setServiceManagerStopped(true);
}
@Override
public void failure(Service service) {
test.setServiceManagerFailure(true);
}
}
Тест для данного слушателя событий:
public void testFailedListenerServiceManager() {
Set<Service> services = new HashSet<Service>();
MyService s1 = new MyService(this, true);
services.add(s1);
ServiceManager sm = new ServiceManager(services);
// register listener to service manager
sm.addListener(new ServiceManagerListener(this), MoreExecutors.sameThreadExecutor());
// check preconditions
assertFalse(this.serviceManagerFailure);
assertFalse(this.serviceManagerStopped);
assertFalse(this.serviceManagerHealthy);
// start services and wait all services to be started
sm.startAsync();
// wait to start and call listener
try {
Thread.sleep(3000);
} catch (InterruptedException ex) {
}
// check conditions
assertTrue(this.serviceManagerFailure);
assertTrue(this.serviceManagerStopped);
assertFalse(this.serviceManagerHealthy);
assertFalse(s1.isRunning());
assertEquals(Service.State.FAILED, s1.state());
}