micronaut-core Agent Skills & AI Rules

GitHub

Micronaut Application Framework

6,436 stars Java Java 1 file ยท ~125,337 tokens #cloudnative#groovy#java#kotlin#microservices#python#python3#serverless
RAW Rules
23 Discovered Rules โ€ข ~28,350 Estimated Tokens
Quick:
Loading skill content...

core-processor - AI Agent Protocol & Instructions

core-processor/AGENTS.md root-instruction ~9,156 tokens
Raw
# `core-processor` Generation Guide

This module is the shared compile-time engine for Micronaut injection. Language-specific processors provide Micronaut AST elements; `core-processor` turns those elements and annotation metadata into generated bytecode models for bean definitions, executable methods, bean introspections, AOP proxies, expression classes, and configuration metadata.

Use this file when changing `core-processor/src/main/java/**` so generated bytecode, runtime interfaces in `inject`, and annotation processing behavior stay aligned.

## Mental Model

The normal compile-time path is:

1. A language module such as `inject-java` supplies `ClassElement`, `MethodElement`, `FieldElement`, `PropertyElement`, and `ParameterElement` implementations.
2. `TypeElementVisitor` implementations inspect and mutate annotation metadata. Examples include `ConfigurationMetadataWriterVisitor`, `ConfigurationReaderVisitor`, `ExecutableVisitor`, and `IntrospectedTypeElementVisitor`.
3. `BeanDefinitionCreatorFactory` chooses the bean creation strategy for each `ClassElement`.
4. Creator classes call `ElementBeanDefinitionBuilder` and `ElementProxyBuilder` APIs.
5. Writers such as `BeanDefinitionWriter`, `ExecutableMethodsDefinitionWriter`, `BeanIntrospectionWriter`, `AopProxyWriter`, `RuntimeProxyBeanDefinitionWriter`, and `EvaluatedExpressionWriter` build `io.micronaut.sourcegen.model.ObjectDef` instances.
6. The language processor writes bytecode through `ByteCodeWriterUtils` and registers service descriptors when an `OutputObjectDef` has a service type.

Do not think of this module as producing Java source. Most writers build sourcegen AST objects such as `ClassDef`, `MethodDef`, `ExpressionDef`, `StatementDef`, and `TypeDef`; `ByteCodeWriterUtils` then emits bytecode with ASM.

## AST Layer

`io.micronaut.inject.ast` is a language-neutral AST over Java, Kotlin, and Groovy. It is the boundary between language-specific symbol APIs and shared generation logic.

Important AST types:

| Type | Role |
| --- | --- |
| `Element` | Common annotated, named, documented, mutable metadata node. It can represent a class, method, field, parameter, package, enum constant, or property-like element. |
| `ClassElement` | Type model with assignability, generics, optional/container helpers, constructors, bean properties, fields, methods, interfaces, super types, records, enums, arrays, and proxy detection. |
| `MethodElement` | Method or constructor model. `getMethodAnnotationMetadata()` is method-only metadata; `getAnnotationMetadata()` may combine class and method metadata except for constructors. |
| `ParameterElement` | Method or constructor argument model used for injection points, executable methods, constructor values, and Kotlin default detection. |
| `FieldElement` | Field model used for field injection, factory field beans, and field-backed bean properties. |
| `PropertyElement` | Synthesized bean property. Configuration readers process these first because the property is the user-facing binding unit. |
| `KotlinParameterElement` | Marks a Kotlin parameter that has a default value. Writers use it to call Kotlin synthetic default constructors and methods. |
| `ElementQuery` and `PropertyElementQuery` | The preferred way to select enclosed elements. Use queries instead of ad hoc scans when possible. |

AST elements are often copied with modified metadata using `withAnnotationMetadata(...)`, `withParameters(...)`, or direct `annotate(...)` calls. This is intentional: visitors and creators add synthetic annotations such as `@Property`, `@ConfigurationReader`, interceptor bindings, validation markers, and `@Introspected` before code generation.

Be careful with metadata layers. `AnnotationMetadataHierarchy` is frequently used to combine type metadata with method, factory, property, or parameter metadata while preserving declared-vs-inherited lookup rules. Generated code often stores an `AnnotationMetadataReference` back to a generated bean definition to avoid duplicating metadata.

## Generated Output

The major generated artifacts are:

| Writer | Generated class shape | Service descriptor |
| --- | --- | --- |
| `BeanDefinitionWriter` | `$Bean$Definition` classes extending `AbstractInitializableBeanDefinitionAndReference` unless a specialized super type is used. | `BeanDefinitionReference` |
| `ExecutableMethodsDefinitionWriter` | `$Bean$Definition$Exec` classes extending `AbstractExecutableMethodsDefinition`. | None |
| `BeanIntrospectionWriter` | `$Bean$Introspection` or `$fully_qualified_Bean$Introspection` classes extending `AbstractInitializableBeanIntrospectionAndReference` or enum equivalent. | `BeanIntrospectionReference` |
| `AopProxyWriter` | A proxy type named from the target definition plus `$Intercepted`, and a proxy bean definition for that proxy type. | Proxy bean definition is a `BeanDefinitionReference` |
| `RuntimeProxyBeanDefinitionWriter` | Runtime-proxy bean definition that calls a `RuntimeProxyCreator`. | `BeanDefinitionReference` |
| `EvaluatedExpressionWriter` | Final expression class extending `AbstractEvaluatedExpression`. | None |

