Dependency Injection в Java на примере эволюции Spring — Guice — CDI/Weld
description
Transcript of Dependency Injection в Java на примере эволюции Spring — Guice — CDI/Weld
Dependency Injection в Java
на примере эволюции
Spring – Guice – CDI/Weld
Сергей Кошель
Ведущий разработчик Java
23 мая 2013 года
Разрабатываем адаптер
Преобразование
2/36
Первая версия
SimpleConverter
FileSource DatabaseStorage
Adapter
3/36
Первая версия
+ Работать будет
− Невозможно написать 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
Выделяем интерфейсы
MockConverter
MockSource MockStorage
FileSource
SimpleConverter
DatabaseStorage
Source
Converter
Storage
Adapter 5/36
И добавляем фабрики
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
Пишем тест
@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
Избавляемся от фабрик
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
Переписываем тест
@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
Последний штрих
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
Dependency Injection (DI)
Паттерн проектирования (design pattern)
О компонентах и их зависимостях
Позволяет отделить объявление
зависимости от разрешения зависимости
(и в пространстве, и во времени)
Является частью более общего принципа
Inversion of Control (Hollywood principle –
«Don't call us, we'll call you».)
11/36
Причем тут тесты?
Тесты не самоцель, но…
С одной стороны, практически,
DI позволяет проще писать
тестопригодный код
А с другой стороны, концептуально,
тесторигодность кода является
индикатором хорошей слабосвязанной
архитектуры (loose coupling)
В конечном итоге DI помогает удобнее
писать слабосвязный код
12/36
Можно было пойти другим путем
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
Паттерны DI и SL
часто противопоставляются
DI зависимости определяет статически
Проще разобраться в связях
Компилятор многое может проверить
и подсказать
Но иногда это является ограничением
SL – динамически
Взаимосвязи запутаны, проще ошибиться
Но иногда без этого не обойтись
14/36
IoC-контейнер
Автоматизирует DI
Разрешает граф зависимостей
Конструирует компоненты по метаописанию
зависимостей
И привносит еще много полезностей
Управление жизненным циклом
Управление конфигурацией
AOP
…
15/36
Disclaimer
Автор не в коем случае не имеет
цели принизить один фреймворк
за счет другого
16/36
Spring Framework
17/36
Описываем зависимости
<?xml version="1.0" encoding="UTF-8"?> <beans …> <bean id="adapter" class="custis.seminars.diinjava.spring.Adapter" > <property name="source" ref="source"/> <property name="converter" ref="converter"/> <property name="storage" ref="storage"/> </bean> <bean id="source" class="custis.seminars.diinjava.spring.FileSource" /> <bean id="converter" class="custis.seminars.diinjava.spring.SimpleConverter" /> <bean id="storage" class="custis.seminars.diinjava.spring.DatabaseStorage" /> </beans>
* Похоже на императив, но это декларатив 18/36
Запускаем контейнер
final ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml"); adapter = applicationContext.getBean("adapter", Adapter.class);
+ Код адаптера не изменился
+ …нет зависимости от Spring’а
+ …не надо его писать в каком-либо специальном стиле
19/36
Autowiring
<bean id="adapter" class="custis.seminars.diinjava.autowiring.Adapter" autowire="byType" />
20/36
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
Метаданные в компоненте
@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/36
<bean id="source" class="custis.seminars.diinjava.autowiring.FileSource" /> @Autowired @Qualifier("source") public void setSource(Source source) {…}
− Легко ошибиться, и проявится это только в рантайме
Google Guice
24/36
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
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
Pure Java config
public class AdapterModule extends AbstractModule { @Override protected void configure() {…} @Provides Source fileSource() { return new FileSource(); } }
* В Spring 3.0 появился JavaConfig
27/36
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
Provider interface
Когда нужно…
отложить создание (тяжелое, условное)
много экземпляров (the new «new»)
вложить более узкий скоуп в широкий
29/36
Выбор
между несколькими реализациями
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 – компилятор проверит
CDI/Weld
JSR 299: Contexts and Dependency Injection
for the Java EE platform
Weld – reference implementation
for JSR-299
31/36
CDI
Конфигурация похожа на Guice
Нет DSL — используется @Produce и сканирование classpath
Стандартизирует @Inject, @Sengleton,
Provider<T> и т. д.
Тесно интегрируется с EJB-контейнером
32/36
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
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
О чем не рассказал
AOP и method intercepting
Генерализованные типы зависимостей
Многозначные зависимости
Scopes
…
35/36