Injeção de dependência com Jersey 2.0

108

Começando do zero, sem nenhum conhecimento prévio de Jersey 1.x, estou tendo dificuldade em entender como configurar a injeção de dependência em meu projeto de Jersey 2.0.

Também entendo que o HK2 está disponível em Jersey 2.0, mas não consigo encontrar documentos que ajudem na integração com Jersey 2.0.

@ManagedBean
@Path("myresource")
public class MyResource {

    @Inject
    MyService myService;

    /**
     * Method handling HTTP GET requests. The returned object will be sent
     * to the client as "text/plain" media type.
     *
     * @return String that will be returned as a text/plain response.
     */
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/getit")
    public String getIt() {
        return "Got it {" + myService + "}";
    }
}

@Resource
@ManagedBean
public class MyService {
    void serviceCall() {
        System.out.print("Service calls");
    }
}

pom.xml

<properties>
    <jersey.version>2.0-rc1</jersey.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.glassfish.jersey</groupId>
            <artifactId>jersey-bom</artifactId>
            <version>${jersey.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>org.glassfish.jersey.core</groupId>
        <artifactId>jersey-common</artifactId>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.core</groupId>
        <artifactId>jersey-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey</groupId>
        <artifactId>jax-rs-ri</artifactId>
    </dependency>
</dependencies>

Posso fazer o contêiner iniciar e servir meu recurso, mas assim que adiciono @Inject a MyService, a estrutura lança uma exceção:

SEVERE: Servlet.service() for servlet [com.noip.MyApplication] in context with path [/jaxrs] threw exception [A MultiException has 3 exceptions.  They are:
1. org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at Injectee(requiredType=MyService,parent=MyResource,qualifiers={}),position=-1,optional=false,self=false,unqualified=null,1039471128)
2. java.lang.IllegalArgumentException: While attempting to resolve the dependencies of com.noip.MyResource errors were found
3. java.lang.IllegalStateException: Unable to perform operation: resolve on com.noip.MyResource
] with root cause
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at Injectee(requiredType=MyService,parent=MyResource,qualifiers={}),position=-1,optional=false,self=false,unqualified=null,1039471128)
    at org.jvnet.hk2.internal.ThreeThirtyResolver.resolve(ThreeThirtyResolver.java:74)


Meu projeto inicial está disponível no GitHub: https://github.com/donaldjarmstrong/jaxrs

donnie_armstrong
fonte

Respostas:

107

Você precisa definir um AbstractBindere registrá-lo em seu aplicativo JAX-RS. O fichário especifica como a injeção de dependência deve criar suas classes.

public class MyApplicationBinder extends AbstractBinder {
    @Override
    protected void configure() {
        bind(MyService.class).to(MyService.class);
    }
}

Quando @Injecté detectado em um parâmetro ou campo do tipo, MyService.classele é instanciado usando a classe MyService. Para usar este fichário, ele precisa ser registrado com o aplicativo JAX-RS. Em seu web.xml, defina um aplicativo JAX-RS como este:

