简体   繁体   中英

Configuring Jetty, Jersey, and Guice

I'm refactoring a legacy Java codebase to provide Guice-powered dependency injection to Jersey resource classes.

Here is a stripped down application that uses the legacy Jetty/Jersey setup (see Main & Application ) along with my attempts to wire up Guice using their wiki article on servlets :

build.gradle

plugins {
    id 'java'
}

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.projectlombok:lombok:1.16.18'
    compile 'com.google.inject:guice:4.1.0'
    compile 'com.google.inject.extensions:guice-servlet:4.1.0'
    compile 'com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.9.3'
    compile 'org.eclipse.jetty:jetty-server:9.4.8.v20171121'
    compile 'org.eclipse.jetty:jetty-servlet:9.4.8.v20171121'
    compile 'org.glassfish.jersey.media:jersey-media-sse:2.26'
    compile 'com.sun.jersey:jersey-servlet:1.19.4'
}

Main.java

package org.arabellan.sandbox;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.ServletModule;

import java.util.ArrayList;
import java.util.List;

public class Main {

    static Injector injector;

    public static void main(String[] args) throws Exception {
        List<AbstractModule> modules = new ArrayList<>();
        modules.add(new ExistingModule());
        modules.add(new ServletModule());
        injector = Guice.createInjector(modules);
        injector.getInstance(Application.class).run();
    }

}

Application.java

package org.arabellan.sandbox;

import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import com.google.inject.servlet.GuiceFilter;
import com.sun.jersey.spi.container.servlet.ServletContainer;
import org.glassfish.jersey.message.DeflateEncoder;
import org.glassfish.jersey.message.GZipEncoder;
import org.glassfish.jersey.server.ResourceConfig;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.glassfish.jersey.server.filter.EncodingFilter;

class Application {

    void run() throws Exception {
        Server jettyServer = new Server(8080);
        ServletContextHandler httpContext = new ServletContextHandler(jettyServer, "/");
        httpContext.addEventListener(new GuiceServletConfig());
        httpContext.addFilter(GuiceFilter.class, "/*", null);
        httpContext.addServlet(new ServletHolder(new ServletContainer(buildResourceConfig())), "/*");
        jettyServer.setHandler(httpContext);
        jettyServer.start();
    }

    private ResourceConfig buildResourceConfig() {
        ResourceConfig config = new ResourceConfig();
        config.register(JacksonJsonProvider.class);
        config.registerClasses(EncodingFilter.class, GZipEncoder.class, DeflateEncoder.class);
        config.packages("org.arabellan.sandbox");
        return config;
    }

}

ExistingModule.java

package org.arabellan.sandbox;

import com.google.inject.AbstractModule;

public class ExistingModule extends AbstractModule {

    protected void configure() {
        bind(FooDao.class).to(DynamoDBFooDao.class);
    }

}

GuiceServletConfig.java

package org.arabellan.sandbox;

import com.google.inject.Injector;
import com.google.inject.servlet.GuiceServletContextListener;

public class GuiceServletConfig extends GuiceServletContextListener {

    @Override
    protected Injector getInjector() {
        return Main.injector;
    }

}

FooResource.java

package org.arabellan.sandbox;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;

@Path("/foo")
public class FooResource {

    private final FooDao dao;

    @Inject
    public FooResource(FooDao dao) {
        this.dao = dao;
    }

    @GET
    @Path("/{id}")
    public Response getById(@PathParam("id") String id) {
        return Response.ok(dao.getById(id)).build();
    }

}

DynamoDBFooDao.java

package org.arabellan.sandbox;

import javax.inject.Singleton;

@Singleton
public class DynamoDBFooDao implements FooDao {

    public String getById(String id) {
        return id;
    }

}

FooDao.java

package org.arabellan.sandbox;

interface FooDao {

    String getById(String id);

}

I'm failing to understand the various components and how they work together. As such I keep getting the following error:

SEVERE: The following errors and warnings have been detected with resource and/or provider classes:
  SEVERE: Missing dependency for constructor public org.arabellan.sandbox.FooResource(org.arabellan.sandbox.FooDao) at parameter index 0

If I access the Guice injector directly in FooResource 's constructor then it works. This tells me the Jetty/Jersey stuff is setup properly to serve the resource and Guice is able to build it's dependency tree correctly. I believe this means the problem lies in getting Jersey to use Guice when constructing the resource.

As pointed out in the comments, I needed to settle on either version 1 or 2 of Jersey before trying to hook up Guice. I went with Jersey 2.

