Common examples
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 |
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;
}
}
- 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 -
publicwins 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 |
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 |
|
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 |
|
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 |
|
TIP
|
When using the Launcher provided by the Fusion, you can use the shutdown hook to call the |
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
|
|
|
TIP
|
if you do use a nested model coming from another jar, ensure it is flagged |
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 |
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
}
-
Get a
ConfiguringContainerwhich 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>
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[](orList) 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
Thanks this definition which commonly agreed to use
If you want to go further you can use a
This is what the class |
Last tip: you interceptor can work with CompletionStage to add some behavior before/after the call even if the result is not computed synchronously ;).