`OutputObjectDef` couples an `ObjectDef`, optional service type, and originating elements. Writers should preserve originating elements so incremental compilation and generated service descriptors stay correct.

## Bean Definition Selection

`BeanDefinitionCreatorFactory` is the switchboard:

- Classes with `ConfigurationReader` stereotypes go through `ConfigurationReaderBeanElementCreator`, or `IntroductionInterfaceBeanElementCreator` when the configuration reader is an interface.
- `@Factory` classes go through `FactoryBeanElementCreator`.
- Types with around advice go through `DeclaredBeanElementCreator` with an AOP proxy builder, except interceptor beans are excluded from around proxy detection.
- Introduction advice goes through `AopIntroductionProxySupportedBeanElementCreator` or `IntroductionInterfaceBeanElementCreator`.
- Ordinary declared beans go through `DeclaredBeanElementCreator`.
- Interfaces without introduction/configuration behavior are ignored.

`DeclaredBeanElementCreator` scans properties and members, then adds constructor, field, method, lifecycle, executable, validation, adapter, and AOP information to the builder.

Important declared-bean rules:

- Injection methods are `@Inject` methods, plus configuration-specific extensions in `ConfigurationReaderBeanElementCreator`.
- Field injection applies to non-static, non-final fields with `@Inject`, qualifier stereotypes, `@Value`, or `@Property`.
- `@Executable` methods are emitted as executable methods if accessible by the generated definition or explicitly allowed.
- AOP methods must not be final, private, or static. Class-level AOP applies to public/package-private methods; method-level advice can force proxy creation.
- Methods marked with `RequiresValidation` are annotated with `io.micronaut.validation.Validated`, except configuration reader beans because configuration validation is handled by introspection.
- `@Adapter` creates an introduction proxy for a SAM interface and also keeps the source method executable/interceptable as needed.

## BeanDefinitionWriter

`BeanDefinitionWriter` generates a compact runtime description and creation plan for one bean. It usually generates one class that is both a `BeanDefinitionReference` and a loaded `BeanDefinition`.

The generated definition contains:

- Static annotation metadata, constructor references, injection method references, field references, annotation injection references, type arguments, executable methods, exposed types, replacement metadata, conditions, and `PrecalculatedInfo`.
- `load()` returning a new definition instance.
- `isEnabled(...)`, `isContextScope()`, `isProxiedBean()`, `isProxyTarget()`, `isParallel()`, `getIndexes()`, `getDefaultImplementation()`, and configuration/proxy target helpers when applicable.
- `instantiate(...)` or `doInstantiate(...)` depending on whether construction is parametrized or intercepted.
- `inject(...)` when fields, methods, or configuration builders need member injection.
- `initialize(...)` and `dispose(...)` methods, or intercepted lifecycle variants, when lifecycle hooks exist.
- Optional `$Exec` support for executable methods and startup method processors.
- Optional evaluated expression classes and `@BuildTimeInit` registration.

Instantiation is generated in three broad forms:

- Normal beans override `instantiate(BeanResolutionContext, BeanContext)` and call the constructor/factory method/field.
- Parametrized beans implement `ParametrizedInstantiatableBeanDefinition` and generate `doInstantiate(...)` so runtime-supplied `@Parameter` values can be resolved before construction.
- Constructor-intercepted beans implement one of the `Intercepted...InstantiateBeanDefinition` interfaces and generate `resolveInstantiationValues(...)` plus `doInstantiate(...)` for the constructor interceptor chain.

`PrecalculatedInfo` is important. It stores booleans for abstract, iterable, singleton, primary, configuration properties, container type, method preprocessing, and evaluated expressions. If a change affects these flags, check context candidate collection and startup behavior.

For configuration properties, `BeanDefinitionWriter` also:

- Treats `@ConfigurationProperties`, `@EachProperty`, and `@EachBean` as configuration/iterable bean definitions.
- Tracks nested configuration reader inner classes.
- Skips storing some value injection point metadata when configured by `micronaut.inject.configuration-properties.omit-injection-points`.
- In `inject(...)`, skips property injection when `containsProperties(...)` is false for a configuration bean.
- Resolves `ConfigurationPath` directly as an injectable special type.

## Executable Methods

`ExecutableMethodsDefinitionWriter` generates the `$Exec` class used by `BeanDefinition.getExecutableMethods()` and AOP.

Each executable method stores an `AbstractExecutableMethodsDefinition.MethodReference` with:

- Declaring type.
- Annotation metadata.
- Method name.
- Return `Argument`.
- Argument array built from `methodElement.getSuspendParameters()`.
- `isAbstract`.
- `isSuspend`.

Dispatch is generated by `DispatchWriter`. It produces switch-based reflection-free calls when possible, reflective fallback when required, and bridge dispatch entries for intercepted proxy methods. Kotlin default methods use a specialized dispatch target and are not supported for reflection invocation.

When a method is annotated `@Executable(processOnStartup = true)`, the writer marks `requiresMethodProcessing()` and the bean definition emits the indexes that startup processors must inspect.

## Bean Introspection

`IntrospectedTypeElementVisitor` handles `@Introspected` and writes `BeanIntrospectionReference` classes. It supports:

- Direct class introspection.
- `classes` and `classNames`.
- Package scanning, but only when `includedAnnotations` limits the scan.
- `targetPackage`.
- Optional annotation metadata emission.
- Indexed annotations.
- Explicit `@Introspected.IntrospectionBuilder`.
- Lombok builders after Lombok-generated methods appear in a later round.