My original assumption however was correct, the linkage between Guice and Jersey (or rather HK2) needed to be setup. I facilitated this with the GuiceToHK2 class. I didn't want to define DI bindings in two places so this solution loops through all of the Guice bindings, filters them to a specific package (optional), and then binds them within HK2.

build.gradle

plugins {
    id 'java'
}

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.projectlombok:lombok:1.16.18'
    compile 'com.google.inject:guice:4.1.0'
    compile 'com.google.inject.extensions:guice-servlet:4.1.0'
    compile 'com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.9.3'
    compile 'org.eclipse.jetty:jetty-server:9.4.8.v20171121'
    compile 'org.eclipse.jetty:jetty-servlet:9.4.8.v20171121'
    compile 'org.glassfish.jersey.containers:jersey-container-jetty-servlet:2.26'
    compile 'org.glassfish.jersey.media:jersey-media-sse:2.26'
    compile 'org.glassfish.jersey.inject:jersey-hk2:2.26'
}

Application.java

package org.arabellan.sandbox;

import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.glassfish.jersey.message.DeflateEncoder;
import org.glassfish.jersey.message.GZipEncoder;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.filter.EncodingFilter;
import org.glassfish.jersey.servlet.ServletContainer;

class Application {

    void run() throws Exception {
        ServletContextHandler httpContext = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
        ServletContainer container = new ServletContainer(buildResourceConfig());
        ServletHolder holder = new ServletHolder(container);
        httpContext.setContextPath("/");
        httpContext.addServlet(holder, "/*");

        Server jettyServer = new Server(8080);
        jettyServer.setHandler(httpContext);
        jettyServer.start();
    }

    private ResourceConfig buildResourceConfig() {
        ResourceConfig config = new ResourceConfig();
        config.register(new GuiceToHK2(Main.injector));
        config.register(JacksonJsonProvider.class);
        config.registerClasses(EncodingFilter.class, GZipEncoder.class, DeflateEncoder.class);
        config.packages("org.arabellan.sandbox");
        return config;
    }

}

GuiceToHK2.java

package com.flightstats.hub.app;

import com.google.inject.Injector;
import com.google.inject.Key;
import lombok.extern.slf4j.Slf4j;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.utilities.binding.AbstractBinder;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

@Slf4j
class GuiceToHK2 extends AbstractBinder {

    private final Injector injector;

    GuiceToHK2(Injector injector) {
        this.injector = injector;
    }

    @Override
    protected void configure() {
        injector.getBindings().forEach((key, value) -> {
            if (isNamedBinding(key)) {
                bindNamedClass(key);
            } else {
                bindClass(key);
            }
        });
    }

    private boolean isNamedBinding(Key<?> key) {
        return key.getAnnotationType() != null && key.getAnnotationType().getSimpleName().equals("Named");
    }

    private void bindClass(Key<?> key) {
        try {
            String typeName = key.getTypeLiteral().getType().getTypeName();
            log.info("mapping guice to hk2: {}", typeName);
            Class boundClass = Class.forName(typeName);
            bindFactory(new ServiceFactory<>(boundClass)).to(boundClass);
        } catch (ClassNotFoundException e) {
            log.warn("unable to bind {}", key);
        }
    }

    private void bindNamedClass(Key<?> key) {
        try {
            String typeName = key.getTypeLiteral().getType().getTypeName();
            Method value = key.getAnnotationType().getDeclaredMethod("value");
            String name = (String) value.invoke(key.getAnnotation());
            log.info("mapping guice to hk2: {} (named: {})", typeName, name);
            Class boundClass = Class.forName(typeName);
            bindFactory(new ServiceFactory<>(boundClass)).to(boundClass).named(name);
        } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            log.warn("unable to bind {}", key);
        }
    }

    private class ServiceFactory<T> implements Factory<T> {

        private final Class<T> serviceClass;

        ServiceFactory(Class<T> serviceClass) {
            this.serviceClass = serviceClass;
        }

        public T provide() {
            return injector.getInstance(serviceClass);
        }

        public void dispose(T versionResource) {
            // do nothing
        }
    }

}

It's not a bulletproof solution but it solved my issue. It assumes that everything that needs to be injected into my resources is in the org.arabellan.sandbox package and isn't @Named .

UPDATE : Made the solution more generic by removing assumptions.

hmmn for me it looks like you execute one of the following URLs:

so that the string-parameter "id" of this function: "public Response getById(@PathParam("id") String id)" is null. which results in your error.

It's just an assumption. Could you check it if i'm right, please

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM