Как стать автором
Обновить

@Autowired для сервлетов в OSGi-контейнере

Время на прочтение4 мин
Количество просмотров3.3K
Вместо введения отправляю читателя к отличной статье Использование Spring в OSGi-контейнере которая и послужила отправной точкой для практического изучения.
Итак, к делу. Рассмотрим классический вариант — есть бизнес-логика приложения и она как-то
взаимодействует с внешним миром. Используем такую связку:
клиент <-> транспорт <-> приемник <-> сериализатор/десериализатор <-> метод бизнес логики.
Сериализатор напрашивается заменяемым модулем, например сериализация в JSON или сериализация в XML.
Про бизнес-логику далее можно забыть, и сосредоточиться на связке приемника и сериализатора.
В качестве приемника используем сервлет, а для сериализатора, для простоты, используем реализацию следующего интерфейса:

interface ISerializer {
String selialize(Object obj);
}

Для связывания компонентов используем SpringFramework. Возвращаясь опять же к ссылке в начале статьи, где инъекция интерфейса строится на аннотациях, сервлет описывается примерно так
@Component
public class TestServlet extends HttpServlet {
@Autowired
private ISerializer serializer;

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
// ... десериализация и вызов метода БЛ
// в ответ получили результат как Object result;

res.setContentType("text/plain");
PrintWriter out = res.getWriter();
out.println(serializer.serialize(obj));
}
}

Оговорюсь, что это надуманный и не оптимальный пример использования сериализации, но
связывание сериализатора с сервлетом вполне правдоподобно.
Запускалось все на сервере Apache Felix с установленными модулями gemini-blueprint и spring.

[INFO] Started jetty 6.1.x at port(s) HTTP:8080
____________________________
Welcome to Apache Felix Gogo

g! lb
START LEVEL 1
ID|State |Level|Name
0|Active | 0|System Bundle (4.0.1)
1|Active | 1|AOP Alliance API (1.0.0)
2|Active | 1|Apache Log4J (1.2.16)
3|Active | 1|SLF4J API (1.6.1)
4|Resolved | 1|SLF4J Log4J Binding (1.6.1)
5|Active | 1|SLF4J Jakarta Commons Logging Over SLF4J Binding (1.6.1)
6|Active | 1|gemini-blueprint-core (1.0.0.RELEASE)
7|Active | 1|gemini-blueprint-extender (1.0.0.RELEASE)
8|Active | 1|gemini-blueprint-io (1.0.0.RELEASE)
9|Active | 1|Apache Felix Bundle Repository (1.6.6)
10|Active | 1|Apache Felix Gogo Command (0.12.0)
11|Active | 1|Apache Felix Gogo Runtime (0.10.0)
12|Active | 1|Apache Felix Gogo Shell (0.10.0)
13|Active | 1|Apache Felix Http Jetty (2.2.0)
14|Active | 1|Apache Felix Http Whiteboard (2.2.0)
15|Active | 1|Spring AOP (3.1.0.RC1)
16|Active | 1|Spring ASM (3.1.0.RC1)
17|Active | 1|Spring Aspects (3.1.0.RC1)
18|Active | 1|Spring Beans (3.1.0.RC1)
19|Active | 1|Spring Context (3.1.0.RC1)
20|Active | 1|Spring Context Support (3.1.0.RC1)
21|Active | 1|Spring Core (3.1.0.RC1)
22|Active | 1|Spring Expression Language (3.1.0.RC1)

Лирическое отступление. Веб-сервер Jetty в поставке с феликсом 4.0.1 идет версией 6.1, а значит о java-ee-6 с аннотацией к сервлету @WebServlet можно забыть. Собирать отдельно Jetty 8 не было желания, а с ходу запустить не удалось.
Итак, к моему удивлению после сборки и установки пакета получил в ответ от Jetty ошибку 404 и свалил все на отсутствие маппинга.
Гугление помогло и для старта сервлета был написан класс активатор:
public class Activator implements BundleActivator {
private ServiceRegistration reg;

@Override
public void start(BundleContext context) throws Exception {
Dictionary<String, String> props = new Hashtable<>();
props.put("alias", "/");
this.reg = context.registerService(Servlet.class.getName(), new TestServlet(), props);
}

@Override
public void stop(BundleContext context) throws Exception {
this.reg.unregister();
}
}

Проект пересобран, запущен.
Пол дела сделано, сервлет стартует и отзывается, но переменная selializator неутешительно указывает на null.
Стоит заметить, что аннотация @Component на сервлете тоже оказала свое влияние. Забегая вперед скажу, что на самом деле создавалось два экземпляра сервлета. Один создавался по аннотации и даже связывался с сериализатором, а второй честно создавался в активаторе и аннотация @Autowired не работала. Поиск сервиса реализующего ISerializer из активатора не увенчались успехом, т.к. вызов активатора проходил раньше, чем нужный мне сервис регистрировался в системе.
Впереди ждало очередное гугление, которое в конечном итоге и позволило решить проблему.
Если класс описан с аннотацией @Component, то к одному из методов класса можно добавить аннотацию @PostConstruct (на метод существуют ограничения, см. документацию к аннотации). В кратце, такой метод вызывается после всех инъекций зависимостей.
Итак, добавляем метод, который зарегистрирует сервлет в Jetty.
@PostConstruct
public void postConstruct() {
BundleContext context = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
Dictionary props = new Properties();
props.put("alias", "/");
context.registerService(Servlet.class.getName(), this, props);
}

В туториалах по связкам servlet-OSGi написано, что если регистрировать сервлет самостоятельно,
но необходимо при выгрузке модуля и резрегистрировать его самостоятельно:
// регистрация
ServiceRegistration reg = context.registerService(Servlet.class.getName(), this, props);

// разрегистирация
reg.unregister();

В моем случае вызов разрегистрации (пытался позвать из метода destroy() сервлета) приводила к
ошибке, которая говорила, что сервис уже не в том статусе, т.е. уже разрегистрирован. Видимо
об этом заботится аннотация @Component. На этом я и успокоился.
В классе-активаторе теперь нет необходимости. В итоге получился сервлет, в который можно заинъектить
необходимую реализацию. И все это без утомительных настроечных XML-файлов.
Надеюсь, что данная статья кому-либо поможет и избавит от долгих поисков информации в нете.

Дополнительные источники


Архив с примером
Теги:
Хабы:
Всего голосов 12: ↑10 и ↓2+8
Комментарии2

Публикации