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

Eclipse4/RCP/Dependency Injection

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