Dropwizard integration based on ideas from dropwizard-guice and dropwizardy-guice (which was derived from first one).
Features:
- Guice injector created on run phase (now both dropwizard-guice and dropwizardry-guice do the same)
- Flexible HK2 integration
- No base classes for application or guice module (only bundle registration required)
- Configurable installers mechanism: each supported feature (task install, health check install, etc) has it's own installer and may be disabled
- Custom feature installers could be added
- Optional classpath scan to search features: resources, tasks, commands, health checks etc (without dependency on reflections library)
- Injections works in commands (environment commands)
- Support injection of Bootstrap, Environment and Configuration objects into guice modules before injector creation
- Guice ServletModule can be used to bind servlets and filters (for main context)
- Servlets and filters could be installed into admin context (using annotations)
- Extensions ordering supported (for some extension types, where it might be useful)
- Dropwizard style reporting of installed extensions
- Custom junit rule for lightweight integration testing
- Spock extensions
- SĂ©bastien Boulet (intactile design) for very useful feedback
- Nicholas Pace for governator integration
Releases are published to bintray jcenter (package appear immediately after release) and then to maven central (require few days after release to be published).
Maven:
<dependency>
<groupId>ru.vyarus</groupId>
<artifactId>dropwizard-guicey</artifactId>
<version>2.2.0</version>
</dependency>
Gradle:
compile 'ru.vyarus:dropwizard-guicey:2.2.0'
for dropwizard 0.7 use version 1.1.0 (see old docs)
You can use classpath scanning or configure everything manually (or combine both). Auto scan configuration example:
@Override
public void initialize(Bootstrap<TestConfiguration> bootstrap) {
bootstrap.addBundle(GuiceBundle.<TestConfiguration>builder()
.enableAutoConfig("package.to.scan")
.searchCommands(true)
.build()
);
}
Auto scan will resolve installers and using installers find features in classpath and install them. Commands will also be searched in classpath, instantiated and set into bootstrap object.
Manual configuration example:
@Override
void initialize(Bootstrap<TestConfiguration> bootstrap) {
bootstrap.addBundle(GuiceBundle.<TestConfiguration> builder()
.installers(ResourceInstaller.class, TaskInstaller.class, ManagedInstaller.class)
.extensions(MyTask.class, MyResource.class, MyManaged.class)
.modules(new MyModule())
.build()
);
bootstrap.addCommand(new MyCommand())
}
Installers defined manually. They will be used to detect provided bean classes and properly install them.
Look tests for configuration examples.
After application start, look application log for dropwizard style extension installation reports.
Bundle options:
injectorFactory
sets custom injector factory (see below)enableAutoConfig
enables auto scan on one or more packages to scan. If not set - no auto scan will be performed and default installers will not be available.searchCommands
if true, command classes will be searched in classpath and registered in bootstrap object. Auto scan must be enabled. By default commands scan is disabled (false), because it may be not obvious.modules
one or more guice modules to start. Not required: context could start even without custom modules.disableInstallers
disables installers, found with auto scan. May be used to override default installer or disable it. Note: when auto scan not enabled no installers will be registered automatically.installers
registers feature installers. Used either to add installers from packages not visible by auto scan or to configure installers when auto scan not used.extensions
manually register classes (for example, when auto scan disabled). Classes will be installed using configured installers.build
allows specifying guice injector stage (production, development). By default, PRODUCTION stage used.
Some Guice extension libraries require injector created by their API.
You can control injector creation with custom InjectorFactory
implementation.
For example, to support governator:
public class GovernatorInjectorFactory implements InjectorFactory {
@Override
public Injector createInjector(final Stage stage, final Iterable<? extends Module> modules) {
return LifecycleInjector.builder().withModules(modules).build().createInjector();
}
}
Configure custom factory in bundle:
@Override
void initialize(Bootstrap<TestConfiguration> bootstrap) {
bootstrap.addBundle(GuiceBundle.<TestConfiguration> builder()
.injectorFactory(new GovernatorInjectorFactory())
.enableAutoConfig("package.to.scan")
.modules(new MyModule())
.build()
);
}
Read more about governator integration
Classpath scanning is activated by specifying packages to scan in bundle .enableAutoConfig("package.to.scan")
.
When auto scan enabled:
- Feature installers searched in classpath (including default installers): classes implementing
FeatureInstaller
. Without auto scan default installers not registered. - Search for features in classpath using
FeatureInstaller#matches
method. - If commands search enabled
.searchCommands(true)
, performs search for all classes extendingCommand
and install them into bootstrap.
@InvisibleForScanner
annotation hides class from scanner (for example, to install it manually or to avoid installation at all)
Because guice modules are registered in init section, it's not possible to get reference for environment and configuration objects.
To overcome this limitation, you can implement BootstrapAwareModule
, EnvironmentAwareModule
or ConfigurationAwareModule
interfaces and reference object will be set to module just before injector creation (allowing you to use it during module configuration).
This will work only for modules set to modules()
bundle option.
Some installers support extensions ordering (managed, lifecycle and admin servlet and filters).
To define extensions order use @Order
annotation. Extensions sorted naturally (e.g. @Order(1)
before @Order(2)
).
Extensions without annotation goes last.
Installer is a core integration concept: every extension point has it's own installer. Installers used for both auto scan and manual modes (the only difference is in manual mode classes specified manually). Installers itself are resolved using classpath scanning, so it's very easy to add custom installers (and possibly override default one by disabling it and registering alternative).
All default installers could be found here
When installer recognize class, it binds it into guice binder.bind(foundClass)
(or bind by installer if it support binding).
But extensions annotated with @LazyBinding
are not bind to guice context. This may be useful to delay bean creation:
by default, guice production stage will instantiate all registered beans.
On run phase (after injector created) all found or manually provided extensions are installed by type or instantiated (injector.getInstance(foundClass)
) and passed to installer
to register extension within dropwizard (installation type is defined by installer).
Installers order is defined by @Order
annotation. Default installers are ordered with indexes from 10 to 100 with gap 10.
If you need to run your installer before/after some installer simply annotate it with @Order
. Installers without annotation goes last.
ResourceInstaller
finds classes annotated with @Path
and register their instance as resources. Resources registered as singletons, even if guice bean scope isn't set. If extension annotated as
@HK2Managed
then jersey HK container will manage bean creation (still guice beans injections are possible).
TaskInstaller
finds classes extending Task
class and register their instance in environment.
ManagedInstaller
finds classes implementing Managed
and register their instance in environment. Support ordering.
LifeCycleInstaller
finds classes implementing jetty LifeCycle
interface and register their instance in environment. Support ordering.
HealthCheckInstaller
finds classes extending NamedHealthCheck
class and register their instance in environment.
Custom base class is required, because default HealthCheck
did not provide check name, which is required for registration.
JerseyProviderInstaller
finds classes annotated with jersey @Provider
annotation and register their instance in jersey (forced singleton). Suitable for all types of extensions,
like Factory,
ExceptionMapper,
InjectionResolver,
ValueFactoryProvider etc
(everything you would normally pass into environment.jersey().register()
.
Due to specifics of HK integration (see below), you may need to use @HK2Managed
to delegate bean creation to HK,
@LazyBinding
to delay bean creation to time when all dependencies will we available and, of course, Provider
(for guice or HK).
EagerSingletonInstaller
finds classes annotated with @EagerSingleton
annotation and register them in guice injector. It is equivalent of eager singleton registration
bind(type).asEagerSingleton()
.
This installer doesn't relate to dropwizard directly, but useful in case when you have bean not injected by other beans (so guice can't register it automatically). Normally you would have to manually register it in module.
Most likely such bean will contain initialization logic.
May be used in conjunction with @PostConstruct annotations (e.g. using ext-annotations): installer finds and register bean and post construct annotation could run some logic. Note: this approach is against guice philosophy and should be used for quick prototyping only.
PluginInstaller used to simplify work with guice multibindings mechanism: when you have different implementations of some interface and want to automatically callect these implementations as set or map.
Suppose you have plugin interface public interface PluginInterface
.
Annotating plugin implementations with @Plugin
@Plugin(PluginInterface.class)
public class PluginImpl1 implements PluginInterface
Now all implementations could be autowired as
@Inject Set<PluginInterface> plugins;
Also named mapping could be used. In this case most likely you would like to use enum for keys:
public enum PluginKey {
FIRST, SECOND
}
To use enum keys new annotation needs to be defined (it's impossible to use enum in annotation without explicit declaration, so no universal annotation could be made)
@Plugin(PluginInterface.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyPlugin {
PluginKey value();
}
Note that annotation itself is annotated with @Plugin
, defining target plugin interface.
Now annotating plugin:
@MyPlugin(PluginKey.FIRST)
public class PluginImpl1 implements PluginInterface
And all plugins could be referenced as map:
@Inject Map<PluginKey, PluginInterface> plugins;
AdminFilterInstaller
installs filters annotated with @AdminFilter
into administration context. Support ordering.
AdminServletInstaller
installs servlets annotated with @AdminServlet
into administration context. Support ordering.
NOTE: guice is too tied to GuiceFilter
, which is registered on main context and can't be registered for admin context too.
So you will not be able to use request scoped beans for filters and servlets (and can't inject request response objects) in admin scope
(ServletModule
can't be used for registration in admin context).
To register servlets and filters for main context use ServletModule
, e.g.
public class WebModule extends ServletModule {
@Override
protected void configureServlets() {
filter("/*").through(MyFilter.class)
serve("/myservlet").with(MyServlet.class)
}
}
Automatic scan for commands is disabled by default. You can enable it using searchCommands(true)
bundle option.
If search enabled, all classes extending Command
are instantiated using default constructor and registered in bootsrap object.
EnvironmentCommand
must have construction with Application
argument.
You can use guice injections only in EnvironmentCommand
's because only these commands start bundles (and so launch guice context creation).
No matter if environment command was registered with classpath scan or manually in bootstrap, injector.injectMembers(commands)
will be called on it
to inject guice dependencies.
You can use request scoped beans in main context.
@RequestScoped
public class MyRequestScopedBean {
To obtain bean reference use provider:
Provider<MyRequestScopedBean> myBeanProvider;
You can get request and response objects in any bean:
Provider<HttpServletRequest> requestProvider
Provider<HttpServletResponse> responseProvider
NOTE: this will work only for application context and will not work for admin context (because guice implementation is limited to one servlet context). As a result request scope and request/response injections will not work in tasks, healthchecks and admin filters and servlets.
The following objects available for injection:
- javax.ws.rs.core.Application
- javax.ws.rs.ext.Providers
- org.glassfish.hk2.api.ServiceLocator
- org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider
The following request-scope objects available for injection:
- javax.ws.rs.core.UriInfo
- javax.ws.rs.core.HttpHeaders
- javax.ws.rs.core.SecurityContext
- javax.ws.rs.core.Request
- org.glassfish.jersey.server.ContainerRequest
- org.glassfish.jersey.server.internal.process.AsyncContext
Tests requires 'io.dropwizard:dropwizard-testing:0.8.0-rc1'
dependency.
For integration testing of guice specific logic you can use GuiceyAppRule
. It works almost like
DropwizardAppRule,
but doesn't start jetty (and so jersey and guice web modules will not be initialized). Managed and lifecycle objects
supported.
public class MyTest {
@Rule
GuiceyAppRule<MyConfiguration> RULE = new GuiceyAppRule<>(MyApplication.class, "path/to/configuration.yaml")
public void testSomething() {
RULE.getBean(MyService.class).doSomething();
...
}
}
As with dropwizard rule, configuration is optional
new GuiceyAppRule<>(MyApplication.class, null)
If you use spock framework you can use spock specific extensions:
@UseGuiceyApp
- internally useGuiceyAppRule
@UseDropwizardApp
- internally useDropwizardAppRule
Both extensions allows using injections directly in specifications (like spock-guice).
@UseGuiceyApp(MyApplication)
class AutoScanModeTest extends Specification {
@Inject MyService service
def "My service test" {
when: 'calling service'
def res = service.getSmth()
then: 'correct result returned'
res == 'hello'
}
Annotation allows you to configure the same things as rules does: application class, configuration file (optional), configuration overrides.
@UseGuiceyApp(value = MyApplication,
config = 'path/to/my/config.yml',
configOverride = [
@ConfigOverride(key = "foo", value = "2"),
@ConfigOverride(key = "bar", value = "12")
])
class ConfigOverrideTest extends Specification {
As with rules, configOverride
may be used without setting config file (simply to fill some configurations)
For complete integration testing (when web part is required):
@UseDropwizardApp(MyApplication)
class WebModuleTest extends Specification {
@Inject MyService service
def "Check web bindings"() {
when: "calling filter"
def res = new URL("http://localhost:8080/dummyFilter").getText()
then: "filter active"
res == 'Sample filter and service called'
service.isCalled()
Annotation supports the same configuration options as @UseGuiceyApp
(see above)
Extensions follow spock-guice style - application started once for all tests in class. It's the same as using rule with
@ClassRule
annotation. Rules may be used with spock too (the same way as in junit), but don't mix them with
annotation extensions.
To better understand how injections works, see this test Also, look other tests - they all use spock extensions.
There are two limitations comparing to rules:
- Application can't be created for each test separately (like with
@Rule
annotation). This is because of@Shared
instances support. - You can't customize application creation: application class must have no-args constructor (with rules you can extend rule class
and override
newApplication
method). But this should be rare requirement.
Jersey2 guice integration is much more complicated, because of HK2 container, used by jersey.
Guice integration done in guice exclusive way as much as possible: everything should be managed by guice and invisibly integrated into HK2. Anyway, it is not always possible to hide integration details, especially if you need to register jersey extensions.
Lifecycle:
- Guice context starts first. This is important for commands support: command did not start jersey and so jersey related extensions will not be activated, still core guice context will be completely operable.
- Guice context includes special module with jersey related bindings.
This bindings are lazy (it's impossible to resolve them before jersey will start). So if these dependencies are used in singleton beans, they must be wrapped with
Provider
. - Guice feature registered in jersey. It will bind guice specific extensions into jersey, when jersey starts.
- JerseyInstaller
installer type is called to bind extensions into HK2 context.
This binding is done, using HK
Factory
as much as possible to make definitions lazy and delay actual creation. - HK guice-bridge is also registered (not bi-directional) (but, in fact, this bridge is not required). Bridge adds just injection points resolution for hk managed beans, and not bean resolution. Anyway, this may be useful in some cases.
So when guice context is created, jersey context doesn't exist and when jersey context is created it doesn't aware of guice existence.
But, JerseyInstaller
installs HK bindings directly in time of hk context creation, which allows to workaround HK's lack of guice knowledge.
Extensions on both sides must be registered lazily (using Factory
and Provider
).
Special utility
helps with this.
The problems may appear with binding of jersey extensions.
Good example is ValueFactoryProvider
. Most likely you will use AbstractValueFactoryProvider
as base class, but it declares
direct binding for MultivaluedParameterExtractorProvider
. So such bean would be impossible to create eagerly in guice context.
There are two options to solve this:
- use
@LazyBinding
: bean instance will not be created together with guice context (whenMultivaluedParameterExtractorProvider
is not available), and creation will be initiated by HK, when binding could be resolved. - or use
@HK2Managed
this will delegate instance management to HK, but still guice specific extensions may be used.
In other cases simply wrap jersey specific bindings into Provider
.
Note, that current integration could be extended: you can write custom installer in order to register additional types into HK directly. On guice side you can register additional bindings for jersey components the same way as in jersey bindings module
If you just want to add some beans in HK context, annotate such beans with @Provider
and @HK2Managed
- provider
will be recognized by installer and hk managed annotation will trigger simple registration (overall it's the same
as write binding manually).
Installer should implement FeatureInstaller
interface. It will be automatically registered if auto scan is enabled. To register manually use .installers()
bundle option.
Installer matches
method implements feature detection logic. You can use FeatureUtils
for type checks, because it's denies
abstract classes. Method is called for classes found during scan to detect installable features and for classes directly specified
with .beans()
bundle option to detect installer.
Three types of installation supported. Installer should implement one or more of these interfaces:
BindingInstaller
allows custom guice bindings. If installer doesn't implement this interface sinmplebind(type)
will be called to register in guice.TypeInstaller
used for registration based on type (no instance created during installation).InstanceInstaller
used for instance registration. Instance created usinginjector.getInstance(type)
.JerseyInstaller
used for registration of bindings in HK context.
Note that extensions may use @LazyBinding
annotation. In general case such extensions will not be registered in guice.
In case of BindingInstaller
, special hint will be passed and installer should decide how to handle it (may throw exception as not supported).
BindingInstaller
called in time of injector creation, whereas TypeInstaller
and InstanceInstaller
are called just after injector creation.
JerseyInstaller
is called on jersey start.
Installers are not guice beans! So injections can't be used inside them. This is because installers also used during initialization phase and instantiated before injector creation.
Example installer:
public class CustomInstaller implements FeatureInstaller<CustomFeature> {
@Override
public boolean matches(final Class<?> type) {
return FeatureUtils.is(type, CustomFeature.class);
}
}
Finds all CustomFeature derived classes and register them in guice (implicit registration). Note that no installer interfaces were used, because guice registration is enough.
Now suppose CustomFeature is a base class for our jersey extensions. Then installer will be:
public class CustomInstaller implements FeatureInstaller<CustomFeature>, JerseyInstaller<CustomFeature> {
@Override
public boolean matches(final Class<?> type) {
return FeatureUtils.is(type, CustomFeature.class);
}
@Override
public void install(final AbstractBinder binder, final Class<CustomFeature> type) {
JerseyBinding.bindComponent(binder, type);
}
@Override
public void report() {
}
}
In order to support ordering, installer must implement Ordered
interface.
If installer doesn't implement it extensions will not be sorted, even if extensions has @Order
annotations.
As example, see ManagedInstaller
Installers report()
method will be called after it finish installation of all found extensions. Report provides
user visibility of installed extensions.
To simplify reporting use predefined Reporter class. See example usage in ManagedInstaller
For complex cases, reporter may be extended to better handle installed extensions. As examples see plugin installer reporter and provider installer reporter
- generics-resolver - runtime generics resolution
- guice-validator - hibernate validator integration for guice (objects validation, method arguments and return type runtime validation)
- guice-ext-annotations - @Log, @PostConstruct, @PreDestroy and utilities for adding new annotations support
- guice-persist-orient - guice integration for orientdb
- dropwizard-orient-server - embedded orientdb server for dropwizard