This page details Common examples.

Defining a Bean without injections

If a bean has an injection or an event listener it will be automatically defined as a bean, but in some cases, you want to define a bean on a plain class without any injection nor event listener. For such a case, you can mark the class with @ApplicationScoped:

@ApplicationScoped
public class MyService {
    public int magicNumber() {
        return 42;
    }
}

This simple definition enables you to inject this bean in other beans.

Defining a Bean with injection(s)

The common use case of a framework is to get injected some bean without exactly knowing how it was created or initialized. Here is how to get an injection with Fusion framework:

public class MyService {
    @Injection
    ConfigService config;

    // ...
}
IMPORTANT

do NOT use private fields, it is not yet supported.

Alternatively, you can use constructor injections:

@ApplicationScoped // any marker making it a bean, can be another scope
public class MyService {
    private final ConfigService config; (1)

    public MyService(final ConfigService service) { (2)
        this.config = service;
    }

    protected MyService() { (3)
        this.config = null;
    }
}
  1. The injected values can be defined normally since injections happen with the constructor, you can even precompute the data you need in the bean and not store the injection itself,
  2. The constructor injection (the selected one is the most public one - public wins over protected ) and with the most parameters,
  3. The no-arg constructor - only needed for scopes using subclassing like @ApplicationScoped .

Create a bean in a custom fashion without a class

It can happen you need to reuse some custom factory and code the initialization of a bean. For such a case you can mark a method with @Bean. Injections can be done in the enclosing bean if needed:

@DefaultScoped // enclosing class is a bean
public class MyProducer {
    @Injection
    ConfigService conf;

    @Bean
    public DataSource dataSource() {
        return new DataSourceFactory(conf).create();
    }
}
IMPORTANT

as of today, you can mark the producer method with a scope but lazy scopes (like @ApplicationScoped are not really lazy until you implement yourself the lazyness - but scope is respected, i.e. if it is @ApplicationScoped it will be a singleton).

TIP

if the returned type implements AutoCloseable, close() will be called to destroy the bean instance(s).

Injection of a list/set

IMPORTANT

injections of List or Set are done by resolving the parameter type of the injection.

public class MyService {
    @Injection
    List<MySpi> implementations;
}
TIP

you can put on the implementations (or beans) the annotation @Order to sort their position in the List (lower values first) - ignored for Set.

Override a single injection

When several beans match a single injection (i.e. not a List/Set injection), the injection is ambiguous and the container throws AmbiguousBeanException. You can mark one of the candidate beans with @Order to elect it: when its @Order value is strictly greater than the ones of all the other matching beans, it is the injected one.

@DefaultScoped
public class MyServiceImpl implements MyService { // default @Order value is 1000
}
@DefaultScoped
@Order(1001) // > 1000 so this implementation overrides MyServiceImpl for MyService injections
public class MyServiceOverride implements MyService {
}
IMPORTANT

if several matching beans share the greatest @Order value, the injection stays ambiguous and AmbiguousBeanException is thrown.

TIP
this also applies to assignable lookups so it is a common way to replace a bean provided by a module/library by your own implementation.

Optional injection

A Optional<T> injection is always satisfied: if no bean matches T, an empty Optional is injected instead of failing:

public class MyService {
    @Injection
    Optional<ConfigService> config; // empty if no ConfigService bean
}

The same behavior applies to a RuntimeContainer.lookup on Optional<T>.

List sorting

Injected List<T> are sorted by @Order (lower values first) and then by type name. If the injected element type implements Comparable, natural ordering wins over @Order:

public class MyService {
    @Injection
    List<MySpi> implementations; // sorted naturally if MySpi extends Comparable<MySpi>
}

Replace beans with a module

FusionModule SPI (discovered via ServiceLoader) exposes beanFilter(), beanMapper(), contextMapper(), listenerMapper() and friends to replace any bean/context/listener before integration. This is what fusion-testing uses to swap application beans with test doubles - see testing .

Manual interception with Wraps

Since there is no declarative interceptor support, io.yupiik.fusion.framework.api.composable.Wraps composes interceptors manually as functions:

