Bindings¶
Note
Guicey is compatible with some guice restrictive options (like disableCircularProxies)
Registered bindings¶
Guicey binds:
- Dropwizard objects:
io.dropwizard.setup.Bootstrap
io.dropwizard.Configuration
io.dropwizard.setup.Environment
- Detailed configuration bindings (by root classes, interfaces, yaml path or unique sub type)
- All installed extensions (extensions are explicitly bound to guice, except some specific installers which do manual bindings).
- Jersey objects (including request scoped)
- Guicey special objects
Configuration¶
Configuration
bound to guice as:
io.dropwizard.Configuration
- Your configuration class (
MyConfiguration extends Configuration
) - All classes between them
For example, if
MyConfiguration extends MyAbstractConfiguration extends Configuration
Then MyAbstractConfiguration
will be also bound and following injection will work:
@Inject MyAbstractConfiguration conf
Note
Configuration object bindings could be declared with or without qualifier @Config
.
So @Inject @Config MyAbstractConfiguration conf
will also work. It is suggested to always
use qualifier (to get in common with direct value bindings), but it's up to you.
If root configuration classes implement interfaces then configuration could be bound by interface.
This may be used to support common Has<Something>
configuration interfaces convention used to recognize your extension configuration in configuration object.
public interface HasFeatureX { FeatureXConfig getFetureXConfig(); } public class MyConfiguration extends Configuration implements HasFeatureXConfig {...} public class MyBean { @Inject @Config HasFeatureX conf; ... }
Important
It is required to use qualifier @Config
for interface binding (to avoid accidental binding clashes).
There is a deprecated bundle option .bindConfigurationInterfaces()
, which enables direct
interfaces bindings without qualifier (feature remains for compatibility, prefer qualified interfaces bindings).
Interface binding will ignore interfaces in java.*
or groovy.*
packages (to avoid unnecessary bindings).
Tip
Consider using direct sub configuration object binding (see below) instead of marker interface if object uniqueness is guaranteed in user configuration.
Configuration tree¶
Guicey introspects configuration instance using jackson serialization api. That means only values visible for jackson serialization will be presented.
Note
Jackson will see all properties which either have getter and setter or annotated with @JsonProperty
. For example,
public class MyConfig extends Configuration { private String one // visible (getter and setter) @JsonProperty private String two // visible (annotation) private String three // invisible (no getter) public void setOne(String one) { ... } public String getOne() { ... } public void setTwo(String two) { ... } public void setThree(String three) { ... } }
Tree of visible configuration paths could be accessed with bound
ru.vyarus.dropwizard.guice.module.yaml.ConfigurationTree
object.
It could be used for config analysis, reporting or something else.
For simple cases, direct configuration value bindings could be used (see below).
Tip
To prevent binding of configuration property use @JsonIgnore
on property getter
private String prop // dropwizard will set value from yaml public void setProp(Stirng prop) { ... } // property will not be available as path binding @JsonIgnore public String getProp() { ... }
Unique sub configuration¶
It is quite common to group configuration properties into sub objects like:
public class MyConfig extends Configuration { @JsonProperty AuthConfig auth; @JsonProperty DbConfig db; }
Guicey detects such unique objects and provides direct bindings for them:
@Inject @Config AuthConfig auth; @Inject @Config DbConfig db;
This is very useful for re-usable modules, which are not aware of your configuration object structure, but require only one sub configuration object:
public class MyConfig extends Configuration { @JsonProperty FeatureXConfig featureX; }
Somewhere in module service:
public class FeatureXService { @Inject @Config FeatureXConfig featureX; }
Important
Sub configuration object uniqueness is checked as direct match, so you may have
@JsonProperty SubConfig sub @JsonProperty SubConfigExt subExt
class SubConfigExt extends SubConfig
, but still both objects would be considered unique.
Whereas
@JsonProperty SubConfig sub1 @JsonProperty SubConfig sub2
Tip
Guicey bundles and guice modules also could use sub configuration objects directly:
GuiceyBootstrap#configuration(SubConfig.class) DropwizardAwareModule#configuration(SubConfig.class)
Configuration by path¶
All visible configuration paths values are directly bindable:
public class MyConfig extends Configuration { SubConf sub; } public class SubConf { String smth; List<String> values; }
@Inject @Config("sub") SubConf sub; @Inject @Config("sub.smth") String smth; @Inject @Config("sub.values") List<String> values;
Note
Path bindings are available even for null values. For example, if sub configuration object is null, all it's sub paths will still be available (by class declarations). The only exception is conditional mappin like dropwizard "server" when available paths could change, depending on configuration (what configuration class will be used)
Note
Generified types are bound only with generics (with all available type information).
If you will have SubConf<T> sub
in config, then it will be bound with correct generic SubConfig<String>
(suppose generic T is declared as String).
Value type, declared in configuration class is used for binding, but there are two exceptions.
If declared type is declared as collection (Set, List, Map) implementation then binding will use base collection interface:
ArrayList<String> value @Inject @Config("value") List<String> vlaue;
If, for some (unforgivable) reason, property is declared as Object in configuration, then binding type will depend on value presence:
@Config("path") Object val
- when value is null@Config("path") ValueType val
- actual value type, when value is not null
It is assumed that in such case value would be always present (some sort of property-selected binding, like dropwizard "server").
Tip
You can declare you own additional bindings using ConfigurationTree
(accessible from guice module),
which contains all paths information (including declaration and actual types with generics).
Configuration bindings report¶
You can print all available configuration bindings with values:
GuiceBundle.builder() ... .printConfigurationBindings() // or printCustomConfigurationBindings() to avoid dropwizard bindings .build()
Will print:
INFO [2018-06-18 05:55:03,532] ru.vyarus.dropwizard.guice.module.yaml.report.DebugConfigBindings: Available configuration bindings = ComplexGenericCase (visible paths) │ ├── sub: ComplexGenericCase.Sub<String> │ └── smth: String = null │ ├── logging: DefaultLoggingFactory │ ├── level: String = "INFO" │ ├── loggers: RegularImmutableMap<String, JsonNode> = {} │ └── appenders: SingletonImmutableList<AppenderFactory<ILoggingEvent>> = [io.dropwizard.logging.ConsoleAppenderFactory@1b7332a7] │ ├── metrics: MetricsFactory │ ├── frequency: Duration = 1 minute │ └── reporters: RegularImmutableList<ReporterFactory> = [] │ └── server: DefaultServerFactory ├── maxThreads: Integer = 1024 ├── minThreads: Integer = 8 ├── maxQueuedRequests: Integer = 1024 ├── idleThreadTimeout: Duration = 1 minute ├── nofileSoftLimit: Integer = null ├── nofileHardLimit: Integer = null ├── gid: Integer = null ├── uid: Integer = null ├── user: String = null ├── group: String = null ├── umask: String = null ├── startsAsRoot: Boolean = null ├── registerDefaultExceptionMappers: Boolean = true ├── detailedJsonProcessingExceptionMapper: Boolean = false ├── shutdownGracePeriod: Duration = 30 seconds ├── allowedMethods: HashSet<String> = [HEAD, DELETE, POST, GET, OPTIONS, PUT, PATCH] ├── enableThreadNameFilter: Boolean = true ├── applicationConnectors: ArrayList<ConnectorFactory> = [io.dropwizard.jetty.HttpConnectorFactory@33063f5b] ├── adminConnectors: ArrayList<ConnectorFactory> = [io.dropwizard.jetty.HttpConnectorFactory@227a47] ├── adminMaxThreads: Integer = 64 ├── adminMinThreads: Integer = 1 ├── applicationContextPath: String = "/" ├── adminContextPath: String = "/" │ ├── serverPush: ServerPushFilterFactory │ ├── enabled: Boolean = false │ ├── associatePeriod: Duration = 4 seconds │ ├── maxAssociations: Integer = 16 │ ├── refererHosts: List<String> = null │ └── refererPorts: List<Integer> = null │ ├── rootPath: Optional<String> = Optional.empty │ ├── requestLog: LogbackAccessRequestLogFactory │ └── appenders: SingletonImmutableList<AppenderFactory<IAccessEvent>> = [io.dropwizard.logging.ConsoleAppenderFactory@58a2b4c] │ └── gzip: GzipHandlerFactory ├── enabled: Boolean = true ├── minimumEntitySize: Size = 256 bytes ├── bufferSize: Size = 8 kilobytes ├── excludedUserAgentPatterns: HashSet<String> = [] ├── compressedMimeTypes: Set<String> = null ├── includedMethods: Set<String> = null ├── deflateCompressionLevel: Integer = -1 ├── gzipCompatibleInflation: Boolean = true └── syncFlush: Boolean = false Configuration object bindings: @Config ComplexGenericCase @Config Configuration Unique sub configuration objects bindings: ComplexGenericCase.sub @Config ComplexGenericCase.Sub<String> = null Configuration.logging @Config LoggingFactory (with actual type DefaultLoggingFactory) = DefaultLoggingFactory{level=INFO, loggers={}, appenders=[io.dropwizard.logging.ConsoleAppenderFactory@1b7332a7]} Configuration.metrics @Config MetricsFactory = MetricsFactory{frequency=1 minute, reporters=[]} Configuration.server @Config ServerFactory (with actual type DefaultServerFactory) = DefaultServerFactory{applicationConnectors=[io.dropwizard.jetty.HttpConnectorFactory@33063f5b], adminConnectors=[io.dropwizard.jetty.HttpConnectorFactory@227a47], adminMaxThreads=64, adminMinThreads=1, applicationContextPath=/, adminContextPath=/} Configuration.server.gzip @Config GzipHandlerFactory = io.dropwizard.jetty.GzipHandlerFactory@15405bd6 Configuration.server.requestLog @Config RequestLogFactory<RequestLog> (with actual type LogbackAccessRequestLogFactory) = io.dropwizard.request.logging.LogbackAccessRequestLogFactory@6de30571 Configuration.server.serverPush @Config ServerPushFilterFactory = io.dropwizard.jetty.ServerPushFilterFactory@3240b2a4 Configuration paths bindings: ComplexGenericCase: @Config("sub") ComplexGenericCase.Sub<String> = null @Config("sub.smth") String = null Configuration: @Config("logging") LoggingFactory (with actual type DefaultLoggingFactory) = DefaultLoggingFactory{level=INFO, loggers={}, appenders=[io.dropwizard.logging.ConsoleAppenderFactory@1b7332a7]} @Config("logging.appenders") List<AppenderFactory<ILoggingEvent>> (with actual type SingletonImmutableList<AppenderFactory<ILoggingEvent>>) = [io.dropwizard.logging.ConsoleAppenderFactory@1b7332a7] @Config("logging.level") String = "INFO" @Config("logging.loggers") Map<String, JsonNode> (with actual type RegularImmutableMap<String, JsonNode>) = {} @Config("metrics") MetricsFactory = MetricsFactory{frequency=1 minute, reporters=[]} @Config("metrics.frequency") Duration = 1 minute @Config("metrics.reporters") List<ReporterFactory> (with actual type RegularImmutableList<ReporterFactory>) = [] @Config("server") ServerFactory (with actual type DefaultServerFactory) = DefaultServerFactory{applicationConnectors=[io.dropwizard.jetty.HttpConnectorFactory@33063f5b], adminConnectors=[io.dropwizard.jetty.HttpConnectorFactory@227a47], adminMaxThreads=64, adminMinThreads=1, applicationContextPath=/, adminContextPath=/} @Config("server.adminConnectors") List<ConnectorFactory> (with actual type ArrayList<ConnectorFactory>) = [io.dropwizard.jetty.HttpConnectorFactory@227a47] @Config("server.adminContextPath") String = "/" @Config("server.adminMaxThreads") Integer = 64 @Config("server.adminMinThreads") Integer = 1 @Config("server.allowedMethods") Set<String> (with actual type HashSet<String>) = [HEAD, DELETE, POST, GET, OPTIONS, PUT, PATCH] @Config("server.applicationConnectors") List<ConnectorFactory> (with actual type ArrayList<ConnectorFactory>) = [io.dropwizard.jetty.HttpConnectorFactory@33063f5b] @Config("server.applicationContextPath") String = "/" @Config("server.detailedJsonProcessingExceptionMapper") Boolean = false @Config("server.enableThreadNameFilter") Boolean = true @Config("server.gid") Integer = null @Config("server.group") String = null @Config("server.gzip") GzipHandlerFactory = io.dropwizard.jetty.GzipHandlerFactory@15405bd6 @Config("server.gzip.bufferSize") Size = 8 kilobytes @Config("server.gzip.compressedMimeTypes") Set<String> = null @Config("server.gzip.deflateCompressionLevel") Integer = -1 @Config("server.gzip.enabled") Boolean = true @Config("server.gzip.excludedUserAgentPatterns") Set<String> (with actual type HashSet<String>) = [] @Config("server.gzip.gzipCompatibleInflation") Boolean = true @Config("server.gzip.includedMethods") Set<String> = null @Config("server.gzip.minimumEntitySize") Size = 256 bytes @Config("server.gzip.syncFlush") Boolean = false @Config("server.idleThreadTimeout") Duration = 1 minute @Config("server.maxQueuedRequests") Integer = 1024 @Config("server.maxThreads") Integer = 1024 @Config("server.minThreads") Integer = 8 @Config("server.nofileHardLimit") Integer = null @Config("server.nofileSoftLimit") Integer = null @Config("server.registerDefaultExceptionMappers") Boolean = true @Config("server.requestLog") RequestLogFactory<RequestLog> (with actual type LogbackAccessRequestLogFactory) = io.dropwizard.request.logging.LogbackAccessRequestLogFactory@6de30571 @Config("server.requestLog.appenders") List<AppenderFactory<IAccessEvent>> (with actual type SingletonImmutableList<AppenderFactory<IAccessEvent>>) = [io.dropwizard.logging.ConsoleAppenderFactory@58a2b4c] @Config("server.rootPath") Optional<String> = Optional.empty @Config("server.serverPush") ServerPushFilterFactory = io.dropwizard.jetty.ServerPushFilterFactory@3240b2a4 @Config("server.serverPush.associatePeriod") Duration = 4 seconds @Config("server.serverPush.enabled") Boolean = false @Config("server.serverPush.maxAssociations") Integer = 16 @Config("server.serverPush.refererHosts") List<String> = null @Config("server.serverPush.refererPorts") List<Integer> = null @Config("server.shutdownGracePeriod") Duration = 30 seconds @Config("server.startsAsRoot") Boolean = null @Config("server.uid") Integer = null @Config("server.umask") String = null @Config("server.user") String = null
Introspected configuration¶
ConfigurationTree
object provides access for introspected configuration tree:
getRootTypes()
- all classes in configuration hierarchy (including interfaces)getPaths()
- all paths (including all steps ("sub", "sub.value"))getUniqueTypePaths()
- paths of unique sub configuration types
Each path item (ConfigPath
) contains:
- Root path reference ("sub.value" reference "sub")
- Child sub-paths ("sub" reference "sub.value")
- Declaration class (type used in configuration class)
- Value type (type of actual value; when value null - declaration type (but they still could be different for collections))
- Current path name
- Current path value
- Generics for declaration and value types (may be incomplete for value type)
- Custom type marker: contains sub paths or just looks like sub configuration
- Declaration type (class where property was declared - configuration object containing property)
You can traverse up or down from any path (tree structure).
ConfigurationTree
provides basic search methods (more as usage example):
findByPath(String)
- search path by case-insensitive matchfindAllByType(Class)
- find all paths with assignable declared valuefindAllFrom(Class<? extends Configuration>)
- find all paths, started in specified configuration classfindAllRootPaths()
- get all root paths (1st level paths)findAllRootPathsFrom(Class<? extends Configuration>)
- all 1st level paths of configuration classvalueByPath(String)
- return path value or null if value null or path not existsvaluesByType(Class)
- all not null values with assignable typevalueByType(Class)
- first not null value with assignable typevalueByUniqueDeclaredType(Class)
- value of unique sub conifguration or null if value is null or config is not unique
Paths are sorted by configuration class (to put custom properties upper) and by path name (for predictable paths order).
Environment binding¶
Dropwizard io.dropwizard.setup.Environment
is bound to guice context.
It is mostly useful to perform additional configurations in guice bean for features not covered with installers. For example:
public class MyBean { @Inject public MyBean(Environment environment) { environment.lifecycle().addServerLifecycleListener(new ServerLifecycleListener { public void serverStarted(Server server) { callSomeMethod(); } }) } }
It's not the best example, but it illustrates usage (and such things usually helps to quick-test something).
See also authentication configuration example.
Jersey specific bindings¶
Jersey bindings are not immediately available, because HK2 context starts after guice,
so use Provider
to inject these bindings.
These bindings available after HK2 context start:
javax.ws.rs.core.Application
javax.ws.rs.ext.Providers
org.glassfish.hk2.api.ServiceLocator
org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider
Request-scoped bindings:
javax.ws.rs.core.UriInfo
javax.ws.rs.container.ResourceInfo
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
Tip
Read about jersey bindings implementation in lifecycle section.
Request and response¶
By default, GuiceFilter
is enabled on both contexts (admin and main). So you can inject
request and response objects and use under filter, servlet or resources calls (guice filter wraps all web interactions).
If you disable guice filter with .noGuiceFilter() then guicey will bridge objects from HK2 context:
javax.servlet.http.HttpServletRequest
javax.servlet.http.HttpServletResponse
This means you can still inject them, but request and response will only be available under resource calls (the only part managed by jersey).
Example usage:
@Inject Provider<HttpServletRequest> requestProvider;
Note
Pay attention, that in guice-managed resources @Context
field bindings
must be replaced with providers.
Options¶
ru.vyarus.dropwizard.guice.module.context.option.Options
binding provides access to guicey options:
@Inject Options options;
Example usage:
Preconditions.checkState(options.get(GuiceyOptions.UseHkBridge), "HK2 guice bridge is required!")
Guicey configuration¶
ru.vyarus.dropwizard.guice.module.GuiceyConfigurationInfo
binding provides access
to guicey configuration details:
@Inject GuiceyConfigurationInfo info
Binding scope¶
As you know, guice assume prototype scope by default (when no scope annotation declared).
Guice scope annotations:
@Singleton
- single instance per context@RequestScoped
- object per request (if guice filter support is not disabled@Prototype
- prototype scope annotation (useful for jersey services to override force singleton scope)
Jersey extensions (resources, providers) force singleton scope for extensions without explicit binding annotation (but this could be disabled with an option). Use explicit scope annotations where singletons are not required.
Request scope transition¶
This is guice feature, it is just mentioned here.
Guice could access request scoped bindings only in current thread. If you need to access request scoped binding in different thread, you need to transfer request scope into that thread:
@Singleton public class RequestBean { // provider will not work in other thread because of request scope @Inject Provider<UriInfo> uri; public void doSomethingInRequestScope() { // wrap logic that require request scope Callable<String> action = ServletScopes.transferRequest(() -> { // access request scoped binding in different thread return uri.get().getQueryParameters().getFirst("q"); }); CompletableFuture.runAsync(() -> { try { // execute action in different thread action.call(); } catch (Exception e) { e.printStackTrace(); } }); } }
Extensions initialisation¶
All guicey extensions are bound to guice context (.bind(ExtType)
, to be compatible with .requireExplicitBindings()
strict option)
that means that all singleton scoped extensions will be initialized in time of injector creation
(because by default, Stage.PRODCUTION
used).
Tip
You can change guice injector stage to Stage.DEVELOPMENT
in the main bundle
to avoid singletons initialization during context creation (only .asEagerSingleton()
)
bindings will be initialized). But don't do it without a really good reason
(for example, legacy codebase required development stage).
Also, you can avoid extension binding by annotating it with @LazyBinding
: this
way extension will not be bound to guice context and so will initialize only
on first usage (obtaining from context).
HK2 scope¶
By default, all jersey extensions (resources, providers) are managed by guice. But you can switch it to be managed by HK2 by default
GuiceBundle.builder() ... .useHK2ForJerseyExtensions() .build()
Warning
You will not be able to use guice AOP on HK2 managed beans.
Depending on default mode selected, you can use annotations to change extension DI:
@HK2Managed
- extension managed by HK2 (useful in default mode when all beans created by guice)@GuiceManaged
- extension managed by guice (default, useful only when.useHK2ForJerseyExtensions()
enabled)
Bindings override¶
Guicey provides direct support for overriding bindings and even override overridden bindings in tests.