Skip to content

Commit

Permalink
Factory types clarified
Browse files Browse the repository at this point in the history
Signed-off-by: David Kral <[email protected]>
  • Loading branch information
Verdent committed Feb 7, 2025
1 parent 05d4b19 commit fffc317
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 21 deletions.
59 changes: 47 additions & 12 deletions docs/src/main/asciidoc/se/injection.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -283,25 +283,49 @@ A factory that creates zero or more implementations of a given contract.
include::{sourcedir}/se/inject/FactoryExample.java[tag=snippet_2, indent=0]
----
If the created services need to be qualified, the relevant qualifier information must also be included in the factory annotations.
If the created services need to be qualified, any relevant qualifier information must also be included in the factory annotations.
Note: If one doesn’t want to list all the names provided by this factory, it is possible use `*` to cover all the possible names.
Note: If one doesn’t want to list all the name qualifiers provided by this factory, it is possible use `*` to cover all the possible names.
[source,java]
.Qualified ServicesFactory example
----
include::{sourcedir}/se/inject/FactoryExample.java[tag=snippet_3, indent=0]
----
=== io.helidon.service.registry.Service.QualifiedFactory
Qualified factory is strictly bound to a specific qualifier type.
It will get executed only for injection points,
which are annotated with this selected qualifier and are intended for a selected contract.
It will be ignored for any other injection points.
It receives a Lookup object as a parameter, which contains all necessary information about the injection point.
This allows the factory to create instances dynamically based on the injection point information.
[source,java]
----
include::{sourcedir}/se/inject/FactoryExample.java[tag=snippet_4, indent=0]
----
=== io.helidon.service.registry.Service.InjectionPointFactory
A factory that provides zero or more instances for each injection point.
This type of factory is very similar to `QualifiedFactory`,
but with one key difference—it is executed for each injection point and is not bound to a specific qualifier/s (unless specified).
//TODO
It receives a Lookup object as a parameter, which contains all necessary information about the injection point.
This allows the factory to create instances dynamically based on the injection point information.
=== io.helidon.service.registry.Service.QualifiedFactory
A factory that provides zero or more instances based on a specific qualifier and contract.
[source,java]
----
include::{sourcedir}/se/inject/FactoryExample.java[tag=snippet_5, indent=0]
----
It is possible to restrict this factory to specific qualifiers by specifying them at the class level of the factory.
//TODO
This ensures that the factory is executed only for injection points that match the intended contract and qualifier.
[source,java]
----
include::{sourcedir}/se/inject/FactoryExample.java[tag=snippet_6, indent=0]
----
== Interceptors
Interception allows intercepting calls to constructors or methods, and even fields when used as injection points.
Expand Down Expand Up @@ -338,7 +362,12 @@ include::{sourcedir}/se/inject/InterceptorExample.java[tag=snippet_2, indent=0]
<2> Passing interceptor processing to another interceptor in the chain
=== Delegate annotation
The `@Interception.Delegate` annotation enables interception for classes or interfaces that aren’t created through the service registry but are instead produced by a factory. To enable interception, this annotation must be present on the class that the factory produces. While it is not required on interfaces, it will still function correctly if applied there.
The `@Interception.Delegate` annotation enables interception for classes
that aren’t created through the service registry
but are instead produced by a factory (More about factories can be found here -
<<Factories, Factory chapter>>).
To enable interception, this annotation must be present on the class that the factory produces.
While it is not required on interfaces, it will still function correctly if applied there.
Let's make the same `@Traced` annotation and Interceptor as in the previous examples
Expand All @@ -356,7 +385,13 @@ Now, let's create the factory of the service instance.
include::{sourcedir}/se/inject/InterceptorDelegateExample.java[tag=snippet_2, indent=0]
----
Method calls on an instance created this way can’t be intercepted. To enable interception in such cases, we use the `@Interception.Delegate` annotation. However, keep in mind that usage of this annotation doesn’t add the ability to intercept constructor calls. Additionally, if a class is annotated with `@Interception.Delegate`, it must have a non-private default constructor.
Method calls on an instance created this way can’t be intercepted. To enable interception in such cases, we use the `@Interception.Delegate` annotation. However, keep in mind that usage of this annotation doesn’t add the ability to intercept constructor calls.
If you need to enable interception for classes using delegation, you should:
- The class must have accessible no-arg constructor (at least package local)
- The constructor should have no side effects, as the instance will act only as a wrapper for the delegate
- All invoked methods must be accessible (at least package local)
[source,java]
.Delegate used on the class
Expand All @@ -366,10 +401,10 @@ include::{sourcedir}/se/inject/InterceptorDelegateExample.java[tag=snippet_3, in
// === ExternalDelegate annotation
// TODO Uncomment this, once this issue is fixed https://github.com/helidon-io/helidon/issues/9726
// The `@Interception.ExternalDelegate` annotation functions similarly to `@Interception.Delegate`. However, the key difference is that `@Interception.ExternalDelegate` is designed for classes or interfaces that you don’t control. This means it allows you to apply the interception mechanism even to third-party classes or interfaces.
// The `@Interception.ExternalDelegate` annotation functions similarly to `@Interception.Delegate`. However, the key difference is that `@Interception.ExternalDelegate` is designed for classes that you don’t control. This means it allows you to apply the interception mechanism even to third-party classes.
//
// [source,java]
// .ExternalDelegate used for external class/interface
// .ExternalDelegate used for external class
// ----
// include::{sourcedir}/se/inject/InterceptorDelegateExample.java[tag=snippet_4, indent=0]
// ----
Expand Down
45 changes: 42 additions & 3 deletions docs/src/main/java/io/helidon/docs/se/inject/FactoryExample.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@
package io.helidon.docs.se.inject;

import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;

import io.helidon.common.GenericType;
import io.helidon.service.registry.Lookup;
import io.helidon.service.registry.Qualifier;
import io.helidon.service.registry.Service;

Expand Down Expand Up @@ -51,12 +54,48 @@ public List<Service.QualifiedInstance<MyService>> services() {

// tag::snippet_3[]
@Service.Singleton
@Service.Named("test")
class MyQualifiedServiceFactory implements Service.ServicesFactory<MyService> {
@Service.Named("name")
class MyServiceFactoryWithQualifier implements Service.ServicesFactory<MyService> {
@Override
public List<Service.QualifiedInstance<MyService>> services() {
return List.of(Service.QualifiedInstance.create(new MyService(), Qualifier.createNamed("test")));
return List.of(Service.QualifiedInstance.create(new MyService(), Qualifier.createNamed("name")));
}
}
// end::snippet_3[]

// tag::snippet_4[]
@Service.Singleton
class QualifiedFactory implements Service.QualifiedFactory<MyService, Service.Named> {

@Override
public Optional<Service.QualifiedInstance<MyService>> first(Qualifier qualifier,
Lookup lookup,
GenericType<MyService> genericType) {
return Optional.of(Service.QualifiedInstance.create(new MyService()));
}
}
// end::snippet_4[]

// tag::snippet_5[]
@Service.Singleton
class InjectionPointFactory implements Service.InjectionPointFactory<MyService> {

@Override
public Optional<Service.QualifiedInstance<MyService>> first(Lookup lookup) {
return Optional.of(Service.QualifiedInstance.create(new MyService()));
}
}
// end::snippet_5[]

// tag::snippet_6[]
@Service.Singleton
@Service.Named("name")
class InjectionPointFactoryWithQualifier implements Service.InjectionPointFactory<MyService> {

@Override
public Optional<Service.QualifiedInstance<MyService>> first(Lookup lookup) {
return Optional.of(Service.QualifiedInstance.create(new MyService(), Qualifier.createNamed("name")));
}
}
// end::snippet_6[]
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,9 @@ class InterceptorExample {
@Service.Singleton
@Service.NamedByType(Traced.class) //<1>
static class MyServiceInterceptor implements Interception.Interceptor {
static final List<String> INVOKED = new ArrayList<>();

@Override
public <V> V proceed(InterceptionContext ctx, Chain<V> chain, Object... args) throws Exception {
INVOKED.add("%s.%s: %s".formatted(
ctx.serviceInfo().serviceType().declaredName(),
ctx.elementInfo().elementName(),
Arrays.asList(args)));
//Do something
return chain.proceed(args); //<2>
}
}
Expand Down

0 comments on commit fffc317

Please sign in to comment.