Skip to content

xvik/dropwizard-guicey

 
 

Repository files navigation

#Dropwizard guice integration Gitter License Build Status Coverage Status

Examples repository

About

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)
  • Compatible with guice restrictive options: disableCircularProxies, requireExactBindingAnnotations and requireExplicitBindings
  • 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
  • Admin context rest emulation
  • Custom junit rule for lightweight integration testing
  • Spock extensions

Thanks to

Setup

Releases are published to bintray jcenter (package appear immediately after release) and then to maven central (require few days after release to be published).

JCenter Maven Central

Maven:

<dependency>
  <groupId>ru.vyarus</groupId>
  <artifactId>dropwizard-guicey</artifactId>
  <version>3.3.0</version>
</dependency>

Gradle:

compile 'ru.vyarus:dropwizard-guicey:3.3.0'
  • for dropwizard 0.8 use version 3.1.0 (see old docs)
  • for dropwizard 0.7 use version 1.1.0 (see old docs)
Snapshots

You can use snapshot versions through JitPack:

  • Go to JitPack project page
  • Select Commits section and click Get it on commit you want to use (top one - the most recent)
  • Follow displayed instruction: add repository and change dependency (NOTE: due to JitPack convention artifact group will be different)

Usage

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 and examples repository for configuration examples.

After application start, look application log for dropwizard style extension installation reports.

Bundle options:

  • injectorFactory sets custom injector factory (see below)
  • bundleLookup overrides default guicey bundle lookup implementation
  • disableBundleLookup disables default bundle lookup
  • 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.
  • bundles registers guicey bundles (see below)
  • configureFromDropwizardBundles enables registered dropwizard bundles lookup if they implement GuiceyBundle (false by default)
  • bindConfigurationInterfaces enables configuration class binding with direct interfaces. This is useful for HasSomeConfig interfaces convention. Without it, configuration will be bound to all classes in configuration hierarchy
  • strictScopeControl shortcut to install HK2DebugBundle, which checks extensions instantiation in correct context (may be useful for debug)
  • printDiagnosticInfo shortcut to install DiagnosticBundle, which prints diagnostic info
  • build allows specifying guice injector stage (production, development). By default, PRODUCTION stage used.

IMPORTANT: configured bundles, modules, installers and extensions are checked for duplicates using class type. Duplicate configurations will be simply ignored. For modules and bundles, which configured using instances, duplicates removes means that if two instances of the same type registered, then second instance will be ignored.

Using custom injector factory

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

Injector instance

In some cases it may be important to get injector instance outside of guice context.

Injector instance could be resolved with:

  • getInjector method on GuiceBundle instance (note that injector initialized on run phase, and NPE will be thrown if injector not initialized)
  • InjectorLookup.getInjector(app).get() static call using application instance (lookup returns Optional and last get() throws exception or returns injector instance).

If you need lazy injector reference, you can use InjectorProvider class (its actually Provider<Injector>):

InjectorProvider provider = new InjectorProvider(app);
// somewhere after run phase
Injector injector = provider.get();

When you are inside your Application class:

InjectorLookup.getInjector(this).get().getInstance(SomeService.class)

Most likely, requirement for injector instance means integration with some third party library. Consider writing custom installer in such cases (it will eliminate need for injector instance).

Authentication

All dropwizard authentication configurations are pretty much the same. Here is an example of oauth configuration:

@Provider
class OAuthDynamicFeature extends AuthDynamicFeature {

    @Inject
    OAuthDynamicFeature(MyAuthenticator authenticator, MyAuthorizer authorizer, Environment environment) {
        super(new OAuthCredentialAuthFilter.Builder<User>()
                .setAuthenticator(authenticator)
                .setAuthorizer(authorizer)
                .setPrefix("Bearer")
                .buildAuthFilter())

        environment.jersey().register(RolesAllowedDynamicFeature.class)
        environment.jersey().register(new AuthValueFactoryProvider.Binder(User.class))
    }
}

Here MyAuthenticator and MyAuthorizer are guice beans. OAuthDynamicFeature is guice bean also (created by guice), but instance registered into jersey (by JerseyProviderInstaller)

