Skip to content

Google guava

Sayapin Alexander edited this page Jul 14, 2015 · 17 revisions

Существует набор библиотек от 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 приведены в соответствующих тестах

Интервалы и проверка на вхождение в интервалы (Ranges)

todo!

Строки

todo!

Объекты

Helper'ы для стандартных методов

toString()

Часто встречающийся метод toString(), который приходиться писать руками или генерировать с помощью IDE. Но в составе Guava есть helper'ы для безопасного создания строки представляющей объект.

@Override
public String toString() {
    return
        MoreObjects
            .toStringHelper(this)
            .add("field1", getField1())
            .add("field2", getField())
            .toString();
}

equals()

todo!

hashCode()

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());
}
Clone this wiki locally