Annotations¶
weftkit's vocabulary is a handful of annotations plus the Loader interface. The processor
validates how you use them during compilation.
@Registry¶
Marks the single class that anchors the wiring, usually your plugin main extending
WeftPlugin. The component registry
is generated into its package, and the class itself is injectable as an ambient dependency along
with any of its constructor parameters. ambient declares further
ambient types for values handed to the loader at runtime,
modules declares third-party modules to weave in, and disable opts out of
modules by id.
@Wired¶
Marks a class as created through the loader. Its constructor is the injection point, and the
processor validates every parameter against the graph. A @Wired class must be a concrete,
top-level or static nested class that is public or package-private, with exactly one accessible
constructor. A package-private component stays invisible outside its package: the processor
generates a small WeftWiring holder next to it that hands its wiring to the registry, so
internals never need to go public just to be injectable.
@Singleton¶
Marks a @Wired component as created once and injected by type from then on. A plain @Wired
component is created fresh for every injection instead. With lazy = true the singleton is
created on its first injection instead of during load. Loader hooks, @Provides,
@Initializes, and @Requires work on lazy singletons too, running at materialization. The
details are in Lazy singletons.
@WeftModule¶
Marks a class as a module, woven into consuming graphs as an eager singleton with
constructor injection. id names it for @Registry(disable = ...), components lists the
@Wired classes the module ships. Implement ModuleLifecycle for activate and deactivate
hooks around the fully loaded graph.
@ModuleSettings¶
Marks an interface as consumer configuration for a module, injected as a plain constructor parameter. Every method needs a default; the processor generates an all-defaults implementation when the consumer wires none. See module settings.
@FeatureFlag¶
Gates a component behind a named flag. An off flag gates construction: the component is never
created and module enumeration skips it, so nothing may require a flagged component. A consumer
may depend on one as Optional<T>, which resolves empty while the flag is off. Flags resolve
against a FeatureFlags implementation, wired or ambient, falling back to enabledByDefault. See
feature flags.
@Provides¶
Marks a no-argument getter on a @Wired singleton, public unless the owner is package-private,
where package visibility suffices. Its return value becomes an injectable
dependency once the owner has loaded. The value is captured right after load, at startup for an
eager owner and at first materialization for a lazy one, and a getter that still returns null at
that point fails the load. Injecting a lazy owner's product materializes the owner on demand.
@Qualified¶
Distinguishes multiple dependencies of the same type. On a @Wired class or a @Provides
getter it tags what is offered, and on a constructor parameter it selects the implementation or
product carrying that tag. On a field the processor ignores it. The position exists so
constructor generators can copy the tag onto the parameter they generate, see
Qualifiers.
@StaticHolder¶
Some plugins keep global state in static fields: a legacy config class or a static service accessor that other code reads directly instead of being injected. Static state is invisible to the dependency graph. Nothing tells weftkit that one component fills those fields during load and others read them, so nothing would order the load accordingly.
@StaticHolder makes that dependency explicit. Mark the class holding the static state, let one
@Wired singleton declare @Initializes(TheHolder.class) and fill the holder in its load
hook, and let every component that reads the holder during its own construction or load declare
@Requires(TheHolder.class). Each requirement becomes a load order edge, so readers load after
the initializer. As a safety net the processor scans every component's constructor, field
initializers, and load method, and a holder access in that window without a matching
@Requires fails the build.
The scan sees direct accesses in your own javac-compiled sources. Reads through helper methods,
reflection, or foreign jars stay invisible to it, which is why @Requires is declared explicitly
instead of being inferred from the scan.
Migration only¶
The holder annotations are a bridge for plugins adopting weftkit: annotating the existing
static state brings it into the managed load order without touching the legacy code that
reads it. @StaticHolder is deprecated without a removal plan to mark the bridge as
temporary. New components use constructor injection from the start, and each holder carries
the deprecation warning until its readers are migrated. Suppress it per holder with
@SuppressWarnings("deprecation") for as long as the holder is needed.
Finishing the migration means rewriting the readers that adoption deferred. Move the static
fields into the singleton that fills them, with the load hook and its parsing logic staying
as they are, and let each reader inject the singleton and call a getter instead of reading
static state. @Initializes and @Requires disappear, since the constructor dependency
already orders the load. Reading through the owner also keeps every reader current if the
plugin later adds an in-place reload:
// The initializer after the migration: the former holder's fields are its own now
@Wired
@Singleton
public final class Config implements Loader {
private Settings settings;
@Override
public boolean load() {
settings = parse();
return true;
}
public Settings settings() {
return settings;
}
}
// A former @Requires reader, ordered by its constructor dependency instead
@Wired
@Singleton
final class Spawner {
private final Config config;
Spawner(Config config) {
this.config = config;
}
}
@Initializes¶
Declares the @StaticHolder classes a @Wired singleton initializes during load. Components
that require those holders load after it. A lazy initializer is materialized before its
requiring components build instead.
@Requires¶
Declares the @StaticHolder classes a component reads, ordering it after the singleton that
initializes them. The declaration stays explicit rather than being inferred from the access scan:
reads through helper methods are invisible to the scan, and the load order must not depend on
which compiler ran. @Requires drives the graph, the scan only catches forgotten declarations.
Loader¶
Implement Loader to hook the lifecycle. load runs when the singleton is created, and
returning false aborts startup. On a lazy singleton load runs at first materialization, and
a false return or exception fails only that injection, with the rest of the graph staying
loaded. unload runs on shutdown in reverse creation order. Only singletons may implement
Loader, since a per-injection component would never have its load called.
Related¶
- Components: the annotations in use, with worked examples
- Modules and feature flags:
@WeftModuleand@FeatureFlagin practice - Plugin lifecycle: the
Loadercontract inside startup and shutdown - Troubleshooting: the build errors behind each contract