For more details see wiki page

Classpath scan

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(), performs search for all classes extending Command and install them into bootstrap.

Classes are searched in specified packages and all their subpackages. Inner static classes are also resolved.

@InvisibleForScanner annotation hides class from scanner (for example, to install it manually or to avoid installation at all)

Module autowiring

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.

Example

Alternatively, abstract module class DropwizardAwareModule may be used. It implements all aware interfaces and reduce implementation boilerplate.

public class MyModule extends DropwizardAwareModule<MyConfiguration> {
    @Override
    protected void configure() {
        bootstrap()     // Bootstrap instance
        environment()   // Environment instance
        configuration() // MyConfiguration instance
        appPackage()    // application class package 
    }
} 

Extension ordering

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.

Installers

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 are registered by CoreInstallersBundle

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.

IMPORTANT: each extension is installed by only one installer! If extension could be recognized by more then one installers, it will be installed only by first matching installer (according to installers order).

Resource

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).

Also recognize resources with @Path annotation on implemented interface. Annotations on interfaces are useful for jersey client proxies (example).

Use Provider for request scoped beans injections.

Task

TaskInstaller finds classes extending Task class and register their instance in environment.

Managed

ManagedInstaller finds classes implementing Managed and register their instance in environment. Support ordering.

Lifecycle

LifeCycleInstaller finds classes implementing jetty LifeCycle interface and register their instance in environment. Support ordering.

Health

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.

Jersey extension