<servlet>
  <servlet-name>MyApplication</servlet-name>
  <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
  <init-param>
    <param-name>javax.ws.rs.Application</param-name>
    <param-value>com.mypackage.MyApplication</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>MyApplication</servlet-name>
  <url-pattern>/*</url-pattern>
</servlet-mapping>

Implemente a MyApplicationclasse (especificada acima no init-param).

public class MyApplication extends ResourceConfig {
    public MyApplication() {
        register(new MyApplicationBinder());
        packages(true, "com.mypackage.rest");
    }
}

O fichário que especifica a injeção de dependência é registrado no construtor da classe e também informamos ao aplicativo onde encontrar os recursos REST (no seu caso MyResource) usando a packages()chamada do método.

joscarsson
fonte
1
E o EntityManager? Alguma dica de como vinculá-lo, para que eu possa injetá-lo via @PersistenceContext?
Johannes Staehlin
4
Não tenho certeza do que EntityManageré, mas a julgar por docs.oracle.com/javaee/6/api/javax/persistence/… parece ser uma interface. Você pode ligá-lo usando bind(EntityManagerImpl.class).to(EntityManager.class)(que vai ligar uma classe EntityManagerImplque implementa a interface EntityManager. Se você precisa usar uma fábrica, dê uma olhada bindFactory()no AbstractBinder. Se você precisar de ajuda com isso, crie uma nova pergunta (eu não vai ter espaço para responda nos comentários). Além disso, não tenho certeza se você deve usar @PersistentContext, basta usar @Inject para tudo
joscarsson
Sim, EntityManager é específico para JPA (Java EE). Obrigado pelo seu comentário, vou abrir outra pergunta se me deparar com um problema específico!
Johannes Staehlin
Apenas para registro, o JPA também roda em Java SE. oracle.com/technetwork/java/javaee/tech/…
prefabSOFT de
2
O que o bind faz? E se eu tiver uma interface e uma implementação?
Dejell
52

Primeiro apenas para responder a um comentário na resposta aceita.

"O que o bind faz? E se eu tiver uma interface e uma implementação?"

Ele simplesmente lê bind( implementation ).to( contract ). Você pode cadeia alternativa .in( scope ). Escopo padrão de PerLookup. Então, se você quiser um singleton, você pode

bind( implementation ).to( contract ).in( Singleton.class );

Também há um RequestScoped disponível

Além disso, em vez de bind(Class).to(Class), você também pode bind(Instance).to(Class), o que será automaticamente um singleton.


Adicionando à resposta aceita

Para aqueles que estão tentando descobrir como registrar sua AbstractBinderimplementação em seu web.xml (ou seja, você não está usando um ResourceConfig), parece que o fichário não será descoberto através da verificação de pacote, ou seja,

<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
    <param-name>jersey.config.server.provider.packages</param-name>
    <param-value>
        your.packages.to.scan
    </param-value>
</init-param>

Ou isso também

<init-param>
    <param-name>jersey.config.server.provider.classnames</param-name>
    <param-value>
        com.foo.YourBinderImpl
    </param-value>
</init-param>

Para fazer funcionar, eu tive que implementar um Feature:

import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.ext.Provider;

@Provider
public class Hk2Feature implements Feature {

    @Override
    public boolean configure(FeatureContext context) {
        context.register(new AppBinder());
        return true;
    }
}

A @Provideranotação deve permitir que o Featureseja captado pela varredura de pacote. Ou sem a verificação de pacote, você pode registrar explicitamente o Featurenoweb.xml

<servlet>
    <servlet-name>Jersey Web Application</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
        <param-name>jersey.config.server.provider.classnames</param-name>
        <param-value>
            com.foo.Hk2Feature
        </param-value>
    </init-param>
    ...
    <load-on-startup>1</load-on-startup>
</servlet>

Veja também:

e para informações gerais da documentação de Jersey


ATUALIZAR

Fábricas

Além da ligação básica na resposta aceita, você também tem fábricas, onde pode ter uma lógica de criação mais complexa e também tem acesso para solicitar informações de contexto. Por exemplo

public class MyServiceFactory implements Factory<MyService> {
    @Context
    private HttpHeaders headers;

    @Override
    public MyService provide() {
        return new MyService(headers.getHeaderString("X-Header"));
    }

    @Override
    public void dispose(MyService service) { /* noop */ }
}

register(new AbstractBinder() {
    @Override
    public void configure() {
        bindFactory(MyServiceFactory.class).to(MyService.class)
                .in(RequestScoped.class);
    }
});

Então você pode injetar MyServiceem sua classe de recursos.

Paul Samsotha
fonte
Eu poderia registrar minha classe de fichário apenas por meio de uma implementação ResourceConfig, como mostrado na resposta aceita. Nenhuma classe de recurso foi necessária.
Patrick Koorevaar
Usando web.xmlmesmo que configure()on Hk2Featureseja chamado, solicitar o recurso lança um NullPointerException. @PaulSamsotha
bytesandcaffeine
12

