Fusion framework Examples
This page lists some common examples using Fusion framework.
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 @Bean
:
@Bean
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 |
Alternatively, you can use constructor injections:
@ApplicationScoped // any marker making it a bean, can be as simple as @Bean
public class MyService {
private final ConfigService config; (1)
public MyService(final ConfigService service) { (2)
this.config = service;
}
protected MyService() { (3)
this.config = null;
}
}
- 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,
-
The constructor injection (the selected one is the most public one -
public
wins overprotected
) and with the most parameters, -
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 |
TIP
|
if the returned type implements |
Injection of a list/set
IMPORTANT
|
injections of |
public class MyService {
@Injection
List<MySpi> implementations;
}
TIP
|
you can put on the implementations (or beans) the annotation |
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 |
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 |
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:
@Bean
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
|
|
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:
@Bean
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
@Bean
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
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 |
NOTE
|
you can review documentation page to see how to render OpenRPC as asciidoc or OpenAPI content. |
Define a "reactive" JSON-RPC endpoint
public class Endpoints {
private final MyRemoteService remote;
public Endpoints(final MyRemoteService remote) {
this.remote = remote;
}
@JsonRpc("remoteById")
public CompletionStage<MyResult> result(final String id) {
return remote.invoke(id);
}
}
Register OpenRPC endpoint
It is possible to register an OpenRPC endpoint named openrpc
to serve JSON-RPC specification:
@Bean
public OpenRPCEndpoint openrpc() {
return new OpenRPCEndpoint()
/*.setInfo(new Info(...))*/;
}
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
}
-
Get a
ConfiguringContainer
which enables you to disable bean autodiscovery, to replace beans etc... - Launch the runtime container (you can look up beans there).
TIP
|
you can also just reuse |
Test with JUnit 5
@FusionSupport (1)
class FusionSupportTest {
@Test
void run(@Fusion final Emitter emitter) { (2)
assertNotNull(emitter);
}
}
- Mark the class to run tests under a container context (it is started/stopped automatically),
-
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>