Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "Eclipse4/RCP/Dependency Injection"

< Eclipse4‎ | RCP
(Using the E4AP DI Framework)
Line 1: Line 1:
With 10 years of experience with the Eclipse platform, a number of problems have surfaced.
+
With 10 years of experience with the Eclipse platform, a number of difficulties were observed.
  
 
# Code frequently uses global singleton accessors (e.g., Platform, PlatformUI) or required navigating a deep chain of dependencies (e.g., obtaining an ''IStatusLineManager'').  Singleton services is particularly problematic for app servers like RAP/Riena.
 
# Code frequently uses global singleton accessors (e.g., Platform, PlatformUI) or required navigating a deep chain of dependencies (e.g., obtaining an ''IStatusLineManager'').  Singleton services is particularly problematic for app servers like RAP/Riena.
# This tightly couples consumers of "things" to their producer/provider, inhibits reuse, etc
+
# The use of singletons tightly coupled consumers of "things" to their producer/provider, and inhibits reuse.
 
# Mechanisms are not very dynamic — plugins react to context changes drastically rather than incrementally (just close the affected controls)
 
# Mechanisms are not very dynamic — plugins react to context changes drastically rather than incrementally (just close the affected controls)
 
# <del>IEvaluationContext has global state that gets swapped according to context change (such as the focus control)</del>
 
# <del>IEvaluationContext has global state that gets swapped according to context change (such as the focus control)</del>
Line 13: Line 13:
 
# No support for services lookup that are composed out of other services on the fly
 
# No support for services lookup that are composed out of other services on the fly
  
