or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

annotations.mdapplication-context.mdbean-definition.mdbean-factory.mdbean-processing.mdbean-providers.mdenvironment-config.mdevents.mdexceptions.mdindex.mdscoping.md

scoping.mddocs/

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

```