В предыдущей части CDI мы видели некоторые инъекции, квалификаторы и область применения. Теперь пришло время просмотреть более продвинутые функции.
Производители
Предыдущие примеры не могут разрешить все наши варианты использования. Некоторые из них включают в себя:
- введение случайных значений
- внедрение контекстно-зависимого значения
- в общем, места, где процесс впрыска не может быть сужен до простого new ()
Эти намеки на очень известный шаблон, заводской. Заводы внедрены в JSR-299 как производители.
Давайте рассмотрим простой пример — внедрение соединения из источника данных. Код, который получает соединение, либо создает его с прямым подключением к базе данных, либо извлекает его из пула источников данных. В последнем случае подойдет следующий код:
public @interface FromDataSource {}
public class ConnectionProducer {
@Produces @FromDataSource
public Connection getConnection() throws Exception {
Context ctx = new InitialContext();
// Read the data source name from web.xml
String name = ...
DataSource ds = (DataSource) ctx.lookup(name);
return ds.getConnection();
}
}
Перехватчики
С Java EE 6 вы можете использовать возможности AOP без AOP. Как и в предыдущем примере, использование перехватчиков очень просто. Есть 3 шага. Давайте реализуем простой таймер для целей бенчмаркинга.
Первым шагом является объявление перехватчика. Для этого просто используйте @InterceptorBinding:
@InterceptorBinding
@Retention(RUNTIME)
@Target({METHOD, TYPE})
public @interface Benchmarkable{}
Второй шаг — реализация перехватчика. Он использует аннотацию @Interceptor в сочетании с ранее определенной:
@Benchmarkable @Interceptor</pre>
public class BenchmarkInterceptor {
@AroundInvoke
public Object logPerformance(InvocationContext ic) throws Exception {
long start = System.currentTimeMillis();
Object value = ic.proceed();
System.out.println(System.currentTimeMillis() - start);
return value;
}
}
Примечание:
- метод, аннотированный @AroundInvoke, возвращает объект
- он использует параметр типа InvocationContext
Последний шаг — объявить такие перехватчики в WEB-INF / beans.xml, потому что перехватчики по умолчанию отключены.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
<interceptors>
<class>ch.frankel.blog.weld.BenchmarkInterceptor</class>
</interceptors>
</beans>
Bean.xml также сообщает контейнеру, как упорядочить перехватчики, если их больше одного.
Есть два других типа перехватчиков, @PostConstruct и @AroundTimeout (для EJB).
Декораторы
Декораторы, думаю , что, реализовать шаблон проектирования Decorator . Они очень похожи на перехватчики с двумя интересными отличиями:
- декоратор должен реализовывать интерфейс, который он украшает (и все же может быть абстрактным, поэтому он не должен реализовывать методы)
- декоратор может иметь ссылку на объект, который он украшает. Это делается через инъекцию
Как и перехватчики, они должны быть указаны в файле beans.xml для активации. Давайте возьмем простой пример и создадим интерфейс, контракт которого должен возвращать HTML-представление объекта:
public interface Htmlable {
String toHtml();
}
Теперь мне нужен класс даты, который знает свое представление HTML. Я знаю, что дизайн довольно плох, но терпите меня.
public class HtmlDate extends Date implements Htmlable {
public String toHtml() {
return toString();
}
}
Если я хочу декоратор, который помещает HTML-код в теги <strong>, вот способ:
@Decorator
public class StrongDecorator implements Htmlable {
@Inject @Delegate @Any
private Htmlable html;
public String toHtml() {
return "<strong>" + html.toHtml() + "</strong>";
}
}
Наблюдатели
CDI также реализует шаблон проектирования Observer , таким образом, наконец, обеспечивая простую парадигму разработки на основе событий на платформе Java EE. Основой для этого является тип события. Тип события — это простой POJO.
Observer также является POJO: чтобы метод Observer вызывался при возникновении события, просто добавьте параметр правильного типа события и аннотируйте его с помощью @Observed:
public class EventObserverService {
public void afterPostEvent(@Observes PostEvent event) {
... // Do what must be done
}
}
С другой стороны, источник событий должен иметь атрибут типа javax.enterprise.Event, параметризованный с тем же типом события. Чтобы вызвать событие, вызовите event.fireEvent () с экземпляром события:
public class WeldServlet extends HttpServlet {
@Inject
private Event<PostEvent> event;
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
event.fire(new PostEvent());
}
}
Теперь при отправке запроса POST сервлету будет вызван метод afterPostEvent () службы EventObserverService.
альтернативы
В предыдущей статье я обращался к фиктивному сервису с вызовом установщика и передачей вновь созданного экземпляра «вручную». Это все хорошо в модульном тестировании, но я также хочу управлять интеграционным тестированием. Таким образом, ситуация следующая:
- на пути к классам есть две реализации одного и того же интерфейса
- Вы не можете изменить код сервлета (например, добавить квалификатор в атрибут сервиса)
Учитывая детерминистическую природу CDI, вы должны быть в основном тостами. На самом деле, ничто не может быть дальше от истины. Просто используйте аннотацию @Alternative, и CDI будет удобно игнорировать аннотированный класс.
@Report(MAIL) @Alternative
public class MockMailReportServiceImpl implements ReportService {
...
}
Какой смысл тогда создавать его в первую очередь? Запомните неиспользуемый до. Он придет к нам на помощь, так как он принимает теги <alternative>. Эти теги активируют альтернативы.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
<alternatives>
<class>ch.frankel.blog.weld.service.MockMailReportServiceImpl</class>
</alternatives>
</beans>
Таким образом, вы можете иметь два beans.xml:
- в основном пустой стандартный контекст
- и другой контекст тестирования интеграции, полный альтернатив
Платформа
Эта статья была написана с использованием GlassFish v3, который использует Weld v1.0.1 в качестве платформы. Weld — это эталонная реализация CDI, а также часть структуры Seam.
У меня не было проблем с использованием платформы в целом, но я не мог заставить работать альтернативы, перехватчики и декораторы. Как ни странно, все три должны быть настроены в WEB-INF / beans.xml. Я не знаю, сделал ли я что-то не так или есть ошибка в текущей реализации.
Вывод
Эта статья, состоящая из двух частей, наносится только на поверхность CDI. Тем не менее, ИМХО, это выглядит очень перспективно, и я желаю ему больших успехов.
Чтобы идти дальше:
- CDI, обзор — часть 1
- Страница аннотаций Commons (JSR-250) : аннотации Commons содержат аннотацию для DI в Java (@Resource)
- Страница CDI (JSR-299) : достаточно удивительно, CDI о DI в Java EE
- Weld в документации : Weld является реализация CDI JBoss , а также эталонная реализация
- Статья о достоинствах JSR-299 по сравнению с достоинствами JSR-330