[http://en.wikipedia.org/wiki/Dependency_injection Dependency Injection (DI)] is one approach to circumvent these problems: rather than require client code to know how to access a service, the client instead describes the service required, and the platform is responsible for configuring the object with an appropriate service.  DI shields the client code from knowing the provenance of the injected objects.
+
These difficulties are seen in other frameworks and are not unique to the Eclipse application platform.  The Eclipse 4 Application Platform has adopted the use of [http://en.wikipedia.org/wiki/Dependency_injection Dependency Injection (DI)] to circumvent these problems: rather than require client code to know how to access a service, the client instead describes the service required, and the platform is responsible for configuring the object with an appropriate service.  DI shields the client code from knowing the provenance of the injected objects.
  
* DI is not new. Pointers to other projects and books (e.g., Dhanji Prasanna's ''Dependency Injection'')
+
The Eclipse 4 Application Platform provides a [http://jcp.org/en/jsr/detail?id=330 JSR 330]-compatible, [http://atinject.googlecode.com/svn/trunk/javadoc/javax/inject/package-summary.html annotation-based] dependency injection (DI) framework, similar to [http://springframework.org/ Spring] or [http://code.google.com/p/google-guice Guice].
* Different approaches: separate configuration, or annotating POJOs with injection points.  
+
  
The Eclipse 4 Application Platform provides a [http://jcp.org/en/jsr/detail?id=330 JSR 330]-compatible dependency injection (DI) framework, similar to [http://springframework.org/ Spring] or [http://code.google.com/p/google-guice Guice].  Instead of PlatformUI.getWorkbench().getHelpSystem(), E4AP plugins can have the HelpSystem directly injected into an object.
 
  
 +
= Overview =
  
== Using the E4AP DI Framework ==
+
DI separates the configuration of an object from its behaviour. Rather than litter an object with details on how to access its required dependencies, the dependencies are instead configured through the use of an injector.
FIXME incorporate questions and comments from http://www.toedter.com/blog/?p=194
+
  
The E4AP DI Framework supports three types of injection:
+
For example, a typical Eclipse 3.x view would access the Eclipse Help system via the PlatformUI singleton, and the status line manager through the part's site:
# Constructor injection: values are matched to constructor arguments
+
<source lang="java">
# Method injection: values are injected into methods annotated with@Inject and that have satisfying arguments
+
class MyView extends ViewPart {
# Field injection: values are injected into fields annotated with @Inject and that have a satisfying type
+
  public void createPartControl(Composite parent) {
 +
    Button button = ...;
 +
    PlatformUI.getWorkbench().getHelpSystem().setHelp(button, "com.example.button.help.id");
  
FIXME: Are methods and fields examined down the class chain? What happens to shadowed methods or fields?
+
    getViewSite().getActionBars().getStatusLineManager().setMessage("Configuring system...");
 +
  }
 +
}
 +
</source>
  
FIXME: Are Annotations on interface methods are
+
With E4AP, parts are POJOs and have their application services directly injected into the part:
 +
<source lang="java">
 +
class MyView {
 +
  @Inject
 +
  public void create(Composite parent, IWorkbenchHelpSystem help) {
 +
    Button button = ...;
 +
    help.setHelp(button, "com.example.button.help.id");
  
FIXME: does the injector support @Singleton
+
    slm.setMessage("Configuring system...");
 +
  }
 +
}
 +
</source>
  
With DI, code no longer requires long chains to access services, like Platform.getDefault().getThis().getThat() or getSite().getThis().getThat().  If the code requires the selection service, it simply declares a field like:
+
DI provides a number of advantages:
  
@Inject protected ESelectionService ess;
+
* Clients are able to write POJOs and list the services they need.
@Inject protected EKeybindingService ekb;
+
* Useful for testing: the assumptions are placed in the DI container rather than in the client code
  
E4AP's injection framework also supports other injection types, such as to retrieve a preference value:
+
DI has some disadvantages too:
 +
* Service discovery: cannot use code completion to find out what is available.
 +
* Debugging why injection has not occurred can be frustrating!
  
@Inject @Preference(node="my.plugin.id", value="dateFormat")
 
protected String dateFormat;
 
  
and the field is updated as the preference changes too.
+
= Supported Annotations =
 +
 
 +
E4AP's injector is based on the standard JSR 330 annotations:
 +
 
 +
== @Inject ==
 +
 
 +
@Inject marks a contructor, method, or field as being available for injection.
 +
 
 +
== @Named ==
 +
 
 +
Injected values are typically identified by a type.  But there may be a number of available objects of a particular type (e.g., an application likely has several MPart)
  
* E4AP's injector uses annotations
+
** value injection: @Inject, @Named
** JSR 330 annotations:
+
*** lifecycle injection: @Singleton, @PostConstruct, @PreDestroy
*** value injection: @Inject, @Named
+
*** lifecycle injection: @PostConstruct, @PreDestroy
+
 
** E4AP-specific annotations: @Preference, @Event, @UIEvent, @Active, @CanExecute, @Execute, @Persist, @Focus, @GroupUpdates
 
** E4AP-specific annotations: @Preference, @Event, @UIEvent, @Active, @CanExecute, @Execute, @Persist, @Focus, @GroupUpdates
* Order of injection classes: Constructor, @Injected variables, @Injected methods, @PostConstruct
 
 
** no guarantees of ordering within injection classes; methods requiring values from injected variables should either be called from @PostConstruct or be injected with both
 
** no guarantees of ordering within injection classes; methods requiring values from injected variables should either be called from @PostConstruct or be injected with both
 
** @Optional and null values for services
 
** @Optional and null values for services
Line 61: Line 80:
 
* re-injection: as the backing context changes, changed values will be re-injected
 
* re-injection: as the backing context changes, changed values will be re-injected
  
== Considerations ==
+
== E4AP-specific Annotations ==
* thread-safety: method injection may happen on any thread
+
 
* final fields can only be supported using constructor injection
+
E4AP's injection framework also supports other E4AP-specific annotations.
* if several injectable fields required, then avoid using several single setter-style methods and use a single method with multiple arguments
+
 
 +
=== @Preferences ===
 +
 
 +
The @Preference provides simple interfacing with the Eclipse preferences framework.  The following snippet illustrates its use:
 +
<source lang="java">
 +
@Inject @Preference(node="my.plugin.id", value="dateFormat")
 +
protected String dateFormat;
 +
</source>
 +
The "dateFormat" field is updated as the preference changes too; the "node" is optional and defaults to the code's defining bundle.
 +
 
 +
A class can receive notification of a preference change by instead receiving the injection through a setter:
 +
<source lang="java">
 +
@Inject
 +
private void setDateFormat(@Preference(node="my.plugin.id", value="dateFormat") String dateFormat) {
 +
this.dateFormat = dateFormat;
 +
// ... and do something ...
 +
}
 +
</source>
 +
 
 +
The preference value can be changed programatically by instead injecting a Provider:
 +
<source lang="java">
 +
@Inject @Preference(node="my.plugin.id", value="dateFormat")
 +
Provider<String> dateFormat;
 +
 
 +
private void use8601Format() {
 +
dateFormat.set(ISO8601_FORMAT);
 +
}
 +
</source>
 +
 
 +
 
 +
= Advanced Topics =
 +
 
 +
== Injection Order ==
 +
 
 +
The E4AP DI Framework supports three types of injection, and is performed in the following order:
 +
 
 +
# Constructor injection: the public or protected constructor annotated with @Inject with the greatest number of resolvable arguments is selected
 +
# Field injection: values are injected into fields annotated with @Inject and that have a satisfying type
 +
# Method injection: values are injected into methods annotated with@Inject and that have satisfying arguments
 +
 
 +
After a newly-created object has been injected, the injector then calls all methods annotated with @PostConstruct.  There is no order implied within any group.
 +
 
 +
The DI framework processes fields and methods found in the class hierarchy, including static fields and methods too.  Shadowed fields are injectable, but overridden methods are not.  The injector does not process methods declared on interfaces.
 +
 
 +
== Batching Updates with @GroupUpdates ==
  
In e4 injected values are dynamic; values that changed in the context get propagated into injected fields/methods. So, if OSGi strategy worked properly, the changes in the service implementations would be propagated. Also, we have the "@Optional" annotation which allows values not currently in the context to be injected as "null" and re-injected later when the values are added to the context.
+
The @GroupUpdates annotation indicates to the framework that updates should be batched.
 +
<source lang="java">
 +
  // injection will be batched
 +
  @Inject @GroupUpdates
 +
  void setInfo(@Named("string1") String s, @Named("string2") String s2) {
 +
      this.s1 = s;
 +
      this.s2 = s2;
 +
  }
 +
</source>
 +
The setInfo() method will be triggered by a call to IEclipseContext#processWaiting():
 +
<source lang="java">
 +
  IEclipseContext context = ...;
 +
  context.set("string1", "a");
 +
  context.set("string2", "b");
 +
  context.processWaiting();  // trigger @GroupUpdates
 +
</source>
  
 
== Extending the DI Framework ==
 
== Extending the DI Framework ==
Line 78: Line 156:
 
*** Why is modify different from set
 
*** Why is modify different from set
  
== Advantages / Disadvantages ==
 
  
DI provides a number of advantages:
 
  
* Clients are able to write POJOs and list the services they need.  The DI framework provides
+
== Configuring Bindings ==
* Useful for testing: the assumptions are placed in the DI container rather than in the client code
+
 
+
DI has some disadvantages too:
+
 
+
* Concerns about discoverability of services - cannot use code completion to find out what is available.
+
 
+
 
+
== Advanced Topics ==
+
 
+
you can have a factory classes by using IBinding:
+
  
 +
You can have a factory classes by using IBinding:
 +
<source lang="java">
 
InjectorFactory.getDefault().addBinding(MyPart.class).implementedBy(MyFactory.class)
 
InjectorFactory.getDefault().addBinding(MyPart.class).implementedBy(MyFactory.class)
 
+
</source>
 
or using ContextFunctions:
 
or using ContextFunctions:
 
+
<source lang="java">
 
public class MyFactory extends ContextFunction {
 
public class MyFactory extends ContextFunction {
 
  public Object compute(IEclipseContext context) {
 
  public Object compute(IEclipseContext context) {
Line 104: Line 172:
 
   return result;
 
   return result;
 
}
 
}
 +
</source>
 +
 +
 +
 +
= Debugging =
 +
 +
Tips for debugging injection issues:
 +
* set an exception breakpoint on org.eclipse.e4.core.di.InjectionException
 +
 +
 +
= Considerations =
 +
 +
* thread-safety: method injection may happen on any thread
 +
* final fields can only be supported using constructor injection
 +
* if several injectable fields required, then use a single @Inject method with multiple arguments and consider @GroupUpdate rather than using several single setter-style @Inject methods.
 +
 +
Be aware that injected values are dynamic: values that are changed in the context may be immediately propagated into injected fields/methods. Also, we have the "@Optional" annotation which allows values not currently in the context to be injected as "null" and re-injected later when the values are added to the context.
 +
 +
 +
= Current Caveats =
 +
 +
Although E4AP's DI will inject OSGi services, it does not currently track changes to the service.  This means that if the service is not available at time of initial injection, but subsequently becomes available, existing requestors for the the service are not notified.  Nor are receivers notified should the service disappear.  This work is being tracked across several bugs: {{bug|331235}}, {{bug| 330865}}, {{bug|317706}}.
 +
 +
= References =
 +
 +
Dhanji Prasanna's ''Dependency Injection'' provides provides guidance on best practices on using DI in your application.

Revision as of 09:31, 12 April 2011

With 10 years of experience with the Eclipse platform, a number of difficulties were observed.

  1. Code frequently uses global singleton accessors (e.g., Platform, PlatformUI) or required navigating a deep chain of dependencies (e.g., obtaining an IStatusLineManager). Singleton services is particularly problematic for app servers like RAP/Riena.
  2. The use of singletons tightly coupled consumers of "things" to their producer/provider, and inhibits reuse.
  3. Mechanisms are not very dynamic — plugins react to context changes drastically rather than incrementally (just close the affected controls)
  4. IEvaluationContext has global state that gets swapped according to context change (such as the focus control)
  5. Current solutions are not multi-threadable - they assume evaluation occurs in the UI thread
  6. Scaling granularity
    • Current solutions don't scale down to services with brief lifetimes
    • Current solutions may not scale up to very large numbers of services
  7. Currently don't track service consumers to notify when services come/go
  8. Client code needs to know the internals of the Eclipse code base
  9. No support for services lookup that are composed out of other services on the fly

These difficulties are seen in other frameworks and are not unique to the Eclipse application platform. The Eclipse 4 Application Platform has adopted the use of Dependency Injection (DI) to circumvent these problems: rather than require client code to know how to access a service, the client instead describes the service required, and the platform is responsible for configuring the object with an appropriate service. DI shields the client code from knowing the provenance of the injected objects.

The Eclipse 4 Application Platform provides a JSR 330-compatible, annotation-based dependency injection (DI) framework, similar to Spring or Guice.


Overview

DI separates the configuration of an object from its behaviour. Rather than litter an object with details on how to access its required dependencies, the dependencies are instead configured through the use of an injector.

For example, a typical Eclipse 3.x view would access the Eclipse Help system via the PlatformUI singleton, and the status line manager through the part's site:

class MyView extends ViewPart {
  public void createPartControl(Composite parent) {
    Button button = ...;
    PlatformUI.getWorkbench().getHelpSystem().setHelp(button, "com.example.button.help.id");
 
    getViewSite().getActionBars().getStatusLineManager().setMessage("Configuring system...");
  }
}

With E4AP, parts are POJOs and have their application services directly injected into the part:

class MyView {
  @Inject
  public void create(Composite parent, IWorkbenchHelpSystem help) {
    Button button = ...;
    help.setHelp(button, "com.example.button.help.id");
 
    slm.setMessage("Configuring system...");
  }
}

DI provides a number of advantages:

  • Clients are able to write POJOs and list the services they need.
  • Useful for testing: the assumptions are placed in the DI container rather than in the client code

DI has some disadvantages too:

  • Service discovery: cannot use code completion to find out what is available.
  • Debugging why injection has not occurred can be frustrating!


Supported Annotations

E4AP's injector is based on the standard JSR 330 annotations:

@Inject

@Inject marks a contructor, method, or field as being available for injection.

@Named

Injected values are typically identified by a type. But there may be a number of available objects of a particular type (e.g., an application likely has several MPart)

    • value injection: @Inject, @Named
      • lifecycle injection: @Singleton, @PostConstruct, @PreDestroy
    • E4AP-specific annotations: @Preference, @Event, @UIEvent, @Active, @CanExecute, @Execute, @Persist, @Focus, @GroupUpdates
    • no guarantees of ordering within injection classes; methods requiring values from injected variables should either be called from @PostConstruct or be injected with both
    • @Optional and null values for services
  • List of available services
    • E4AP services
    • OSGi services
  • plugins: org.eclipse.e4.core.di, org.eclipse.e4.core.di.extensions, and org.eclipse.e4.ui.di
  • re-injection: as the backing context changes, changed values will be re-injected

E4AP-specific Annotations

E4AP's injection framework also supports other E4AP-specific annotations.

@Preferences

The @Preference provides simple interfacing with the Eclipse preferences framework. The following snippet illustrates its use:

	@Inject @Preference(node="my.plugin.id", value="dateFormat")
	protected String dateFormat;

The "dateFormat" field is updated as the preference changes too; the "node" is optional and defaults to the code's defining bundle.

A class can receive notification of a preference change by instead receiving the injection through a setter:

	@Inject 
	private void setDateFormat(@Preference(node="my.plugin.id", value="dateFormat") String dateFormat) {
		this.dateFormat = dateFormat;
		// ... and do something ...
	}

The preference value can be changed programatically by instead injecting a Provider:

	@Inject @Preference(node="my.plugin.id", value="dateFormat") 
	Provider<String> dateFormat;
 
	private void use8601Format() {
		dateFormat.set(ISO8601_FORMAT);
	}


Advanced Topics

Injection Order

The E4AP DI Framework supports three types of injection, and is performed in the following order:

  1. Constructor injection: the public or protected constructor annotated with @Inject with the greatest number of resolvable arguments is selected
  2. Field injection: values are injected into fields annotated with @Inject and that have a satisfying type
  3. Method injection: values are injected into methods annotated with@Inject and that have satisfying arguments

After a newly-created object has been injected, the injector then calls all methods annotated with @PostConstruct. There is no order implied within any group.

The DI framework processes fields and methods found in the class hierarchy, including static fields and methods too. Shadowed fields are injectable, but overridden methods are not. The injector does not process methods declared on interfaces.

Batching Updates with @GroupUpdates

The @GroupUpdates annotation indicates to the framework that updates should be batched.

   // injection will be batched
   @Inject @GroupUpdates
   void setInfo(@Named("string1") String s, @Named("string2") String s2) {
      this.s1 = s;
      this.s2 = s2;
   }

The setInfo() method will be triggered by a call to IEclipseContext#processWaiting():

   IEclipseContext context = ...;
   context.set("string1", "a");
   context.set("string2", "b");
   context.processWaiting();  // trigger @GroupUpdates

Extending the DI Framework

ExtendedObjectSupplier LookupContextStrategy


  • Accessing the IEclipseContext; see Eclipse4/RCP/Contexts
    • Adding, setting, or modifying variables
      • Why is modify different from set


Configuring Bindings

You can have a factory classes by using IBinding:

InjectorFactory.getDefault().addBinding(MyPart.class).implementedBy(MyFactory.class)

or using ContextFunctions:

public class MyFactory extends ContextFunction {
 public Object compute(IEclipseContext context) {
  MPart result = ContextInjectionFactory.make(MyPart.class, context);
  doWhatever(result);
  return result;
}


Debugging

Tips for debugging injection issues:

  • set an exception breakpoint on org.eclipse.e4.core.di.InjectionException


Considerations

  • thread-safety: method injection may happen on any thread
  • final fields can only be supported using constructor injection
  • if several injectable fields required, then use a single @Inject method with multiple arguments and consider @GroupUpdate rather than using several single setter-style @Inject methods.

Be aware that injected values are dynamic: values that are changed in the context may be immediately propagated into injected fields/methods. Also, we have the "@Optional" annotation which allows values not currently in the context to be injected as "null" and re-injected later when the values are added to the context.


Current Caveats

Although E4AP's DI will inject OSGi services, it does not currently track changes to the service. This means that if the service is not available at time of initial injection, but subsequently becomes available, existing requestors for the the service are not notified. Nor are receivers notified should the service disappear. This work is being tracked across several bugs: bug 331235, 330865 bug 330865, bug 317706.

References

Dhanji Prasanna's Dependency Injection provides provides guidance on best practices on using DI in your application.

Back to the top