or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

events.mdindex.mdruntime.mdscheduling.mdscopes.mdserver.mdshutdown.md

scopes.mddocs/

0

# Scope Management

1

2

Advanced bean scope management beyond singleton and prototype scopes. Provides custom scopes including refreshable configurations that reload on property changes and thread-local storage for request isolation.

3

4

## Capabilities

5

6

### @Refreshable Scope

7

8

Custom scope that allows beans to be refreshed when configuration properties change, enabling dynamic reconfiguration without application restart.

9

10

```java { .api }

11

/**

12

* Annotation for beans that should be refreshed when configuration changes

13

* Creates proxy beans that can be invalidated and recreated on demand

14

*/

15

@Target({ElementType.TYPE, ElementType.METHOD})

16

@Retention(RetentionPolicy.RUNTIME)

17

@Scope

18

@ScopedProxy

19

public @interface Refreshable {

20

/**

21

* Configuration key prefixes that trigger refresh of this bean

22

* Empty array means refresh on any configuration change

23

* @return Array of configuration prefixes to watch

24

*/

25

String[] value() default {};

26

}

27

```

28

29

**Usage Examples:**

30

31

```java

32

import io.micronaut.runtime.context.scope.Refreshable;

33

import io.micronaut.context.annotation.ConfigurationProperties;

34

35

// Database configuration that refreshes when database properties change

36

@Refreshable("database")

37

@ConfigurationProperties("database")

38

@Singleton

39

public class DatabaseConfig {

40

private String url;

41

private String username;

42

private String password;

43

private int maxConnections;

44

45

// Getters and setters...

46

public String getUrl() { return url; }

47

public void setUrl(String url) { this.url = url; }

48

49

// When database.* properties change, this bean is recreated

50

}

51

52

// Service that depends on refreshable configuration

53

@Refreshable

54

@Singleton

55

public class DatabaseService {

56

57

private final DatabaseConfig config;

58

private DataSource dataSource;

59

60

public DatabaseService(DatabaseConfig config) {

61

this.config = config;

62

this.dataSource = createDataSource(config);

63

}

64

65

// This service will be recreated when its dependencies are refreshed

66

public Connection getConnection() throws SQLException {

67

return dataSource.getConnection();

68

}

69

}

70

71

// Multiple configuration prefixes

72

@Refreshable({"cache", "redis"})

73

@Singleton

74

public class CacheService {

75

// Refreshes when cache.* or redis.* properties change

76

}

77

78

// Refresh on any configuration change

79

@Refreshable

80

@Singleton

81

public class FlexibleService {

82

// Refreshes when any configuration property changes

83

}

84

```

85

86

### @ThreadLocal Scope

87

88

Scope that stores bean instances in thread-local storage, providing isolated instances per thread.

89

90

```java { .api }

91

/**

92

* Scope annotation for thread-local bean storage

93

* Each thread gets its own instance of the bean

94

*/

95

@Target({ElementType.TYPE, ElementType.METHOD})

96

@Retention(RetentionPolicy.RUNTIME)

97

@Scope

98

@ScopedProxy

99

public @interface ThreadLocal {

100

/**

101

* Enable lifecycle support for thread-local beans

102

* When true, beans will receive lifecycle callbacks (PostConstruct, PreDestroy)

103

* @return true to enable lifecycle support

104

*/

105

boolean lifecycle() default false;

106

}

107

```

108

109

**Usage Examples:**

110

111

```java

112

import io.micronaut.runtime.context.scope.ThreadLocal;

113

114

// Thread-local context for request processing

115

@ThreadLocal

116

@Singleton

117

public class RequestContext {

118

private String requestId;

119

private String userId;

120

private Instant startTime;

121

private Map<String, Object> attributes = new HashMap<>();

122

123

public void initialize(String requestId, String userId) {

124

this.requestId = requestId;

125

this.userId = userId;

126

this.startTime = Instant.now();

127

this.attributes.clear();

128

}

129

130

// Each thread gets its own instance

131

public String getRequestId() { return requestId; }

132

public String getUserId() { return userId; }

133

public void setAttribute(String key, Object value) { attributes.put(key, value); }

134

public Object getAttribute(String key) { return attributes.get(key); }

135

}

136

137

// Thread-local with lifecycle support

138

@ThreadLocal(lifecycle = true)

139

@Singleton

140

public class ThreadLocalResourceManager {

141

142

private Connection connection;

143

private ExecutorService executor;

144

145

@PostConstruct

146

public void initialize() {

147

// Called once per thread when first accessed

148

this.connection = createConnection();

149

this.executor = Executors.newSingleThreadExecutor();

150

}

151

152

@PreDestroy

153

public void cleanup() {

154

// Called when thread terminates (if lifecycle = true)

155

if (connection != null) {

156

try {

157

connection.close();

158

} catch (SQLException e) {

159

logger.warn("Error closing connection", e);

160

}

161

}

162

if (executor != null) {

163

executor.shutdown();

164

}

165

}

166

}

167

168

// Service using thread-local dependencies

169

@Singleton

170

public class RequestProcessor {

171

172

private final RequestContext requestContext;

173

174

public RequestProcessor(RequestContext requestContext) {

175

this.requestContext = requestContext; // Proxy injected

176

}

177

178

public void processRequest(HttpRequest request) {

179

// Initialize thread-local context

180

requestContext.initialize(

181

UUID.randomUUID().toString(),

182

extractUserId(request)

183

);

184

185

// Process request - each thread has isolated context

186

String requestId = requestContext.getRequestId();

187

System.out.println("Processing request: " + requestId);

188

}

189

}

190

```

