Skip to content

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.