Search code examples
javadependency-injectionjerseyhk2

Jersey HK2 injection in manually created objects


Is there any way to inject dependencies into manually created objects?

public class MyCommand {
    @Inject Repository repository;
}

public Repository {
    @Inject EntityManager em;
}

MyCommand command = new MyCommand();

Repository is properly registered the jersey ResourceConfig and can be injected in objects that are created through the CDI container for example a resource class.

But since I create the Command myself the @Inject annotation gets ignored.

Is there a way to get a registered class beside @Inject and @Context? Something like Application.get(Repository.class)

public class MyCommand {
    Repository repository;

    public MyCommand() {
        repository = Application.get(Repository.class);
    }
}

----- EDIT -----

Thanks to your help and some rethinking I found a solution for my problem.

The first thing is that it's possible to inject the ServiceLocator without any preperation into you objects.

The second thing is that I moved from normal commands with a execute method to a a command bus system. The reason for that is I have no controle over the creation of commands so there clean way to get dependencies injected.

The new approach looks like this:

class CommandBus {
    private final ServiceLocator serviceLocator;

    @Inject
    public CommandBus(ServiceLocator serviceLocator) {
        this.serviceLocator = serviceLocator;
    }

    public void dispatch(Command command) {
        Class handlerClass = findHandlerClassForCommand(command);
        CommandHandler handler = (CommandHandler) serviceLocator.getService(handlerClass);
        handler.handle(command);
    }
}

interface CommandHandler {
    void handle(Command command);
}

interface Command {
}

class ConcreteCommand implements Command {
    // I'm just a dto with getters and setters
}

class ConcreteHandler implements CommandHandler {
    private final SomeDependency dependency;

    @Inject
    public ConcreteHandler(SomeDependency dependency) {
        this.dependency = dependency;
    }
    @Override
    public void handle(ConcreteCommand command) {
        // do some things
    }
}

And in my resources I have something like this:

@Path("/some-resource")
class Resource {

    @Context
    private CommandBus bus;

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public void runCommand(ConcreteCommand command) {
        bus.dispatch(command);
    }
}

Solution

  • As pointed out by jwells - HK2 is an injection framework :)

    I spent some time looking into it - I have to say, I find it much more complicated than say guice or spring. Maybe this is due to the fact that I use Dropwizard and it makes it not as easy to access the Service locators.

    However, here is how you can do that.

    First, you will have to get a reference to your ServiceLocator. It must be the same ServiceLocator that jersey is using as well. You can access it for example like:

    How to get HK2 ServiceLocator in Jersey 2.12?

    In my example code I will use an event listener, which is due to my Dropwizard Setup.

    You now have 2 choices: Register your command with your Service Locator and have the injection framework handle creation, or pass the ServiceLocator to your command in order to use it.

    I wrote up a quick example using Dropwizard and jersey:

    public class ViewApplication extends io.dropwizard.Application<Configuration> {
    
        @Override
        public void run(Configuration configuration, Environment environment) throws Exception {
    
            environment.jersey().register(new ApplicationEventListener() {
                @Override
                public void onEvent(ApplicationEvent event) {
                    if (event.getType() == ApplicationEvent.Type.INITIALIZATION_FINISHED) {
                        ServiceLocator serviceLocator = ((ServletContainer) environment.getJerseyServletContainer())
                                .getApplicationHandler().getServiceLocator();
    
                        ServiceLocatorUtilities.bind(serviceLocator, new AbstractBinder() {
    
                            @Override
                            protected void configure() {
                                bind(new Repository("test")).to(Repository.class);
                                bind(MyCommandInjected.class).to(MyCommandInjected.class);
                            }
                        });
    
                        MyCommandInjected service = serviceLocator.getService(MyCommandInjected.class);
                        MyCommandManual tmp = new MyCommandManual(serviceLocator);
                    }
                }
                @Override
                public RequestEventListener onRequest(RequestEvent requestEvent) {
                    return null;
                }
            });
    
    
        }
    
        @Override
        public void initialize(Bootstrap<Configuration> bootstrap) {
            super.initialize(bootstrap);
        }
    
        public static void main(String[] args) throws Exception {
            new ViewApplication().run("server", "/home/artur/dev/repo/sandbox/src/main/resources/config/test.yaml");
        }
    
        @Path("test")
        @Produces(MediaType.APPLICATION_JSON)
        public static class HelloResource {
    
            @GET
            @Path("asd")
            public String test(String x) {
                return "Hello";
            }
    
        }
    
        public static class Repository {
    
            @Inject
            public Repository(String something) {
            }
        }
    
        public static class MyCommandInjected {
    
            @Inject
            public MyCommandInjected(final Repository repo) {
                System.out.println("Repo injected " + repo);
            }
        }
    
        public static class MyCommandManual {
    
            public MyCommandManual(final ServiceLocator sl) {
                Repository service = sl.getService(Repository.class);
                System.out.println("Repo found: " + service);
            }
        }
    
    }
    

    In the Run method, i get access to my ServiceLocator. I bind my classes in there (so there is an example of how to do that). You can alternatively also register Binders with jersey directly - they will use the correct ServiceLocator.

    The 2 classes MyCommandInjected and MyCommandManual are examples of how you can create this command.

    The relevant line for you is probably:

    Repository service = sl.getService(Repository.class);
    

    This asks the service locator for a new instance of the Repository.

    Now, this is just a quick example. I am much more fond of the guice bridge than using HK2 directly :) I find it much easier to use and much clearer. Using the guice-jersey-bridge you can do everything through guice and it will automatically do the right thing.

    Hope that brings some inside,

    Artur