Открытый семинар для студентов в компании CUSTIS (23 мая 2013 года).
Лектор: Сергей Кошель, ведущий разработчик Java, аналитик.
Аннотация: Из этого семинара вы узнаете о практическом применении паттерна Dependency Injection в мире Java и предоставляемых им возможностях на примере развития DI-фреймворков: от Spring и Guice до CDI/Weld. Формат встречи – динамичный с элементами Live Coding и демонстрацией особенностей реализации.
Видеозапись семинара: https://vimeo.com/67125102.
4. Первая версия
+ Работать будет
− Невозможно написать unit-тест
public class Adapter {
public void processMessage() {
final FileSource fileSource = new FileSource();
final SimpleConverter simpleConverter = new SimpleConverter();
final DatabaseStorage databaseStorage = new DatabaseStorage();
final Message inputMessage = fileSource.getMessage();
final Message convertedMessage = simpleConverter.convert(inputMessage);
databaseStorage.store(convertedMessage);
}
}
4/36
6. И добавляем фабрики
public class Adapter {
public void processMessage() {
final Source source = SourceFactory.getSource();
final Converter converter = ConverterFactory.getConverter();
final Storage storage = StorageFactory.getStorage();
final Message inputMessage = source.getMessage();
final Message convertedMessage = converter.convert(inputMessage);
storage.store(convertedMessage);
}
}
6/36
7. Пишем тест
@Test
public void processMessage() throws Exception {
SourceFactory.setSource(new MockSource("Hello from test!"));
StorageFactory.setStorage(new MockStorage());
final Adapter adapter = new Adapter();
adapter.processMessage();
// assert that...
}
+ Получилось написать тест
− Статический (глобальный) контекст
− Много бойлерплейта
− Зависимости неочевидны
7/36
8. Избавляемся от фабрик
public class Adapter {
private Source source;
private Converter converter;
private Storage storage;
public void setSource(Source source) {…}
public void setConverter(Converter converter) {…}
public void setStorage(Storage storage) {…}
public void processMessage() {
final Message inputMessage = source.getMessage();
final Message convertedMessage = converter.convert(inputMessage);
storage.store(convertedMessage);
}
}
8/36
9. Переписываем тест
@Test
public void processMessage() throws Exception {
final Adapter adapter = new Adapter();
adapter.setSource(new MockSource("Hello from test!"));
adapter.setConverter(new SimpleConverter());
adapter.setStorage(new MockStorage());
adapter.processMessage();
// assert that...
}
+ Получилось:
Setter based Dependency Injection by Hand
9/36
10. Последний штрих
public class Adapter {
private final Source source;
private final Converter converter;
private final Storage storage;
public Adapter(Source source, Converter converter, Storage storage) {
this.source = source;
this.converter = converter;
this.storage = storage;
}
public void processMessage() {…}
}
10/36
11. Dependency Injection (DI)
Паттерн проектирования (design pattern)
О компонентах и их зависимостях
Позволяет отделить объявление
зависимости от разрешения зависимости
(и в пространстве, и во времени)
Является частью более общего принципа
Inversion of Control (Hollywood principle –
«Don't call us, we'll call you».)
11/36
12. Причем тут тесты?
Тесты не самоцель, но…
С одной стороны, практически,
DI позволяет проще писать
тестопригодный код
А с другой стороны, концептуально,
тесторигодность кода является
индикатором хорошей слабосвязанной
архитектуры (loose coupling)
В конечном итоге DI помогает удобнее
писать слабосвязный код
12/36
13. Можно было пойти другим путем
public class Adapter {
public void processMessage() {
final Source source = UniversalFactory.get("source", Source.class);
final Converter converter = UniversalFactory.get("converter",
Converter.class);
final Storage storage = UniversalFactory.get("storage", Storage.class);
final Message inputMessage = source.getMessage();
final Message convertedMessage = converter.convert(inputMessage);
storage.store(convertedMessage);
}
}
+ Получилось: Service Locator
13/36
14. Паттерны DI и SL
часто противопоставляются
DI зависимости определяет статически
Проще разобраться в связях
Компилятор многое может проверить
и подсказать
Но иногда это является ограничением
SL – динамически
Взаимосвязи запутаны, проще ошибиться
Но иногда без этого не обойтись
14/36
15. IoC-контейнер
Автоматизирует DI
Разрешает граф зависимостей
Конструирует компоненты по метаописанию
зависимостей
И привносит еще много полезностей
Управление жизненным циклом
Управление конфигурацией
AOP
…
15/36
16. Disclaimer
Автор не в коем случае не имеет
цели принизить один фреймворк
за счет другого
16/36
19. Запускаем контейнер
final ApplicationContext applicationContext
= new ClassPathXmlApplicationContext("spring-config.xml");
adapter = applicationContext.getBean("adapter", Adapter.class);
+ Код адаптера не изменился
+ …нет зависимости от Spring’а
+ …не надо его писать в каком-либо
специальном стиле
19/36
21. Lifecycle Management
singleton – создается один экземпляр
prototype – создается отдельный экземпляр
при каждом обращении
<bean id="adapter" class="custis.seminars.diinjava.autowiring.Adapter"
autowire="byType"
scope="singleton"
init-method="init"
destroy-method="close" />
[…]
applicationContext.destroy();
21/36
22. Метаданные в компоненте
@Scope(SCOPE_SINGLETON)
public class Adapter {
private Source source;
private Converter converter;
private Storage storage;
@Autowired
public void setSource(Source source) {…}
@Autowired
public void setConverter(Converter converter) {…}
@Autowired(required = false)
public void setStorage(Storage storage) {…}
@PostConstruct
public void init() {…}
@PreDestroy
public void close() {…}
} 22/36
23. Выбор
между несколькими реализациями
23/36
<bean id="source" class="custis.seminars.diinjava.autowiring.FileSource" />
@Autowired
@Qualifier("source")
public void setSource(Source source) {…}
− Легко ошибиться, и проявится это только в рантайме
25. Pure Java config
public class AdapterModule extends AbstractModule {
@Override
protected void configure() {
bind(SimpleConverter.class);
bind(Source.class).to(FileSource.class);
bind(Storage.class).toInstance(new DatabaseStorage());
}
}
final Injector injector = Guice.createInjector(new AdapterModule());
adapter = injector.getInstance(Adapter.class);
25/36
26. Annotation based
@Singleton
public class Adapter {
private final Source source;
private final Converter converter;
private final Storage storage;
@Inject
public Adapter(Source source, Converter converter, Storage storage) {
this.source = source;
this.converter = converter;
this.storage = storage;
}
public void processMessage() {…}
}
* Зависимость от аннотаций, но они стандартные
26/36
27. Pure Java config
public class AdapterModule extends AbstractModule {
@Override
protected void configure() {…}
@Provides
Source fileSource() {
return new FileSource();
}
}
* В Spring 3.0 появился JavaConfig
27/36
28. Provider interface
public interface Provider <T> {
T get();
}
bind(Validator.class).to(SimpleValidator.class);
public class Adapter {
private Provider<Validator> validator;
@Inject
public void setValidator(Provider<Validator> validator) {…}
public void processMessage() {
...
validator.get().validate(inputMessage);
...
}
}
28/36
29. Provider interface
Когда нужно…
отложить создание (тяжелое, условное)
много экземпляров (the new «new»)
вложить более узкий скоуп в широкий
29/36
30. Выбор
между несколькими реализациями
30/36
@BindingAnnotation
@Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)
public @interface FileBased {}
@Inject
public Adapter(@FileBased Source source, Converter converter, Storage storage) {…}
bind(Source.class).annotatedWith(FileBased.class).to(FileSource.class);
+ Typesafe – компилятор проверит
31. CDI/Weld
JSR 299: Contexts and Dependency Injection
for the Java EE platform
Weld – reference implementation
for JSR-299
31/36
32. CDI
Конфигурация похожа на Guice
Нет DSL — используется @Produce
и сканирование classpath
Стандартизирует @Inject, @Sengleton,
Provider<T> и т. д.
Тесно интегрируется с EJB-контейнером
32/36
33. Instance – Provider на стероидах
Расширяет возможности Provider
Instance<T> extends Provider<T>
…опциональные зависимости
if (instance.isUnsatisfied()) {…}
…многозначные зависимости
if (instance.isAmbiguous()) {
for (T t : instance) {...}
}
…динамическое разрешение зависимостей (SL)
adapter = instance.select(Adapter.class).get();
33/36
34. Event<T>
public class Adapter {
@Inject
Event<AdapterStarted> adapterStartedEvent;
@PostConstruct
public void init() {
adapterStartedEvent.fire(new AdapterStarted());
}
}
public class AnyOtherManagedBean{
public void onAdapterStart(
@Observes AdapterStarted adapterStarted) {…}
}
34/36
35. О чем не рассказал
AOP и method intercepting
Генерализованные типы зависимостей
Многозначные зависимости
Scopes
…
35/36