- * Configuration items (modules, extensions, bundles) are not aware of each other and listeners - * could be used to tie them. For example, to tell bundle if some other bundles registered (limited - * applicability, but just for example). - *
- * You can also use {@link ru.vyarus.dropwizard.guice.module.lifecycle.GuiceyLifecycleAdapter} when you need to - * handle multiple events (it replaces direct events handling with simple methods). - *
- * Listener is not registered if equal listener were already registered ({@link java.util.Set} used as
- * listeners storage), so if you need to be sure that only one instance of some listener will be used
- * implement {@link Object#equals(Object)} and {@link Object#hashCode()}.
- *
- * @param listeners guicey lifecycle listeners
- * @return builder instance for chained calls
- * @see ru.vyarus.dropwizard.guice.module.lifecycle.GuiceyLifecycle
- * @see ru.vyarus.dropwizard.guice.module.lifecycle.GuiceyLifecycleAdapter
- * @see ru.vyarus.dropwizard.guice.module.lifecycle.UniqueGuiceyLifecycleListener
- */
- public Builder listen(final GuiceyLifecycleListener... listeners) {
- bundle.context.lifecycle().register(listeners);
- return this;
- }
+ private boolean buildCalled;
/**
* Options is a generic mechanism to provide internal configuration values for guicey and 3rd party bundles.
@@ -238,7 +246,7 @@ public Builder listen(final GuiceyLifecycleListener... listeners) {
* @see GuiceyOptions
* @see ru.vyarus.dropwizard.guice.module.installer.InstallersOptions
*/
- public
+ * When no packages specified, scan would be performed for application class package.
*
* @param basePackages packages to scan extensions in
* @return builder instance for chained calls
* @see GuiceyOptions#ScanPackages
*/
public Builder enableAutoConfig(final String... basePackages) {
- Preconditions.checkState(basePackages.length > 0, "Specify at least one package to scan");
- return option(ScanPackages, basePackages);
+ final String[] packs = basePackages.length > 0 ? basePackages : new String[]{GuiceyInitializer.APP_PKG};
+ return option(ScanPackages, packs);
+ }
+
+ /**
+ * Filter classes which could be recognized as an extension. Filter applied before extension recognition,
+ * so you could slightly increase startup time by avoiding extension detection for some classes.
+ * Filter also applied for guice bindings (extensions recognized from guice bindings).
+ *
+ * For example, if you want to exclude all annotated classes:
+ * {@code autoConfigFilter(type -> !type.isAnnotationPresent(Stub.class))}
+ *
+ * For simplicity, use {@link ru.vyarus.dropwizard.guice.test.util.ClassFilters} utility with static import
+ * for building predicates:
+ * {@code autoConfigFilter(ignoreAnnotated(Stub.class))}
+ *
+ * Another example, suppose you want spring-style configuration: accept only classes with some annotation:
+ * {@code autoConfigFilter(annotated(Component.class))}
+ *
+ * The difference with {@link #disable(java.util.function.Predicate[])} predicate: by default, guicey would
+ * apply all installers for each class to detect extension and disable predicate prevents recognized extension
+ * installation, whereas this filter prevents classes from being detected. As a result, even if filtered class
+ * was an actual extension - it would not be recognized and not registered (not visible in reports).
+ *
+ * Multiple filters could be specified.
+ * Filter applied only for extensions (does not affect commands and installers search).
+ *
+ * Be aware that filter would be also called for all guice bindings (if extension from binding recognition
+ * enabled). So filter could be used to filter recognitions from bindings too.
+ *
+ * @param filter filter predicate for classes suitable for extensions detection
+ * @return builder instance for chained calls
+ * @see ru.vyarus.dropwizard.guice.test.util.ClassFilters for simple annotation predicates implementation
+ */
+ public Builder autoConfigFilter(final Predicate
+ * Alternatively, registration could be delayed with
+ * {@link #whenConfigurationReady(java.util.function.Consumer)}.And another option is to register module inside
* {@link GuiceyBundle#run(GuiceyEnvironment)}, which is called under run phase.
*
* @param modules one or more guice modules
@@ -433,8 +480,9 @@ public Builder modulesOverride(final Module... modules) {
* with {@link #enableAutoConfig(String...)}).
*
* Enables commands classpath search. All found commands are instantiated and registered in
- * bootstrap. Default constructor is used for simple commands, but {@link io.dropwizard.cli.EnvironmentCommand}
- * must have constructor with {@link io.dropwizard.Application} argument.
+ * bootstrap. Default constructor is used for simple commands, but
+ * {@link io.dropwizard.core.cli.EnvironmentCommand} must have constructor with
+ * {@link io.dropwizard.core.Application} argument.
*
* By default, commands search is disabled.
*
@@ -466,12 +514,13 @@ public Builder noDefaultInstallers() {
* objects provider injections still may be used in resources (will work through HK2 provider).
*
* Guice servlets initialization took ~50ms, so injector creation will be a bit faster after disabling.
+ *
+ * Soft deprecation: try to avoid hk2 direct usage if possible, someday HK2 support will be removed.
+ * In case of hk2 remove, guice request scope will be mandatory.
*
* @return builder instance for chained calls
* @see GuiceyOptions#GuiceFilterRegistration
- * @deprecated in the next version HK2 support will be removed and guice request scope will be mandatory
*/
- @Deprecated
public Builder noGuiceFilter() {
return option(GuiceFilterRegistration, EnumSet.noneOf(DispatcherType.class));
}
@@ -502,6 +551,10 @@ public final Builder installers(final Class extends FeatureInstaller>... insta
*
* Alternatively, you can manually bind extensions in guice module and they would be recognized
* ({@link GuiceyOptions#AnalyzeGuiceModules}).
+ *
+ * If extension registration depends on configuration value then use
+ * {@link #whenConfigurationReady(java.util.function.Consumer)} or register extension inside
+ * {@link GuiceyBundle#run(GuiceyEnvironment)}, which is called under run phase.
*
* @param extensionClasses extension bean classes to register
* @return builder instance for chained calls
@@ -588,9 +641,13 @@ public final Builder disableInstallers(final Class extends FeatureInstaller>..
}
/**
- * Extensions disable is mostly useful for testing. In some cases, it could be used to disable some extra
- * extensions installed with classpath scan or bundle. But, generally, try to avoid manual extensions
- * disabling for clearer application configuration.
+ * Extensions disable mostly useful for testing. In some cases, it could be used to disable some extra
+ * extensions installed with classpath scan or bundle.
+ *
+ * Disabling could be used for removing some temporal extensions like rest api stubs.
+ *
+ * For extensions, detected from guice bindings, disabling extension would remove existing guice binding
+ * (when module analysis enabled).
*
* @param extensions extensions to disable (manually added, registered by bundles or with classpath scan)
* @return builder instance for chained calls
@@ -662,7 +719,8 @@ public final Builder disableDropwizardBundles(final Class extends ConfiguredBu
* Essentially, predicates are the same as calling direct disable methods: items, disabled by predicate,
* will be marked as disabled by predicate registration context (application or guicey bundle).
*
- * Mostly useful for testing, but in some cases could be used directly.
+ * Mostly useful for testing, but in some cases could be used directly (e.g. could be used for sisabling
+ * temporary extensions, like rest api stubs).
*
* Use {@link Predicate#and(Predicate)}, {@link Predicate#or(Predicate)} and {@link Predicate#negate()}
* to combine complex predicates from simple ones from
@@ -700,11 +758,41 @@ public final Builder disableDropwizardBundles(final Class extends ConfiguredBu
* @see ru.vyarus.dropwizard.guice.module.context.Disables for common predicates
*/
@SafeVarargs
- public final Builder disable(final Predicate
+ * Configuration items (modules, extensions, bundles) are not aware of each other and listeners
+ * could be used to tie them. For example, to tell bundle if some other bundles registered (limited
+ * applicability, but just for example).
+ *
+ * You can also use {@link ru.vyarus.dropwizard.guice.module.lifecycle.GuiceyLifecycleAdapter} when you need to
+ * handle multiple events (it replaces direct events handling with simple methods).
+ *
+ * Listener is not registered if equal listener were already registered ({@link java.util.Set} used as
+ * listeners storage), so if you need to be sure that only one instance of some listener will be used
+ * implement {@link Object#equals(Object)} and {@link Object#hashCode()}.
+ *
+ * @param listeners guicey lifecycle listeners
+ * @return builder instance for chained calls
+ * @see ru.vyarus.dropwizard.guice.module.lifecycle.GuiceyLifecycle
+ * @see ru.vyarus.dropwizard.guice.module.lifecycle.GuiceyLifecycleAdapter
+ * @see ru.vyarus.dropwizard.guice.module.lifecycle.UniqueGuiceyLifecycleListener
+ */
+ public Builder listen(final GuiceyLifecycleListener... listeners) {
+ bundle.context.lifecycle().register(listeners);
+ return this;
+ }
+
/**
* Enables strict control of beans instantiation context: all beans must be instantiated by guice, except
* beans annotated with {@link ru.vyarus.dropwizard.guice.module.installer.feature.jersey.JerseyManaged}.
@@ -717,12 +805,12 @@ public final Builder disable(final Predicate
* To implicitly enable this check in all tests use
* {@code PropertyBundleLookup.enableBundles(HK2DebugBundle.class)}.
+ *
+ * Soft deprecation: try to avoid hk2 direct usage if possible, someday HK2 support will be removed
*
* @return builder instance for chained calls
* @see HK2DebugBundle
- * @deprecated in the next version HK2 support will be removed and option will become useless
*/
- @Deprecated
public Builder strictScopeControl() {
bundle.context.registerBundles(new HK2DebugBundle());
return this;
@@ -739,14 +827,14 @@ public Builder strictScopeControl() {
* dependency is not available.
*
* WARNING: you will not be able to use guice AOP on beans managed by HK2!
+ *
+ * Soft deprecation: try to avoid hk2 direct usage if possible, someday HK2 support will be removed
*
* @return builder instance for chained calls
* @see InstallersOptions#JerseyExtensionsManagedByGuice
* @see ru.vyarus.dropwizard.guice.module.installer.feature.jersey.JerseyManaged
* @see ru.vyarus.dropwizard.guice.module.installer.feature.jersey.GuiceManaged
- * @deprecated in the next version HK2 support will be removed
*/
- @Deprecated
public Builder useHK2ForJerseyExtensions() {
option(JerseyExtensionsManagedByGuice, false);
option(UseHkBridge, true);
@@ -768,11 +856,25 @@ public Builder useHK2ForJerseyExtensions() {
*
* @return builder instance for chained calls
* @see ConfigurationDiagnostic
+ * @see ru.vyarus.dropwizard.guice.debug.hook.DiagnosticHook
*/
public Builder printDiagnosticInfo() {
return listen(new ConfigurationDiagnostic());
}
+ /**
+ * Prints extensions usage help: all extension signs recognized by installers. Installers printed in
+ * execution order.
+ *
+ * Not that custom installers must provide this information by overriding
+ * {@link ru.vyarus.dropwizard.guice.module.installer.FeatureInstaller#getRecognizableSigns()}.
+ *
+ * @return builder instance for chained calls
+ */
+ public Builder printExtensionsHelp() {
+ return listen(new ExtensionsHelpDiagnostic());
+ }
+
/**
* Prints all registered (not disabled) installers with registration source. Useful to see all supported
* extension types when multiple guicey bundles registered and available features become not obvious
@@ -893,6 +995,44 @@ public Builder printGuiceAopMap(final GuiceAopConfig config) {
return listen(new GuiceAopDiagnostic(config));
}
+ /**
+ * Prints guice beans construction time (during application startup) and number of created beans.
+ * Suitable for:
+ *
+ * By default, the report prints only startup time data. If you need to measure time for application runtime,
+ * construct diagnostic manually:
+ *
+ * For compiled application report could be activated with: {@code -Dguicey.hooks=provision-time}
+ *
+ * @return builder instance for chained calls
+ * @see ru.vyarus.dropwizard.guice.debug.hook.GuiceProvisionTimeHook for simple usage in tests
+ */
+ public Builder printGuiceProvisionTime() {
+ return bundles(new GuiceProvisionDiagnostic(true));
+ }
+
/**
* Split logs with major lifecycle stage names. Useful for debugging (to better understand
* at what stage your code is executed). Also, could be used for light profiling as
@@ -949,6 +1089,53 @@ public Builder printJerseyConfig() {
return listen(new JerseyConfigDiagnostic());
}
+ /**
+ * Prints detailed application startup (and shutdown) times. Useful for startup slowness investigations and
+ * initialization order validation (executed hooks and budles order).
+ *
+ * Time measured from guice bundle creation. Report instruments {@link io.dropwizard.core.setup.Bootstrap}
+ * and {@link io.dropwizard.lifecycle.setup.LifecycleEnvironment} objects to measure bunldes and managed
+ * objects times.
+ *
+ * Init time measured as time from guice bundle creation until the last dropwizard bundle init
+ * (bundles registered before guice bundle can't be measured; application init method time will go
+ * to run phase).
+ * Run tim measured as time from init until the last dropwizard bundle run (configuration and environment
+ * creation measured separately appears under run phase; application run method time will go to web phase).
+ * Web time is a time after run until jersey lifecycle notification (server started).
+ *
+ * Overall, report shows almost all application startup time with exact phases. Plus all guicey internal
+ * actions.
+ *
+ * For compiled application report could be activated with: {@code -Dguicey.hooks=startup-time}
+ *
+ * @return builder instance for chained calls
+ * @see ru.vyarus.dropwizard.guice.debug.hook.StartupTimeHook
+ */
+ public Builder printStartupTime() {
+ return listen(new StartupTimeDiagnostic());
+ }
+
+ /**
+ * Prints {@link ru.vyarus.dropwizard.guice.module.context.SharedConfigurationState} usage history after
+ * application startup. The report shows:
+ *
+ * Note that report could be obtained at any time from the shared state object directly:
+ * {@link ru.vyarus.dropwizard.guice.module.context.SharedConfigurationState#getAccessReport()}
+ * (could be useful for resolving problems before application startup).
+ *
+ * @return builder instance for chained calls
+ */
+ public Builder printSharedStateUsage() {
+ return listen(new SharedStateDiagnostic());
+ }
+
/**
* Guicey hooks ({@link GuiceyConfigurationHook}) may be loaded with system property "guicey.hooks". But
* it may be not comfortable to always declare full class name (e.g. -Dguicey.hooks=com.foo.bar.Hook,..).
@@ -966,16 +1153,39 @@ public Builder hookAlias(final String name, final Class extends GuiceyConfigur
return this;
}
+ /**
+ * Delayed configuration block. Would run under run phase where configuration and environment objects would
+ * be available. Essentially, this is the same point as guicey bundles run
+ * ({@link ru.vyarus.dropwizard.guice.module.installer.bundle.GuiceyBundle#run(
+ * ru.vyarus.dropwizard.guice.module.installer.bundle.GuiceyEnvironment)}), but called just before bundles
+ * processing.
+ *
+ * Useful when guice modules require configuration values or when extensions registration depends on
+ * configuration values.
+ *
+ * Note: guice injector is not available yet!
+ *
+ * Method is not started with "on" or "listen" as other methods to differentiate this configuration
+ * method from pure listeners.
+ *
+ * @param runPhaseAction action to execute under run phase
+ * @return builder instance for chained calls
+ */
+ public Builder whenConfigurationReady(final Consumer
* Caution: this is intended to be used only in cases when there is no other option except global state.
*
- * This method could be useful for possible hook needы (maybe hooks communications) because there is no
+ * This method could be useful for possible hook needs (maybe hooks communications) because there is no
* other way to access shared state by hooks (bundles may use special api or reference by application instance)
*
* @param stateAction state action
@@ -986,6 +1196,113 @@ public Builder withSharedState(final Consumer
+ * Listener will be called on environment command start too.
+ *
+ * Note: there is no registration method for this listener in main guice bundle builder
+ * ({@link ru.vyarus.dropwizard.guice.GuiceBundle.Builder}) because it is assumed, that such blocks would
+ * always be wrapped with bundles to improve application readability.
+ *
+ * @param listener listener to call after injector creation
+ * @param
+ * If you need to listen only for real server startup then use
+ * {@link #listenServer(io.dropwizard.lifecycle.ServerLifecycleListener)} instead.
+ *
+ * Not called on custom command execution (because no lifecycle involved in this case). In this case you can
+ * use {@link #onGuiceyStartup(GuiceyStartupListener)} as always executed point.
+ *
+ * @param listener listener to call on server startup
+ * @return builder instance for chained calls
+ */
+ public Builder onApplicationStartup(final ApplicationStartupListener listener) {
+ return listen(new ApplicationStartupListenerAdapter(listener));
+ }
+
+ /**
+ * Code to execute after complete application shutdown. Called not only for real application but for
+ * environment commands and lightweight guicey test helpers
+ * ({@link ru.vyarus.dropwizard.guice.test.jupiter.TestGuiceyApp}). Suitable for closing additional resources.
+ *
+ * If you need to listen only for real server shutdown then use
+ * {@link #listenServer(io.dropwizard.lifecycle.ServerLifecycleListener)} instead.
+ *
+ * Not called on command execution because no lifecycle involved in this case.
+ *
+ * @param listener listener to call on server startup
+ * @return builder instance for chained calls
+ */
+ public Builder onApplicationShutdown(final ApplicationShutdownListener listener) {
+ return listen(new ApplicationShutdownListenerAdapter(listener));
+ }
+
+ /**
+ * Shortcut for {@code environment().lifecycle().addServerLifecycleListener} registration.
+ *
+ * Note that server listener is called only when jetty starts up and so will not be called with lightweight
+ * guicey test helpers {@link ru.vyarus.dropwizard.guice.test.jupiter.TestGuiceyApp}. Prefer using
+ * {@link #onApplicationStartup(ApplicationStartupListener)} to be correctly called in tests (of course, if not
+ * server-only execution is desired).
+ *
+ * Not called for custom command execution.
+ *
+ * @param listener server startup listener.
+ * @return builder instance for chained calls
+ */
+ public Builder listenServer(final ServerLifecycleListener listener) {
+ withEnvironment(environment -> environment.lifecycle().addServerLifecycleListener(listener));
+ return this;
+ }
+
+ /**
+ * Shortcut for jetty lifecycle listener {@code environment().lifecycle().addEventListener(listener)}
+ * registration.
+ *
+ * Lifecycle listeners are called with lightweight guicey test helpers
+ * {@link ru.vyarus.dropwizard.guice.test.jupiter.TestGuiceyApp} which makes them perfectly suitable for
+ * reporting.
+ *
+ * If only startup event is required, prefer {@link #onApplicationStartup(ApplicationStartupListener)} method
+ * as more expressive and easier to use.
+ *
+ * Listeners are not called on custom command execution.
+ *
+ * @param listener jetty
+ * @return builder instance for chained calls
+ */
+ public Builder listenJetty(final LifeCycle.Listener listener) {
+ withEnvironment(environment -> environment.lifecycle().addEventListener(listener));
+ return this;
+ }
+
+ /**
+ * Shortcut for jetty events and requests listener {@code environment().jersey().register(listener)}
+ * registration.
+ *
+ * Listeners are not called on custom command execution.
+ *
+ * @param listener listener instance
+ * @return builder instance for chained calls
+ */
+ public Builder listenJersey(final ApplicationEventListener listener) {
+ withEnvironment(environment -> environment.jersey().register(listener));
+ return this;
+ }
+
/**
* @param stage stage to run injector with
* @return bundle instance
@@ -1001,8 +1318,17 @@ public GuiceBundle build(final Stage stage) {
* @see GuiceyOptions#InjectorStage
*/
public GuiceBundle build() {
+ Preconditions.checkState(!buildCalled,
+ ".build() was already called for guice bundle. Most likely, it was called second time in "
+ + GuiceyConfigurationHook.class.getSimpleName());
+ buildCalled = true;
bundle.context.runHooks(this);
+ bundle.context.stat().stopTimer(Stat.BundleBuilderTime);
return bundle;
}
+
+ private void withEnvironment(final Consumer
+ * Special value {@link ru.vyarus.dropwizard.guice.module.GuiceyInitializer#APP_PKG} might be used to configure
+ * application package (replaced into real package during initialization).
*
* @see GuiceBundle.Builder#enableAutoConfig(String...)
*/
ScanPackages(String[].class, new String[0]),
+ /**
+ * Enables package-private and protected (nested) classes acception by scanner (as extension).
+ * By default, only public extension classes are searched.
+ *
+ * Option exists only for rare cases when extension classes are protected by historical reasons.
+ * Otherwise, try to avoid using protected classes as extensions (no problems with that, just semantically not
+ * correct)
+ */
+ ScanProtectedClasses(Boolean.class, false),
+
/**
* Enables commands search in classpath and dynamic installation. Requires auto scan mode.
* Disabled by default.
@@ -117,6 +130,17 @@ public enum GuiceyOptions implements Option {
*/
AnalyzeGuiceModules(Boolean.class, true),
+ /**
+ * Support private modules analysis: in private module only exposed services could be visible.
+ * As guicey works with extension classes only (for simplicity), for private modules it might be required to
+ * add additional exposes (to expose extension binding directly). Also, for disabled extensions bindings would
+ * be removed inside private modules too.
+ *
+ * There are many different possible situations with private modules and quite possibly that not all of them
+ * covered. Disable this option in case of problems to avoid disabling analysis entirely.
+ */
+ AnalyzePrivateGuiceModules(Boolean.class, true),
+
/**
* Guice injector stage used for injector creation.
* Production by default.
@@ -154,13 +178,13 @@ public enum GuiceyOptions implements Option {
* service by HK2 when it also depends on guice services.
*
* IMPORTANT: requires extra dependency on HK2 guice-bridge: 'org.glassfish.hk2:guice-bridge:2.6.1'
- * @deprecated in the next version HK2 support will be removed
+ *
+ * Soft deprecation: try to avoid hk2 direct usage if possible, someday HK2 support will be removed
*/
- @Deprecated
UseHkBridge(Boolean.class, false);
- private Class> type;
- private Object value;
+ private final Class> type;
+ private final Object value;
+ * In order to support this report, installer must implement
+ * {@link ru.vyarus.dropwizard.guice.module.installer.FeatureInstaller#getRecognizableSigns()} (default interface
+ * method).
+ *
+ * If multiple reports would be registered only one instance would be accepted (to prevent report duplications).
+ *
+ * @author Vyacheslav Rusakov
+ * @since 06.12.2022
+ */
+public class ExtensionsHelpDiagnostic extends UniqueGuiceyLifecycleListener {
+ private final Logger logger = LoggerFactory.getLogger(ExtensionsHelpDiagnostic.class);
+
+ @Override
+ protected void installersResolved(final InstallersResolvedEvent event) {
+ logger.info("Recognized extension signs"
+ + new ExtensionsHelpRenderer(event.getInstallers()).renderReport(null));
+ }
+}
diff --git a/src/main/java/ru/vyarus/dropwizard/guice/debug/GuiceAopDiagnostic.java b/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/debug/GuiceAopDiagnostic.java
similarity index 93%
rename from src/main/java/ru/vyarus/dropwizard/guice/debug/GuiceAopDiagnostic.java
rename to dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/debug/GuiceAopDiagnostic.java
index 341f304d1..0967bbab4 100644
--- a/src/main/java/ru/vyarus/dropwizard/guice/debug/GuiceAopDiagnostic.java
+++ b/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/debug/GuiceAopDiagnostic.java
@@ -21,6 +21,11 @@ public class GuiceAopDiagnostic extends GuiceyLifecycleAdapter {
private final Logger logger = LoggerFactory.getLogger(GuiceAopDiagnostic.class);
private final GuiceAopConfig config;
+ /**
+ * Create AOP diagnostic report.
+ *
+ * @param config report config
+ */
public GuiceAopDiagnostic(final GuiceAopConfig config) {
this.config = config;
}
diff --git a/src/main/java/ru/vyarus/dropwizard/guice/debug/GuiceBindingsDiagnostic.java b/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/debug/GuiceBindingsDiagnostic.java
similarity index 94%
rename from src/main/java/ru/vyarus/dropwizard/guice/debug/GuiceBindingsDiagnostic.java
rename to dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/debug/GuiceBindingsDiagnostic.java
index d198e3cc0..3220068b7 100644
--- a/src/main/java/ru/vyarus/dropwizard/guice/debug/GuiceBindingsDiagnostic.java
+++ b/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/debug/GuiceBindingsDiagnostic.java
@@ -23,6 +23,11 @@ public class GuiceBindingsDiagnostic extends UniqueGuiceyLifecycleListener {
private final Logger logger = LoggerFactory.getLogger(GuiceBindingsDiagnostic.class);
private final GuiceConfig config;
+ /**
+ * Create bindings report.
+ *
+ * @param config report config
+ */
public GuiceBindingsDiagnostic(final GuiceConfig config) {
this.config = config;
}
diff --git a/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/debug/GuiceProvisionDiagnostic.java b/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/debug/GuiceProvisionDiagnostic.java
new file mode 100644
index 000000000..e5d19e0bd
--- /dev/null
+++ b/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/debug/GuiceProvisionDiagnostic.java
@@ -0,0 +1,128 @@
+package ru.vyarus.dropwizard.guice.debug;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.inject.AbstractModule;
+import com.google.inject.Binding;
+import com.google.inject.matcher.Matchers;
+import com.google.inject.spi.ProvisionListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import ru.vyarus.dropwizard.guice.debug.report.guice.GuiceProvisionRenderer;
+import ru.vyarus.dropwizard.guice.module.installer.bundle.GuiceyBundle;
+import ru.vyarus.dropwizard.guice.module.installer.bundle.GuiceyEnvironment;
+
+import java.time.Duration;
+
+/**
+ * Guice beans creation (provision) time diagnostic. Records all requested beans creation time. By default,
+ * prints collected data after application startup (most of the beans created at startup).
+ *
+ * The report shows:
+ *
+ * The report also could be used to measure runtime creations:
+ *
+ * Report hacks dropwizard bundles and managed objects to track all bundles time. Note that it is impossible to
+ * track init time of bundles registered before guice bundle.
+ *
+ * Entire application startup time (measured since guice bundle creation) is split into 3 chunks:
+ *
+ * To avoid confusion with server startup time - jvm start time is also shown (time from jvm start and before
+ * guice bundle creation).
+ *
+ * @author Vyacheslav Rusakov
+ * @since 07.03.2025
+ */
+@SuppressWarnings({"ClassDataAbstractionCoupling", "ClassFanOutComplexity", "PMD.ExcessiveImports"})
+public class StartupTimeDiagnostic extends UniqueGuiceyLifecycleListener {
+ private final Logger logger = LoggerFactory.getLogger(StartupTimeDiagnostic.class);
+
+ private final StartupTimeInfo start = new StartupTimeInfo();
+ private final ShutdownTimeInfo stop = new ShutdownTimeInfo();
+
+ // for tracking dw phases in bundles tracker
+ @SuppressWarnings("PMD.LooseCoupling")
+ private DropwizardBundlesTracker bundlesTracker;
+
+ @Override
+ protected void beforeInit(final BeforeInitEvent event) {
+ bundlesTracker = new DropwizardBundlesTracker(event.getStats(), start, event.getBootstrap());
+ }
+
+ @Override
+ protected void bundlesInitialized(final BundlesInitializedEvent event) {
+ // store bundles init order to show them correctly
+ start.setGuiceyBundlesInitOrder(event.getBundles().stream()
+ .map(GuiceyBundle::getClass)
+ .collect(Collectors.toList()));
+ }
+
+ // just before guicey run
+ @Override
+ protected void beforeRun(final BeforeRunEvent event) {
+ // for tracking managed objects execution
+ new ManagedTracker(start, stop, event.getEnvironment().lifecycle());
+ final Stopwatch jerseyTime = Stopwatch.createUnstarted();
+ // listener would be called on normal run and for grizzly 2 rest stubs
+ event.getEnvironment().jersey().register(new ApplicationEventListener() {
+ @Override
+ public void onEvent(final ApplicationEvent applicationEvent) {
+ final ApplicationEvent.Type type = applicationEvent.getType();
+ if (type == ApplicationEvent.Type.INITIALIZATION_START) {
+ jerseyTime.start();
+ } else if (type == ApplicationEvent.Type.INITIALIZATION_FINISHED) {
+ start.setJerseyTime(jerseyTime.elapsed());
+ }
+ }
+
+ @Override
+ public RequestEventListener onRequest(final RequestEvent requestEvent) {
+ return null;
+ }
+ });
+ }
+
+ // guicey run done
+ @Override
+ @SuppressWarnings("AnonInnerLength")
+ protected void applicationRun(final ApplicationRunEvent event) {
+ // remember transitive bundles to correctly calculate each bundle time
+ event.getConfigurationInfo().getGuiceyBundleIds().forEach(id -> {
+ final List
+ * Also, hook could be used in tests to track created guice beans:
+ *
+ *
+ * Report tries to detect common mistakes: JIT binding appeared for configuration values due to missed
+ * qualifier annotation (with annotation it is a correct instance injection, whereas without annotation it
+ * would be a new JIT binding). Such situations might appear due to lombok usage: lombok generated constructor,
+ * but did not copy qualifier annotation from fields.
+ *
+ * This might be used in tests to control guice beans creation amount and time.
+ *
+ * // false to avoid startup report
+ * GuiceProvisionDiagnostic report = new GuiceProvisionDiagnostic(false);
+ * // registre report
+ * GuiceBundle....bundles(report);
+ *
+ * // clear collected data before required point
+ * report.clear();
+ * ...
+ * // generate report after measured actions
+ * logger.info("Guice provision time {}", report.renderReport());
+ *
+ *
+ *
+ *
+ * The report is sorted by overall spent time: if guice bean (in prototype) scope was created several times - the summ
+ * of all creations is counted.
+ *
+ *
+ * @author Vyacheslav Rusakov
+ * @since 24.03.2025
+ */
+public class GuiceProvisionDiagnostic implements GuiceyBundle {
+ private final Logger logger = LoggerFactory.getLogger(GuiceProvisionDiagnostic.class);
+ private final ListMultimap
+ * // false to avoid startup report
+ * GuiceProvisionDiagnostic report = new GuiceProvisionDiagnostic(false);
+ * // registre report
+ * GuiceBundle....bundles(report);
+ *
+ * // clear collected data before required point
+ * report.clear();
+ * // do something requiring new beans creation
+ * injector.getInstance(JitService.class); // just an example
+ *
+ * // generate report after measured actions
+ * logger.info("Guice provision time {}", report.renderReport());
+ *
+ *
+ *
+ * @author Vyacheslav Rusakov
+ * @since 20.03.2025
+ */
+public class SharedStateDiagnostic extends UniqueGuiceyLifecycleListener {
+ private final Logger logger = LoggerFactory.getLogger(SharedStateDiagnostic.class);
+
+ @Override
+ protected void applicationStarted(final ApplicationStartedEvent event) {
+ logger.info("Shared configuration state usage: \n{}", event.getSharedState().getAccessReport());
+ }
+}
diff --git a/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/debug/StartupTimeDiagnostic.java b/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/debug/StartupTimeDiagnostic.java
new file mode 100644
index 000000000..da4b26c03
--- /dev/null
+++ b/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/debug/StartupTimeDiagnostic.java
@@ -0,0 +1,175 @@
+package ru.vyarus.dropwizard.guice.debug;
+
+import com.google.common.base.Stopwatch;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.glassfish.jersey.server.monitoring.ApplicationEvent;
+import org.glassfish.jersey.server.monitoring.ApplicationEventListener;
+import org.glassfish.jersey.server.monitoring.RequestEvent;
+import org.glassfish.jersey.server.monitoring.RequestEventListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import ru.vyarus.dropwizard.guice.debug.report.start.DropwizardBundlesTracker;
+import ru.vyarus.dropwizard.guice.debug.report.start.ManagedTracker;
+import ru.vyarus.dropwizard.guice.debug.report.start.ShutdownTimeInfo;
+import ru.vyarus.dropwizard.guice.debug.report.start.ShutdownTimeRenderer;
+import ru.vyarus.dropwizard.guice.debug.report.start.StartupTimeInfo;
+import ru.vyarus.dropwizard.guice.debug.report.start.StartupTimeRenderer;
+import ru.vyarus.dropwizard.guice.module.GuiceyConfigurationInfo;
+import ru.vyarus.dropwizard.guice.module.context.ConfigItem;
+import ru.vyarus.dropwizard.guice.module.context.info.ItemId;
+import ru.vyarus.dropwizard.guice.module.context.stat.DetailStat;
+import ru.vyarus.dropwizard.guice.module.context.stat.Stat;
+import ru.vyarus.dropwizard.guice.module.context.stat.StatsInfo;
+import ru.vyarus.dropwizard.guice.module.installer.bundle.GuiceyBundle;
+import ru.vyarus.dropwizard.guice.module.lifecycle.UniqueGuiceyLifecycleListener;
+import ru.vyarus.dropwizard.guice.module.lifecycle.event.configuration.BeforeInitEvent;
+import ru.vyarus.dropwizard.guice.module.lifecycle.event.configuration.BundlesInitializedEvent;
+import ru.vyarus.dropwizard.guice.module.lifecycle.event.run.ApplicationRunEvent;
+import ru.vyarus.dropwizard.guice.module.lifecycle.event.run.BeforeRunEvent;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Startup time report. Timers could count only time AFTER guice bundle creation (with guice bundle itself).
+ *
+ *
+ *
+ *
+ * @author Vyacheslav Rusakov
+ * @since 25.03.2025
+ */
+public class GuiceProvisionTimeHook implements GuiceyConfigurationHook {
+
+ /**
+ * System property hook alias name.
+ */
+ public static final String ALIAS = "provision-time";
+ private final GuiceProvisionDiagnostic diagnostic = new GuiceProvisionDiagnostic(true);
+
+ @Override
+ public void configure(final GuiceBundle.Builder builder) {
+ // this is the same as .printGuiceProvisionTime() call, but hook could be used in tests
+ builder.bundles(diagnostic);
+ }
+
+ /**
+ * Clear collected data.
+ */
+ public void clearData() {
+ diagnostic.clear();
+ }
+
+ /**
+ * Map format: binding - provisions time.
+ *
+ * @return recorded provision data
+ */
+ public ListMultimap
+ * {@literal @}EnableHook
+ * static GuiceProvisionTimeHook hook = new GuiceProvisionTimeHook();
+ *
+ * {@literal @}Test
+ * public void test() {
+ * hook.clearData()
+ * // anything requiring provision
+ * injector.getInstance(SomeService.class);
+ * // the report would contain only one bean creation
+ * System.out.println(hook.renderReport())
+ * }
+ *