Lightweight dependency injection framework for Java 8 and above that eliminates factories and the use of 'new' through @Inject annotation
—
Key annotations for marking injection points, scoping instances, and creating binding qualifiers.
Marks members (constructors, methods, fields) for dependency injection.
/**
* Annotates members of your implementation class (constructors, methods,
* and fields) into which the Injector should inject values.
*/
@Target({METHOD, CONSTRUCTOR, FIELD})
@Retention(RUNTIME)
public @interface Inject {
/**
* If true, and the appropriate binding is not found,
* the Injector will skip injection of this method or field
* rather than produce an error.
* @return true if injection is optional
*/
boolean optional() default false;
}Usage Examples:
public class UserService {
// Field injection
@Inject
private DatabaseService databaseService;
// Optional field injection
@Inject(optional = true)
private CacheService cacheService;
// Constructor injection (recommended)
@Inject
public UserService(DatabaseService databaseService, LoggingService logger) {
this.databaseService = databaseService;
this.logger = logger;
}
// Method injection
@Inject
public void setConfiguration(Configuration config) {
this.config = config;
}
// Optional method injection
@Inject(optional = true)
public void setMetrics(MetricsService metrics) {
this.metrics = metrics; // Only called if binding exists
}
}Scope annotation ensuring only one instance per Injector.
/**
* Apply this to implementation classes when you want only one instance
* (per Injector) to be reused for all injections.
*/
@Target({TYPE, METHOD})
@Retention(RUNTIME)
@ScopeAnnotation
public @interface Singleton {}Usage Examples:
// Singleton class
@Singleton
public class DatabaseConnectionPool {
private final List<Connection> connections;
@Inject
public DatabaseConnectionPool(DatabaseConfig config) {
this.connections = createConnections(config);
}
}
// Singleton binding in module
public class DatabaseModule extends AbstractModule {
@Override
protected void configure() {
bind(DatabaseService.class).to(PostgreSQLService.class).in(Singleton.class);
}
}
// Singleton provider method
public class ConfigModule extends AbstractModule {
@Provides
@Singleton
Configuration provideConfiguration() {
return Configuration.load("app.properties");
}
}Marks methods in Modules as provider methods for creating bindings.
/**
* Annotates methods in Modules to create bindings. The method's return type
* is bound to its returned value. Guice will pass dependencies to the method
* as parameters.
*/
@Target(METHOD)
@Retention(RUNTIME)
public @interface Provides {}Usage Examples:
public class ApplicationModule extends AbstractModule {
@Override
protected void configure() {
// Regular bindings here
}
// Simple provider method
@Provides
DatabaseConfig provideDatabaseConfig() {
return new DatabaseConfig("localhost", 5432, "myapp");
}
// Provider method with dependencies
@Provides
DatabaseService provideDatabaseService(DatabaseConfig config, Logger logger) {
return new PostgreSQLService(config, logger);
}
// Singleton provider method
@Provides
@Singleton
ConnectionPool provideConnectionPool(DatabaseConfig config) {
return new HikariConnectionPool(config);
}
// Named provider method
@Provides
@Named("primary")
Cache providePrimaryCache() {
return new RedisCache("primary-redis:6379");
}
// Provider method with complex initialization
@Provides
EmailService provideEmailService(
@Named("smtp.host") String smtpHost,
@Named("smtp.port") int smtpPort,
EmailConfig config
) {
EmailService service = new EmailService();
service.configure(smtpHost, smtpPort);
service.setTemplateDirectory(config.getTemplateDir());
service.initialize();
return service;
}
}Binding annotation for distinguishing multiple bindings of the same type by name.
/**
* Annotates named things.
*/
@Retention(RUNTIME)
@Target({FIELD, PARAMETER, METHOD})
@BindingAnnotation
public @interface Named {
String value();
}Usage Examples:
// Multiple bindings of the same type
public class CacheModule extends AbstractModule {
@Override
protected void configure() {
bind(Cache.class).annotatedWith(Names.named("primary"))
.to(RedisCache.class);
bind(Cache.class).annotatedWith(Names.named("secondary"))
.to(MemcachedCache.class);
bind(Cache.class).annotatedWith(Names.named("local"))
.to(InMemoryCache.class);
}
}
// Provider methods with names
public class DatabaseModule extends AbstractModule {
@Provides
@Named("primary-db")
DatabaseConnection providePrimaryConnection() {
return DriverManager.getConnection("jdbc:postgresql://primary-db/app");
}
@Provides
@Named("secondary-db")
DatabaseConnection provideSecondaryConnection() {
return DriverManager.getConnection("jdbc:postgresql://secondary-db/app");
}
}
// Injection with named dependencies
public class UserRepository {
private final DatabaseConnection primaryDb;
private final DatabaseConnection secondaryDb;
private final Cache primaryCache;
@Inject
public UserRepository(
@Named("primary-db") DatabaseConnection primaryDb,
@Named("secondary-db") DatabaseConnection secondaryDb,
@Named("primary") Cache primaryCache
) {
this.primaryDb = primaryDb;
this.secondaryDb = secondaryDb;
this.primaryCache = primaryCache;
}
}
// Constants binding with names
public class ConfigModule extends AbstractModule {
@Override
protected void configure() {
bindConstant().annotatedWith(Names.named("api.timeout")).to(30000);
bindConstant().annotatedWith(Names.named("api.retries")).to(3);
bindConstant().annotatedWith(Names.named("app.version")).to("1.2.0");
}
}Meta-annotation for creating custom binding annotations.
/**
* Annotates annotations which are used for binding. Only one such annotation
* may apply to a single injection point. You must also annotate the annotation
* with @Retention(RUNTIME).
*/
@Target(ANNOTATION_TYPE)
@Retention(RUNTIME)
public @interface BindingAnnotation {}Usage Examples:
// Create custom binding annotations
@BindingAnnotation
@Target({FIELD, PARAMETER, METHOD})
@Retention(RUNTIME)
public @interface Primary {}
@BindingAnnotation
@Target({FIELD, PARAMETER, METHOD})
@Retention(RUNTIME)
public @interface Secondary {}
@BindingAnnotation
@Target({FIELD, PARAMETER, METHOD})
@Retention(RUNTIME)
public @interface LoggerFor {
Class<?> value();
}
// Use custom binding annotations
public class ServiceModule extends AbstractModule {
@Override
protected void configure() {
bind(DatabaseService.class).annotatedWith(Primary.class)
.to(PrimaryDatabaseService.class);
bind(DatabaseService.class).annotatedWith(Secondary.class)
.to(SecondaryDatabaseService.class);
}
}
// Inject with custom annotations
public class DataProcessor {
@Inject @Primary DatabaseService primaryDb;
@Inject @Secondary DatabaseService secondaryDb;
@Inject @LoggerFor(DataProcessor.class) Logger logger;
}Meta-annotation for creating custom scope annotations.
/**
* Annotates annotations which are used for scoping. Only one such annotation
* may apply to a single implementation class. You must also annotate the
* annotation with @Retention(RUNTIME).
*/
@Target(ANNOTATION_TYPE)
@Retention(RUNTIME)
public @interface ScopeAnnotation {}Usage Examples:
// Create custom scope annotation
@ScopeAnnotation
@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface RequestScoped {}
// Implement the scope
public class RequestScope implements Scope {
private final ThreadLocal<Map<Key<?>, Object>> requestScopedObjects =
new ThreadLocal<Map<Key<?>, Object>>();
@Override
public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) {
return () -> {
Map<Key<?>, Object> scopedObjects = requestScopedObjects.get();
if (scopedObjects == null) {
scopedObjects = new HashMap<>();
requestScopedObjects.set(scopedObjects);
}
@SuppressWarnings("unchecked")
T current = (T) scopedObjects.get(key);
if (current == null) {
current = unscoped.get();
scopedObjects.put(key, current);
}
return current;
};
}
}
// Bind the scope
public class WebModule extends AbstractModule {
@Override
protected void configure() {
bindScope(RequestScoped.class, new RequestScope());
bind(UserSession.class).in(RequestScoped.class);
}
}
// Use the custom scope
@RequestScoped
public class UserSession {
private String userId;
private Map<String, Object> attributes = new HashMap<>();
// Implementation
}Utilities for working with @Named annotations.
/**
* Utility methods for use with @Named.
*/
public final class Names {
/**
* Creates a @Named annotation with the given name.
* @param name Name for the annotation
* @return Named annotation instance
*/
public static Named named(String name);
/**
* Binds properties from a Map to named constants.
* @param binder Binder to use
* @param properties Map of property names to values
*/
public static void bindProperties(Binder binder, Map<String, String> properties);
/**
* Binds properties from Properties to named constants.
* @param binder Binder to use
* @param properties Properties object
*/
public static void bindProperties(Binder binder, Properties properties);
}Usage Examples:
// Create named annotations programmatically
Named primaryNamed = Names.named("primary");
Key<Cache> primaryCacheKey = Key.get(Cache.class, primaryNamed);
// Bind properties from configuration files
public class ConfigModule extends AbstractModule {
@Override
protected void configure() {
Properties props = new Properties();
props.load(getClass().getResourceAsStream("/app.properties"));
Names.bindProperties(binder(), props);
// Now you can inject properties like:
// @Inject @Named("database.url") String dbUrl;
// @Inject @Named("connection.pool.size") int poolSize;
}
}Install with Tessl CLI
npx tessl i tessl/maven-com-google-inject--guice