`BeanIntrospectionWriter` emits the actual introspection class. It stores:

- Constructor metadata and constructor `Argument[]`.
- `BeanPropertyRef[]` with read, write, and with/copy-constructor dispatch indexes.
- `BeanMethodRef[]` for introspected executable methods.
- Enum constant refs for enum introspections.
- Indexed property lookup methods when `@Introspected(indexed = ...)` is used.
- Dispatch methods for property get/set/with, bean methods, copy-constructor mutation, and construction.

Property mutation order matters:

- A setter or writable field is used when present.
- If a read-only property corresponds to a constructor argument, the property is still considered mutable through constructor reconstruction.
- If no setter exists, the writer looks for a with-method using the configured `withPrefix`.
- If neither exists, mutation dispatch throws `UnsupportedOperationException`.

Constructor support:

- A no-arg default constructor enables `instantiate()`.
- A primary constructor enables `instantiateInternal(Object[])` and `isBuildable()`.
- If every constructor parameter is a Kotlin default parameter, the writer also emits no-arg `instantiate()` by calling Kotlin defaults.

Configuration beans that need validation are annotated with `@Introspected` so validation can operate on introspection metadata rather than by adding validation advice to every configuration setter.

## AOP Proxies

`DefaultElementBeanDefinitionBuilderFactory` creates proxy builders. It rejects final classes for compile-time subclass proxying, forces factory-produced around-advised beans into proxy-target mode, and chooses `RuntimeProxyBeanDefinitionWriter` for `@RuntimeProxy` types or `AopProxyWriter` otherwise.

Proxy modes:

| Mode | Target ownership | Main generated behavior |
| --- | --- | --- |
| Default around proxy | The proxy is the bean instance. | Generated proxy subclasses the target when possible. Intercepted methods pass `this` to `MethodInterceptorChain`. Concrete original methods use generated `$$access$$...` bridge methods so `proceed()` can call `super`. |
| Proxy-target around proxy | Proxy and target are separate beans. | The proxy bean definition implements `ProxyBeanDefinition` and points back to the target definition. The proxy resolves a target with `BeanResolutionContext.getProxyTargetBean(...)`. |
| Lazy proxy-target | Proxy target is separate and resolved on first use. | The proxy stores a copied `BeanResolutionContext`; `interceptedTarget()` resolves the target. With `cacheableLazyTarget`, it caches the target after first lookup. |
| Hotswap proxy-target | Proxy target is separate and mutable. | The proxy implements `HotSwappableInterceptedProxy`, stores target behind read/write locks, and exposes `swap(...)`. |
| Introduction proxy | No concrete target implementation is required. | Generated proxy implements the introduced interface/type. Abstract/interface methods are satisfied by introduction interceptors. |
| Runtime proxy | Proxy object is produced by a runtime creator. | The bean definition builds a `DefaultRuntimeProxyDefinition` and invokes the configured `RuntimeProxyCreator`. |

### Around vs Introduction

Around advice decorates an implementation that already exists. Introduction advice supplies or routes an implementation for methods that may not exist on a concrete target.

The compile-time distinction starts with interceptor binding metadata:

- `@Around` contributes `@InterceptorBinding(kind = AROUND)`.
- `@Introduction` contributes `@InterceptorBinding(kind = INTRODUCTION)`.
- `@AroundConstruct` contributes `@InterceptorBinding(kind = AROUND_CONSTRUCT)`.
- `InterceptedMethodUtil.hasAroundStereotype(...)` and `hasIntroductionStereotype(...)` determine which creator path runs.
- `InterceptedMethodUtil.resolveInterceptorBinding(metadata, kind)` extracts the binding values that become the proxy constructor's interceptor qualifier.

Around proxy generation:

- `DeclaredBeanElementCreator` creates a normal target bean definition and, when necessary, an around proxy builder.
- A class-level around stereotype causes public/package-private methods to be considered for interception.
- A method-level around stereotype can force proxy creation for a method even when the class is not already an AOP proxy.
- Methods must be non-final, non-private, and non-static because generated compile-time proxies need an overridable method.
- Default around proxies subclass the target and invoke original behavior through generated bridge methods.
- Proxy-target around proxies keep a separate target definition and resolve the target through the context.

Introduction proxy generation:

- `IntroductionInterfaceBeanElementCreator` handles interfaces with introduction advice or configuration-reader introduction advice.
- `AopIntroductionProxySupportedBeanElementCreator` handles concrete classes that combine introduction behavior with possible around advice.
- `DefaultElementBeanDefinitionBuilderFactory.introductionProxy(...)` builds a proxy definition that includes both around and introduction interceptor bindings.
- Interface introduction processes abstract methods, property accessors, and non-abstract default methods only when those non-abstract methods declare around advice.
- Concrete-class introduction reuses the introduction proxy builder as the bean definition builder; it does not keep a separate ordinary target definition for introduced behavior.
- Introduction proxies implement `Introduced`. Around proxies implement `Intercepted`; proxy-target variants also implement `InterceptedProxy` or `HotSwappableInterceptedProxy`.

Runtime chain differences:

- Around method resolution calls `InterceptorChain.resolveAroundInterceptors(...)` and gets only `AROUND` interceptors.
- Introduction method resolution calls `InterceptorChain.resolveIntroductionInterceptors(...)`, which resolves introduction interceptors and around interceptors, then concatenates around interceptors before introduction interceptors.
- If an introduction method has no introduction interceptor and is not an adapter method, runtime resolution fails with an error because there is no implementation to invoke.
- If the method is an adapter method, the runtime creates an internal `AdapterIntroduction` interceptor as the implementation fallback.