191

192

### @ScopedProxy Annotation

193

194

Meta-annotation indicating that a scope should use proxy-based implementation.

195

196

```java { .api }

197

/**

198

* Meta-annotation indicating that a scope should use proxies

199

* Enables lazy evaluation and dynamic behavior for custom scopes

200

*/

201

@Target({ElementType.ANNOTATION_TYPE})

202

@Retention(RetentionPolicy.RUNTIME)

203

public @interface ScopedProxy {

204

// Marker annotation for proxy-based scopes

205

}

206

```

207

208

### RefreshEvent

209

210

Event published when configuration refresh occurs, allowing fine-grained control of refresh behavior.

211

212

```java { .api }

213

/**

214

* Event fired when configuration refresh occurs

215

* Allows listeners to respond to configuration changes

216

*/

217

public class RefreshEvent extends ApplicationEvent {

218

219

/**

220

* Create refresh event for specific configuration changes

221

* @param changes Map of changed configuration keys and their new values

222

*/

223

public RefreshEvent(Map<String, Object> changes);

224

225

/**

226

* Create refresh event for full configuration refresh

227

* Triggers refresh of all @Refreshable beans

228

*/

229

public RefreshEvent();

230

231

/**

232

* Get the configuration changes that triggered this refresh

233

* @return Map of changed properties, or empty for full refresh

234

*/

235

public Map<String, Object> getSource();

236

237

}

238

```

239

240

**Usage Examples:**

241

242

```java

243

import io.micronaut.runtime.context.scope.refresh.RefreshEvent;

244

import io.micronaut.runtime.event.annotation.EventListener;

245

246

@Singleton

247

public class RefreshEventListener {

248

249

@EventListener

250

public void onRefresh(RefreshEvent event) {

251

Map<String, Object> changes = event.getSource();

252

System.out.println("Configuration refresh triggered");

253

System.out.println("Changed properties: " + changes.keySet());

254

255

// Handle specific property changes by checking the changes map

256

if (changes.containsKey("database.url")) {

257

System.out.println("Database URL changed to: " + changes.get("database.url"));

258

System.out.println("Reconnecting to database...");

259

}

260

261

if (changes.containsKey("cache.ttl")) {

262

System.out.println("Cache TTL changed to: " + changes.get("cache.ttl"));

263

System.out.println("Clearing cache...");

264

}

265

266

// Check for full refresh (when "all" key is present)

267

if (changes.containsKey("all")) {

268

System.out.println("Full configuration refresh detected");

269

}

270

}

271

}

272

273

// Programmatic refresh triggering

274

@Singleton

275

public class ConfigurationManager {

276

277

@Inject

278

private ApplicationEventPublisher<RefreshEvent> eventPublisher;

279

280

public void refreshConfiguration() {

281

// Trigger full refresh

282

eventPublisher.publishEvent(new RefreshEvent());

283

}

284

285

public void refreshDatabaseConfig(Map<String, Object> newDatabaseProps) {

286

// Trigger partial refresh for database properties

287

eventPublisher.publishEvent(new RefreshEvent(newDatabaseProps));

288

}

289

}

290

```

291

292

### RefreshScope Implementation

293

294

The actual scope implementation for refreshable beans.

295

296

```java { .api }

297

/**

298

* Scope implementation for refreshable beans

299

* Manages bean lifecycle and invalidation on configuration changes

300

*/

301

@Singleton

302

public class RefreshScope implements CustomScope<Refreshable> {

303

304

/**

305

* Get or create bean instance for the refresh scope

306

* @param creationContext Bean creation context

307

* @param bean Bean definition

308

* @param identifier Scope identifier

309

* @param provider Bean instance provider

310

* @return Bean instance (may be cached or newly created)

311

*/

312

@Override

313

public <T> T getOrCreate(BeanCreationContext creationContext,

314

BeanDefinition<T> bean,

315

BeanIdentifier identifier,

316

Provider<T> provider);

317

318

/**

319

* Remove bean instance from scope (invalidate)

320

* @param creationContext Bean creation context

321

* @param bean Bean definition

322

* @param identifier Scope identifier

323

* @return Removed bean instance, if any

324

*/

325

@Override

326

public <T> Optional<T> remove(BeanCreationContext creationContext,

327

BeanDefinition<T> bean,

328

BeanIdentifier identifier);

329

330

/**

331

* Refresh specific beans based on configuration prefixes

332

* @param configurationPrefixes Prefixes that changed

333

*/

334

public void refresh(String... configurationPrefixes);

335

336

/**

337

* Refresh all beans in this scope

338

*/

339

public void refreshAll();

340

}

341

```

