Core dependency injection interfaces and components for the Micronaut Framework
—
Micronaut provides comprehensive bean scoping and lifecycle management capabilities, including built-in scopes like Singleton and Prototype, as well as support for custom scopes. The framework also manages bean lifecycle through interfaces and annotations.
Singleton beans are created once per application context and shared across all injection points.
@Target({TYPE, METHOD})
@Retention(RUNTIME)
@Scope
public @interface Singleton {
}Usage Examples:
import jakarta.inject.Singleton;
@Singleton
public class ConfigurationService {
private final Properties config;
public ConfigurationService() {
this.config = loadConfiguration();
System.out.println("ConfigurationService created once");
}
public String getProperty(String key) {
return config.getProperty(key);
}
}
// Multiple injections share the same instance
@Singleton
public class ServiceA {
@Inject
private ConfigurationService config; // Same instance
}
@Singleton
public class ServiceB {
@Inject
private ConfigurationService config; // Same instance as in ServiceA
}Prototype beans create a new instance for each injection point.
@Target({TYPE, METHOD})
@Retention(RUNTIME)
@Scope
public @interface Prototype {
}Usage Examples:
import io.micronaut.context.annotation.Prototype;
@Prototype
public class RequestProcessor {
private final String id;
private final long createdAt;
public RequestProcessor() {
this.id = UUID.randomUUID().toString();
this.createdAt = System.currentTimeMillis();
System.out.println("New RequestProcessor created: " + id);
}
public void processRequest(String data) {
System.out.println("Processing with ID: " + id + " at " + createdAt);
}
}
@Singleton
public class RequestHandler {
@Inject
private RequestProcessor processor1; // New instance
@Inject
private RequestProcessor processor2; // Different instance
public void handleRequests() {
processor1.processRequest("data1"); // Different ID
processor2.processRequest("data2"); // Different ID
}
}Interface for implementing custom bean scopes.
public interface CustomScope<T extends Annotation> {
Class<T> annotationType();
<B> B get(BeanCreationContext<B> creationContext);
default <B> Optional<B> remove(BeanIdentifier identifier) {
return Optional.empty();
}
}import io.micronaut.context.scope.CustomScope;
import io.micronaut.inject.BeanCreationContext;
import jakarta.inject.Singleton;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
// Custom scope annotation
@CustomScope
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestScoped {
}
// Scope implementation
@Singleton
public class RequestScopeImpl implements CustomScope<RequestScoped> {
private final ThreadLocal<Map<String, Object>> scopedBeans =
ThreadLocal.withInitial(HashMap::new);
@Override
public Class<RequestScoped> annotationType() {
return RequestScoped.class;
}
@Override
public <B> B get(BeanCreationContext<B> creationContext) {
String key = creationContext.id().toString();
Map<String, Object> beans = scopedBeans.get();
return (B) beans.computeIfAbsent(key, k -> creationContext.create());
}
@Override
public <B> Optional<B> remove(BeanIdentifier identifier) {
Map<String, Object> beans = scopedBeans.get();
return Optional.ofNullable((B) beans.remove(identifier.toString()));
}
public void clearScope() {
scopedBeans.get().clear();
}
public void removeScope() {
scopedBeans.remove();
}
}
// Usage of custom scope
@RequestScoped
public class UserSession {
private final Map<String, Object> attributes = new HashMap<>();
private final String sessionId;
public UserSession() {
this.sessionId = UUID.randomUUID().toString();
System.out.println("UserSession created: " + sessionId);
}
public void setAttribute(String key, Object value) {
attributes.put(key, value);
}
public Object getAttribute(String key) {
return attributes.get(key);
}
}import io.micronaut.context.scope.CustomScope;
import jakarta.inject.Singleton;
@CustomScope
@Retention(RetentionPolicy.RUNTIME)
public @interface SessionScoped {
}
@Singleton
public class SessionScopeImpl implements CustomScope<SessionScoped> {
private final Map<String, Map<String, Object>> sessions = new ConcurrentHashMap<>();
@Override
public Class<SessionScoped> annotationType() {
return SessionScoped.class;
}
@Override
public <B> B get(BeanCreationContext<B> creationContext) {
String sessionId = getCurrentSessionId();
String beanKey = creationContext.id().toString();
Map<String, Object> sessionBeans = sessions.computeIfAbsent(sessionId, k -> new ConcurrentHashMap<>());
return (B) sessionBeans.computeIfAbsent(beanKey, k -> creationContext.create());
}
@Override
public <B> Optional<B> remove(BeanIdentifier identifier) {
String sessionId = getCurrentSessionId();
Map<String, Object> sessionBeans = sessions.get(sessionId);
if (sessionBeans != null) {
return Optional.ofNullable((B) sessionBeans.remove(identifier.toString()));
}
return Optional.empty();
}
public void destroySession(String sessionId) {
Map<String, Object> sessionBeans = sessions.remove(sessionId);
if (sessionBeans != null) {
// Perform cleanup for session beans
sessionBeans.clear();
}
}
private String getCurrentSessionId() {
// Get session ID from HTTP request, security context, etc.
return "session-" + Thread.currentThread().getId();
}
}Interface for managing component lifecycle.
public interface LifeCycle<T extends LifeCycle<T>> {
boolean isRunning();
T start();
T stop();
default T refresh() {
if (isRunning()) {
stop();
}
return start();
}
}import io.micronaut.context.LifeCycle;
import jakarta.inject.Singleton;
@Singleton
public class DatabaseConnectionPool implements LifeCycle<DatabaseConnectionPool> {
private boolean running = false;
private final List<Connection> connections = new ArrayList<>();
private final int maxConnections = 10;
@Override
public boolean isRunning() {
return running;
}
@Override
public DatabaseConnectionPool start() {
if (!running) {
System.out.println("Starting database connection pool...");
// Initialize connections
for (int i = 0; i < maxConnections; i++) {
Connection conn = createConnection();
connections.add(conn);
}
running = true;
System.out.println("Database connection pool started with " + connections.size() + " connections");
}
return this;
}
@Override
public DatabaseConnectionPool stop() {
if (running) {
System.out.println("Stopping database connection pool...");
// Close all connections
for (Connection conn : connections) {
try {
conn.close();
} catch (SQLException e) {
System.err.println("Error closing connection: " + e.getMessage());
}
}
connections.clear();
running = false;
System.out.println("Database connection pool stopped");
}
return this;
}
public Connection getConnection() {
if (!running) {
throw new IllegalStateException("Connection pool is not running");
}
// Return available connection (simplified)
return connections.isEmpty() ? null : connections.get(0);
}
private Connection createConnection() {
// Create database connection
try {
return DriverManager.getConnection("jdbc:h2:mem:test");
} catch (SQLException e) {
throw new RuntimeException("Failed to create connection", e);
}
}
}Method called after bean construction and dependency injection.
@Target(METHOD)
@Retention(RUNTIME)
public @interface PostConstruct {
}Method called before bean destruction.
@Target(METHOD)
@Retention(RUNTIME)
public @interface PreDestroy {
}import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.inject.Singleton;
@Singleton
public class EmailService {
private SMTPConnection connection;
private boolean initialized = false;
@PostConstruct
public void initialize() {
System.out.println("Initializing EmailService...");
// Setup SMTP connection
connection = new SMTPConnection("smtp.example.com", 587);
connection.authenticate("user", "password");
// Load templates
loadEmailTemplates();
initialized = true;
System.out.println("EmailService initialized successfully");
}
@PreDestroy
public void cleanup() {
System.out.println("Cleaning up EmailService...");
if (connection != null) {
connection.disconnect();
}
// Clear templates cache
clearTemplateCache();
initialized = false;
System.out.println("EmailService cleanup completed");
}
public void sendEmail(String to, String subject, String body) {
if (!initialized) {
throw new IllegalStateException("EmailService not initialized");
}
// Send email using connection
connection.sendEmail(to, subject, body);
}
private void loadEmailTemplates() {
// Load email templates from resources
}
private void clearTemplateCache() {
// Clear template cache
}
}import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.inject.Singleton;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@Singleton
public class MetricsCollector {
private ScheduledExecutorService scheduler;
private final Map<String, AtomicLong> metrics = new ConcurrentHashMap<>();
private volatile boolean collecting = false;
@PostConstruct
public void startCollection() {
System.out.println("Starting metrics collection...");
scheduler = Executors.newScheduledThreadPool(2);
collecting = true;
// Schedule periodic metric collection
scheduler.scheduleAtFixedRate(this::collectSystemMetrics, 0, 30, TimeUnit.SECONDS);
scheduler.scheduleAtFixedRate(this::collectApplicationMetrics, 5, 60, TimeUnit.SECONDS);
System.out.println("Metrics collection started");
}
@PreDestroy
public void stopCollection() {
System.out.println("Stopping metrics collection...");
collecting = false;
if (scheduler != null) {
scheduler.shutdown();
try {
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
scheduler.shutdownNow();
}
} catch (InterruptedException e) {
scheduler.shutdownNow();
Thread.currentThread().interrupt();
}
}
// Flush final metrics
flushMetrics();
System.out.println("Metrics collection stopped");
}
private void collectSystemMetrics() {
if (collecting) {
metrics.put("memory.used", new AtomicLong(Runtime.getRuntime().totalMemory()));
metrics.put("memory.max", new AtomicLong(Runtime.getRuntime().maxMemory()));
}
}
private void collectApplicationMetrics() {
if (collecting) {
// Collect application-specific metrics
metrics.put("requests.count", new AtomicLong(getRequestCount()));
}
}
private void flushMetrics() {
// Send metrics to monitoring system
System.out.println("Flushing metrics: " + metrics.size() + " entries");
}
}import jakarta.inject.Singleton;
import jakarta.inject.Inject;
@Singleton
public class OrderController {
// Singleton injection - same instance always
@Inject
private OrderService orderService;
// Prototype injection - new instance each time accessed
@Inject
private RequestProcessor requestProcessor;
// Custom scope injection - scope-specific instance
@Inject
private UserSession userSession;
public void processOrder(OrderRequest request) {
// Use singleton service
Order order = orderService.createOrder(request);
// Use prototype processor (new instance)
requestProcessor.process(request);
// Use session-scoped bean
userSession.setAttribute("lastOrder", order);
}
}import jakarta.inject.Provider;
import jakarta.inject.Singleton;
import jakarta.inject.Inject;
@Singleton
public class BatchProcessor {
// Provider for prototype beans - creates new instance on each get()
@Inject
private Provider<TaskProcessor> processorProvider;
public void processBatch(List<Task> tasks) {
for (Task task : tasks) {
// Get new processor instance for each task
TaskProcessor processor = processorProvider.get();
processor.process(task);
}
}
}import io.micronaut.context.scope.CustomScope;
import io.micronaut.inject.BeanCreationContext;
import jakarta.inject.Singleton;
@Singleton
public class TimedScopeImpl implements CustomScope<TimedScoped> {
private final Map<String, ScopedBean> scopedBeans = new ConcurrentHashMap<>();
private final ScheduledExecutorService cleanup = Executors.newScheduledThreadPool(1);
@Override
public Class<TimedScoped> annotationType() {
return TimedScoped.class;
}
@Override
public <B> B get(BeanCreationContext<B> creationContext) {
String key = creationContext.id().toString();
ScopedBean scopedBean = scopedBeans.computeIfAbsent(key, k -> {
B bean = creationContext.create();
long expirationTime = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5);
// Schedule cleanup
cleanup.schedule(() -> {
ScopedBean removed = scopedBeans.remove(k);
if (removed != null && removed.bean instanceof AutoCloseable) {
try {
((AutoCloseable) removed.bean).close();
} catch (Exception e) {
System.err.println("Error closing scoped bean: " + e.getMessage());
}
}
}, 5, TimeUnit.MINUTES);
return new ScopedBean(bean, expirationTime);
});
return (B) scopedBean.bean;
}
private static class ScopedBean {
final Object bean;
final long expirationTime;
ScopedBean(Object bean, long expirationTime) {
this.bean = bean;
this.expirationTime = expirationTime;
}
}
}Context for creating scoped beans.
public interface BeanCreationContext<T> {
BeanIdentifier id();
BeanDefinition<T> definition();
T create();
}Identifier for beans within scopes.
public interface BeanIdentifier {
String getName();
Class<?> getBeanType();
@Override
String toString();
}Install with Tessl CLI
npx tessl i tessl/maven-io-micronaut--micronaut-inject