Reputation: 2040
In a JAX-RS web-app we make use of subresources:
@Path("/some/things")
public class ThingsListResource {
@Inject
SomeStorage store;
@GET
public List<Thing> getAllThings() {
return store.getAllThings();
}
@Path("{id}")
public ThingResource getThingResource(@PathParam("id") String id) {
return new ThingResource(id); // PROBLEMATIC
}
}
public class ThingResource {
@Inject
SomeOtherDependecy dep;
@Inject
SomeStorage store;
private final String id;
public ThingResource(String id) {
this.id = id;
}
@GET
public Thing getThisThing() {
return store.getThing(id);
}
@DELETE
public void removeThisThing() {
store.removeThing(id);
}
// following is a list of methods useful enough
// to make ThingResource a useable subresource
}
As you noticed, there are injections which are made with Guice and its GuiceResteasyBootstrapServletContextListener
. The dependencies of root resources are injected without problems. The problematic line above is marked with PROBLEM
: the subresource is created by-hand, which omits all Guice injections.
What is the elegeant way to inject dependencies with Guice into subresources? I can think of a few options here:
Inject the injector into the root resource and use it to create a subresource, probably with some @Assisted
magic in the subresource:
@Inject
Injector injector
// ...
return injector.getInstance(ThingResource.class); // via some provider accepting id?
but I can't wrap my head arround injecting the id
of the "thing" into the provider or @Assisted
injections. Also, it looks like a lot of Guice boilerplate is required here.
Forget about Guice management of the subresource and pass every dependency to its constructor by hand from the root resource. Simple, yet very "dependency injection by hand"-like.
Make the subresource and inner class of ThingsListResource
and thus let it have an access to the outer class (injected) fields. Not very extensible (e.g. if one want to have different subresources implementing the common interface), but simple...
Forget about subresources and "upgrade" them to root resources. It's a bit breaking of DRY rule, as you will specify full URL paths to each resource.
Is there any other way to go or simpler way to realize the above ideas?
Upvotes: 3
Views: 958
Reputation: 128131
This is an ideal use-case for assisted inject. You need to define a factory interface:
public interface ThingResourceFactory {
public ThingResource create(String id);
}
Then you bind it in one of your modules:
install(new FactoryModuleBuilder().build(ThingResourceFactory.class));
Then you modify ThingResource
constructor:
private final String id;
@Inject
public ThingResource(@Assisted String id) {
this.id = id;
}
(BTW, I would use constructor injection instead of field injection if I were you, but this is just a side note)
Then you inject ThingResourceFactory
into your ThingsListResource
and use it in your resource method:
@Path("/some/things")
public class ThingsListResource {
@Inject
SomeStorage store;
@Inject
ThingResourceFactory thingResourceFactory;
@GET
public List<Thing> getAllThings() {
return store.getAllThings();
}
@Path("{id}")
public ThingResource getThingResource(@PathParam("id") String id) {
return thingResourceFactory.create(id);
}
}
See, almost no boilerplate and very easy to use! Guice will automatically create ThingResourceFactory
instance for you which will pass create()
arguments directly to @Assisted
parameters in class constructor, injecting other parameters/fields/methods/etc with usual means.
Upvotes: 8