Method interception flow in `AopProxyWriter`:

1. `addAroundMethod(...)` records the method and contributes interceptor binding metadata from method annotations.
2. Concrete default-around methods get executable-method entries and bridge methods as needed.
3. The proxy type gets `$proxyMethods` and `$interceptors` fields.
4. The proxy constructor appends five hidden parameters after user constructor parameters: `BeanResolutionContext`, `BeanContext`, `Qualifier`, interceptor bean registrations list, and `InterceptorRegistry`.
5. Constructor code initializes `$proxyMethods` from the executable-methods definition or target bean definition.
6. Constructor code resolves each method's interceptors using `InterceptorChain.resolveAroundInterceptors(...)` or `resolveIntroductionInterceptors(...)`.
7. Each intercepted method override chooses a target: `this` for default around, a target field for eager proxy-target, or `interceptedTarget()` for lazy/hotswap.
8. The override creates a `MethodInterceptorChain` with method-specific interceptors, target, executable method, and arguments, then returns `proceed()`.

Constructor interception:

- `BeanDefinitionWriter` detects `AROUND_CONSTRUCT` bindings only for valid candidate definitions. For proxy-target beans, only the target definition should intercept construction; for default around proxies, the generated AOP implementation intercepts construction.
- Non-parametrized constructor interception uses `InterceptedInstantiateBeanDefinition` or `ProxyInterceptedInstantiateBeanDefinition`.
- Parametrized constructor interception uses `InterceptedParametrizedInstantiateBeanDefinition` or `ProxyInterceptedParametrizedInstantiateBeanDefinition`.
- The proxy variants know that proxy constructors contain the five hidden parameters and keep them out of the user-visible constructor interception argument list.

Factory-produced around-advised beans are always proxy-target beans because the factory method owns the real object creation. If the produced type has constructor arguments, `proxyTargetMode` controls handling:

- `ERROR`: fail compilation.
- `WARN`: allow construction, but warn.
- `ALLOW`: allow construction silently.

For `WARN` and `ALLOW`, primitive constructor parameters are annotated with `@Value(false)` or `@Value(0)`, and reference parameters are marked nullable so proxy construction has placeholder values.

### Adapters

`@Adapter` creates a new bean definition that adapts a user method to a single-abstract-method interface.

Compile-time adapter generation is in `DeclaredBeanElementCreator.visitAdaptedMethod(...)`:

1. The source method must declare `@Adapter(TargetInterface.class)`.
2. The target must be an interface.
3. The target interface must have exactly one abstract method.
4. Source and target parameter counts must match.
5. Kotlin suspend source methods are rejected.
6. Each source parameter generic type must be assignable to the matching target method generic type.
7. Generic placeholders on the target interface are remapped from the source method parameter types.
8. A synthetic adapter proxy class name is created from the declaring bean name, target interface simple name, source method name, and an index.
9. The target interface `ClassElement` is annotated with internal `@Adapter` members:
   - `adaptedBean`: source bean class.
   - `adaptedMethod`: source method name.
   - `adaptedArgumentTypes`: source argument types.
   - `adaptedQualifier`: source bean `@Named` qualifier, when present.
10. The target interface is annotated `@Indexed` for the adapted interface type.
11. A merged metadata hierarchy of the source bean and adapted interface becomes the proxy metadata.
12. `beanDefinitionBuilderFactory.introductionProxy(adapterProxyClassName, metadata, interfaceToAdapt)` creates an introduction proxy.
13. `implementInterface(interfaceToAdapt)` adds the SAM methods to the generated proxy.

At runtime, adapter methods are implemented by `AdapterIntroduction`:

- `DefaultInterceptorRegistry.resolveToNone(...)` returns an `AdapterIntroduction` when the method has `@Adapter` metadata and no introduction interceptor beans matched.
- `AdapterIntroduction` reads the internal adapter metadata from the executable method.
- It finds an `ExecutionHandle` for the original source bean and method, using `adaptedQualifier` when present.
- Its `intercept(...)` method invokes the source execution handle with the intercepted SAM method arguments.

Adapters are therefore introduction proxies, but they are special because the implementation interceptor is synthesized at runtime instead of being a user-defined `@InterceptorBean`.

### Interceptor Binding Recognition

Interceptor binding metadata is normalized at compile time before bean/proxy writers consume it.

`InterceptorBindingMembers` remaps annotation values that have AOP stereotypes:

- Applied annotations that are themselves `@Around`, `@Introduction`, `@AroundConstruct`, or `@InterceptorBinding` are left as direct AOP annotations.
- User annotations meta-annotated with those stereotypes get an internal `@InterceptorBinding` value whose `value` member is the user annotation type.
- The binding `kind` is set to `AROUND`, `INTRODUCTION`, or `AROUND_CONSTRUCT`.
- If the stereotype uses `@InterceptorBinding(bindMembers = true)`, the remapper stores selected annotation member values under `InterceptorBindingQualifier.META_BINDING_VALUES`.
- Members annotated with `@NonBinding` are removed from those binding values.
- If the advice stereotype has `@Type(SomeInterceptor.class)`, the remapper copies that type to the binding as `interceptorType`. This preserves the old type-based interceptor selection path.

`InterceptorBeanMapper` remaps `@InterceptorBean(...)` on interceptor beans:

