Migaoli Migaoli - 2 months ago 51
Java Question

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);
}
}

Answer

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

Comments