Внедрить EJB в JAX-RS (служба RESTful)

Я пытаюсь внедрить EJB Stateless в свой веб-сервис JAX-RS через аннотации. К сожалению, EJB просто null, и я пытаюсь использовать его NullPointerException.

@Path("book")
public class BookResource {

    @EJB
    private BookEJB bookEJB;

    public BookResource() {
    }

    @GET
    @Produces("application/xml")
    @Path("/{bookId}")
    public Book getBookById(@PathParam("bookId") Integer id)
    {
        return bookEJB.findById(id);
    }
}

Что я делаю неправильно?

Ниже приведена информация о моей машине:

  • Glassfish 3.1
  • Netbeans 6.9 RC 2
  • Java EE 6

Можете ли вы, ребята, показать какой-нибудь рабочий пример?

Ответ 1

Я не уверен, что это должно работать. Итак, либо:

Вариант 1: используйте SPI поставщика инъекций

Внедрите поставщика, который будет выполнять поиск и вводит EJB. См:

Пример для com.sun.jersey: jersey-server: 1.17:

import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;

import javax.ejb.EJB;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.ws.rs.ext.Provider;
import java.lang.reflect.Type;

/**
 * JAX-RS EJB Injection provider.
 */
@Provider
public class EJBProvider implements InjectableProvider<EJB, Type> {

    public ComponentScope getScope() {
        return ComponentScope.Singleton;
    }

    public Injectable getInjectable(ComponentContext cc, EJB ejb, Type t) {
        if (!(t instanceof Class)) return null;

        try {
            Class c = (Class)t;
            Context ic = new InitialContext();

            final Object o = ic.lookup(c.getName());

            return new Injectable<Object>() {
                public Object getValue() {
                    return o;
                }
            };
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

Вариант 2: Сделайте BookResource EJB

@Stateless
@Path("book")
public class BookResource {

    @EJB
    private BookEJB bookEJB;

    //...
}

См:

Вариант 3. Использование CDI

@Path("book")
@RequestScoped
public class BookResource {

    @Inject
    private BookEJB bookEJB;

    //...
}

См:

Ответ 2

Эта ветка довольно старая, тем не менее я боролся с той же проблемой только вчера. Вот мое решение:

Просто сделайте BookResource управляемым bean через @javax.annotation.ManagedBean на уровне класса.

Для этого вам нужно включить CDI с 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">
</beans>

Этот файл должен быть в WEB-INF, если BookResource является частью военного файла. Если BookResource упакован с ejbs, поместите его в META-INF.

Если вы хотите использовать @EJB, все готово. Если вы хотите вставить EJB через @Inject, а beans.xml должен быть помещен в файл jar файла ejbs в META-INF.

Что вы делаете: вы просто указываете контейнеру, что ресурс должен управляться контейнером. Поэтому он поддерживает инъекции, а также события жизненного цикла. Таким образом, у вас есть свой бизнес-фасад, не продвигая его в EJB.

Вам не нужно расширять javax.ws.rs.core.Application, чтобы это работало. BookResource как корневой ресурс автоматически запрашивает область действия.

Протестировано с Glassfish 3.1.2 и проектом maven.

Счастливое кодирование.

Ответ 3

Вы сможете делать инъекцию в ресурс JAX-RS, не создавая его компонент EJB или CDI. Но вы должны помнить, что ваш ресурс JAX-RS не должен быть одиночным.

Итак, вы настраиваете приложение с помощью этого кода. Это делает ресурс BookResource класса для каждого запроса JAX-RS.

@javax.ws.rs.ApplicationPath("application")
public class InjectionApplication extends javax.ws.rs.core.Application {
  private Set<Object> singletons = new HashSet<Object>();
  private Set<Class<?>> classes = new HashSet<Class<?>>();

  public InjectionApplication() {
    // no instance is created, just class is listed
    classes.add(BookResource.class);
  }

  @Override
  public Set<Class<?>> getClasses() {
    return classes;
  }

  @Override
  public Set<Object> getSingletons() {
    return singletons;
  }
}

С помощью этой настройки вы позволяете JAX-RS создавать экземпляры BookResource для вас по запросу и также вводить все необходимые зависимости. Если вы создаете ресурс BookResource класса singleton JAX-RS, это означает, что вы помещаете getSingletons

public Set<Object> getSingletons() {
  singletons.add(new BookResource());
  return singletons;
}

то вы создали экземпляр, который не управляется приложением JAX-RS, и никто в контейнере не хочет ничего вводить.

Ответ 4

К сожалению, мой ответ слишком длинный для комментария, так что здесь идет.:)

Zeck, я надеюсь, что вы знаете, что именно вы делаете, продвигая ваш bean в EJB, как это предлагает Паскаль. К сожалению, так же легко, как сейчас, когда Java EE "делает класс EJB", вы должны знать о последствиях этого. Каждый EJB создает накладные расходы вместе с дополнительными функциями, которые он предоставляет: они осведомлены о транзакциях, имеют свои собственные контексты, участвуют в полном жизненном цикле EJB и т.д.

То, что я думаю, что вы должны делать для чистого и многоразового подхода, заключается в следующем: извлеките доступ к вашим серверам (к которым, надеюсь, обращаются через SessionFacade:) в BusinessDelegate. Этот делегат должен использовать какой-то поиск JNDI (возможно, ServiceLocator - да, они все еще действительны в Java EE!) Для доступа к вашему бэкенд.

Хорошо, вне записи: если вам действительно нужна действительно инъекция, потому что вы не хотите писать JNDI-доступ вручную, вы все равно можете сделать свой делегат EJB, хотя он... ну, он просто чувствует себя не так,:) Таким образом, по крайней мере, будет легко заменить его потом чем-то другим, если вы решите переключиться на подход поиска JNDI...

Ответ 5

Я пытался сделать то же самое. Я использую EJB 3.1 и имею развернутое мое приложение как EAR с отдельным проектом EJB. Как указывал Jav_Rock, я использую контекстный поиск.

@Path("book")
public class BookResource {

  @EJB
  BookEJB bookEJB;

  public BookResource() {
    try {
        String lookupName = "java:global/my_app/my_ejb_module/BookEJB";
        bookEJB = (BookEJB) InitialContext.doLookup(lookupName);
    } catch (NamingException e) {
        e.printStackTrace();
    }
  }

  @GET
  @Produces("application/xml")
  @Path("/{bookId}")
  public Book getBookById(@PathParam("bookId") Integer id) {
    return bookEJB.findById(id);
  }
}

См. приведенную ниже ссылку для очень полезных подсказок JNDI.

Советы JNDI по поиску

Ответ 6

Арьян прав. Я создал еще один класс для инициализации EJB вместо создания bean для RS

@Singleton
@LocalBean
public class Mediator {
    @EJB
    DatabaseInterface databaseFacade;

чтобы избежать пустого указателя с помощью:

@Path("stock")
public class StockResource {
    @EJB
    DatabaseInterface databaseFacade;
...

он действительно работает на GF

Ответ 7

У меня та же проблема, и я решил ее вызвать te EJB с помощью контекстного поиска (инъекция была невозможна, у меня была такая же ошибка NullPointerException).