- `@InterceptorBean` is itself a bean stereotype and has default singleton scope.
- Its `value` member names the advice annotations the interceptor supports.
- The mapper converts each supported annotation into an internal `@InterceptorBinding` value.
- Those values are stored in the internal interceptor-bindings container on the interceptor bean metadata.

Generated proxies use these normalized bindings in two places:

- Proxy writers collect type-level, method-level, and constructor-level binding values using `InterceptedMethodUtil.resolveInterceptorBinding(...)`.
- `AopProxyWriter` annotates the generated proxy constructor's hidden interceptor-registration-list parameter with `AnnotationUtil.ANN_INTERCEPTOR_BINDING_QUALIFIER` and the collected binding values.
- `BeanDefinitionWriter.getQualifier(...)` sees that annotation on the constructor parameter and emits `Qualifiers.byInterceptorBinding(argument.getAnnotationMetadata())`.
- The context uses `InterceptorBindingQualifier` to preselect candidate interceptor bean registrations for the proxy constructor argument.

`InterceptorBindingQualifier` supports two matching modes:

- Binding by annotation name and optional `$bindingValues`.
- Binding by explicit `interceptorType`.

The constructor-injected list is intentionally a candidate set, not the final per-method chain. It narrows the list cheaply during bean construction; `DefaultInterceptorRegistry` performs the final method-specific selection.

### Interceptor Beans And Runtime Resolution

`InterceptorRegistryBean` is the built-in bean definition for `InterceptorRegistry`.

- It is a hand-written `BeanDefinitionReference` and `InstantiatableBeanDefinition`.
- It exposes `InterceptorRegistry` and `DefaultInterceptorRegistry`.
- It is singleton, always present, and bootstrap-context compatible.
- Its `instantiate(...)` method returns a new `DefaultInterceptorRegistry` for the active `BeanContext`.

Runtime method interceptor resolution is handled by `DefaultInterceptorRegistry.resolveInterceptors(...)`:

1. It reads the intercepted method annotation metadata.
2. If no interceptor registrations were injected, it returns none for around advice, creates `AdapterIntroduction` for adapter introduction methods, or fails for other missing introduction implementations.
3. It configures expression/environment-aware metadata before evaluating bindings.
4. `AbstractInterceptorChain.resolveInterceptorValues(metadata, kind)` extracts the applicable `@InterceptorBinding` values for the requested kind.
5. For `AnnotationMetadataHierarchy`, declared method-level bindings are preferred over parent/type bindings for non-repeatable annotations.
6. Each candidate interceptor registration is checked by `selectInterceptor(...)`.
7. Candidate registrations are sorted with `OrderUtil.ORDERED_COMPARATOR`.
8. Method resolution keeps `MethodInterceptor` instances and generic `Interceptor` instances that are not constructor-only.
9. Constructor resolution keeps `ConstructorInterceptor` instances and generic `Interceptor` instances that are not method-only.

`DefaultInterceptorRegistry.selectInterceptor(...)` checks:

- Constructor interceptor type arguments, when present, must be assignable from the intercepted declaring type.
- If an intercept point binding carries `interceptorType`, the interceptor bean instance type can match directly.
- Otherwise the interceptor bean definition must have its own interceptor binding metadata for the same kind.
- Each interceptor binding declared on the interceptor must match an intercept point binding.
- If binding member values are present, the `$bindingValues` metadata must match as well.

The runtime chain then executes through `MethodInterceptorChain` or `ConstructorInterceptorChain`:

- `MethodInterceptorChain.proceed()` invokes interceptors in order until exhausted, then calls the generated `ExecutableMethod`.
- Introduction methods normally reach an introduction interceptor instead of a concrete target method.
- Adapter introduction interceptors invoke the original adapted source method.
- Constructor chains use `BeanConstructor` metadata and eventually call the generated intercepted `doInstantiate(...)`.

### Factory Methods And Proxies

`@Factory` classes are processed by `FactoryBeanElementCreator`, which extends the ordinary declared-bean creator. The factory class is still a bean definition with its own constructor, injection points, lifecycle methods, executable methods, validation, and AOP behavior; each producer member creates an additional bean definition.

Producer member discovery:

- Factory methods are methods whose metadata declares bean-producing semantics, normally `@Bean` or a scope stereotype.
- Factory fields are fields annotated as beans; inaccessible/private factory fields fail compilation.
- Bean-producing property read elements are converted to a factory method when they have a getter, or to a factory field when the backing field is accessible.
- Bean-producing property write elements are ignored as producers because the read side owns the produced value.
- Producer methods can also remain executable or interceptable methods on the factory bean itself. After the produced bean definition is created, `FactoryBeanElementCreator` still visits the factory method as an AOP method when the factory is proxied, or as an executable method otherwise.

Produced metadata is deliberately not just the method metadata:

1. For non-primitive produced types, the processor merges the produced class metadata with the producing method/field/property metadata.
2. If the producing element declares a scope or qualifier, scopes and qualifiers inherited from the produced type are removed unless the producing element also declares them.
3. The producing element then receives a hierarchy of factory-class metadata plus produced metadata, but factory-class qualifiers are stripped when they are not present on the produced bean. This prevents a qualifier on the factory bean from accidentally qualifying every product.
4. For produced types with `@EachProperty`, both the produced type and the producing element are annotated with a computed `@ConfigurationReader(prefix = ...)` so iterable configuration path generation works for factory products.

The generated produced bean definition is a separate `BeanDefinitionWriter`:

