0
# Scoping and Lifecycle
1
2
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.
3
4
## Built-in Scopes
5
6
### Singleton Scope
7
8
Singleton beans are created once per application context and shared across all injection points.
9
10
```java { .api }
11
@Target({TYPE, METHOD})
12
@Retention(RUNTIME)
13
@Scope
14
public @interface Singleton {
15
}
16
```
17
18
**Usage Examples:**
19
20
```java
21
import jakarta.inject.Singleton;
22
23
@Singleton
24
public class ConfigurationService {
25
private final Properties config;
26
27
public ConfigurationService() {
28
this.config = loadConfiguration();
29
System.out.println("ConfigurationService created once");
30
}
31
32
public String getProperty(String key) {
33
return config.getProperty(key);
34
}
35
}
36
37
// Multiple injections share the same instance
38
@Singleton
39
public class ServiceA {
40
@Inject
41
private ConfigurationService config; // Same instance
42
}
43
44
@Singleton
45
public class ServiceB {
46
@Inject
47
private ConfigurationService config; // Same instance as in ServiceA
48
}
49
```
50
51
### Prototype Scope
52
53
Prototype beans create a new instance for each injection point.
54
55
```java { .api }
56
@Target({TYPE, METHOD})
57
@Retention(RUNTIME)
58
@Scope
59
public @interface Prototype {
60
}
61
```
62
63
**Usage Examples:**
64
65
```java
66
import io.micronaut.context.annotation.Prototype;
67
68
@Prototype
69
public class RequestProcessor {
70
private final String id;
71
private final long createdAt;
72
73
public RequestProcessor() {
74
this.id = UUID.randomUUID().toString();
75
this.createdAt = System.currentTimeMillis();
76
System.out.println("New RequestProcessor created: " + id);
77
}
78
79
public void processRequest(String data) {
80
System.out.println("Processing with ID: " + id + " at " + createdAt);
81
}
82
}
83
84
@Singleton
85
public class RequestHandler {
86
87
@Inject
88
private RequestProcessor processor1; // New instance
89
90
@Inject
91
private RequestProcessor processor2; // Different instance
92
93
public void handleRequests() {
94
processor1.processRequest("data1"); // Different ID
95
processor2.processRequest("data2"); // Different ID
96
}
97
}
98
```
99
100
## Custom Scopes
101
102
### CustomScope Interface
103
104
Interface for implementing custom bean scopes.
105
106
```java { .api }
107
public interface CustomScope<T extends Annotation> {
108
Class<T> annotationType();
109
<B> B get(BeanCreationContext<B> creationContext);
110
default <B> Optional<B> remove(BeanIdentifier identifier) {
111
return Optional.empty();
112
}
113
}
114
```
115
116
### Creating Custom Scopes
117
118
```java
119
import io.micronaut.context.scope.CustomScope;
120
import io.micronaut.inject.BeanCreationContext;
121
import jakarta.inject.Singleton;
122
import java.lang.annotation.Retention;
123
import java.lang.annotation.RetentionPolicy;
124
125
// Custom scope annotation
126
@CustomScope
127
@Retention(RetentionPolicy.RUNTIME)
128
public @interface RequestScoped {
129
}
130
131
// Scope implementation
132
@Singleton
133
public class RequestScopeImpl implements CustomScope<RequestScoped> {
134
135
private final ThreadLocal<Map<String, Object>> scopedBeans =
136
ThreadLocal.withInitial(HashMap::new);
137
138
@Override
139
public Class<RequestScoped> annotationType() {
140
return RequestScoped.class;
141
}
142
143
@Override
144
public <B> B get(BeanCreationContext<B> creationContext) {
145
String key = creationContext.id().toString();
146
Map<String, Object> beans = scopedBeans.get();
147
148
return (B) beans.computeIfAbsent(key, k -> creationContext.create());
149
}
150
151
@Override
152
public <B> Optional<B> remove(BeanIdentifier identifier) {
153
Map<String, Object> beans = scopedBeans.get();
154
return Optional.ofNullable((B) beans.remove(identifier.toString()));
155
}
156
157
public void clearScope() {
158
scopedBeans.get().clear();
159
}
160
161
public void removeScope() {
162
scopedBeans.remove();
163
}
164
}
165
166
// Usage of custom scope
167
@RequestScoped
168
public class UserSession {
169
private final Map<String, Object> attributes = new HashMap<>();
170
private final String sessionId;
171
172
public UserSession() {
173
this.sessionId = UUID.randomUUID().toString();
174
System.out.println("UserSession created: " + sessionId);
175
}
176
177
public void setAttribute(String key, Object value) {
178
attributes.put(key, value);
179
}
180
181
public Object getAttribute(String key) {
182
return attributes.get(key);
183
}
184
}
185
```
186
187
### Session Scope Example
188
189
```java
190
import io.micronaut.context.scope.CustomScope;
191
import jakarta.inject.Singleton;
192
193
@CustomScope
194
@Retention(RetentionPolicy.RUNTIME)
195
public @interface SessionScoped {
196
}
197
198
@Singleton
199
public class SessionScopeImpl implements CustomScope<SessionScoped> {
200
201
private final Map<String, Map<String, Object>> sessions = new ConcurrentHashMap<>();
202
203
@Override
204
public Class<SessionScoped> annotationType() {
205
return SessionScoped.class;
206
}
207
208
@Override
209
public <B> B get(BeanCreationContext<B> creationContext) {
210
String sessionId = getCurrentSessionId();
211
String beanKey = creationContext.id().toString();
212
213
Map<String, Object> sessionBeans = sessions.computeIfAbsent(sessionId, k -> new ConcurrentHashMap<>());
214
215
return (B) sessionBeans.computeIfAbsent(beanKey, k -> creationContext.create());
216
}
217
218
@Override
219
public <B> Optional<B> remove(BeanIdentifier identifier) {
220
String sessionId = getCurrentSessionId();
221
Map<String, Object> sessionBeans = sessions.get(sessionId);
222
223
if (sessionBeans != null) {
224
return Optional.ofNullable((B) sessionBeans.remove(identifier.toString()));
225
}
226
return Optional.empty();
227
}
228
229
public void destroySession(String sessionId) {
230
Map<String, Object> sessionBeans = sessions.remove(sessionId);
231
if (sessionBeans != null) {
232
// Perform cleanup for session beans
233
sessionBeans.clear();
234
}
235
}
236
237
private String getCurrentSessionId() {
238
// Get session ID from HTTP request, security context, etc.
239
return "session-" + Thread.currentThread().getId();
240
}
241
}
242
```
243
244
## Lifecycle Management
245
246
### LifeCycle Interface
247
248
Interface for managing component lifecycle.
249
250
```java { .api }
251
public interface LifeCycle<T extends LifeCycle<T>> {
252
boolean isRunning();
253
T start();
254
T stop();
255
256
default T refresh() {
257
if (isRunning()) {
258
stop();
259
}
260
return start();
261
}
262
}
263
```
264
265
### Lifecycle Implementation
266
267
```java
268
import io.micronaut.context.LifeCycle;
269
import jakarta.inject.Singleton;
270
271
@Singleton
272
public class DatabaseConnectionPool implements LifeCycle<DatabaseConnectionPool> {
273
274
private boolean running = false;
275
private final List<Connection> connections = new ArrayList<>();
276
private final int maxConnections = 10;
277
278
@Override
279
public boolean isRunning() {
280
return running;
281
}
282
283
@Override
284
public DatabaseConnectionPool start() {
285
if (!running) {
286
System.out.println("Starting database connection pool...");
287
288
// Initialize connections
289
for (int i = 0; i < maxConnections; i++) {
290
Connection conn = createConnection();
291
connections.add(conn);
292
}
293
294
running = true;
295
System.out.println("Database connection pool started with " + connections.size() + " connections");
296
}
297
return this;
298
}
299
300
@Override
301
public DatabaseConnectionPool stop() {
302
if (running) {
303
System.out.println("Stopping database connection pool...");
304
305
// Close all connections
306
for (Connection conn : connections) {
307
try {
308
conn.close();
309
} catch (SQLException e) {
310
System.err.println("Error closing connection: " + e.getMessage());
311
}
312
}
313
314
connections.clear();
315
running = false;
316
System.out.println("Database connection pool stopped");
317
}
318
return this;
319
}
320
321
public Connection getConnection() {
322
if (!running) {
323
throw new IllegalStateException("Connection pool is not running");
324
}
325
326
// Return available connection (simplified)
327
return connections.isEmpty() ? null : connections.get(0);
328
}
329
330
private Connection createConnection() {
331
// Create database connection
332
try {
333
return DriverManager.getConnection("jdbc:h2:mem:test");
334
} catch (SQLException e) {
335
throw new RuntimeException("Failed to create connection", e);
336
}
337
}
338
}
339
```
340
341
## Bean Lifecycle Annotations
342
343
### @PostConstruct
344
345
Method called after bean construction and dependency injection.
346
347
```java { .api }
348
@Target(METHOD)
349
@Retention(RUNTIME)
350
public @interface PostConstruct {
351
}
352
```
353
354
### @PreDestroy
355
356
Method called before bean destruction.
357
358
```java { .api }
359
@Target(METHOD)
360
@Retention(RUNTIME)
361
public @interface PreDestroy {
362
}
363
```
364
365
### Lifecycle Method Examples
366
367
```java
368
import jakarta.annotation.PostConstruct;
369
import jakarta.annotation.PreDestroy;
370
import jakarta.inject.Singleton;
371
372
@Singleton
373
public class EmailService {
374
375
private SMTPConnection connection;
376
private boolean initialized = false;
377
378
@PostConstruct
379
public void initialize() {
380
System.out.println("Initializing EmailService...");
381
382
// Setup SMTP connection
383
connection = new SMTPConnection("smtp.example.com", 587);
384
connection.authenticate("user", "password");
385
386
// Load templates
387
loadEmailTemplates();
388
389
initialized = true;
390
System.out.println("EmailService initialized successfully");
391
}
392
393
@PreDestroy
394
public void cleanup() {
395
System.out.println("Cleaning up EmailService...");
396
397
if (connection != null) {
398
connection.disconnect();
399
}
400
401
// Clear templates cache
402
clearTemplateCache();
403
404
initialized = false;
405
System.out.println("EmailService cleanup completed");
406
}
407
408
public void sendEmail(String to, String subject, String body) {
409
if (!initialized) {
410
throw new IllegalStateException("EmailService not initialized");
411
}
412
413
// Send email using connection
414
connection.sendEmail(to, subject, body);
415
}
416
417
private void loadEmailTemplates() {
418
// Load email templates from resources
419
}
420
421
private void clearTemplateCache() {
422
// Clear template cache
423
}
424
}
425
```
426
427
### Complex Lifecycle Management
428
429
```java
430
import jakarta.annotation.PostConstruct;
431
import jakarta.annotation.PreDestroy;
432
import jakarta.inject.Singleton;
433
import java.util.concurrent.ScheduledExecutorService;
434
import java.util.concurrent.Executors;
435
import java.util.concurrent.TimeUnit;
436
437
@Singleton
438
public class MetricsCollector {
439
440
private ScheduledExecutorService scheduler;
441
private final Map<String, AtomicLong> metrics = new ConcurrentHashMap<>();
442
private volatile boolean collecting = false;
443
444
@PostConstruct
445
public void startCollection() {
446
System.out.println("Starting metrics collection...");
447
448
scheduler = Executors.newScheduledThreadPool(2);
449
collecting = true;
450
451
// Schedule periodic metric collection
452
scheduler.scheduleAtFixedRate(this::collectSystemMetrics, 0, 30, TimeUnit.SECONDS);
453
scheduler.scheduleAtFixedRate(this::collectApplicationMetrics, 5, 60, TimeUnit.SECONDS);
454
455
System.out.println("Metrics collection started");
456
}
457
458
@PreDestroy
459
public void stopCollection() {
460
System.out.println("Stopping metrics collection...");
461
462
collecting = false;
463
464
if (scheduler != null) {
465
scheduler.shutdown();
466
try {
467
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
468
scheduler.shutdownNow();
469
}
470
} catch (InterruptedException e) {
471
scheduler.shutdownNow();
472
Thread.currentThread().interrupt();
473
}
474
}
475
476
// Flush final metrics
477
flushMetrics();
478
479
System.out.println("Metrics collection stopped");
480
}
481
482
private void collectSystemMetrics() {
483
if (collecting) {
484
metrics.put("memory.used", new AtomicLong(Runtime.getRuntime().totalMemory()));
485
metrics.put("memory.max", new AtomicLong(Runtime.getRuntime().maxMemory()));
486
}
487
}
488
489
private void collectApplicationMetrics() {
490
if (collecting) {
491
// Collect application-specific metrics
492
metrics.put("requests.count", new AtomicLong(getRequestCount()));
493
}
494
}
495
496
private void flushMetrics() {
497
// Send metrics to monitoring system
498
System.out.println("Flushing metrics: " + metrics.size() + " entries");
499
}
500
}
501
```
502
503
## Scoped Bean Injection
504
505
### Injecting Scoped Beans
506
507
```java
508
import jakarta.inject.Singleton;
509
import jakarta.inject.Inject;
510
511
@Singleton
512
public class OrderController {
513
514
// Singleton injection - same instance always
515
@Inject
516
private OrderService orderService;
517
518
// Prototype injection - new instance each time accessed
519
@Inject
520
private RequestProcessor requestProcessor;
521
522
// Custom scope injection - scope-specific instance
523
@Inject
524
private UserSession userSession;
525
526
public void processOrder(OrderRequest request) {
527
// Use singleton service
528
Order order = orderService.createOrder(request);
529
530
// Use prototype processor (new instance)
531
requestProcessor.process(request);
532
533
// Use session-scoped bean
534
userSession.setAttribute("lastOrder", order);
535
}
536
}
537
```
538
539
### Provider-based Injection
540
541
```java
542
import jakarta.inject.Provider;
543
import jakarta.inject.Singleton;
544
import jakarta.inject.Inject;
545
546
@Singleton
547
public class BatchProcessor {
548
549
// Provider for prototype beans - creates new instance on each get()
550
@Inject
551
private Provider<TaskProcessor> processorProvider;
552
553
public void processBatch(List<Task> tasks) {
554
for (Task task : tasks) {
555
// Get new processor instance for each task
556
TaskProcessor processor = processorProvider.get();
557
processor.process(task);
558
}
559
}
560
}
561
```
562
563
## Scope-based Bean Destruction
564
565
### Handling Bean Destruction
566
567
```java
568
import io.micronaut.context.scope.CustomScope;
569
import io.micronaut.inject.BeanCreationContext;
570
import jakarta.inject.Singleton;
571
572
@Singleton
573
public class TimedScopeImpl implements CustomScope<TimedScoped> {
574
575
private final Map<String, ScopedBean> scopedBeans = new ConcurrentHashMap<>();
576
private final ScheduledExecutorService cleanup = Executors.newScheduledThreadPool(1);
577
578
@Override
579
public Class<TimedScoped> annotationType() {
580
return TimedScoped.class;
581
}
582
583
@Override
584
public <B> B get(BeanCreationContext<B> creationContext) {
585
String key = creationContext.id().toString();
586
587
ScopedBean scopedBean = scopedBeans.computeIfAbsent(key, k -> {
588
B bean = creationContext.create();
589
long expirationTime = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5);
590
591
// Schedule cleanup
592
cleanup.schedule(() -> {
593
ScopedBean removed = scopedBeans.remove(k);
594
if (removed != null && removed.bean instanceof AutoCloseable) {
595
try {
596
((AutoCloseable) removed.bean).close();
597
} catch (Exception e) {
598
System.err.println("Error closing scoped bean: " + e.getMessage());
599
}
600
}
601
}, 5, TimeUnit.MINUTES);
602
603
return new ScopedBean(bean, expirationTime);
604
});
605
606
return (B) scopedBean.bean;
607
}
608
609
private static class ScopedBean {
610
final Object bean;
611
final long expirationTime;
612
613
ScopedBean(Object bean, long expirationTime) {
614
this.bean = bean;
615
this.expirationTime = expirationTime;
616
}
617
}
618
}
619
```
620
621
## Implementation Classes
622
623
### BeanCreationContext
624
625
Context for creating scoped beans.
626
627
```java { .api }
628
public interface BeanCreationContext<T> {
629
BeanIdentifier id();
630
BeanDefinition<T> definition();
631
T create();
632
}
633
```
634
635
### BeanIdentifier
636
637
Identifier for beans within scopes.
638
639
```java { .api }
640
public interface BeanIdentifier {
641
String getName();
642
Class<?> getBeanType();
643
@Override
644
String toString();
645
}
646
```