CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-micronaut--micronaut-inject

Core dependency injection interfaces and components for the Micronaut Framework

Pending
Overview
Eval results
Files

scoping.mddocs/

Scoping and Lifecycle

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.

Built-in Scopes

Singleton Scope

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 Scope

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

Custom Scopes

CustomScope Interface

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

Creating Custom Scopes

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

Session Scope Example

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

Lifecycle Management

LifeCycle Interface

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

Lifecycle Implementation

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

Bean Lifecycle Annotations

@PostConstruct

Method called after bean construction and dependency injection.

@Target(METHOD)
@Retention(RUNTIME)
public @interface PostConstruct {
}

@PreDestroy

Method called before bean destruction.

@Target(METHOD)  
@Retention(RUNTIME)
public @interface PreDestroy {
}

Lifecycle Method Examples

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

Complex Lifecycle Management

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

Scoped Bean Injection

Injecting Scoped Beans

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

Provider-based Injection

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

Scope-based Bean Destruction

Handling Bean Destruction

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

Implementation Classes

BeanCreationContext

Context for creating scoped beans.

public interface BeanCreationContext<T> {
    BeanIdentifier id();
    BeanDefinition<T> definition();
    T create();
}

BeanIdentifier

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

docs

annotations.md

application-context.md

bean-definition.md

bean-factory.md

bean-processing.md

bean-providers.md

environment-config.md

events.md

exceptions.md

index.md

scoping.md

tile.json