- `DefaultElementBeanDefinitionBuilderFactory.factoryMethod(...)` creates a writer whose bean type is the generic return type and whose producer definition is a `MethodDefinition`.
- `factoryField(...)` creates a writer whose bean type is the generic field type and whose producer definition is a `FieldDefinition`.
- Factory method definition names include a unique index so overloaded producer methods do not collide.
- `BeanDefinitionWriter.resolveConstructorParameters()` treats factory method parameters like construction parameters and factory fields as zero-argument construction.
- If any factory method parameter is annotated `@Parameter`, the produced definition becomes parametrized in the same way as a parametrized constructor bean.

Runtime instantiation is generated by switching on the producer definition:

- Constructor-produced beans call `initializeBean(...)`.
- Field-produced beans call `buildFactoryFieldCall(...)`.
- Method-produced beans call `buildFactoryMethodCall(...)`.
- Static factory members are invoked with no factory bean instance.
- Instance factory members first resolve the factory bean from the `BeanResolutionContext` using the factory type and factory qualifier, mark the resolution as factory-dependent, then invoke the method or read the field.
- Inaccessible factory methods and fields use the generated reflection helper path; accessible static and instance members use direct sourcegen dispatch.

Factory-produced AOP beans are special because the factory method or field owns creation of the real target object. A default subclass around proxy would create a proxy instance instead of using the factory product, so `DefaultElementBeanDefinitionBuilderFactory.aroundProxy(...)` forces `Interceptor.PROXY_TARGET = true` whenever the target writer's producer is not a constructor.

Factory proxy generation rules:

- The target produced bean definition remains the factory method/field definition.
- The proxy bean definition is an additional definition that points back to the target definition as a proxy target.
- Factory products use `visitSuperBeanDefinitionFactory(...)` rather than normal `visitSuperBeanDefinition(...)`; this marks the proxy definition as being backed by a factory target without subclassing the target bean definition class.
- Runtime proxy products follow the same factory-target rule, but use `RuntimeProxyBeanDefinitionWriter` instead of `AopProxyWriter`.
- The proxy scans the produced type for public, non-final, non-static methods, then also scans synthetic bean property read/write methods. This is necessary because the producing factory method does not declare the methods that will be intercepted on the product.
- Factory-produced arrays and primitive beans cannot receive AOP advice, and the original produced class must not be final.

Factory products with constructor arguments need an extra compile-time decision. Even though the real target comes from the factory, a compile-time subclass proxy still has to call a superclass constructor. `@Around(proxyTargetMode = ...)` controls this:

- `ERROR` is the default and fails when the produced non-interface type has a primary constructor with parameters.
- `WARN` allows proxy construction and emits a warning.
- `ALLOW` allows proxy construction silently.

For `WARN` and `ALLOW`, `FactoryBeanElementCreator.allowProxyConstruction(...)` mutates the produced type constructor parameters so the proxy can be instantiated with placeholders: primitive parameters get synthetic `@Value(false)` or `@Value(0)`, reference parameters are marked nullable, and non-null metadata is removed. These placeholder values are for the proxy superclass constructor, not for creating the real factory target.

Constructor interception for factory products follows the proxy-target split:

- A factory method counts as a factory/proxy-target construction path in `BeanDefinitionWriter.isInterceptedLifeCycleByType(...)`.
- `@AroundConstruct` on a factory-produced around bean should wrap the target construction path, which is the generated factory method invocation path.
- The generated proxy's hidden constructor contains AOP infrastructure arguments and is not the user construction point.
- The intercepted instantiate interfaces used for proxy definitions keep proxy infrastructure parameters separate from the user-visible constructor/factory arguments.

Other factory product features:

- `@Executable` on produced metadata registers all produced type methods as executable methods, except for arrays and primitives.
- `@Bean(preDestroy = "...")` resolves an accessible no-argument destroy method on the produced type and emits a pre-destroy hook.
- Produced `@EachProperty` and `@EachBean` definitions stay iterable definitions; the factory method parameters can still be filled from `@Parameter` values when the runtime creates each delegate.

## Evaluated Expressions

Evaluated expressions are annotation values represented by `EvaluatedExpressionReference`. They can appear in direct and nested annotation values.

The generation flow is:

1. Writers call `EvaluatedExpressionProcessor.processEvaluatedExpressions(...)` for class, constructor, method, parameter, field, property, and introspection metadata.
2. `EvaluatedExpressionsUtils.findEvaluatedExpressionReferences(...)` scans annotation and stereotype metadata, including nested annotation values.
3. `DefaultExpressionCompilationContextFactory` builds an expression context. It includes shared registered context types, annotation-level `@AnnotationExpressionContext`, annotation member contexts, method parameters when compiling method expressions, and optional `this`.
4. `EvaluatedExpressionWriter` generates a final class extending `AbstractEvaluatedExpression`.
5. The generated `doEvaluate(ExpressionEvaluationContext)` parses the original annotation value with `CompoundEvaluatedExpressionParser` and compiles it into sourcegen statements.
6. The owning generated definition/introspection is annotated with `@BuildTimeInit` listing expression class names.

Expression classes are deduplicated by generated class name in `EvaluatedExpressionProcessor`. `BeanDefinitionWriter.finish()` resets expression and metadata processor caches after a processing run.

## Configuration Metadata And Paths

Configuration path calculation is split across compile-time and runtime.

Compile-time path helpers:

