There is a dropwizard app, which is jersey based.
I rewrote Hk2 bean definitions into Guice and now I can inject Guice beans into Jersey Resources,
but I noticed that Hk2 beans, defined in dropwizard bundles, which I cannot rewrite, are not
visible by Guice and it fails to inject dependencies defined in Hk2.
Guice doesn't see beans defined in Hk2 bundles and Guice creates new uninitialized beans by default.
I disabled this behavior with requireExplicitBindings.
I experimented with HK2IntoGuiceBridge, but its matcher is not invoked for beans I am interested in.
ConfiguredBundleX is located in external artifact.
I tried to copy and translate bean definitions from bundles and stuck with jersey bean Provider<ContainerRequest>, I have no idea where it comes from.
public class ConfiguredBundleX implements ConfiguredBundle<MyAppConf> {
public void run(T configuration, Environment environment) throws Exception {
environment.jersey().register(new AbstractBinder() {
protected void configure() {
this.bind(new MyHk2Bean()).to(MyHk2Bean.class);
}
});
}
}
public class DependsOnHk2Bean { #Inject public DependsOnHk2Bean(MyHk2Bean b) {} }
public class MainModule extends AbstractModule {
private final ServiceLocator locator;
protected void configure() {
binder().requireExplicitBindings();
install(new HK2IntoGuiceBridge(locator));
bind(DependsOnHk2Bean.class);
}
public class GuiceFeature implements Feature {
public boolean configure(FeatureContext context) {
ServiceLocator locator = ServiceLocatorProvider.getServiceLocator(context);
GuiceBridge.getGuiceBridge().initializeGuiceBridge(locator);
Injector injector = Guice.createInjector(
new HK2IntoGuiceBridge(locator),
new MainModule(locator));
GuiceIntoHK2Bridge guiceBridge = locator.getService(GuiceIntoHK2Bridge.class);
guiceBridge.bridgeGuiceInjector(injector);
return true;
}
}
// ...
public void initialize(Bootstrap<X> bootstrap) {
bootstrap.addBundle(new ConfiguredBundleX());
}
public void run(X config, Environment env) {
env.jersey().register(new GuiceFeature());
}
Unfortunately in Guice beans you have to use #HK2Inject rather than #Inject in order to inject hk2 beans into Guice. So in your code above you would do:
public class DependsOnHk2Bean { #HK2Inject public DependsOnHk2Bean(MyHk2Bean b) {} }
This is because of limitation in guice (it may be fixed by now) such that #Inject behavior could not overwritten
I have not tried the above code myself so I'm not sure it'll work, but that was the deal back when the bridge was written...
See HK2Inject and injecting-hk2-services-into-guice-services
After digging Guice and HK2ToGuiceTypeListenerImpl I figured out that there is bindListener to kind of intercept missing bindings and pull them from somewhere. #HKInject code is there, but I noticed that the listener is not called for some bean including the bean I was interested in. Yes HKInject doesn't support constructor injection (4.2.1 version)
So I decided to manually import HK beans and bind them in Guice.
Dropwizard terminology is horrible there are methods get context something, get admin context is totally something different and beans must be get with getService method!
#RequiredArgsConstructor
public class HkModule extends AbstractModule {
private final ServiceLocator locator;
#Override
protected void configure() {
binder().requireExplicitBindings();
Provider<Bar> barProvider = locator.getService(
new TypeLiteral<Provider<Bar>>(){}.getType());
bind(Bar.class).toProvider(barProvider);
bind(Foo.class).toInstance(locator.getService(Foo.class));
}
}
Related
I am unable to use JerseyTest (v2.25) together with Guice dependency injection. My current setup is inspired by an answer on another stackoverflow question. This setup still tries to wire dependency of #Provides annotated classes through HK2, failing my test case. If I remove the .packages() invocation from my test ResourceConfig it seems the listener is not initialized at all and my test cases all return 404's.
This is my current jersey-test setup:
public class MyTestSuite extends JerseyTest {
#Override
protected Application configure() {
return new ResourceConfig().packages("com.example.api");
}
#Override
protected DeploymentContext configureDeployment() {
return ServletDeploymentContext.builder(configure())
.addListener(GuiceConfig.class)
.addFilter(GuiceFilter.class, "guiceFilter")
.addFilter(ServletContainer.class, "jerseyFilter", Collections.singletonMap("javax.ws.rs.Application", JerseyConfig.class.getName()))
.build();
}
}
Here are the classes referenced in the deployment context:
public class GuiceConfig extends GuiceServletContextListener {
static Injector injector;
#Override
protected Injector getInjector() {
injector = Guice.createInjector(new WebModule());
return injector;
}
}
public class JerseyConfig extends ResourceConfig {
#Inject
public JerseyConfig(ServiceLocator serviceLocator) {
Injector injector = (Injector) serviceLocator.getService(ServletContext.class).getAttribute(Injector.class.getName());
GuiceBridge.getGuiceBridge().initializeGuiceBridge(serviceLocator);
serviceLocator.getService(GuiceIntoHK2Bridge.class).bridgeGuiceInjector(injector.createChildInjector(new HK2IntoGuiceBridge(serviceLocator)));
packages("com.example.api");
}
}
public class WebModule extends ServletModule {
#Override
protected void configureServlets() {
serve("/*").with(ServletContainer.class, Collections.singletonMap(ServletProperties.JAXRS_APPLICATION_CLASS, JerseyConfig.class.getName()));
}
}
Instead of writing your own, You can use existing library Jersey Guice Module
you need to bridge the gap between the two DI frameworks. This module aims to do just that by booting Jetty based Jersey server and initializing the bridge between HK2 and Guice.
Getting Started
Add JerseyModule to your Guice Injector
Configure packages to scan for resources and a port to expose
Get instance of JerseyServer and start consuming your Restful resources
compile 'io.logz:guice-jersey:1.0.8'
Notice you are missing GuiceIntoHK2Bridge
Injector injector = (Injector) servletContext.getAttribute(Injector.class.getName());
GuiceBridge.getGuiceBridge().initializeGuiceBridge(serviceLocator);
GuiceIntoHK2Bridge guiceBridge = serviceLocator.getService(GuiceIntoHK2Bridge.class);
guiceBridge.bridgeGuiceInjector(injector);
I have a simple REST API project using Jersey 2.x. I tried using Google Guice to inject my dependencies, but it doesn't seem to work. I get this error:
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=AccountService,parent=AccountsResource,qualifiers={},position=0,optional=false,self=false,unqualified=null,1658198405)
I have this simple resource class
#Path("/accounts")
#Produces(MediaType.APPLICATION_JSON)
public class AccountsResource {
private final AccountService accountService;
#Inject
public AccountsResource(AccountService accountService) {
this.accountService = accountService;
}
#GET
#Path("test")
public String test() {
return this.accountService.test();
}
I want to inject this service into my resource class
public class AccountService {
public AccountService() {}
public String test() {
return "test";
}
}
So, following Guice's guide, I created this module class
import com.google.inject.*;
public class AccountsResourceModule extends AbstractModule {
#Override
protected void configure() {
bind(AccountService.class);
}
}
Finally, I added the injector in my main method
public class TradingServer implements Runnable {
private static final int PORT = 8181;
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AccountsResourceModule());
AccountsResource accountsResource = injector.getInstance(AccountsResource.class);
new TradingServer().run();
}
public void run() {
Server server = new Server(PORT);
ServletContextHandler contextHandler = new ServletContextHandler(server, "/");
ResourceConfig packageConfig = new ResourceConfig().packages("ca.ulaval.glo4002.trading");
ServletContainer container = new ServletContainer(packageConfig);
ServletHolder servletHolder = new ServletHolder(container);
contextHandler.addServlet(servletHolder, "/*");
try {
server.start();
server.join();
} catch (Exception e) {
e.printStackTrace();
} finally {
server.destroy();
}
}
}
When I call my server, I get the error mentioned above. It seems like the dependency injection didn't work. Please help
So Jersey knows nothing about Guice. It already uses it's own DI framework, HK2. There are a couple things you can do. You can either tie Guice together with HK2 so that HK2 can find services that are bound inside Guice, or another way is to just bind your resource classes inside Guice and and register instances of those resources with Jersey.
Tie Guice with HK2
To tie Guice with HK2, you need to use the Guice HK2 Bridge. First you need to add the following dependency
<dependency>
<groupId>org.glassfish.hk2</groupId>
<artifactId>guice-bridge</artifactId>
<version>${hk2.version}</version>
</dependency>
To get the hk2.version look at your Jersey dependencies (you can run mvn dependency:tree and see what version of HK2 Jersey is pulling in). You want to make sure you are using the exact same version.
Next thing you need to do is to programmatically link the two systems. One way to do this is inside a Feature.
public class GuiceFeature implements Feature {
#Override
public boolean configure(FeatureContext context) {
// This is the way in Jersey 2.26+ to get the ServiceLocator.
// In earlier versions, use
// ServiceLocatorProvider.getServiceLocator(context);
ServiceLocator locator = InjectionManagerProvider.getInjectionManager(context)
.getInstance(ServiceLocator.class);
Injector injector = Guice.createInjector(new AccountResourceModule());
GuiceBridge.getGuiceBridge().initializeGuiceBridge(locator);
GuiceIntoHK2Bridge guiceBridge = locator.getService(GuiceIntoHK2Bridge.class);
guiceBridge.bridgeGuiceInjector(injector);
return true;
}
}
Then just register the feature with Jersey.
ResourceConfig packageConfig = new ResourceConfig()
.packages("ca.ulaval.glo4002.trading")
.register(GuiceFeature.class);
And that's it. It should work, as I have tested.
Bind resources with Guice
With the above configuration, Jersey will be creating instances of your resource classes (#Path annotated classes). The reason we need the bridge is that Jersey is tightly coupled with HK2, so when we inject our resources classes, when creating the instance, Jersey will call HK2 to try to find all the dependencies for the resource.
In this case though, we will not rely on Jersey to create the instance of the resource. We will bind the resource to Guice and let Guice create the instance when we request it. We will use that instance to register with Jersey.
First bind the resource
public class AccountResourceModule extends AbstractModule {
#Override
protected void configure() {
bind(AccountService.class);
bind(AccountResource.class);
}
}
Also make sure that the #Inject annotation in the resource class is com.google.inject.Inject.
Get instance of resource and register it
Injector injector = Guice.createInjector(new AccountResourceModule());
AccountResource accountResource = injector.getInstance(AccountResource.class);
ResourceConfig config = new ResourceConfig()
.register(accountResource);
You probably have to figure out a cleaner way to do this as you don't want to have to do this for every resource you have. But this is the gist if what you need to do.
Update
So here's a quick implementation to clean up the second solution. What we can do is scan a package recursively to get all the #Path annotated classes and then bind them in Guice and register them with Jersey.
From this SO post, we can use the Reflections library to easily get all the classes. Just add the following dependency
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.11</version>
</dependency>
Then make a little helper classes
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.Path;
import org.reflections.Reflections;
public class ResourceClassHelper {
private static Set<Class<?>> resourceClasses;
public static Set<Class<?>> getResourceClasses() {
if (resourceClasses != null) {
return resourceClasses;
}
// the package to scan for #Path classes "com.example"
Reflections reflections = new Reflections("com.example");
resourceClasses = reflections.getTypesAnnotatedWith(Path.class);
resourceClasses = Collections.unmodifiableSet(resourceClasses);
return resourceClasses;
}
}
Then in your Guice module
public class AccountResourceModule extends AbstractModule {
#Override
protected void configure() {
bind(AccountService.class);
ResourceClassHelper.getResourceClasses().forEach(this::bind);
}
}
And your resource registration
Injector injector = Guice.createInjector(new AccountResourceModule());
ResourceConfig config = new ResourceConfig();
ResourceClassHelper.getResourceClasses()
.forEach(cls -> config.register(injector.getInstance(cls)));
I am in the process of refactoring an old module, by adding CDI.
I end with
public interface ApiFactory {
...
}
public class ApiFactorySp
implements ApiFactory {
#Inject
UrlProducer urlProducer; // <-- Does not get injected
...
}
and
public interface UrlProducer {
public String getUrl();
}
#Alternative
public class UrlProducerTest
implements UrlProducer {
#Override
public String getUrl() {
return "https://myTestEnv.mydomain/myWebApp";
}
}
For testing, I create a beans.xml file in META-INF:
<beans
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
bean-discovery-mode="all">
<alternatives>
<class>myOrg.myProject.myPackage.UrlProducerTest</class>
</alternatives>
</beans>
To test it, I am doing like shown in this blog
public class WeldContext {
public static final WeldContext INSTANCE = new WeldContext();
private final Weld weld;
private final WeldContainer container;
private WeldContext() {
this.weld = new Weld();
this.container = weld.initialize();
Runtime.getRuntime().addShutdownHook(new Thread() {
#Override
public void run() {
weld.shutdown();
}
});
}
public <T> T getBean(Class<T> type) {
return container.instance().select(type).get();
}
}
and
public class WeldJUnit4Runner extends BlockJUnit4ClassRunner {
public WeldJUnit4Runner(Class<Object> clazz) throws InitializationError {
super(clazz);
}
#Override
protected Object createTest() {
final Class<?> test = getTestClass().getJavaClass();
return WeldContext.INSTANCE.getBean(test);
}
}
Now, when I try to test the logic, I do
#RunWith(WeldJUnit4Runner.class)
public class MyTest {
#Inject
UrlProducer urlProducer;
#Inject
ApiFactory apiFactory;
#Test
public void test() {
apiFactory.doSomethingThatRequiresUrlProducer();
}
}
When I run this, both of the test attributes are inject, but I get NPE because the urlProducer attribute inside of the apiFactory instance has not been assigned a value.
Why is Weld not recognizing the #Inject attribute inside ApiFactory?
JDK 7, Weld 2.2.10, Junit 4.12
UPDATE: After posting the question, started trying with a simpler, brand new project (with just two interfaces and three classes). Using Weld "standalone" did not solve the issue, using CDI-Unit did solve it.
Then I modified my original project to use CDI-Unit, but it did not improve anything. After that I change the injection of UrlProducerTest in ApiFactory from field to constructor (i.e., defining the #Inject ApiFactory(UrlProducer urlProducer) constructor) solved it. I still have not tried this solution with "standalone" Weld (that is for tomorrow), but nonetheless I am still interested in know why field injection is not working.
If UrlProducerTest is an alternative and you want to inject this bean this class should be added to beans.xml into <alternatives> tag.
EDIT:
I believe if some bean can't be injected you get exception with 'unsatisfied/ambiguous dependencies' message. Null could be injected if you used CDI producer method that returned null but this is not your scenario.
So if there are no errors in console I have two assumptions:
Injection doesn't work at all and you get NPE because apiFactory is null
You use urlProducer before injection. For example, from constructor or initialization block (apiFactory.doSomethingThatRequiresUrlProducer() is not provided). So move this logic to some method and annotate it by #PostConstruct
Because ApiFactorySp isn't a CDI bean. You need to annotate the class with #Named to identify the class as a CDI bean for CDI to perform dependency injection.
After reading through many code examples for Google Guice, I see a lot of code that looks like this:
public class MyModule implements Module {
public void configure(Binder binder) {
binder.bind(Service.class).to(ServiceImpl.class);
}
}
public class Widget {
private Injector injector = Guice.createInjector(new MyModule());
public void doSomething() {
Service service = injector.getInstance(Service.class);
service.run();
}
}
It is my understanding that code which explicitly calls the IoC mechanism (such as injector.getInstance(Class<T>) in Guice or appContext.getBean(String) in Spring) is indeed an IoC anti-pattern.
In these examples I notice there is no mention of the #Inject or #ImplementedBy annotations, which I believe are Guice's workarounds to this anti-pattern.
How could I rewrite the code snippet above so that I'm not manually/explicitly invoking the injector? I'd like everything configured so that by the time execution gets to the doSomething() method, the application already "knows" what Service implementation we'll be using...
Thanks in advance!
Make Guice aware of your Widget by adding it to a module and injecting it when needed using annotations.
You will need to add Service to its constructor:
public class Widget {
private final Service service;
#Inject
public Widget(Service service) {
this.service = service;
}
}
I'm newbie with Guice, but why not injecting the service to Widget?
#Inject
public Widget (Service service) {
this.service = service;
...
}
public void doSomething() {
service.run();
...
}
Or, inject a provider that returns a service.
I have written some modules with guice. These are working great.
I have also some singletons or a logger I need in my modules which I want to inject into these modules.
For example I have my JpaModule where I need my Configuration.
ConfigurationModule:
#Singleton
public class ConfigurationModule extends AbstractModule {
#Override
protected void configure() {
bind(Configuration.class).toProvider(ConfigurationProvider.class).in(Singleton.class);
}
}
JpaModule:
public class JpaDaoModule extends AbstractModule {
#Inject
Configuration config;
#Override
protected void configure() {
// ... Read config and do something
}
}
Call to Guice:
Guice.createInjector(new ConfigurationModule(), new JpaDaoModule());
How can I accomplish this? Or how can I provide the configuration to the JpaModule the guicy way?
/Kind regards
Christian
This is not possible. In the configure() method you set up your bindings. You cannot expect them to be available already. Also, modules are not eligible for injection per se. You can, however, get access to Guice-managed instances in providers or #Provides methods.
#Provides
#Named("myConfigItem")
String provideSomeConfigItem(Configuration config) {
return config.get("myConfigItem");
}