С выпуском Apache CXF 3.0 пару недель назад, проект делает еще один важный шаг для выполнения требований спецификации JAX-RS 2.0: интеграция с CDI 1.1 . В этом сообщении мы рассмотрим несколько примеров совместной работы Apache CXF 3.0 и Apache CXF 3.0 .
Начиная с версии 3.0 , Apache CXF включает новый модуль с именем cxf -gration-cdi, который можно легко добавить в POM-файл Apache Maven :
|
1
2
3
4
5
|
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-integration-cdi</artifactId> <version>3.0.0</version></dependency> |
Этот новый модуль содержит всего два компонента (на самом деле, немного больше, но это ключевые):
- CXFCdiServlet : сервлет для загрузки приложения Apache CXF , служащий той же цели, что и CXFServlet и CXFNonSpringJaxrsServlet ,…
- JAXRSCdiResourceExtension : портативное расширение CDI 1.1, где происходит вся магия
При запуске в среде с поддержкой CDI 1.1 переносимые расширения обнаруживаются контейнером CDI 1.1 и инициализируются с использованием событий жизненного цикла. И это буквально все, что вам нужно! Давайте посмотрим на настоящее приложение в действии.
Мы собираемся создать очень простое приложение JAX-RS 2.0 для управления людьми, используя Apache CXF 3.0 и JBoss Weld 2.1 , эталонную реализацию CDI 1.1 . Класс Person, который мы собираемся использовать для представления лица, является простым Java-бином:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
package com.example.model;public class Person { private String email; private String firstName; private String lastName; public Person() { } public Person( final String email, final String firstName, final String lastName ) { this.email = email; this.firstName = firstName; this.lastName = lastName; } // Getters and setters are ommited // ... } |
Как это сейчас довольно часто, мы собираемся запустить наше приложение во встроенном контейнере Jetty 9.1, и наш класс Starter делает именно это:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
package com.example;import org.apache.cxf.cdi.CXFCdiServlet;import org.eclipse.jetty.server.Server;import org.eclipse.jetty.servlet.ServletContextHandler;import org.eclipse.jetty.servlet.ServletHolder;import org.jboss.weld.environment.servlet.BeanManagerResourceBindingListener;import org.jboss.weld.environment.servlet.Listener;public class Starter { public static void main( final String[] args ) throws Exception { final Server server = new Server( 8080 ); // Register and map the dispatcher servlet final ServletHolder servletHolder = new ServletHolder( new CXFCdiServlet() ); final ServletContextHandler context = new ServletContextHandler(); context.setContextPath( "/" ); context.addEventListener( new Listener() ); context.addEventListener( new BeanManagerResourceBindingListener() ); context.addServlet( servletHolder, "/rest/*" ); server.setHandler( context ); server.start(); server.join(); }} |
Обратите внимание на наличие CXFCdiServlet и двух обязательных прослушивателей, которые были добавлены в контекст:
- org.jboss.weld.environment.servlet.Listener отвечает за инъекции CDI
- org.jboss.weld.environment.servlet.BeanManagerResourceBindingListener привязывает ссылку на BeanManager к расположению JNDI java: comp / env / BeanManager, чтобы сделать его доступным из приложения.
При этом вся мощь CDI 1.1 в вашем распоряжении. Давайте представим класс PeopleService с аннотацией @Named и с методом инициализации, объявленным и аннотированным с помощью @PostConstruct, только для создания одного человека.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
@Namedpublic class PeopleService { private final ConcurrentMap< String, Person > persons = new ConcurrentHashMap< String, Person >(); @PostConstruct public void init() { persons.put( "a@b.com", new Person( "a@b.com", "Tom", "Bombadilt" ) ); } // Additional methods // ...} |
До сих пор мы ничего не говорили о настройке приложений и ресурсов JAX-RS 2.0 в среде CDI 1.1 . Причина этого очень проста: в зависимости от приложения вы можете использовать конфигурацию с минимальными усилиями или полностью настраиваемую. Давайте рассмотрим оба подхода.
При конфигурации без усилий вы можете определить пустое приложение JAX-RS 2.0 и любое количество ресурсов JAX-RS 2.0: Apache CXF 3.0 неявно свяжет их вместе, связав каждый класс ресурсов с этим приложением. Вот пример приложения JAX-RS 2.0:
|
1
2
3
4
5
6
7
8
|
package com.example.rs;import javax.ws.rs.ApplicationPath;import javax.ws.rs.core.Application;@ApplicationPath( "api" )public class JaxRsApiApplication extends Application {} |
А вот ресурс PeopleRestService JAX-RS 2.0, который внедряет управляемый компонент PeopleService :
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
package com.example.rs;import java.util.Collection;import javax.inject.Inject;import javax.ws.rs.DELETE;import javax.ws.rs.DefaultValue;import javax.ws.rs.FormParam;import javax.ws.rs.GET;import javax.ws.rs.POST;import javax.ws.rs.PUT;import javax.ws.rs.Path;import javax.ws.rs.PathParam;import javax.ws.rs.Produces;import javax.ws.rs.QueryParam;import javax.ws.rs.core.Context;import javax.ws.rs.core.MediaType;import javax.ws.rs.core.Response;import javax.ws.rs.core.UriInfo;import com.example.model.Person;import com.example.services.PeopleService;@Path( "/people" )public class PeopleRestService { @Inject private PeopleService peopleService; @Produces( { MediaType.APPLICATION_JSON } ) @GET public Collection< Person > getPeople( @QueryParam( "page") @DefaultValue( "1" ) final int page ) { // ... } @Produces( { MediaType.APPLICATION_JSON } ) @Path( "/{email}" ) @GET public Person getPerson( @PathParam( "email" ) final String email ) { // ... } @Produces( { MediaType.APPLICATION_JSON } ) @POST public Response addPerson( @Context final UriInfo uriInfo, @FormParam( "email" ) final String email, @FormParam( "firstName" ) final String firstName, @FormParam( "lastName" ) final String lastName ) { // ... } // More HTTP methods here // ...} |
Больше ничего не требуется: приложение Apache CXF 3.0 может запускаться таким образом и быть полностью функциональным. Полный исходный код примера проекта доступен на GitHub . Помните, что если вы придерживаетесь этого стиля, должно быть объявлено только одно пустое приложение JAX-RS 2.0.
При настраиваемом подходе доступно больше опций, но нужно сделать немного больше. Каждое приложение JAX-RS 2.0 должно предоставлять непустую реализацию коллекций getClasses () или / и getSingletons () . Однако классы ресурсов JAX-RS 2.0 остаются неизменными. Вот пример (который в основном приводит к той же конфигурации приложения, которую мы видели ранее):
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
package com.example.rs;import java.util.Arrays;import java.util.HashSet;import java.util.Set;import javax.enterprise.inject.Produces;import javax.inject.Inject;import javax.ws.rs.ApplicationPath;import javax.ws.rs.core.Application;import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;@ApplicationPath( "api" )public class JaxRsApiApplication extends Application { @Inject private PeopleRestService peopleRestService; @Produces private JacksonJsonProvider jacksonJsonProvider = new JacksonJsonProvider(); @Override public Set< Object > getSingletons() { return new HashSet<>( Arrays.asList( peopleRestService, jacksonJsonProvider ) ); }} |
Обратите внимание, что расширение JAXRSCdiResourceExtension portable CDI 1.1 автоматически создает управляемые bean-компоненты для каждого приложения JAX-RS 2.0 (те, которые расширяют Application ) и ресурсы ( помеченные @Path ). Как таковые, они сразу же доступны для внедрения (как, например, PeopleRestService во фрагменте выше). Класс JacksonJsonProvider помечен аннотацией @Provider и как таковой будет рассматриваться как поставщик JAX-RS 2.0. Для приложений JAX-RS 2.0 нет ограничений, которые могут быть определены таким образом. Полный исходный код примера проекта с использованием этого appoarch доступен на GitHub .
Независимо от того, какой подход вы выбрали, наше приложение будет работать одинаково. Давайте построим и запустим:
|
1
2
|
> mvn clean package> java -jar target/jax-rs-2.0-cdi-0.0.1-SNAPSHOT.jar |
Вызов пары реализованных API REST подтверждает, что приложение функционирует и настроено правильно. Давайте выполним команду GET, чтобы гарантировать, что метод PeopleService, аннотированный @PostConstruct , был вызван при создании управляемого компонента.
|
1
2
3
4
5
6
7
8
9
|
> curl http://localhost:8080/rest/api/peopleHTTP/1.1 200 OKContent-Type: application/jsonDate: Thu, 29 May 2014 22:39:35 GMTTransfer-Encoding: chunkedServer: Jetty(9.1.z-SNAPSHOT)[{"email":"a@b.com","firstName":"Tom","lastName":"Bombadilt"}] |
А вот пример команды POST :
|
01
02
03
04
05
06
07
08
09
10
|
> curl -i http://localhost:8080/rest/api/people -X POST -d "email=a@c.com&firstName=Tom&lastName=Knocker"HTTP/1.1 201 CreatedContent-Type: application/jsonDate: Thu, 29 May 2014 22:40:08 GMTLocation: http://localhost:8080/rest/api/people/a@c.comTransfer-Encoding: chunkedServer: Jetty(9.1.z-SNAPSHOT){"email":"a@c.com","firstName":"Tom","lastName":"Knocker"} |
В этом посте мы только что коснулись того, что теперь возможно с интеграцией Apache CXF и CDI 1.1 . Напомним, что встроенные Apache Tomcat 7.x / 8.x, а также развертывание Apache CXF на основе WAR с CDI 1.1 возможны на большинстве серверов приложений JEE и контейнеров сервлетов.
Пожалуйста, ознакомьтесь с официальной документацией и попробуйте!
- Полный исходный код доступен на GitHub .
| Ссылка: | Apache CXF 3.0: поддержка CDI 1.1 в качестве альтернативы Spring от нашего партнера по JCG Андрея Редько в блоге Андрея Редько (devmind) . |