342

343

### Thread-Local Scope Implementation

344

345

The actual scope implementation for thread-local beans.

346

347

```java { .api }

348

/**

349

* Custom scope implementation for thread-local bean storage

350

* Each thread maintains its own bean instances

351

*/

352

@Singleton

353

public class ThreadLocalCustomScope implements CustomScope<ThreadLocal> {

354

355

/**

356

* Get or create thread-local bean instance

357

* @param creationContext Bean creation context

358

* @param bean Bean definition

359

* @param identifier Scope identifier

360

* @param provider Bean instance provider

361

* @return Thread-local bean instance

362

*/

363

@Override

364

public <T> T getOrCreate(BeanCreationContext creationContext,

365

BeanDefinition<T> bean,

366

BeanIdentifier identifier,

367

Provider<T> provider);

368

369

/**

370

* Remove bean from current thread's scope

371

* @param creationContext Bean creation context

372

* @param bean Bean definition

373

* @param identifier Scope identifier

374

* @return Removed bean instance, if any

375

*/

376

@Override

377

public <T> Optional<T> remove(BeanCreationContext creationContext,

378

BeanDefinition<T> bean,

379

BeanIdentifier identifier);

380

381

/**

382

* Clear all thread-local instances for current thread

383

*/

384

public void clear();

385

386

/**

387

* Check if lifecycle callbacks should be invoked

388

* @param bean Bean definition

389

* @return true if lifecycle is enabled for this bean

390

*/

391

public boolean isLifecycleEnabled(BeanDefinition<?> bean);

392

}

393

```

394

395

## Advanced Scope Usage

396

397

### Combining Scopes with Configuration

398

399

```java { .api }

400

// Configuration class that refreshes database connections

401

@Refreshable("datasource")

402

@ConfigurationProperties("datasource")

403

@Singleton

404

public class DataSourceConfiguration {

405

private String url;

406

private String username;

407

private String password;

408

private int maxPoolSize = 10;

409

410

// Configuration will be reloaded when datasource.* properties change

411

412

@Bean

413

@Refreshable("datasource")

414

public DataSource dataSource() {

415

HikariConfig config = new HikariConfig();

416

config.setJdbcUrl(url);

417

config.setUsername(username);

418

config.setPassword(password);

419

config.setMaximumPoolSize(maxPoolSize);

420

return new HikariDataSource(config);

421

}

422

}

423

424

// Thread-local database transaction manager

425

@ThreadLocal(lifecycle = true)

426

@Singleton

427

public class TransactionManager {

428

429

private Transaction currentTransaction;

430

431

@PostConstruct

432

public void initialize() {

433

System.out.println("Transaction manager initialized for thread: "

434

+ Thread.currentThread().getName());

435

}

436

437

public void begin() {

438

if (currentTransaction != null) {

439

throw new IllegalStateException("Transaction already active");

440

}

441

currentTransaction = new Transaction();

442

}

443

444

public void commit() {

445

if (currentTransaction == null) {

446

throw new IllegalStateException("No active transaction");

447

}

448

currentTransaction.commit();

449

currentTransaction = null;

450

}

451

452

@PreDestroy

453

public void cleanup() {

454

if (currentTransaction != null) {

455

currentTransaction.rollback();

456

}

457

}

458

}

459

```

460

461

### Scope Testing

462

463

```java { .api }

464

// Testing refreshable beans

465

@MicronautTest

466

public class RefreshableScopeTest {

467

468

@Inject

469

private RefreshScope refreshScope;

470

471

@Inject

472

private ApplicationEventPublisher<RefreshEvent> eventPublisher;

473

474

@Test

475

public void testConfigurationRefresh() {

476

// Change configuration

477

System.setProperty("database.url", "jdbc:postgresql://new-host:5432/db");

478

479

// Trigger refresh

480

eventPublisher.publishEvent(new RefreshEvent(

481

Map.of("database.url", "jdbc:postgresql://new-host:5432/db")

482

));

483

484

// Verify beans are refreshed

485

// Fresh instances should use new configuration

486

}

487

}

488

489

// Testing thread-local beans

490

@MicronautTest

491

public class ThreadLocalScopeTest {

492

493

@Inject

494

private RequestContext requestContext;

495

496

@Test

497

public void testThreadIsolation() throws InterruptedException {

498

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {

499

requestContext.initialize("req-1", "user-1");

500

return requestContext.getRequestId();

501

});

502

503

CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {

504

requestContext.initialize("req-2", "user-2");

505

return requestContext.getRequestId();

506

});

507

508

// Each thread should have its own context

509

assertThat(future1.get()).isEqualTo("req-1");

510

assertThat(future2.get()).isEqualTo("req-2");

511

}

512

}

513

```