A resposta selecionada data de um tempo atrás. Não é prático declarar todas as ligações em um fichário HK2 personalizado. Estou usando o Tomcat e só precisei adicionar uma dependência. Embora tenha sido projetado para Glassfish, ele se encaixa perfeitamente em outros recipientes.

   <dependency>
        <groupId>org.glassfish.jersey.containers.glassfish</groupId>
        <artifactId>jersey-gf-cdi</artifactId>
        <version>${jersey.version}</version>
    </dependency>

Verifique se o seu contêiner também está configurado corretamente ( consulte a documentação ).

Otonglet
fonte
A última linha (verifique se o seu contêiner também está configurado corretamente) é um pouco vaga. Alguma ajuda aqui? Quais anotações usamos onde?
markthegrea
Estávamos usando o Weld para injeção de dependência, que exigia alguma configuração especial para funcionar com o Tomcat (nosso "contêiner" de aplicativo). Se você estiver usando o Spring, ele funciona fora da caixa.
otonglet
5

Atrasado, mas espero que isso ajude alguém.

Tenho meu JAX RS definido assim:

@Path("/examplepath")
@RequestScoped //this make the diference
public class ExampleResource {

Então, no meu código finalmente posso injetar:

@Inject
SomeManagedBean bean;

No meu caso, SomeManagedBeané um bean ApplicationScoped.

Espero que isso ajude alguém.

gjijon
fonte
3

A Oracle recomenda adicionar a anotação @Path a todos os tipos a serem injetados ao combinar JAX-RS com CDI: http://docs.oracle.com/javaee/7/tutorial/jaxrs-advanced004.htm Embora isso esteja longe de ser perfeito ( (por exemplo, você receberá um aviso de Jersey na inicialização), decidi seguir esse caminho, o que me poupa de manter todos os tipos suportados em um fichário.

Exemplo:

@Singleton
@Path("singleton-configuration-service")
public class ConfigurationService {
  .. 
}

@Path("my-path")
class MyProvider {
  @Inject ConfigurationService _configuration;

  @GET
  public Object get() {..}
}
Benjamin Mesing
fonte
1
Link está morto, deve apontar para aqui
Hank
0

Se preferir usar o Guice e não quiser declarar todas as ligações, você também pode tentar este adaptador:

guice-bridge-jit-injector

Choi
fonte
0

Para mim, funciona sem o AbstractBinderse eu incluir as seguintes dependências em meu aplicativo da web (em execução no Tomcat 8.5, Jersey 2.27):

<dependency>
    <groupId>javax.ws.rs</groupId>
    <artifactId>javax.ws.rs-api</artifactId>
    <version>2.1</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-servlet</artifactId>
    <version>${jersey-version}</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.ext.cdi</groupId>
    <artifactId>jersey-cdi1x</artifactId>
    <version>${jersey-version}</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.inject</groupId>
    <artifactId>jersey-hk2</artifactId>
    <version>${jersey-version}</version>
</dependency>

Funciona com CDI 1.2 / CDI 2.0 para mim (usando Weld 2/3 respectivamente).

Jansohn
fonte
0

Dependência necessária para o serviço de descanso de jersey e o Tomcat é o servidor. onde $ {jersey.version} é 2.29.1

    <dependency>
        <groupId>javax.enterprise</groupId>
        <artifactId>cdi-api</artifactId>
        <version>2.0.SP1</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.core</groupId>
        <artifactId>jersey-server</artifactId>
        <version>${jersey.version}</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.containers</groupId>
        <artifactId>jersey-container-servlet</artifactId>
        <version>${jersey.version}</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.inject</groupId>
        <artifactId>jersey-hk2</artifactId>
        <version>${jersey.version}</version>
    </dependency>

O código básico será o seguinte:

@RequestScoped
@Path("test")
public class RESTEndpoint {

   @GET
   public String getMessage() {
alokj
fonte