- `ConfigurationUtils.getTypePath(...)` reads `ConfigurationReader.PREFIX`, `BASE_PREFIX`, supertype prefixes, and enclosing configuration reader prefixes. It caches the computed prefix by annotating `ConfigurationReader.PREFIX_CALCULATED`.
- `ConfigurationUtils` appends `.*` for map-style `@EachProperty` and `[*]` for list-style `@EachProperty`.
- `ConfigurationMetadataBuilder.calculatePath(...)` creates property paths. Nested configuration reader property types use their own reader path; ordinary properties use owner/declaring type path plus the property name, then hyphenation.
- `ConfigurationMetadataWriterVisitor` writes IDE metadata through `ConfigurationMetadataWriter` implementations at `finish(...)`.

Runtime path helpers:

- `ConfigurationPath` models a stack of configuration reader and `EachProperty` segments.
- `DefaultApplicationContext.collectIterableBeans(...)` expands iterable definitions into `BeanDefinitionDelegate` instances.
- `BeanDefinitionDelegate` temporarily pushes its `ConfigurationPath` into the active `BeanResolutionContext` during `instantiate(...)` and `inject(...)`.
- Property placeholders and generated `@Property` names can be resolved against the current dynamic path.

## `@ConfigurationProperties`

`@ConfigurationProperties` is meta-annotated with `@ConfigurationReader`, so it enters the configuration reader path.

For classes and records:

1. `ConfigurationMetadataWriterVisitor` visits the class, computes and writes the reader prefix, and records configuration metadata.
2. If the class has validation markers, it adds `@Introspected` so validation can use bean introspection.
3. For records and `@ConfigurationInject` constructors/methods, constructor or method parameters that are property-like get synthetic `@Property(name = ...)` annotations.
4. For writable bean properties, the visitor records property metadata and annotates setters with `@Property(name = ...)`.
5. `ConfigurationReaderBeanElementCreator` processes bean properties rather than hidden fields/methods first.
6. Writable properties become generated value injection points using `@Property(name = calculatedPath)`.
7. `@ConfigurationBuilder` properties or fields become builder injection commands instead of normal property injection.
8. Read methods annotated `@Executable` are still emitted as executable methods.
9. `BeanDefinitionWriter.inject(...)` only performs property injection if the environment contains matching properties for the configuration bean.

For interfaces:

1. `ConfigurationMetadataWriterVisitor` annotates the interface with `io.micronaut.runtime.context.env.ConfigurationAdvice`.
2. `BeanDefinitionCreatorFactory` routes it through introduction proxy generation.
3. Getter methods are validated as configuration accessors.
4. Getter methods returning property-like values are annotated with `@Property`.
5. Getter methods returning bean/configuration-like values are marked as bean access through `ConfigurationAdvice`.
6. Runtime `ConfigurationIntroductionAdvice` resolves values using the active `ConfigurationPath` and the environment.

Do not add validation AOP to configuration setters as a substitute for introspection validation. The creator intentionally suppresses method validation interception for configuration readers.

## `@EachProperty`

`@EachProperty` is both a `ConfigurationReader` and an iterable bean definition.

Compile-time behavior:

- `ConfigurationUtils.computeIterablePrefix(...)` appends `.*` by default or `[*]` when `list = true`.
- The computed prefix is stored as `ConfigurationReader.PREFIX`.
- `BeanDefinitionWriter.isIterable(...)` returns true because the metadata has an `EachProperty` stereotype.
- Factory-produced bean types with `@EachProperty` are explicitly annotated with the computed `ConfigurationReader` prefix on both the produced type and producing element.
- Properties inside an `EachProperty` bean are still generated as normal configuration reader property injection points, but the property paths contain dynamic segments.

Runtime behavior:

1. `DefaultApplicationContext.collectIterableBeans(...)` sees the bean definition is iterable and calls `transformEachPropertyBeanDefinition(...)`.
2. The context creates or reuses a `ConfigurationPath`.
3. It pushes an `EachProperty` root segment for the definition.
4. `ConfigurationPath.traverseResolvableSegments(environment, callback)` finds map keys or list indexes present in the environment.
5. For each resolvable segment, the context creates a `BeanDefinitionDelegate` with a qualifier from `path.beanQualifier()` or `PrimaryQualifier`.
6. The delegate sets the path during construction and injection, so generated `@Property` values resolve against the current segment.
7. `@Parameter` constructor arguments can receive the current map key/name, enum value, list index, or matching nested configuration bean through `BeanDefinitionDelegate.getParametersValues(...)`.

Primary selection for `@EachProperty` comes from the current `ConfigurationPath` segment. `BeanDefinitionDelegate.isPrimary()` returns true when the path segment name matches the configured primary name.

Nested `@ConfigurationProperties` inside `@EachProperty` are handled by pushing additional configuration reader segments onto the same path. Direct resolution of a nested dynamic configuration reader may rebuild the path by finding the enclosing delegate.

## `@EachBean`

`@EachBean` is an iterable bean definition driven by another bean type rather than by environment keys.

Compile-time behavior:

- The annotation remains metadata on the bean definition; the processor does not generate one class per origin bean.
- `BeanDefinitionWriter.isIterable(...)` returns true because the metadata has an `EachBean` stereotype.
- Constructor parameters annotated `@Parameter` make the generated definition parametrized when the value must be supplied at runtime.

Runtime behavior:

