CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-google-inject--guice

Lightweight dependency injection framework for Java 8 and above that eliminates factories and the use of 'new' through @Inject annotation

Pending
Overview
Eval results
Files

providers-scopes.mddocs/

Providers & Scopes

Custom instance creation and lifecycle management including built-in scopes and provider interfaces for complex object construction.

Capabilities

Provider Interface

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

Scope Interface

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

Scopes Class

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 Implementation

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

Provider Utilities

Providers Class

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

Advanced Provider Patterns

Conditional Providers

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

Caching Providers

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

Resource Management Providers

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

Exception Types

OutOfScopeException

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

docs

advanced.md

annotations.md

core-injection.md

index.md

modules.md

multibindings.md

providers-scopes.md

spi.md

types-keys.md

tile.json