JerseyProviderInstaller finds classes annotated with jersey @Provider annotation and register their instance in jersey (forced singleton). Supports the following extensions: like Factory, ExceptionMapper, InjectionResolver, ValueFactoryProvider, ParamConverterProvider, ContextResolver, MessageBodyReader, MessageBodyWriter, ReaderInterceptor, WriterInterceptor, ContainerRequestFilter, ContainerResponseFilter, DynamicFeature, ApplicationEventListener (all of this usually registered through 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 be available and, of course, Provider (for guice or HK).

Jersey Feature

JerseyFeatureInstaller finds classes implementing javax.ws.rs.core.Feature and register their instance in jersey.

It may be useful to configure jersey inside guice components:

public class MyClass ... {
    ...   
    public static class ConfigurationFeature implements Feature {
        @Override
        boolean configure(FeatureContext context) {
            context.register(RolesAllowedDynamicFeature.class)
            context.register(new AuthValueFactoryProvider.Binder(User.class))
            return true;
        }
    }
}

But often the same could be achieved by injecting Environment instance.

Eager

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.

Plugin

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;
Admin filter

AdminFilterInstaller installs filters annotated with @AdminFilter into administration context. Support ordering.

Admin servlet

AdminServletInstaller installs servlets annotated with @AdminServlet into administration context. Support ordering.

Guicey bundles

By analogy with dropwizard bundles, guicey has its own GuiceyBundle. These bundles contains almost the same options as main GuiceBundle builder. The main purpose is to group installers, extensions and guice modules related to specific feature.

Guicey bundles are initialized during dropwizard run phase. All guice modules registered in bundles will also be checked if dropwizard objects autowiring required.

For example, custom integration with some scheduler framework will require installers to register tasks and guice module to configure framework. GuiceyBundle will allow reduce integration to just one bundle installation.

public class XLibIntegrationBundle implements GuiceyBundle {

    @Override
    public void initialize(final GuiceyBootstrap bootstrap) {
        bootstrap.installers(
                XLibFeature1Installer.class,
                XLibFeature2Installer.class,                
        )
        .modules(new XLibGuiceModule());
    }
}

bootstrap.addBundle(GuiceBundle.<TestConfiguration>builder()
        .bundles(new XLibIntegrationBundle())
        .enableAutoConfig("package.to.scan")
        .build()
);

Or, if auto-scan is not used, bundles may be used to group application features: e.g. ResourcesBundle, TasksBundle.

Bundles are transitive - bundle can install other bundles. Duplicate bundles are detected using bundle type, so infinite configuration loops or duplicate configurations are not possible.

NOTE: be careful if bundle is configurable (for example, requires constructor arguments). If two such bundles will be registered, only first registration will be actually used and other instance ignored. Note that application configurations (using main GuiceBundle methods) performed before bundles processing and so bundle with correct parameters could be registered there.

Transitive bundles (or simply a lot of bundles) may cause confusion. Use diagnostic info to see how guicey was actually configured.

Core bundle

Core installers are grouped into CoreInstallersBundle. If classpath scan is not active, all core installers may be registered like this:

bootstrap.addBundle(GuiceBundle.<TestConfiguration>builder()
        .bundles(new CoreInstallersBundle())
        .extensions(MyTask.class, MyResource.class, MyManaged.class)
        .build()
);
HK debug bundle

HK2DebugBundle is special debug bundle to check that beans properly instantiated by guice or HK (and no beans are instantiated by both).

Only beans installed by installers implementing JerseyInstaller (ResourceInstaller, JerseyProviderInstaller). All beans must be created by guice and only beans annotated with @HK2Managed must be instantiated by HK.

Bundle may be used in tests. For example using guicey.bundles property (see bundles lookup below).

May be enabled by `.strictScopeControl()' shortcut method.

Diagnostic bundle

Bundle renders collected guicey diagnostic information (see below): example output.

Output is highly configurable, use: DiagnosticBundle.builder() to configure reporting (if required).

Bundle may be registered with bundle lookup mechanism: for example, using guicey.bundles property (see bundles lookup below).

May be enabled by `.printDiagnosticInfo()' shortcut method.

Dropwizard bundles unification

Guicey bundles and dropwizard bundles may be unified providing single (standard) extension point for both dropwizard and guicey features.

Feature is disabled by default, to enable it use configureFromDropwizardBundles option.

bootstrap.addBundle(new XLibBundle());
bootstrap.addBundle(GuiceBundle.<TestConfiguration>builder()
        .configureFromDropwizardBundles(true)
        .build()
);

where

public class XLibBundle implements Bundle, GuiceyBundle {
    public void initialize(Bootstrap<?> bootstrap) {...}
    public void initialize(GuiceyBootstrap bootstrap){...}
    public void run(Environment environment) {...}
}

When active, all registered bundles are checked if they implement GuiceyBundle. Also, works with dropwizard ConfiguredBundle.

WARNING: don't assume if guicey bundle's initialize method will be called before/after dropwizard bundle's run method. Both are possible (it depends if bundle registered before or after GuiceBundle).

Bundle lookup

Bundle lookup mechanism used to lookup guicey bundles in various sources. It may be used to activate specific bundles in tests (e.g. HK2DebugBundle) or to install 3rd party extensions from classpath.

Bundle lookup is equivalent to registering bundle directly using builder bundles method.

By default, 2 lookup mechanisms active. All found bundles are logged into console. Duplicate bundles are removed (using bundle class to detect duplicate).

To disable default lookups use disableBundleLookup:

bootstrap.addBundle(GuiceBundle.<TestConfiguration>builder()
        .disableBundleLookup()
        .build()

System property lookup

System property guicey.bundles could contain comma separated list of guicey bundle classes. These bundles must have no-args constructor.

For example, activate HK debug bundle for tests:

java ... -Dguicey.bundles=ru.vyarus.dropwizard.guice.module.jersey.debug.HK2DebugBundle

Alternatively, system property may be set in code:

PropertyBundleLookup.enableBundles(HK2DebugBundle.class)

Service loader lookup

Using default java ServiceLoader mechanism, loads all GuiceyBundle services.

This is useful for automatically install 3rd party extensions (additional installers, extensions, guice modules).

3rd party jar must contain services file:

META-INF/services/ru.vyarus.dropwizard.guice.module.installer.bundle.GuiceyBundle

File contain one or more (per line) GuiceyBundle implementations. E.g.

com.foo.Bundle1
com.foo.Bundle2

Then Bundle1, Bundle2 would be loaded automatically on startup.

Customizing lookup mechanism

Custom bundle lookup must implement GuiceyBundleLookup interface:

public class CustomBundleLookup implements GuiceyBundleLookup {

    @Override
    public List<GuiceyBundle> lookup() {
        List<GuiceyBundle> bundles = Lists.newArrayList();
        ...
        return bundles;
    }
}

Custom lookup implementation may be registered through:

bootstrap.addBundle(GuiceBundle.<TestConfiguration>builder()
        .bundleLookup(new CustomBundleLookup())
        .build()

But its better to register it through default implementation DefaultBundleLookup, which performs composition of multiple lookup implementations and logs resolved bundles to console.

bootstrap.addBundle(GuiceBundle.<TestConfiguration>builder()
        .bundleLookup(new DefaultBundleLookup().addLookup(new CustomBundleLookup()))
        .build()

To override list of default lookups:

bootstrap.addBundle(GuiceBundle.<TestConfiguration>builder()
        .bundleLookup(new DefaultBundleLookup(new ServiceLoaderBundleLookup(), new CustomBundleLookup()))
        .build()

Here two lookup mechanisms registered (property lookup is not registered and will not be implicitly added).

Diagnostic info

During startup guicey records main action timings and configuration process details. All this information is accessible through GuiceyConfigurationInfo. This guice bean is always registered and available for injection.

Provided DiagnosticBundle (see above) is an example of collected info representation. It may serve as api usage examples.

Example of recorded timings report:

    GUICEY started in 453.3 ms
    │   
    ├── [0,88%] CLASSPATH scanned in 4.282 ms
    │   ├── scanned 5 classes
    │   └── recognized 4 classes (80% of scanned)
    │   
    ├── [4,2%] COMMANDS processed in 19.10 ms
    │   └── registered 2 commands
    │   
    ├── [6,4%] BUNDLES processed in 29.72 ms
    │   ├── 2 resolved in 8.149 ms
    │   └── 6 processed
    │   
    ├── [86%] INJECTOR created in 390.3 ms
    │   ├── installers prepared in 13.79 ms
    │   │   
    │   ├── extensions recognized in 9.259 ms
    │   │   ├── using 11 installers
    │   │   └── from 7 classes
    │   │   
    │   └── 3 extensions installed in 4.188 ms
    │   
    ├── [1,3%] HK bridged in 6.583 ms
    │   ├── using 2 jersey installers
    │   └── 2 jersey extensions installed in 660.9 μs
    │   
    └── [1,1%] remaining 5 ms

See complete diagnostics demo

Admin REST

All rest resources could be "published" in the admin context too. This is just an emulation of rest: the same resources are accessible in both contexts. On admin side special servlet simply redirects all incoming requests into jersey context.

Such approach is better than registering completely separate jersey context for admin rest because of no overhead and simplicity of jersey extensions management.

To install admin rest servlet, register bundle:

bootstrap.addBundle(new AdminRestBundle());

In this case rest is registered either to '/api/', if main context rest is mapped to root ('/') or to the same path as main context rest.

To register on custom path:

bootstrap.addBundle(new AdminRestBundle("/custom/*"));
Security

In order to hide specific resource methods or entire resources on main context, annotate resource methods or resource class with @AdminResource annotation.

For example:

@GET
@Path("/admin")
@AdminResource
public String admin() {
    return "admin"
}

This (annotated) method will return 403 error when called from main context and process when called from admin context.

This is just the simplest option to control resources access. Any other method may be used (with some security framework or something else).

Servlets and filters

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

Commands support

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.

Request scoped beans

You can use request scoped beans in both main and admin contexts.

@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

Jersey objects available for injection

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

Testing

Tests requires 'io.dropwizard:dropwizard-testing:0.9.3' 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)

Spock

If you use spock framework you can use spock specific extensions:

  • @UseGuiceyApp - internally use GuiceyAppRule
  • @UseDropwizardApp - internally use DropwizardAppRule

Both extensions allows using injections directly in specifications (like spock-guice).

UseGuiceyApp
@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)

UseDropwizardApp

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)

Spock extensions details

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.

Jersey guice integration

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 (when MultivaluedParameterExtractorProvider 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).

Writing custom installer

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 sinmple bind(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 using injector.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() {
    }
}

Ordering

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

Reporting

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

Might also like

java lib generator