Как интегрировать JAX-RS с CDI в контейнере сервлета 3.0
У меня есть веб-приложение, работающее на контейнере сервлета 3.0 (Jetty 9.0.4) с использованием JSF 2.2 (Mojorra 2.1.3) и CDI 1.1 (Weld 2.0.3). Полноценный сервер приложений не используется. В этом приложении у меня также есть класс ресурсов JAX-RS 2.0 (Jersey 2.2), обслуживающий запросы REST. Я интегрировал привязку JAXB, а также маршалинг JSON (Jackson 2.2). Я использую Maven 3.0.5 для управления сборкой. Вот соответствующие части настройки моего проекта:
Maven ПФЛ.xml:
...
<dependencies>
<!-- Servlet 3.0 API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Contexts and Dependency Injection for Java EE -->
<dependency>
<groupId>org.jboss.weld.servlet</groupId>
<artifactId>weld-servlet</artifactId>
<version>2.0.3.Final</version>
</dependency>
<!-- JavaServer Faces -->
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.2.2</version>
</dependency>
<!-- JAX-RS RESTful Web Services -->
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>2.2</version>
</dependency>
<!-- JSON Mapping Framework -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.2</version>
</dependency>
</dependencies>
...
Дескриптор развертывания web.xml:
...
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/jsf/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>Jersey REST Service</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>my.package.config.RestApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey REST Service</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<listener>
<listener-class>org.jboss.weld.environment.servlet.Listener</listener-class>
</listener>
<listener>
<listener-class>com.sun.faces.config.ConfigureListener</listener-class>
</listener>
<resource-env-ref>
<description>Object factory for the CDI Bean Manager</description>
<resource-env-ref-name>BeanManager</resource-env-ref-name>
<resource-env-ref-type>javax.enterprise.inject.spi.BeanManager</resource-env-ref-type>
</resource-env-ref>
...
Класс корневых ресурсов JAX-RS:
@Path("/person")
public class PersonController
{
@Inject
private PersonService personService;
@GET
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public List<Person> getAllPersons()
{
return personService.getAll();
}
@GET
@Path("/{index}")
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Person getPerson(@PathParam("index") int index)
{
return personService.get(index);
}
@POST
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public void savePerson(Person person)
{
personService.add(person);
}
}
Конфигурация приложения JAX-RS:
public class RestApplication extends ResourceConfig
{
public RestApplication()
{
// For JSON binding
register(new JacksonFeature());
register(new ApplicationBinder());
packages("my.package.controller");
}
}
Конфигурация привязки впрыска JAX-RS:
public class ApplicationBinder extends AbstractBinder
{
@Override
protected void configure()
{
// Means something like: bind the field at an injection point of type PersonService to an instance of type PersonService
bind(PersonService.class).to(PersonService.class);
}
}
И, наконец, управляемый Боб JSF:
@Named
@SessionScoped
public class PersonBean implements Serializable
{
private static final long serialVersionUID = 1L;
@Inject
private PersonService personService;
private Person newPerson = new Person();
public List<Person> getAll()
{
return personService.getAll();
}
public Person getNewPerson()
{
return newPerson;
}
public void setNewPerson(Person newPerson)
{
this.newPerson = newPerson;
}
public Gender[] getGenders()
{
return Gender.values();
}
public String saveNewPerson()
{
personService.add(newPerson);
newPerson = new Person();
return "index";
}
}
В конце концов, я хочу иметь возможность использовать те же экземпляры служб с областью применения в классах ресурсов REST, а также в бобах JSF, но я не могу заставить CDI и JAX-RS работать вместе.
Часть JSF/CDI работает нормально, но инъекция в остальные классы ресурсов на самом деле не работает. Я читал некоторые статьи, где они показали два различных подхода к объединению CDI и JAX-RS: первый-это аннотирование класса ресурсов REST с помощью @ManagedBean
, чтобы класс был создан контейнером CDI и управлялся контейнером JAX-RS:
@ManagedBean
@Path("/person")
public class PersonController
{
@Inject
private PersonService personService;
...
Второй подход заключается в том, чтобы дать классу область CDI, например @RequestScoped
, чтобы класс, который будет создан и управляться контейнером CDI.
@Path("/person")
@RequestScoped
public class PersonController
{
@Inject
private PersonService personService;
...
Ни один из подходов не работает для меня. Я всегда заканчиваю со следующим исключением:
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at Injectee(requiredType=PersonService,parent=PersonController,qualifiers={}),position=-1,optional=false,self=false,unqualified=null,5643079)
at org.jvnet.hk2.internal.ThreeThirtyResolver.resolve(ThreeThirtyResolver.java:74)
at org.jvnet.hk2.internal.ClazzCreator.resolve(ClazzCreator.java:208)
at org.jvnet.hk2.internal.ClazzCreator.resolveAllDependencies(ClazzCreator.java:231)
at org.jvnet.hk2.internal.ClazzCreator.create(ClazzCreator.java:328)
at org.jvnet.hk2.internal.SystemDescriptor.create(SystemDescriptor.java:454)
at org.glassfish.jersey.process.internal.RequestScope.findOrCreate(RequestScope.java:158)
at org.jvnet.hk2.internal.Utilities.createService(Utilities.java:2296)
at org.jvnet.hk2.internal.ServiceLocatorImpl.getService(ServiceLocatorImpl.java:590)
at org.jvnet.hk2.internal.ServiceLocatorImpl.getService(ServiceLocatorImpl.java:577)
at org.glassfish.jersey.internal.inject.Injections.getOrCreate(Injections.java:172)
Но эта ошибка исчезает при изменении конфигурации привязки инъекции на:
bind(PersonServiceImpl.class).to(PersonService.class);
Теперь инъекция каким-то образом работает, но для каждого запроса REST я получаю новый экземпляр PersonServiceImpl
, даже если эта служба имеет область применения. Для меня это показатель того, что компонент JAX-RS полностью отделенный от CDI материала и живет в совершенно другой среде или контейнере, как CDI / JSF материал делает.
3 ответа:
Я решил свою проблему.
Проблема заключается в том, что реализация JAX-RS использует структуру внедрения зависимостей HK2, и эта структура просто не знает о компонентах CDI. И следуя идее принятого ответа в этом посте , я делаю бобы CDI доступными для инъекционных Привязок HK2, и инъекция моих бобов с областью применения теперь работает нормально.
Но я действительно удивляюсь, почему это так громоздко объединить две составные части Java EE.
Обновление: Как отметил Г. Демецки в комментарии, это решение больше не нужно! Но это помогло мне в тот момент, когда я задал этот вопрос.
Вы можете просто включить эту зависимость, чтобы включить интеграцию Джерси-CDI
<dependency> <groupId>org.glassfish.jersey.containers.glassfish</groupId> <artifactId>jersey-gf-cdi</artifactId> <version>2.2</version> </dependency>
Он работает для меня, используя Jersey 2.8, Jetty 9.1 и Weld 2.2.0.С пакетом обновления 1.
Я создал примерный проект со встроенными Jetty, Jersey 2 и CDI https://drive.google.com/file/d/0B9j0dIS5bS0wSEg0NTk2eGpPSGs/view?usp=sharing
Пожалуйста, дайте мне знать, если вы найдете какие-либо проблемы