Skip to content

Instantly share code, notes, and snippets.

@sameb
Last active April 24, 2023 14:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sameb/56871be6994052ad2db8c007d5df2948 to your computer and use it in GitHub Desktop.
Save sameb/56871be6994052ad2db8c007d5df2948 to your computer and use it in GitHub Desktop.
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.google.inject.Provides;
import com.google.inject.ScopeAnnotation;
import com.google.inject.Stage;
import com.google.inject.binder.ScopedBindingBuilder;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* A scope annotation that indicates the binding is a singleton that should be loaded eagerly.
* Used in conjunction with EagerSingletonModule.
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RUNTIME)
@ScopeAnnotation
public @interface EagerSingleton {}
import com.google.inject.AbstractModule;
import com.google.inject.Binding;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Provides;
import com.google.inject.Scope;
import com.google.inject.Scopes;
import com.google.inject.Singleton;
import com.google.inject.Stage;
import com.google.inject.binder.ScopedBindingBuilder;
import com.google.inject.spi.DefaultBindingScopingVisitor;
import java.lang.annotation.Annotation;
/**
* Binds the EagerSingleton scope annotation. It is just like Singleton, except the singleton loading
* is manually triggered.
*
* <p>Eager singletons must be loaded by injecting EagerSingletonLoader and calling loadEagerSingletons.
*
* <p>This is typically most useful as an annotation on {@literal @} {@link Provides} methods, as an
* easy way to simulate the behavior of {@link ScopedBindingBuilder#asEagerSingleton()}. It is also
* useful if your injector starts in {@link Stage#DEVELOPMENT} but you want some specific singletons
* to eagerly load (without using asEagerSingleton). Tests that do not call loadEagerSingletons
* will not load the eager singletons.
*/
public final class EagerSingletonModule extends AbstractModule {
private static final Scope SCOPE =
new Scope() {
@Override
public <T> Provider<T> scope(Key<T> key, Provider<T> creator) {
return Scopes.SINGLETON.scope(key, creator);
}
@Override
public String toString() {
return "EagerSingleton";
}
};
/** Loads all things annotated with EagerSingleton. */
@Singleton
public static class EagerSingletonLoader {
private final Injector injector;
@Inject
EagerSingletonLoader(Injector injector) {
this.injector = injector;
}
public void loadEagerSingletons() {
Visitor visitor = new Visitor();
for (Binding<?> binding : injector.getAllBindings().values()) {
if (binding.acceptScopingVisitor(visitor)) {
binding.getProvider().get(); // construct it.
}
}
}
}
/** Returns whether the given binding is an eager singleton. */
public static boolean isEagerSingleton(Binding<?> binding) {
return Scopes.isScoped(binding, SCOPE, EagerSingleton.class);
}
/** A simple visitor that returns true if the scope is EagerSingleton. */
private static class Visitor extends DefaultBindingScopingVisitor<Boolean> {
@Override
public Boolean visitScope(Scope scope) {
return scope == SCOPE;
}
@Override
public Boolean visitScopeAnnotation(Class<? extends Annotation> scopeAnnotation) {
return scopeAnnotation == EagerSingleton.class;
}
@Override
protected Boolean visitOther() {
return false;
}
}
@Override
protected void configure() {
bindScope(EagerSingleton.class, SCOPE);
bind(EagerSingletonLoader.class);
}
/** Overridden so multiple of these modules can be installed without creating conflicting bindings. */
@Override
public boolean equals(Object o) {
return o != null && this.getClass().equals(o.getClass());
}
/** Overridden so multiple of these modules can be installed without creating conflicting bindings. */
@Override
public int hashCode() {
return this.getClass().hashCode();
}
/**
* <p>Overridden to emulate the expected behavior of {@link Object#toString} by using {@link
* System#identityHashCode}. The inhereted implementation will use {@link #hashCode}, which is the
* same across all instances and is likely not what users expect when they see a {@code
* Name@1234}-style {@code toString} output.
*/
@Override
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(this));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment