-
Notifications
You must be signed in to change notification settings - Fork 77
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial specification text for method invokers
- Loading branch information
Showing
8 changed files
with
253 additions
and
111 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
[[method_invokers]] | ||
== Method invokers | ||
|
||
CDI-based frameworks often need to invoke application methods declared on managed beans. | ||
Frameworks cannot invoke application methods directly, because they are not compiled against the application code. | ||
However, during application deployment, frameworks may observe application methods through CDI extensions and build an `Invoker` for each relevant method. | ||
The invokers can then be used at application runtime to invoke the methods indirectly. | ||
|
||
Method invokers are not supposed to be used by application code, as applications may invoke their own methods directly. | ||
|
||
[[building_invoker]] | ||
=== Building an `Invoker` | ||
|
||
The CDI container allows building an `Invoker` for a method of an <<enablement,enabled>> <<managed_beans,managed bean>>. | ||
The method for which the invoker is built is called the _target method_ of the invoker, and the managed bean is called the _target bean_ of the invoker. | ||
|
||
Invalid target methods are: | ||
|
||
* private methods, | ||
* constructors, | ||
* methods declared on the `java.lang.Object` class, except of the `toString()` method, | ||
* methods that are not declared on the bean class of the target bean or inherited from its supertypes. | ||
|
||
Attempting to build an invoker for an invalid target method leads to a deployment problem. | ||
|
||
When the target bean is not a managed bean, attempting to build an invoker leads to a deployment problem. | ||
When the target bean is an interceptor, attempting to build an invoker leads to a deployment problem. | ||
|
||
Multiple managed beans may inherit a method from a common supertype. | ||
In that case, an invoker must be built for each target bean individually. | ||
An invoker built for one target bean may not be used to invoke the target method on an instance of another target bean. | ||
|
||
The only way to build an invoker is using the `InvokerBuilder`. | ||
An `InvokerBuilder` can only be obtained in CDI portable extensions and build compatible extensions. | ||
See <<invoker_builder>> for more information. | ||
|
||
[[using_invoker]] | ||
=== Using an `Invoker` | ||
|
||
The `Invoker` interface contains a single method: | ||
|
||
[source,java] | ||
---- | ||
public interface Invoker<T, R> { | ||
R invoke(T instance, Object[] arguments); | ||
} | ||
---- | ||
|
||
Calling `invoke()` invokes the target method on given `instance` of the target bean, passing given `arguments`. | ||
|
||
Invoker implementations must be thread-safe. | ||
A single invoker instance may be used to perform multiple, possibly concurrent, invocations of the target method, possibly on different instances of the target bean, possibly with different arguments. | ||
|
||
Whenever a direct invocation of a method on an object is a business method invocation, an indirect invocation of that method on that object through an invoker is also a business method invocation. | ||
|
||
==== Behavior of `invoke()` | ||
|
||
If the target method is `static`, the `instance` is ignored; by convention, it should be `null`. | ||
If the target method returns normally, its return value is returned, unless the target method is declared `void`, in which case `null` is returned. | ||
If the target method throws an exception, it is rethrown. | ||
The `invoke()` method does not declare any checked exception; implementations are supposed to use the "sneaky throw" idiom. | ||
// TODO maybe change that? | ||
|
||
If the target method is not `static` and `instance` is `null`, a `NullPointerException` is thrown. | ||
If the target method is not `static` and the `instance` is not <<invoker_assignability,assignable>> to the target bean, a `ClassCastException` is thrown. | ||
|
||
Correspondence between given `arguments` and declared parameters of the target method is positional: the Nth element of the `arguments` array is passed as the Nth argument to the target method. | ||
If the target method is a variable arity method, the last element of the `arguments` array corresponds to the variable arity parameter (and therefore must be an array). | ||
|
||
If the target method declares no parameter, `arguments` are ignored. | ||
If the target method declares any parameter and `arguments` is `null`, `NullPointerException` is thrown. | ||
If the `arguments` array has fewer elements than the number of parameters of the target method, `ArrayIndexOutOfBoundsException` is thrown. | ||
If the `arguments` array has more elements than the number of parameters of the target method, the excess elements are ignored. | ||
If some of the `arguments` is not <<invoker_assignability,assignable>> to the corresponding parameter of the target method, `ClassCastException` is thrown. | ||
|
||
If the declared type of a parameter of the target method is a primitive type, then: | ||
|
||
* if the corresponding argument is `null`, the default value of the primitive type is passed; | ||
* if the corresponding argument is not `null`, unboxing conversion is performed on the argument and the result is passed. | ||
|
||
If the declared type of a parameter of the target method is not a primitive type, the corresponding argument is passed as is. | ||
|
||
// TODO when the `InvokerBuilder` applies transformations, some of the requirements above are no longer strictly necessary, we should reflect that in this text somehow | ||
|
||
==== Example | ||
|
||
To illustrate how method invokers work, let's take a look at an example. | ||
Say that the following bean exists in an application and has a method that you, the framework author, want to invoke indirectly: | ||
|
||
[source,java] | ||
---- | ||
@Dependent | ||
public class MyService { | ||
public String hello(String name) { | ||
return "Hello " + name + "!"; | ||
} | ||
} | ||
---- | ||
|
||
In a CDI extension, you obtain an `InvokerBuilder` for the `hello()` method and use it to build an invoker. | ||
In a portable extension, this results in an invoker which should be stored for later usage: | ||
|
||
[source,java] | ||
---- | ||
InvokerBuilder<Invoker<MyService, String>> builder = ...; | ||
Invoker<MyService, String> invoker = builder.build(); | ||
---- | ||
|
||
In a build compatible extension, this results in an opaque token that materializes as an `Invoker` at application runtime: | ||
|
||
[source,java] | ||
---- | ||
InvokerBuilder<InvokerInfo> builder = ...; | ||
InvokerInfo invoker = builder.build(); | ||
---- | ||
|
||
To call the `hello()` method through this invoker, assuming that `myService` is an instance of the bean, call: | ||
|
||
[source,java] | ||
---- | ||
invoker.invoke(myService, new Object[] {"world"}) | ||
---- | ||
|
||
The return value is `"Hello world!"`. | ||
|
||
An implementation of the invoker above is equivalent to the following class: | ||
|
||
[source,java] | ||
---- | ||
public class TheInvoker implements Invoker<MyService, String> { | ||
public String invoke(MyService instance, Object[] arguments) { | ||
return instance.hello((String) arguments[0]); | ||
} | ||
} | ||
---- | ||
|
||
[[invoker_builder]] | ||
=== Using `InvokerBuilder` | ||
|
||
`InvokerBuilder` can be obtained in build compatible extensions from `BeanInfo.createInvoker()`: | ||
|
||
[source,java] | ||
---- | ||
public interface BeanInfo { | ||
... | ||
InvokerBuilder<InvokerInfo> createInvoker(MethodInfo method); | ||
} | ||
---- | ||
|
||
The target bean of the created invoker is the bean represented by the `BeanInfo` object. | ||
The target method of the created invoker is the method represented by the `MethodInfo` object passed to `createInvoker()`. | ||
|
||
Calling `InvokerBuilder.build()` produces an opaque token (`InvokerInfo`) that can be passed as a parameter to a `SyntheticBeanBuilder` or `SyntheticObserverBuilder` and materializes as an `Invoker` at application runtime. | ||
|
||
// TODO lookups, transformers, wrappers | ||
|
||
[[invoker_assignability]] | ||
=== Assignability for invokers | ||
|
||
For the purpose of `Invoker.invoke()`, assignability is defined as: | ||
|
||
* a non-`null` instance is assignable to the target bean when the class of the instance is a subclass of the bean class of the target bean; | ||
* a `null` argument is assignable to any parameter of the target method; | ||
* a non-`null` argument is assignable to a parameter of the target method when the class of the argument is assignable to the erasure of the declared type of the parameter, as defined below. | ||
|
||
If an argument class is denoted `A` and the erasure of the declared type of a parameter is denoted `P`, then: | ||
|
||
* `A` is assignable to a primitive type `P` when `A` is the primitive wrapper class of `P`; | ||
* `A` is assignable to a class or interface type `P` when: | ||
** `A` is a class or interface type and `A` is a subclass of `P`, or | ||
** `A` is an array type and `P` is `java.lang.Object`, `java.lang.Cloneable`, or `java.io.Serializable`; | ||
* `A` is assignable to an array type `P` when `A` is an array type and: | ||
** the component type of `P` is a primitive type and the component types of `A` and `P` are identical, or | ||
** the component type of `P` is not a primitive type and the component type of `A` is assignable to the component type of `P` according to these rules. | ||
|
||
NOTE: This is a reformulation of the JVM assignability rules as defined by the specification of the `checkcast` and `instanceof` instructions, with addition of primitive types. | ||
It is expected that CDI implementations do not implement these rules and rely on JVM runtime checks. | ||
|
||
When the declared type of a parameter of the target method is not a reifiable type, callers of `Invoker.invoke()` must also ensure that the corresponding argument is constructed appropriately. | ||
Otherwise, runtime failures are likely to occur. |
Oops, something went wrong.