final var result = Wraps.wrap(
    () -> { /* business code */ return value; },
    interceptor1,
    interceptor2);

Each interceptor is a function turning a Supplier into another Supplier, enabling a functional composition of cross-cutting concerns without reflection.

Listen to an event

Beans can communicate loosely between them thanks to events. The bus is synchronous and sorted using @Order annotation.

public class MyListener {
    public void onStart(@OnEvent final Start start) {
        System.out.println("Application started");
    }

    public void onStop(@OnEvent final Stop stop) {
        System.out.println("Application stopped");
    }
}

Example of ordered event listener (default being 1000):

public void onStart(@OnEvent @Order(990) final Start start);
TIP

listening to Start event can enable a lazy instance (@ApplicationScoped) to be forced to be initialized.

TIP

an event can have more parameters, other parameters will be considered as injections (but the lookup will be destroyed after the method call if it is not @ApplicationScoped)

TIP

When using the Launcher provided by the Fusion, you can use the shutdown hook to call the container.close() method and send the Stop event by settings the Fusion configuration property fusion.launcher.useHook to true. The JVM catches signals to implement shutdown hooks for unexpected termination. The JVM uses SIGHUP, SIGINT, and SIGTERM to initiate the running of shutdown hooks (see https://github.com/openjdk/jdk/blob/master/src/java.base/share/man/java.md?plain=1#L817 )

Emit an event

To emit an event simply inject the Emitter and send the needed event:

public class CustomerService {
    @Injection
    Emitter emitter;

    public void createCustomer(final Customer customer) {
        emitter.emit(customer);
    }
}

Create a configuration model

A configuration model is a record marked with @RootConfiguration:

@RootConfiguration("server")
public record ServerConfiguration(int port, String accessLogPattern) {}

This simple configuration will read the system properties server.port, server.accessLogPattern (or environment variables SERVER_PORT, SERVER_ACCESSLOGPATTERN) to fill the values. The instance of ServerConfiguration can be injected in any bean:

@ApplicationScoped
public class MyServer {
    private final ServerConfiguration conf;

    public MyServer(final ServerConfiguration conf) {
        this.conf = conf;
    }

    // ...
}

If you want to customize the name of the property you can use @Property.

Finally, you can register you own source of values creating a bean of type ConfigurationSource.

IMPORTANT

List<OtherConfig> are supported, but you must set in the configuration <prefix for this list>.length to the length value of the list then the nested instances are configured using <prefix>.<index> starting at index 0. Ex: myconf.mylist.0.name=foo. Similarly, Map<String,X> are supported. For primitives, it uses properties syntax and when X is another configuration model you must use an index based notation with length property and key, value suffixes.

TIP

if you do use a nested model coming from another jar, ensure it is flagged @ConfigurationModel and annotation processing is enabled on this other module otherwise you'll loose default values, custom names and documentation.

Example

Here is an example of a simple configuration model using both List and Map of properties.

@RootConfiguration("app") // will be the prefix of the system properties filtered to bind on the instance
public record AppConfig(
        @Property(value="name", documentation = "Name of the application.")
        String appName,

        @Property(value="host", documentation = "Host.", defaultValue = "\"localhost\"")
        String host,

        //When no @Property annotation is added, the name of the property is the name of the field (port)
        int port,

        @Property(value="frequency")
        Frequency objectConfig,

        @Property(value="sources", documentation = "Some source to be processed.")
        List<Source> sources,

        @Property(value="stamps", documentation = "Some documentation about stamps.")
        List<String> stamps,

        @Property(value="tags", documentation = "Some documentation about tags.")
        Map<String, String> tags
){
    public record Frequency(
        @Property(value = "interval", documentation = "Interval - How often to do something.", defaultValue = "60000L")
        long interval
    ) {}

    public record Source(
        String host,
        int port
    ) {}
}

Environment variables (shell script):

export APP_NAME="app1"
export APP_HOST="app-1"
export APP_PORT="8380"
export APP_FREQUENCY_INTERVAL="20000L"
export APP_SOURCES_LENGTH="2"
export APP_SOURCES_0_HOST="external-host-1"
export APP_SOURCES_0_PORT="8080"
export APP_SOURCES_1_HOST="external-host-2"
export APP_SOURCES_1_PORT="8080"
export APP_STAMPS="application,server,integration"
export APP_TAGS="
                       application = app1
                       env = local
                       phase = tests
                       "

Environment variables (yaml):

APP_NAME: "app1"
APP_HOST: "app-1"
APP_PORT: "8380"
APP_FREQUENCY_INTERVAL: "20000L"
APP_SOURCES_LENGTH: "2"
APP_SOURCES_0_HOST: "external-host-1"
APP_SOURCES_0_PORT: "8080"
APP_SOURCES_1_HOST: "external-host-2"
APP_SOURCES_1_PORT: "8080"
APP_STAMPS: "application,server,integration"
APP_TAGS: |
    application = app1
    env = local
    phase = tests

Setting system properties in java:

public class App{
    static {
        System.setProperty("app.name", "app1");
        System.setProperty("app.host", "app-1");
        System.setProperty("app.port", "8380");
        System.setProperty("app.frequency.interval", "20000l");
        System.setProperty("app.sources.length", "2");
        System.setProperty("app.sources.0.host", "ext");
        System.setProperty("app.sources.0.port", "808");
        System.setProperty("app.sources.1.host", "ext");
        System.setProperty("app.sources.1.port", "808");
        System.setProperty("app.stamps", "application,server,integration");
        System.setProperty("app.tags"," \napplication = app1 \nenv = local \nphase = tests" );
    }
}

Create a JSON model

A JSON model is a record marked with @JsonModel:

@JsonModel
public record ServerConfiguration(int port, String accessLogPattern) {}

Then simply inject the JsonMapper in any bean to read/write such a model:

@ApplicationScoped
public class MyServer {
    private final JsonMapper mapper;

    public MyServer(final JsonMapper mapper) {
        this.mapper = mapper;
    }

    // ... mapper.toString(serverconf) / mapper.fromString(ServerConfiguration.class, "{}");
}

Handle unknown JSON attributes

A JSON model is a record marked with @JsonModel:

@JsonModel
public record MyModel(
        // known attribute
        String name,
        // unknown attributes/extensions
        @JsonOthers Map<String, Object> extensions) {}

This will match this JSON:

{
  "name": "fusion",
  "x-foo": true,
  "boney": "M"
}

And convert it to the following record mapping: MyModel[name=fusion, extensions={x-foo=true,boney=M}].

Define a custom HTTP endpoint

Implement a custom explicit Endpoint bean

@ApplicationScoped
public class MyEndpoint implements Endpoint {
    ....
}

Implement a custom implicit Endpoint

@HttpMatcher(...)
public CompletionStage<Response> myEndpoint(final Request request) {
    ....
}

// or


@HttpMatcher(...)
public Response myEndpoint(final Request request) {
    ....
}

Define a JSON-RPC endpoint

Mark a method with @JsonRpc to expose it as a JSON-RPC method. Input and output are @JsonModel records mapped by name:

public class Endpoints {
    @JsonRpc("copy")
    public MyResult result(final MyInput input) {
        return new MyResult(input.name());
    }

    @JsonModel
    public record MyResult(String name) {
    }

    @JsonModel
    public record MyInput(String name) {
    }
}
TIP

you can use the configuration entry fusion.jsonrpc.binding to change the /jsonrpc default binding. You can also set fusion.jsonrpc.forceInputStreamUsage to true to force the input to be reactive instead of using default request Reader.

See json-rpc for reactive endpoints, pre-request hooks and OpenRPC registration.

Start the container

To launch the application you need to start the container. It is done in two phases:

  • Configure the runtime

  • Launch the runtime.

Here is how to do it:

try (
    final var container = ConfiguringContainer
        .of() (1)
        .start() (2)
) {
    // use the container or just await for the end of the application
}
  1. Get a ConfiguringContainer which enables you to disable bean autodiscovery, to replace beans etc...
  2. Launch the runtime container (you can look up beans there).
TIP

you can also just reuse io.yupiik.fusion.framework.api.main.Launcher (or io.yupiik.fusion.framework.api.main.CliLauncher for CLI applications) main which will start the default container. Launcher arguments are bound to Configuration as dash-led options (--key value, --key=value); bare arguments are ignored, so CLI command paths of any depth work seamlessly. Every option requires a value (--help is skipped, --no-key implicitly binds false), a valueless option is an error. You can implement a custom Awaiter to not let the container shutdown immediately if you need - webserver does it by default. Finally you can also, using this launcher, inject Args to read the main arguments.

Test with JUnit 5

@FusionSupport (1)
class FusionSupportTest {
    @Test
    void run(@Fusion final Emitter emitter) { (2)
        assertNotNull(emitter);
    }
}
  1. Mark the class to run tests under a container context (it is started/stopped automatically),
  2. Inject container beans in test parameters (mark them with @Fusion ).

Alternatively you can run a single container for all tests:

@MonoFusionSupport
class FusionSupportTest {
    // same as before
}

Do a fat jar with maven shade plugin

Fatjar are not recommended in general but can be convenient for demo or CLI applications. Here is how to do it very easily with Apache Maven shade plugin:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-shade-plugin</artifactId>
  <version>3.5.2</version>
  <dependencies>
    <dependency>
      <groupId>io.yupiik.maven</groupId>
      <artifactId>maven-shade-transformers</artifactId>
      <version>0.0.5</version>
    </dependency>
  </dependencies>
  <executions>
    <execution>
      <goals>
        <goal>shade</goal>
      </goals>
      <configuration>
        <shadedArtifactAttached>true</shadedArtifactAttached>
        <shadedClassifierName>fat</shadedClassifierName>
        <createDependencyReducedPom>false</createDependencyReducedPom>
        <dependencyReducedPomLocation>${project.build.directory}/reduced-pom.xml</dependencyReducedPomLocation>
        <transformers>
          <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
            <mainClass>io.yupiik.fusion.framework.api.main.Launcher</mainClass>
          </transformer>
          <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
          <transformer implementation="io.yupiik.maven.shade.transformer.FusionDocumentationTransformer" />
          <transformer implementation="io.yupiik.maven.shade.transformer.FusionJsonSchemaTransformer" />
          <transformer implementation="io.yupiik.maven.shade.transformer.FusionOpenRPCTransformer" />
        </transformers>
        <filters>
          <filter> <!-- optional but generally saner -->
            <artifact>*:*</artifact>
            <excludes>
              <exclude>module-info.class</exclude>
              <exclude>META-INF/*.SF</exclude>
              <exclude>META-INF/*.DSA</exclude>
              <exclude>META-INF/*.RSA</exclude>
              <exclude>META-INF/LICENSE.txt</exclude>
              <exclude>META-INF/LICENSE</exclude>
              <exclude>META-INF/NOTICE.txt</exclude>
              <exclude>META-INF/NOTICE</exclude>
              <exclude>META-INF/MANIFEST.MF</exclude>
              <exclude>META-INF/DEPENDENCIES</exclude>
            </excludes>
          </filter>
        </filters>
      </configuration>
    </execution>
  </executions>
</plugin>

Design notes

No interceptor support

Since some years we got used to see declarative interceptors (annotations) like in this snippet:

public class MyBean {
    @Traced
    public void doSomething() {
        // ...
    }
}

These are great and the container is actually linking the annotation to an implementation (a bean in general) which intercepts the call. This is not bad but has some design pitfalls:

  • Most interceptors will use parameters and for such a generic approach to work, it needs an Object[] (or List) of parameters. This is really not fast (it requires to allocate an array for that purpose).

  • It requires to know and understand the rules between class interceptors, method interceptors, appending/overriding when relevant plus the same with parent classes. All that can quickly become complex.

  • It is often static: once put on a method disabling an interceptor requires the underlying library to be able to do that or to use some advanced customization at startup to do it.

For these reasons, we think that we don't need an interceptor solution in Fusion framework but we don't say the underlying feature is pointless, not at all. However, thanks to a more modern programming style, we can use a more functional approach to solve the same problem. Therefore, previous example would rather become:

public class MyBean {
    public void doSomething() {
        tracing(() -> {
            // ...
        });
    }
}

The big advantage is you can use some static utility if you want but also rely on beans and even combine more efficiently interceptions in a custom and configurable fashion:

public class MyBean {
    public void doSomething() {
        tracing(() -> timed(() -> logged(() -> {
            // ...
        })));
    }
}

can become:

public class MyBean {
    @Injection
    MyObservabilityService obs;

    public void doSomething() {
        obs.instrumented(() -> {
            // ...
        });
    }
}

If you compare the case with parameters it is way more efficient in general since you just do a standard parameter passing call:

public class MyBean {
    @Injection
    EntityManager em; // assume the application used JPA - not required

    @Injection
    JpaService jpa; // custom bean to handle transactions for ex

    public void store(final Transaction tx) {
        tracing(
          // no Object[] created for an interceptor
          // and no reflection to extract the id
          tx.id(),
          () -> jpa.tx(() -> em.persist(tx)));
    }
}
TIP

Going with this solution can, however, get the chaining lambda pitfall (a.k.a. callback hell in JavaScript). To solve this one we encourage you to ensure your "interceptor" can be chained properly using the same kind of callback.

Here is an example (the important part is more the signature than the fact it is a static method or a bean method):

public static <T> Supplier<T> interceptor1(String marker, Map<String, String> data, Supplier<T> nested) {
    return () -> {
        logger.info(message(marker, data)); // interceptor role
        return task.get();  // intercepted business, "ic.proceed()" in jakarta interceptor API
    };
}


public static <T> Supplier<T> interceptor12(Params params, Supplier<T> nested) {
    // same kind of logic for the impl
}

Thanks this definition which commonly agreed to use Supplier<T> as the intercepted call and the fact interceptor methods return a call and not execute it directly, you can chain them more easily:

public void storeCustomer(final Customer customer) {
    interceptor2(
            Params.of(customer),
            interceptor1(
                "incoming-customer", Map.of("id", customer.id()),
                () -> {
                    // business code
                }))
    .get(); // trigger the actual execution, it is the terminal operation for the chain
}

If you want to go further you can use a Stream to represent that. Now an interceptor is a Function<Supplier<T>, Supplier<T>> so if you define the list of interceptors in a Stream, then you can just reduce them using the business function/logic as identity to have the actual invocation and execute it. Only detail to take care: ensure to reverse the stream to call the interceptor in order:

public void storeCustomer(final Customer customer) {
    Stream.<Function<Supplier<Void>, Supplier<Void>>>of(
                // reversed chain of interceptor (i1 will be executed before i2)
                delegate -> interceptor2(Params.of(customer), delegate),
                delegate -> interceptor1("incoming-customer", Map.of("id", customer.id()), delegate)
        )
        // merge the stream of interceptors as one execution wrapper
        .reduce(identity(), Function::andThen)
        .apply(() -> { // apply to the actual business logic
            System.out.println(">Business");
            return null;
        })
        .get(); // execute it
}
Indeed in practise you can extract that kind of code in an utility and use something like:
// utility
public static <T> T intercepted(final Supplier<T> execution, final Function<Supplier<T>, Supplier<T>>... interceptors) {
    return Stream.of(interceptors)
            .reduce(identity(), Function::andThen)
            .apply(execution)
            .get();
}

// usage
intercepted(
    () -> { // business logic
        System.out.println(">Business");
        return null;
    },
    // interceptors
    delegate -> interceptor2(Params.of(customer), delegate),
    delegate -> interceptor1("incoming-customer", Map.of("id", customer.id()), delegate)
);

This is what the class io.yupiik.fusion.framework.api.composable.Wraps does.

Last tip: you interceptor can work with CompletionStage to add some behavior before/after the call even if the result is not computed synchronously ;).