Hk2 Features proposals for V3.1 This is the design specification for the Hk2 features in the GlassFish 3.1 release.
- Jerome Dochez (dochez@dev.java.net)
Progress Tracking Summary
Status |
FS Section |
Requirement |
Hk2 Solution |
AnnoDriver Solution |
Comments |
Completed |
|
Basic Service Description |
@Service, @Contract |
@Service, @Contract |
|
Discussed |
4.1.1 |
Active Code Instancing |
@DependentOf |
@OnePerBean |
1. Lifecycle dependent upon underlying bean; 2. Suggestion to rename to @DependsOn |
Completed |
4.1.3 |
Bean Injection |
@Inject |
@Inject @Bean |
Agreed to use ctor style injection and ignore field level (re-)injection or annotations for beans. |
Discussed |
4.1.6 |
2PC, Active Code Creation |
PostConstruct iface |
@Prepare method / no iface |
1. Performed during activate, after any bean ctor injection; 2. active code must support rejection / rollback behavior. |
Not Discussed |
4.1.6 |
2PC, Active Code Modification |
@Prepare (or iface)? |
@DynamicUpdate method / no iface |
|
Not Discussed |
4.1.6 |
2PC, Active Code Removal |
PreDestroy iface |
@PreDestroy method / no iface |
|
Discussed |
4.1.2, 4.1.4 |
Active Code Injection |
@Inject |
@Inject |
Discussed dynamic and optional cases, and the need to (re-)injection via annotated @Inject setter methods for complex locking conditions. |
Discussed |
4.1.2, 4.1.4 |
Complex Injection Key Matching |
|
@Inject (@WithKey) |
|
Discussed |
4.1.2, 4.1.4 |
Aggregate Injection (with optional key matching) |
|
@Inject (@WithKey) Collection |
|
Completed |
4.1.2, 4.1.4 |
Complex Advertisement Keys |
KeyProvider & Key iface |
@AdvertisementKeys |
|
Completed |
4.1.7 |
Selecting Implementation |
@FactoryFor |
@Factory |
|
Discussed |
4.1.5 |
Orderly / Timely Shutdown |
GC based |
Reverse Dependency Ordering |
See attachments. |
Not Discussed |
4.1.5 |
Destruction type notification |
|
@PreDestroy method takes an optional boolean |
|
Near Completion |
4.3 |
Phased Booting |
RunLevelService API & @State |
@State and @Gate |
Wiki missing latest API modifications but completed otherwise |
Near Completion |
4.3 |
Phased Booting Exception Handling |
RunLevelService API |
Wiki missing latest API modifications but completed otherwise |
Discussed |
4.4 |
Bean Aggregation |
|
@ConfigurationBean |
|
Not Discussed |
4.4 |
Bean (Tree) Integration |
|
@ConfigurationBean w/ "ripper" |
|
Discussed |
|
OSGi |
Runs on OSGi; importing into habitat one-time early |
Runs on OSGi SR |
Open vs Closed module implications discussed. |
Discussed |
|
Lazy vs Eager |
Lazy |
Eager |
Would like the final model to be either depending on annotations and/or environment |
Instantiation enhancements. Companion Beans Any Bean entering the habitat can already trigger the instantiation of a second bean called a dependent bean (in fact multiple dependent beans are possible). For instance the following Bean :
@Service
public class LeadBean implements SomeContract {
...
}
has the following Dependent
@DependentOf(LeadBean.class)
public class CompanionBean implements SomethingElse {
...
}
The lifecycle of the dependent bean is entirely linked to the lifecycle of its parent bean (the one that triggered the instantiation). There is a need for WebLogic to have a two phase instantiation of the dependent bean when the parent bean is a configuration bean (@Configured in hk2 terms). The first phase should make the parent bean available but no other injection or services availability should have happened. The first phase should have the ability to raise exceptions. The second phase should have the normal injection performed as well as activation. I propose we map the first phase to the constructor of this dependent bean and the parent bean should be passed as the sole parameter to the constructor. The normal injection will happen after that using the @Inject annotations on fields or methods. The activation phase should naturally map to the postConstruct method.
@Dependent(LeadBean.class)
public class CompanionBean implements PostConstruct, SomethingElse {
@Inject
RandomService random;
final LeadBean parent;
public CompanionBean(LeadBean parent) {
this.parent = parent;
// random is not available.
// this is the 1st phase, exceptions can be thrown,
// rollbacking the configuration transaction.
}
public void postConstruct() {
// second phase, exceptions still possible.
// random and parent are available
}
}
For the dependent beans that do not want to have the two phase activation required by WebLogic, the current mechanism will remain unchanged.
@Dependent(LeadBean.class)
public class CompanionBean implements SomethingElse {
@Inject
RandomService random;
@Lead (we should rename this)
LeadBean parent;
}
Service Listener I should be possible to be notified when services implementation are swapped in the registry. Field injection
@Service
public RandomService implements RandomContract {
@Inject(dynamic=true)
Holder<RandomContract> myContract;
public void foo() {
myContract.do(); // will always invoke do() on the currently registered service.
}
}
if RandomContract is an interface, we can use Java SE proxies and therefore replace the injection declaration with
@Inject(dynamic=true)
RandomContract myContract;
Method injection With method injection, no holder or proxying is necessary as the injection method will be called each time the service is changed in the registry
@Service
public RandomService implements RandomContract {
RandomContract myContract;
@Inject(dynamic=true)
public void setRandomContract(RandomContract contract) {
// will be invoked one on construction, and each time the contract is changed
// in the registry.
}
}
Configuration events enhancements. Today we have the ability to automatically listen to injected configuration beans changes. In order to get notified of configuration changes, one can just do today :
@Service
public RandomService implements RandomContract, ConfigListener {
@Inject
SomeConfig myConfig
public PropertyChangeEvents[] changed(List<PropertyChangeEvent> events) {
// events represent the list of all applied changes, after the transaction
// is committed.
// myConfig is ONE of the bean changed, and already points to the new values.
}
In order to simplify the sorting of changes per bean, I propose a more type safe approach, coupled with three new annotations (@Prepare, @Commit, @Rollback).
@Service
public RandomService implements RandomContract {
@Inject
SomeConfig myConfig;
@Prepare
public void prepare(@Inject SomeConfig newConfig) {
// compare myConfig with newConfig)
}
@Commit
public PropertyChangeEvent[] commit(SomeConfig newConfig) {
// called after each update
}
@Rollback
public void rollback() {
// called after prepare if failure
}
This obviously can get to code a bit too complicated :
@Service
public RandomService implements RandomContract {
@Inject SomeConfig myConfig;
@Inject OtherConfig otherConfig;
@Prepare
public void prepare(@Inject SomeConfig newConfig) {
// compare myConfig with newConfig)
}
@Prepare
public void prepare(@Inject OtherConfig newConfig) throws TransactionFailure {
// compare otherConfig with newConfig)
// throw a transaction failure to abort the configuration change
}
@Commit
public PropertyChangeEvent[] commit(SomeConfig newConfig) {
// called after each successful transaction
// cannot throw an exception ,cannot rollback the transaction
// return the list of unprocessed changes (requiring a restart)
}
@Commit
public PropertyChangeEvent[] commit(OtherConfig newConfig) {
// called after each transaction success
}
@Rollback
public void rollback() {
// if prepare was called and did not throw an exception,
// will be called if the transaction is a failure
}
Configuration changes and Dependent beans. When coupling the @DependentOf annotation with configuration changes (we may need a @DependentOfConfig annotation to restrict the dependency on a configuration type) we should support the following :
@DependentOfConfig(Cluster.class)
public class ClusterService implements PostConstruct, PreDestroy {
@Inject
RandomService someService;
final Cluster myConfig;
DynamicService myDynService;
public ClusterService(Cluster config) {
// 1st phase
myConfig = config;
}
public void postConstruct() {
// 2nd phase : mySevice are myDynService are available
}
public void preDestroy() {
// myConfig is removed
}
@Inject(name="foo", dynamic=true)
public void setDynService(DynamicService dynService) {
// called once between constructor and postConstruct)
// and each time "foo" DynamicService is changed in the ServiceRegistry
myDynService = dynService;
}
@Prepare
public void prepare(@Inject Cluster newConfig) {
// compare myConfig with newConfig)
}
@Commit
public PropertyChangeEvent[] commit() {
// called after constructor (once !) and after each update
}
@Rollback
public void rollback() {
// called after constructor or each prepare if the transaction is aborted
}
}
Registration indirection. There is a proposal to provide a level of indirection to @Named semantics, for instance :
@AdvertisementKeys(keys="$getName()")
@Service
public FooImpl {
public String getName() {
....
}
}
will use fooImpl instance getName() method as a @Named value Simple Version I think we should use a more type safe approach, something like for the simple non reusable case :
/**
* Interface that define the notion of a key provider. Keys
* will be used to register services in the service registry
* aka the habitat
*/
public interface KeysProvider<T>() {
List<String> for(T instance);
}
@Service(name="random")
public class FooImpl implements KeyProvider<FooImpl> {
List<String> for(FooImpl instance) {
assert this==instance;
List<String> keys = new ArrayList<String>();
keys.add(instance.getName());
return keys;
}
}
Generic implementation When the KeyProvider should be reusable among different services, the following patter should be followed.
public interface Named {
String getName();
}
@Configured
public interface ClusterConfig extends Named {
.. some cluster related config methods ...
}
@Configured
public interface ServerConfig extends Named {
.. some server related config methods ...
}
Now one can provide a generic KeyProvider<Named> implementation :
public class NamedKeysProvider implements KeyProvider<Named> {
public List<String> for(Named instance) {
return instance.getName();
}
}
{{{
Now we need an annotation to qualify who can use this generic provider :
/**
* Annotation to qualify a service registration keys provider
*/
public @interface Keys {
/**
* @return the key provider implementation class
*/
Class<? extends KeysProvider> value();
}
Taking back the example above :
@Service
@Keys(NamedKeysProvider.class);
public class FooImpl implements Named {
public String getName() {
.. do something really complicated...
return it;
}
}
yes it's more work but it's worth the trouble for type safety, and if we were to change the API for(T instance) to return String rather than List<String>, it would simplify the code a lot. DependentOf and KeyProvider Sometimes, the keys provider needs the lead object that created this instance : Therefore we might need to modify the @DependentOf annotation
public @interface DependentOf {
/**
* @return the type this service is dependent of.
*/
Class value();
/**
* @return the key provider implementation class
*/
Class<? extends KeysProvider> keys();
}
The service implementation can then become :
@Service
@DependentOf(ClusterConfig.class, keys = ClusterService.KeyProvider.class)
public class ClusterService {
@Inject ClusterConfig config;
// can be called before an instance of ClusterService is even created.
public final static ClusterService.KeyProvider implements KeyProvider<ClusterConfig>() {
List<String> for(ClusterConfig config) {
List<String> keys = new ArrayList<String>();
// calculate the keys based on the configuration object and
// the receiving type : ClusterService
keys.add(config.getXXX());
return keys;
}
}
}
Selector improvements TBD. there is a need to register complex keys and have some sort of Selector to look up in the registry. Contract/Services enhancements. Priority Independent services of the same kind (Contract) may still need to be prioritized. Although it may seem to the casual reader that independent services should be instantiated in parallel or in any order, there are cases where existing code, jdk semantics get in the way of expressing all dependencies. Logger example. A logger service (LoggerService) for instance is responsible for setting up the Logging subsystem, install filter, output to particular files and set levels. This should be done early enough so that any startup sequence logging happens correctly, yet startup sequence do not necessarily depend on the LoggerService. There is 2 existing solution to the problem :
- make all services dependent on LoggerService. This is incorrect, each services only require the Logger instances, not the LoggerService.
- have a FactoryFor(Logger.class) registered to intercept all loggers instantiation when Loggers are injected. The factory can ensure the LoggerService is properly set up. Although this is the correct solution, it is difficult to enforce, with existing code, or with distracted programmers that may use JDK APIs to get a Logger directly rather than rely on injection. Such usage would bypass the factory and therefore the dependency would be un-met.
Ordering To work around the type of issue described above, I propose to introduce a new set of annotations and types
public @interface OrderedBy {
public Class<? extends Sorter> value();
}
so far, Sorter is pretty simple, could be extended if necessary :
public interface Sorter<T extends Annotation> {
public void sort(List<T> elements);
}
Any @Contract annotated interface with @OrderedBy will have the services returned ordered by the Sorter implementation as opposed as undefined as it is today. TBD: The above seems to pertain to ServiceRanking, and we would propose using something like ServiceTrackers that handle this case already. Recorder A recorder allows you to record the instantiation order of a set of Contract implementation.
public interface InstantiationRecorder<T> {
/**
* returns a (double?) linked list of T services in the order
* T instances were instantiated.
*/
public LinkedList<T> records();
/**
* records a new instance creation
*/
public void record(T instance);
}
You set a recorder implementation using the @Recorder annotation on a @Contract interface definition (anything else should be an error).
public @interface Recorder {
public Class<? extends InstantiationRecorder> value();
}
{{{
The class value() of the @Recorder will be looked up in the habitat by its type and no name, therefore it must be a singleton object and serve a unique contract.
Here is an simple example :
/**
* Startup interface is a markup interface for all startup services
* Startup services are run on startup.
*/
@Recorder(StartupRecorder.class)
@Contract
public interface Startup {
}
@Service
public class StartupRecoder implements Recorder<Startup> {
LinkedList<Startup> getRecords();
// should it be a LinkedList<Inhabitant<Startup>> instead ?
}
Startup enhancements RunLevel We need to have the ability to define Run Levels that allow to start a set of dependent services in stages. However, the run-levels or states of the application server can differ :
- by application type (WebLogic wants more States than GlassFish)
- by environment (the client jar may require different run-levels than the application server)
- by definition (WebLogic team want to chain their conceptually and relatively, while GlassFish would like to use a simple static number ordering scheme).
Therefore there is a need to define what a RunLevel type is :
/**
* meta-annotation to denote a RunLevel type which can be used
* by the HK2 startup sequence to look up the annotated
* startup services annotated with the annotation annotated
* by a @RunLevelType.
*/
public @interface RunLevelType {
/**
* defines the environment this run-level-type should
* apply to
*/
String value() default "default";
}
WebLogic States In WebLogic which requires a static chaining of States, they would define the run-level-type as follow :
@RunLevelType
@OrderedBy(ChainedState.ChainedLevelSorter.class)
public @interface ChainedState {
Class previous() default Void.class;
public static class ChainedLevelSorter implements Sorter {
@Override
public void sort(List<AnnotatedElement> elements) {
// simple insertion sort based on previous...
}
}
}
Then the chain for states can be defined as :
// first in the chain
@ChainedState()
public @interface WLSAdmin {
}
@ChainedState(previous=WLSAdmin.class)
public @interface WLSResuming {
}
@ChainedState(previous=WLSResuming.class)
public @interface WLSRunning {
}
After that, startup services can be annotated with the proper State.
@WLSAdmin
public class WLSStartup {
}
@WLSAdmin
public class WLSStartup2 {
}
@WLSRunning
public class WLSRandomService {
}
GlassFish RunLevels In GlassFish, the situation will be similar, this is our definition of a RunLevel :
@RunLevelType()
@OrderedBy(RunLevel.RunLevelSorter.class)
public @interface RunLevel {
int value() default 0;
public Class<? extends Sorter> sorter() default RunLevelSorter.class;
public static class RunLevelSorter implements Sorter {
@Override
public void sort(List<AnnotatedElement> elements) {
// ... sort elements based on value...//
}
}
}
Since we like to sort our services within one runlevel, our runlevels can become :
@RunLevel(10)
@OrderedBy(AdminLevel.ALSorter.class)
public @interface AdminLevel {
int priority() default 50;
public final class ALSorter implements Sorter {
@Override
public void sort(List<AnnotatedElement> elements) {
// ... sort AdminLevel services based on priority ... //
}
}
}
@RunLevelType("default")
@OrderedBy(RunLevel.RunLevelSorter.class)
public @interface RunLevel {
int value() default 0;
public Class<? extends Sorter> sorter() default RunLevelSorter.class;
public static class RunLevelSorter implements Sorter {
@Override
public void sort(List<AnnotatedElement> elements) {
// ... sort elements based on value...//
}
}
}
@Retention(RetentionPolicy.RUNTIME)
@OrderedBy(RunLevel.RunLevelSorter.class)
public @interface RunningLevel {
int value();
public static class RunLevelSorter implements Sorter<RunLevel> {
@Override
public void sort(List<RunLevel> elements) {
// ... sort elements based on value...//
}
}
}
Note that the fact value() is an int is for discussion purposes at this point. We might decide to have a different representation of a RunLevel. In the previous examples, all annotated Services with the @AdminLevel annotation can also optionally provide a priority to further sort out the desired order of instantiation. A simpler version that consider all services in a particular Level to be equal could be represented :
@RunLevel(10)
public @interface RunningLevel {
// running level, does not have priority and all services are considered equals...
}
Our startup services become :
@AdminLevel
public class SomeStartupService {
}
@AdminLevel(priority=10)
public class SomeOtherStartup {
}
@RunningLevel
public class YetAnotherStartup {
}
Other environment Whether States or RunLevels are considered, it should be possible to to define run levels for different execution environment like embedded or an application client.
/**
* defines the notion of a run-level when the
* hk2 is starting in an "embedded" mode
@RunLevelType("embedded")
public @interface EmbeddedRunLevel {
}
Multi-environment services Of course, nothing prevent services to be eligible for startup in various execution environment
@AdminLevel(30)
@Embedded(20)
@JMSClient
public class JMSStartupService {
}
Startup code TBD : we need to decide the relationship between RunLevel's and Startup services in Hk2. TBD : we need to decide what creates "demand" between Configuration Beans and Run Levels. TBD : we need a way to capture which @RunLevelType should be used to lookup States definition in this execution environment, default being "default" in the examples above.
@Contract
@Singleton
public interface RunLevelService<T> {
RunLevel currentLevel();
/**
* Moves up or down to a particular run level
**/
RunLevelWorker proceedTo(RunLevel level, RunLevelServiceListener listener);
}
@Contract
public interface RunLevelWorker {
void cancel(); // cancel the job (i.e., stop dead in the tracks). This may leave the system in an ambiguous state that will require either a recover to last checkpoint, or a subsequent proceedTo() call issued.
boolean isCompleted(); // indicated the work has completed (or the cancel is completed if cancel was called)
}
@Contract
public interface RunLevelServiceListener {
boolean onError(Exception error); // called when error encountered, caller should return true to continue and false to cancel/abort
void onProgress(RunLevel level); // called when proceedTo() is called with multiple level "jumps" involved
void onCompleted(); // used to notify that isCompleted() has been set to true
}
public void mainCode() {
...
goToLevel(AdminLevel.class);
...
goToLevel(RunningLevel.class);
}
Our proposal is to put all the new annotations in the existing annotation package: org.jvnet.hk2.annotations Our proposal is to put the new startup interfaces into package: org.jvnet.hk2.startup
|