1. `DefaultApplicationContext.collectIterableBeans(...)` calls `transformEachBeanBeanDefinition(...)`.
2. The annotation's required value is the dependent/origin bean type.
3. The context finds candidate bean definitions for that origin type.
4. For each origin candidate, the context creates a `BeanDefinitionDelegate` of the `@EachBean` definition.
5. If the origin is already a configuration delegate, its `ConfigurationPath` is reused. This is what connects `@EachBean(ExampleConfiguration)` to `@EachProperty` entries.
6. Otherwise the delegate qualifier is the origin's declared qualifier, `PrimaryQualifier` for primary origins, or an internal `EachBeanQualifier` that matches the exact origin `BeanDefinition`.
7. `remapGenerics` metadata can remap type arguments from the origin bean into the created delegate.
8. The delegate carries the origin definition for ordering and type argument behavior.
9. During instantiation, `BeanDefinitionDelegate.getParametersValues(...)` fills `@Parameter` arguments from the current qualifier, path, origin bean, or matching bean lookup.

`EachBeanQualifier` is an internal identity qualifier. It lets unqualified origin beans still drive distinct `@EachBean` delegates, but user-visible names are only available when the origin has a named/primary qualifier or when the origin came from a configuration path.

If a required `@EachBean` constructor parameter cannot be supplied, `ParametrizedInstantiatableBeanDefinition.resolveParameterizedArgumentValues(...)` treats the missing origin-type argument as a disabled bean rather than a normal missing argument.

## Kotlin-Specific Generation

Kotlin support is mostly expressed through AST flags, not by checking Kotlin compiler symbols directly in this module.

Key rules:

- `MethodElement.isSuspend()` marks Kotlin suspend functions.
- `MethodElement.getSuspendParameters()` includes the synthetic continuation parameter. Writers use this when building `Argument[]`, executable method references, AOP method lookups, dispatch methods, and lifecycle/injection method definitions.
- `ExecutableMethodsDefinitionWriter` stores `isSuspend` on method references.
- AOP interception returns `Object` for suspend methods and passes the continuation through the normal argument array. Runtime AOP adapts suspend calls through the Kotlin intercepted-method utilities in the AOP runtime.
- `KotlinParameterElement.hasDefault()` marks constructor or method parameters with Kotlin defaults.

Kotlin default constructor calls:

- `MethodGenUtils.invokeBeanConstructor(...)` detects default parameters and calls the Kotlin synthetic constructor with original parameters, one or more integer default masks, and a final `kotlin.jvm.internal.DefaultConstructorMarker` argument.
- A mask bit means Kotlin should use the default value for that parameter.
- For constructor injection, the writer computes per-argument "has value" expressions. For `@Property` parameters, the expression checks whether the property is present in the environment; for bean parameters, it checks non-null.
- Primitive values cannot use null as the missing sentinel, so default primitive placeholders are `false` or numeric zero.
- `BeanDefinitionWriter.initializeBean(...)` only does this direct Kotlin-default constructor path for non-proxy definitions. Proxies handle their own super constructor calls.

Kotlin default method calls:

- `DispatchWriter` uses `KotlinMethodWithDefaultsDispatchTarget` when any suspend/normal parameter is a `KotlinParameterElement` with a default.
- The dispatch target calls `MethodGenUtils.invokeKotlinDefaultMethod(...)`, which invokes the static `$default` method with target, parameters, masks, and a null marker.
- Kotlin default methods are rejected for reflection dispatch because the default-mask call shape is not a normal reflective method invocation.

Other Kotlin-specific behavior:

- `BeanIntrospectionWriter` emits no-arg `instantiate()` for a constructor when all parameters are Kotlin defaults.
- `ExecutableVisitor` does not warn for `@Nullable` primitive parameters when the parameter is a Kotlin default parameter.
- `AopProxyWriter.invokeSuperConstructor(...)` allows Kotlin defaults when calling the proxied superclass constructor.
- Factory-produced proxy construction in `WARN` or `ALLOW` mode annotates primitive placeholders with `@Value` and reference placeholders as nullable, which also helps Kotlin proxy construction.

## Configuration Builders

`@ConfigurationBuilder` is handled in both metadata and injection generation.

`ConfigurationMetadataWriterVisitor` records a configuration group for the builder type and records each builder method as property metadata. `ConfigurationReaderBeanElementCreator` turns the builder property or field into `InjectFieldConfigurationBuilder` or `InjectMethodConfigurationBuilder` commands.

Generated injection creates or accesses the builder, then invokes generated builder method statements. A zero-argument builder method can be represented by a boolean property injection point; one-argument builder methods receive the resolved property value.

Do not treat builder properties like normal writable configuration bean properties. Excludes and includes do not suppress builders in the same way, and inaccessible builder fields require a non-private getter.

## Change Guidance

Most source changes here affect generated bytecode contracts. When changing a writer, also inspect the runtime interfaces and bases in `inject/src/main/java/io/micronaut/inject/**` and `inject/src/main/java/io/micronaut/context/**`.

Useful checks after source changes usually include:

- `./gradlew -q :micronaut-core-processor:compileJava`
- `./gradlew -q :micronaut-inject-java:compileTestJava :micronaut-inject-java:compileTestGroovy`
- Targeted tests in `inject-java`, `inject-groovy`, or `inject-kotlin` that compile sample beans.
- AOP changes should include around, around-construct, proxy-target, introduction, and Kotlin tests when applicable.
- Configuration changes should include `@ConfigurationProperties`, interface configuration, nested configuration, `@EachProperty` map/list, and `@EachBean` tests.

For markdown-only edits in this file, `git diff --cached --check` and `./gradlew -q spotlessCheck` are enough.