Lightweight dependency injection framework for Java 8 and above that eliminates factories and the use of 'new' through @Inject annotation
—
Custom instance creation and lifecycle management including built-in scopes and provider interfaces for complex object construction.
Provides instances of type T, used for custom object creation and lazy initialization.
/**
* An object capable of providing instances of type T. Providers are used in numerous
* ways by Guice when the default means for obtaining instances is insufficient.
* @param <T> the type of object this provides
*/
public interface Provider<T> extends jakarta.inject.Provider<T> {
/**
* Provides an instance of T.
* @return instance of T
* @throws OutOfScopeException when an attempt is made to access a scoped object
* while the scope in question is not currently active
* @throws ProvisionException if an instance cannot be provided. Such exceptions include messages
* and throwables to describe why provision failed.
*/
@Override
T get();
}Usage Examples:
// Custom provider implementation
public class DatabaseConnectionProvider implements Provider<DatabaseConnection> {
private final String connectionUrl;
private final int maxRetries;
@Inject
public DatabaseConnectionProvider(
@Named("db.url") String connectionUrl,
@Named("db.max.retries") int maxRetries
) {
this.connectionUrl = connectionUrl;
this.maxRetries = maxRetries;
}
@Override
public DatabaseConnection get() {
for (int i = 0; i < maxRetries; i++) {
try {
return DriverManager.getConnection(connectionUrl);
} catch (SQLException e) {
if (i == maxRetries - 1) {
throw new RuntimeException("Failed to connect after " + maxRetries + " attempts", e);
}
try {
Thread.sleep(1000 * (i + 1)); // Exponential backoff
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted while retrying connection", ie);
}
}
}
throw new RuntimeException("Should never reach here");
}
}
// Bind to provider
public class DatabaseModule extends AbstractModule {
@Override
protected void configure() {
bind(DatabaseConnection.class).toProvider(DatabaseConnectionProvider.class);
}
}
// Inject providers for lazy initialization
public class UserRepository {
private final Provider<DatabaseConnection> connectionProvider;
@Inject
public UserRepository(Provider<DatabaseConnection> connectionProvider) {
this.connectionProvider = connectionProvider;
}
public User findById(String id) {
// Connection created only when needed
DatabaseConnection conn = connectionProvider.get();
return conn.query("SELECT * FROM users WHERE id = ?", id);
}
}Strategy for controlling instance lifecycle and reuse.
/**
* A scope is a level of visibility that instances provided by Guice may have.
* By default, an instance created by the Injector has no scope, meaning it is
* created each time it is needed.
*/
public interface Scope {
/**
* Scopes a provider. The returned provider returns objects from this scope.
* If an object does not exist in this scope, the provider can use the given
* unscoped provider to retrieve one.
* @param key Key identifying the binding
* @param unscoped Provider to use when no scoped instance exists
* @return Scoped provider
*/
<T> Provider<T> scope(Key<T> key, Provider<T> unscoped);
/**
* Returns a string representation of this scope. Used for error messages.
* @return String representation
*/
String toString();
}Built-in scope implementations.
/**
* Built-in scope instances.
*/
public final class Scopes {
/**
* One instance per Injector. Also see @Singleton.
*/
public static final Scope SINGLETON = new SingletonScope();
/**
* No scoping; instances are created every time they're requested.
*/
public static final Scope NO_SCOPE = new NoScope();
/**
* Returns true if the given scope is a singleton scope.
* @param scope Scope to check
* @return true if singleton scope
*/
public static boolean isSingleton(Scope scope);
}Usage Examples:
// Using built-in scopes
public class ServiceModule extends AbstractModule {
@Override
protected void configure() {
// Singleton scope - one instance per injector
bind(DatabaseConnectionPool.class).in(Scopes.SINGLETON);
bind(Configuration.class).in(Singleton.class); // Equivalent
// No scope - new instance every time
bind(RequestHandler.class).in(Scopes.NO_SCOPE);
// Default is no scope, so this is equivalent:
bind(RequestHandler.class);
}
}// Custom scope example: Request scope for web applications
public class RequestScope implements Scope {
private final ThreadLocal<Map<Key<?>, Object>> requestScopedObjects =
new ThreadLocal<Map<Key<?>, Object>>();
public void enter() {
requestScopedObjects.set(new HashMap<Key<?>, Object>());
}
public void exit() {
requestScopedObjects.remove();
}
@Override
public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) {
return () -> {
Map<Key<?>, Object> scopedObjects = requestScopedObjects.get();
if (scopedObjects == null) {
throw new OutOfScopeException("Cannot access " + key + " outside of request scope");
}
@SuppressWarnings("unchecked")
T current = (T) scopedObjects.get(key);
if (current == null) {
current = unscoped.get();
scopedObjects.put(key, current);
}
return current;
};
}
@Override
public String toString() {
return "RequestScope";
}
}
// Custom scope annotation
@ScopeAnnotation
@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface RequestScoped {}
// Register custom scope
public class WebModule extends AbstractModule {
private final RequestScope requestScope = new RequestScope();
@Override
protected void configure() {
bindScope(RequestScoped.class, requestScope);
// Bind request-scoped objects
bind(UserSession.class).in(RequestScoped.class);
bind(RequestContext.class).in(RequestScoped.class);
}
@Provides
@Exposed
RequestScope provideRequestScope() {
return requestScope;
}
}
// Use custom scope
@RequestScoped
public class UserSession {
private String userId;
private long startTime;
@Inject
public UserSession() {
this.startTime = System.currentTimeMillis();
}
// Session methods
}Static utilities for creating Provider instances.
/**
* Static utility methods for creating and working with Provider instances.
*/
public final class Providers {
/**
* Returns a provider that always provides the same instance.
* @param instance Instance to provide
* @return Provider that returns the instance
*/
public static <T> Provider<T> of(T instance);
/**
* Converts a JSR-330 Provider to a Guice Provider.
* @param provider JSR-330 provider
* @return Guice provider
*/
public static <T> Provider<T> guicify(javax.inject.Provider<T> provider);
}Usage Examples:
// Create providers for constant values
Provider<String> appNameProvider = Providers.of("MyApplication");
Provider<Integer> versionProvider = Providers.of(42);
// Use in bindings
public class ConstantModule extends AbstractModule {
@Override
protected void configure() {
bind(String.class).annotatedWith(Names.named("app.name"))
.toProvider(Providers.of("MyApplication"));
bind(Integer.class).annotatedWith(Names.named("app.version"))
.toProvider(Providers.of(2));
}
}
// Convert JSR-330 providers
javax.inject.Provider<DatabaseService> jsr330Provider = () -> new DatabaseServiceImpl();
Provider<DatabaseService> guiceProvider = Providers.guicify(jsr330Provider);public class ConditionalDatabaseProvider implements Provider<DatabaseService> {
private final String environment;
private final Provider<ProductionDatabase> prodProvider;
private final Provider<TestDatabase> testProvider;
@Inject
public ConditionalDatabaseProvider(
@Named("environment") String environment,
Provider<ProductionDatabase> prodProvider,
Provider<TestDatabase> testProvider
) {
this.environment = environment;
this.prodProvider = prodProvider;
this.testProvider = testProvider;
}
@Override
public DatabaseService get() {
if ("production".equals(environment)) {
return prodProvider.get();
} else {
return testProvider.get();
}
}
}public class CachingProvider<T> implements Provider<T> {
private final Provider<T> delegate;
private volatile T instance;
public CachingProvider(Provider<T> delegate) {
this.delegate = delegate;
}
@Override
public T get() {
if (instance == null) {
synchronized (this) {
if (instance == null) {
instance = delegate.get();
}
}
}
return instance;
}
}public class ManagedResourceProvider implements Provider<DatabaseConnection> {
private final String connectionUrl;
private final Set<DatabaseConnection> activeConnections = new ConcurrentHashMap<>();
@Inject
public ManagedResourceProvider(@Named("db.url") String connectionUrl) {
this.connectionUrl = connectionUrl;
// Register shutdown hook to clean up connections
Runtime.getRuntime().addShutdownHook(new Thread(this::cleanup));
}
@Override
public DatabaseConnection get() {
try {
DatabaseConnection conn = DriverManager.getConnection(connectionUrl);
activeConnections.add(conn);
return conn;
} catch (SQLException e) {
throw new RuntimeException("Failed to create database connection", e);
}
}
private void cleanup() {
for (DatabaseConnection conn : activeConnections) {
try {
conn.close();
} catch (SQLException e) {
// Log error but continue cleanup
}
}
activeConnections.clear();
}
}Thrown when accessing scoped objects outside their scope.
/**
* Thrown when an attempt is made to access a scoped object when the scope
* in question is not currently active.
*/
public class OutOfScopeException extends RuntimeException {
/**
* Creates a new OutOfScopeException.
* @param message Error message
*/
public OutOfScopeException(String message);
/**
* Creates a new OutOfScopeException.
* @param message Error message
* @param cause Underlying cause
*/
public OutOfScopeException(String message, Throwable cause);
}Install with Tessl CLI
npx tessl i tessl/maven-com-google-inject--guice