Статьи

Apache CXF 3.0: поддержка CDI 1.1 как альтернатива Spring

С выпуском 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
@Named
public class PeopleService {
    private final ConcurrentMap< String, Person > persons =
        new ConcurrentHashMap< String, Person >();
  
    @PostConstruct
    public void init() { 
        persons.put( "[email protected]", new Person( "[email protected]", "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/people
 
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 29 May 2014 22:39:35 GMT
Transfer-Encoding: chunked
Server: Jetty(9.1.z-SNAPSHOT)
 
[{"email":"[email protected]","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 protected]&firstName=Tom&lastName=Knocker"
 
HTTP/1.1 201 Created
Content-Type: application/json
Date: Thu, 29 May 2014 22:40:08 GMT
Location: http://localhost:8080/rest/api/people/[email protected]
Transfer-Encoding: chunked
Server: Jetty(9.1.z-SNAPSHOT)
 
{"email":"[email protected]","firstName":"Tom","lastName":"Knocker"}

В этом посте мы только что коснулись того, что теперь возможно с интеграцией Apache CXF и CDI 1.1 . Напомним, что встроенные Apache Tomcat 7.x / 8.x, а также развертывание Apache CXF на основе WAR с CDI 1.1 возможны на большинстве серверов приложений JEE и контейнеров сервлетов.

Пожалуйста, ознакомьтесь с официальной документацией и попробуйте!

  • Полный исходный код доступен на GitHub .