Advanced Use of Eclipse 4s Dependency Injection Framework Brian de - - PowerPoint PPT Presentation

advanced use of eclipse 4 s dependency injection framework
SMART_READER_LITE
LIVE PREVIEW

Advanced Use of Eclipse 4s Dependency Injection Framework Brian de - - PowerPoint PPT Presentation

Advanced Use of Eclipse 4s Dependency Injection Framework Brian de Alwis Manumitting Technologies, Inc (includes work with Landmark Graphics Corp) Agenda Problem (Why DI?) Why use DI in Eclipse? Whats different about Eclipses


slide-1
SLIDE 1

Advanced Use of Eclipse 4’s Dependency Injection Framework

Brian de Alwis Manumitting Technologies, Inc (includes work with Landmark Graphics Corp)

slide-2
SLIDE 2

Agenda

  • Problem (Why DI?)
  • Why use DI in Eclipse? What’s different about

Eclipse’s DI?

  • How can I use Eclipse’s DI?
  • Context functions
  • Custom annotations
  • Custom services
slide-3
SLIDE 3

Cardinal Rules of SE

  • Increase cohesion between related objects
  • Together, they form a meaningful unit
  • Decrease coupling between unrelated components
  • Improves isolation: a component can be

modified without breaking others

  • Improves flexibility: can reuse components in

new situations But it’s a blurry distinction between what’s “related” and “unrelated”

slide-4
SLIDE 4

PlatformUI.getWorkbench().getActiveWorkbenchWindow() .getSelectionService() MyAppThingy.getInstance() Platform.getAdapterManager()

The Problem: Most objects are not created independently, but are created as part of something larger. Yet we build these objects as if they were independent, and must specify how to access their needed services. Result: structural dependencies (e.g., singletons), or creator responsible for extensive configuration (or service locators) There are over 1600 references to PlatformUI.getWorkbench() in the Eclipse Platform alone! (Which likely makes the RAP developers cry.)

  • (Image from RJ Walker, GC Murphy (2000). Implicit Context: Easing Software Evolution and Reuse. FSE; they called

these details “EEK”: External Extraneous Knowledge)

slide-5
SLIDE 5

DI as Operating Theatre

@Inject Scalpel scalpel;

Let’s consider a different situation: medical surgery. A surgeon obtains items and information from those in the environment. People in the environment notify surgeon when circumstances change. The surgeon is not responsible for knowing what is going on and where items are found. Thus transplant surgeons can travel long distances to unfamiliar hospitals and yet be immediately effective. They make assumptions of their environment, and their environment provides.

  • How do we take this approach to programming?

Annotations: the annotations spare us from writing a lot of boilerplate

slide-6
SLIDE 6

PlatformUI.getWorkbench().getActiveWorkbenchWindow() .getSelectionService() MyAppThingy.getInstance() Platform.getAdapterManager() @Inject ISelectionService ss; @Inject IAdapterManager am; @Inject MyAppThingy thingy;

What we want to be able to do is get rid of this EEK and instead say “find me the appropriate selection service / adapter manager / thingy.” But how do we get these “thingies”? We push this problem to whomever created us, transforming the problem into a runtime configuration issue.

slide-7
SLIDE 7

Workbench Window Editor View

Containers

  • Most objects are created

in the context of some

  • ther object
  • Creator determines the

lifecycle

  • That creator is responsible

for providing the configuration required by its children

Servlet Webapp Servlet Jetty Container: some other component who determines this object’s lifecycle Often used in the context of web apps (e.g., Tomcat, Jetty). A container may have been itself created by some other container. We now use DI extensively within the Eclipse UI, and due to how we need injection to work, we have our own injector.

slide-8
SLIDE 8

Why use DI for Eclipse?

  • Simplify access to workbench services
  • Removes need for singletons
  • Remove boilerplate (particularly listeners)
  • Shrink service lifetimes
  • Avoid need to create on startup
  • Cleanup when no longer needed

DI provides advantages for code: separation of configuration from use Simplify access: avoid IStatusLineManager problem

slide-9
SLIDE 9

Other Forms of DI

  • (AKA: Inversion of Control)
  • Maven / Plexus
  • OSGi Declarative Services (<reference>)
  • Spring
  • JSR330: Guice, Spring, and E4

In other DI systems, like Guice or Spring, there is a single-level of configuration. UIs are a bit different, and we needed something more flexible.

slide-10
SLIDE 10

E4 DI Differences

  • Supports field, method, and constructor injection
  • Supports custom annotations
  • Will re-inject whenever the injected values are

changed or the injection situation changes The last item is key differentiator from other injectors

slide-11
SLIDE 11

Simplify Code (POJOs)

public class PropertiesPart {

  • @Inject IExtensionRegistry registry;
  • @Inject @Optional
  • public void setInput(
  • @Named(IServiceConstants.ACTIVE_PART) MPart activePart,
  • @Named(IServiceConstants.ACTIVE_SELECTION) Object selection) {
  • // look up corresponding property page in extension registry
  • // flip out the current page and replace
  • }

}

Contrast @Named vs FQTN Avoids boilerplate for installing/removing listeners. Compare & contrast the code for tracking the active part with the IPartService.

slide-12
SLIDE 12

Simplify Code: Handlers

public class CommandHandler {

  • @CanExecute
  • public boolean canExecute(ESelectionService selService) {
  • return selService.getSelection() != null;
  • }
  • @Execute
  • public void execute(MWindow window, EPartService partService,
  • EModelService modelService) {
  • MPart part = modelService.createModelElement(MPart.class);
  • part.setLabel("New Part");
  • part.setContributionURI("platform:/plugin/bundle/classname");
  • List<MPartStack> stacks = modelService.findElements(window, null,
  • MPartStack.class, null);
  • stacks.get(0).getChildren().add(part);
  • partService.showPart(part, PartState.ACTIVATE);
  • }

}

slide-13
SLIDE 13

Simplify Code: Preferences

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

  • @Inject

private void setDateFormat(

  • @Preference(nodePath="my.plugin.id", value="dateFormat")
  • String dateFormat) {
  • this.dateFormat = dateFormat;
  • // ... and do something ...

}

  • @Inject @Preference(nodePath="my.plugin.id")

IEclipsePreferences preferences;

  • private void use8601Format() {
  • preferences.put(dateFormat, ISO8601_FORMAT);

}

slide-14
SLIDE 14

Simplify Code: OSGi Events

public class GameDisplayPart {

  • @Inject MPart me;
  • @Inject IEventBroker eventBroker;
  • Game game;
  • @Inject
  • public void raisedToTop(
  • @UIEventTopic(UIEvents.UILifeCycle.BRINGTOTOP) MPart part) {
  • if (me == part) {
  • refresh();
  • }
  • }
  • public void newGame() {
  • game = new Game();
  • eventBroker.post(“ca/mt/game/set", game);
  • }

}

  • UIEventTopic ensures the event is delivered on the UI thread using the Realm found on the part’s context.
  • IEventBroker is a wrapper around the OSGi EventAdmin service that presents a simplified listener; its lifetime is

also bounded by the context in which it was created (rather than the bundle).

  • Every model change in the E4 EMF-based UI model is turned into an event that is then reflected to the SWT/

JavaFX/etc widgets

slide-15
SLIDE 15

JSR 330: Annotations

  • JSR 330 introduces a set of standard annotations for

DI in javax.inject

  • @Inject, @Named, @Singleton
  • Provider<T>: supports deferred injection
  • (@Scope & @Qualifier: used when defining other

injection-related annotations)

  • JSR 250 java.annotation: @PostConstruct,

@PreDestroy

But we have common underpinnings. It’s possible to write injectable code that should work across any JSR330- compliant injector, like the Eclipse injector.

slide-16
SLIDE 16

Eclipse DI Extended Annotations

  • org.eclipse.e4.core.di.annotations:
  • @Optional, @Creatable
  • @GroupUpdates
  • @CanExecute / @Execute
  • org.eclipse.e4.core.di.extensions:

@EventTopic / @UIEventTopic, @Preference, @OSGiBundle

  • org.eclipse.e4.core.contexts: @Active

And you can define your own field and parameter annotations to have them be custom injected

slide-17
SLIDE 17

How Does E4 DI Work?

public class PropertiesPart {

  • @Inject IExtensionRegistry registry;
  • @Inject
  • PropertiesPart(IEclipseContext context) { /* … */ }
  • @Inject @Optional
  • public void setInput(
  • @Named(IServiceConstants.ACTIVE_PART) MPart activePart,
  • @Named(IServiceConstants.ACTIVE_SELECTION) Object selection) {
  • // look up corresponding property page in extension registry
  • // flip out the current page and replace
  • }
  • @PostConstruct
  • public void init(Composite parent) {
  • // build up widget representation
  • }
  • @PreDestroy
  • public void dispose() { /* … */ }

}

Process @Inject items:

  • 1. If created via IInjector#make(): Constructors marked with @Inject (and public or package protected) or 0-arg

constructor

  • 2. Fields with @Inject
  • 3. Methods with @Inject
  • 4. Methods with @PostConstruct
slide-18
SLIDE 18

Where Do Injected Objects Come From?

  • IEclipseContext: tree-based String → Object map

with inheritance in lookups

  • Key lookup starts at some child and proceeds

up the chain until satisfied

  • Starting context is likely, but not necessarily, a

leaf

  • You can create your own context hierarchy

(ContextInjectionFactory) or…

slide-19
SLIDE 19

ViewPart Application Persp Window Persp Window EditorPart OSGi Services

“org.eclipse.ui.ISelectionService” a SelectionService “localContexts” [“org.eclipse.ui…”, “…”] “org.eclipse.swt.widgets.Composite” host composite “customValue” domain object “other state value” “a string” “org.eclipse.core.runtime.IExtensionRegistry” a Registry “org.eclipse.e4.core.services.adapter.Adapter” an Adapter “activePart” a Part “activePartId” “org.eclipse.ui.views.Pro…” “activeEditor" an EditorPart “activeEditorId” “org.eclipse.e4.tools.emf…” “activeWorkbenchWindow” a Window “applicationXMI” “org.eclipse.platform/LegacyIDE.xmi”

Key: a string; often the FQTN of the value’s class or interface Value: any object, including null

Key workbench components have a corresponding IEclipseContext Key: any string An unsuccessful lookup at the editor level (e.g. for “IExtensionRegistry”) traverses upwards You can store whatever values you like, including custom app state Top level looks up services in OSGi level, including DS

slide-20
SLIDE 20

What If a Value is Unavailable from the Context?

  • Injection fails
  • …unless marked as @Optional: then injected

with null

slide-21
SLIDE 21

Injecting OSGi Services

  • Top-level of the Workbench IEclipseContext

hierarchy is the OSGi service layer

  • Services obtained via BundleContext for
  • rg.eclipse.e4.ui.workbench
slide-22
SLIDE 22

How Do I Change Values in the Context?

  • Get the context, and call “context.set(name,

value)”

  • Will cause any places that requested injection to

be re-injected with the new value

  • Can also declare a variable name at a particular

context, then use “context.modify(name, value)” to set that value from any child context

  • Variables can be configured from App.e4xmi

Can always obtain the context used for injection via “@Inject IEclipseContext context”

slide-23
SLIDE 23

Advanced DI

slide-24
SLIDE 24

RunAndTracks (RATs)

  • A runnable that tracks changes relative to a

particular context

  • Changes to those context variables causes

the RAT to be re-run while the RAT returns “true”.

slide-25
SLIDE 25

Context Functions: Computing Values on the Fly

  • Instead of setting a value, compute it using an

IContextFunction

  • Provided the originating context
  • Values are (currently) aggressively cached by

the originating context

  • Must subclass abstract ContextFunction class
slide-26
SLIDE 26

Creating/Injecting

IInjector has 4 principle methods:

  • #make(Class, PrimaryObjectSupplier)
  • #inject(Object, PrimaryObjectSupplier)
  • #uninject(Object)
  • #invoke(Object, Annotation, PrimaryObjectSupplier)

(But typically use ContextInjectionFactory) An injector is used to create or inject objects; will be required for objects that aren’t managed by the Eclipse framework. The injector attempts to resolve injection requests using a set of provided object suppliers; our primary supplier is based around the IEclipseContext hierarchy. Typically use the wrappers on ContextInjectionFactory.

slide-27
SLIDE 27

Explicit Injection

public abstract class InjectableViewPart extends ViewPart { private IEclipseContext _context;

  • public void init(IViewSite site) throws PartInitException {

_context = (IEclipseContext) site.getService(IEclipseContext.class); assert _context != null; super.init(site); ContextInjectionFactory.inject(this, _context); }

  • public void dispose() {

super.dispose(); ContextInjectionFactory.uninject(this, _context); } }

If you create/inject an object, you should remember to uninject But IECs have a lifecycle: can be disposed, which cleans up any previously-injected objects. Child context lifetimes are bounded by the parent.

slide-28
SLIDE 28

Custom Annotations

  • Create custom annotation
  • Register corresponding Extended Object Supplier

as an OSGi service

slide-29
SLIDE 29

Example: @ActiveEditor

@Inject private void editorSelectionChanged(

  • @Optional @Named(“bug398728")
  • @ActiveEditor("org.eclipse.ui.output.selection") Object selection) {
  • // called whenever the editor’s selection changes

}

  • // track editor state changes

@Inject private void editorCursorLocation(

  • @Optional @Named(“bug398728”)
  • @ActiveEditor IEditorPart editor,
  • @Optional @Named(“bug398728")
  • @ActiveEditor("cursorLocation") CursorLocation selection) {
  • // called whenever the editor or the editor’s cursor location changes

}

@Named(“bug398728”) required prior to 4.4

  • Need to be able to somehow identify a scope — what if we had multiple windows? A viewpart would likely want to

be notified only of changes within its windows?

slide-30
SLIDE 30

Custom annotation

/** * This annotation causes arguments and fields in the requesting object to be resolved from the active editor's context * rather than the requesting object's source context. Values will be re-injected when a different editor is activated. * * <p>The scope of the editor is determined by the requesting object. For example, if the object is a {@link ViewPart} * then the search scope is normally restricted to finding the active editor within the same window. The requesting object * may implement or be adaptable to {@link LookupScope} to explicitly provide a different scope. If the scope cannot * be determined, then it is assumed to be workbench-wide and the receiver will receive updates for the active editor * and active window.</p> * * <p>The procedure for specifying the identifier used to resolve an object for injection is slightly different from * normal injection. Unlike normal resolution, which uses the {@link Named} annotation, the object identifier is taken * either from the annotation's value (e.g., {@code "@ActiveEditor("identifier")}) or otherwise from the field/method * type name. This difference is required as the &#064;ActiveEditor annotations are processed as a second stage of injection. * The {@link Named} annotation is instead used to provide a nonsense value so as to prevent the successful * resolution in the primary stage of injection; this nonsense value is usually required when the injected object * is provided as an OSGi service. See Eclipse bug 398728 for details.</p> * * <p>Use of the {@link Optional} annotation is normally required since there is no guarantee that an active editor is available to inject.</p> * […] */

@Qualifier @Documented @Target({ ElementType.FIELD, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) public @interface ActiveEditor { /** The context key: if unspecified, uses the requestor type */ String value() default ""; }

slide-31
SLIDE 31

Register Supplier

<?xml version="1.0" encoding="UTF-8"?> <scr:component xmlns:scr=“http://www.osgi.org/xmlns/scr/v1.1.0"

  • name="com.lgc.dsp.dsaf.editorcontextsupplier">

<implementation class="com.lgc.dsaf.ui.impl.ActiveEditorObjectSupplier"/> <service> <provide interface="org.eclipse.e4.core.di.suppliers.ExtendedObjectSupplier"/> </service> <property name="dependency.injection.annotation" type=“String"

  • value="com.lgc.dsaf.ui.di.ActiveEditor"/>

</scr:component>

We expose the annotation and extended supplier via an OSGi Service. This is a specific detail to the default InjectorImpl.

slide-32
SLIDE 32

Object Suppliers

  • Object suppliers (primary & extended) are

provide object describing the injection site

  • IObjectDescriptor: provides access to the

type and annotations of the field/parameter

  • IRequestor: the requesting location: the

method, field, constructor; can re-trigger The IRequestor also provides access to the object being injected.

slide-33
SLIDE 33

public class ActiveEditorObjectSupplier extends ScopedObjectSupplier {

  • @Override
  • public Object get(IObjectDescriptor descriptor, IRequestor requestor, boolean track,
  • boolean group) {
  • if (descriptor == null) { return null; }
  • String serviceName = getServiceName(descriptor);
  • assert serviceName != null;
  • Object scope = getLookupScope(requestor);
  • assert scope != null;
  • trackScopedRequestor(track, scope, requestor);
  • IEditorPart editor = getSuitableEditor(scope);
  • // IInjector.NOT_A_VALUE is analogous to undefined in JavaScript. it is distinct from a null value
  • if (editor == null) { return IInjector.NOT_A_VALUE; }
  • if (serviceName == null || serviceName.isEmpty() || serviceName.equals(IEditorPart.class.getName())) { return editor; }
  • IEclipseContext context = (IEclipseContext) editor.getSite().getService(IEclipseContext.class);
  • if (context == null) { return IInjector.NOT_A_VALUE; }
  • if (track) {
  • Map.Entry<IRequestor,String> key = new AbstractMap.SimpleEntry<IRequestor,String>(requestor, serviceName);
  • ValueRequestor valueRequestor = _valueRequestors.get(key);
  • if (valueRequestor == null) {
  • _valueRequestors.put(key, valueRequestor = new ValueRequestor(key, context));
  • }
  • return valueRequestor.getValue(context);
  • }
  • Object value = context.get(serviceName);
  • return value != null || context.containsKey(serviceName) ? value : IInjector.NOT_A_VALUE;

} }

We’ll explain scope in a sec Note get(): “track” means whether changes to this value should be tracked and cause re-injection; “group” whether the requestor changes can be grouped together getServiceName() looks at the IObjectDescriptor to get details of the @Named or the requested type name.

slide-34
SLIDE 34

class ValueRequestor { IEclipseContext context; Map.Entry<IRequestor,String> key; Object value = IInjector.NOT_A_VALUE;

  • public ValueRequestor(

Map.Entry<IRequestor,String> key, IEclipseContext context) { this.key = key; this.context = context; updateValueAndInstallTracker(); }

  • private RunAndTrack contextTracker =

new RunAndTrack() { @Override public boolean changed(

  • IEclipseContext cxt) {

if (!key.getKey().isValid()) { _valueRequestors.remove(key); return false; } if (context != cxt) { return false; } Object x = context.get(key.getValue()); if (!Objects.equal(x, value)) { value = x; if (value != IInjector.NOT_A_VALUE) { runExternalCode(toReinject); } } return true; } }

  • private Runnable toReinject = new Runnable() {

@Override public void run() { key.getKey().resolveArguments(false); key.getKey().execute(); } };

  • private void updateValueAndInstallTracker() {

value = context.get(key.getValue()); context.runAndTrack(contextTracker); }

  • public Object getValue(IEclipseContext cxt) {

// the context of an editor should not // ever change. boolean newContext = cxt != context; context = cxt; if (newContext) { updateValueAndInstallTracker(); } return value; } }

We’ll explain scope in a sec ValueRequestor uses

slide-35
SLIDE 35

public interface LookupScope { Object getLookupScope(); }

  • public abstract class ScopedObjectSupplier

extends ExtendedObjectSupplier { ListMultimap<Object, IRequestor> _scopedRequestors …;

  • protected Object getLookupScope(

IRequestor requestor) { Object o = requestor.getRequestingObject(); LookupScope lookup; Object scope = o; if (o instanceof LookupScope) { scope = ((LookupScope) o).getLookupScope(); } else if ((lookup =_adapter.adapt(o, LookupScope.class)) != null) { scope = lookup.getLookupScope(); } if (scope instanceof IEditorPart || scope instanceof IWorkbenchWindow || scope instanceof IWorkbench) { return scope; } else if (scope instanceof IWorkbenchPart) { return ((IWorkbenchPart) o).getSite() .getWorkbenchWindow(); } else if (scope instanceof WorkbenchWindowControlContribution) { return ((WorkbenchWindowControlContribution) scope).getWorkbenchWindow(); } return getWorkbench(); }

  • protected void trackScopedRequestor(

boolean track, Object scope, IRequestor requestor) { if(track && requestor.isValid()) { if(!scopedRequestors. containsEntry(scope, requestor)) { _scopedRequestors.put(scope, requestor); } } else { _scopedRequestors.remove(scope, requestor); } }

  • // continued

We’ll explain scope in a sec Note get(): “track” means whether changes to this value should be tracked and cause re-injection; “group” whether the requestor changes can be grouped together

slide-36
SLIDE 36

public abstract class ScopedObjectSupplier extends ExtendedObjectSupplier { ListMultimap<Object, IRequestor> _scopedRequestors …;

  • @PostConstruct

protected void init() { /* install part and window listeners that reinject all scoped IRequestors when the active part/editor/window changes */ }

  • protected void editorActivated(IEditorPart editor,

IWorkbenchWindow window) { processRequestors(_scopedRequestors.get(editor)); processRequestors(_scopedRequestors.get(window)); processRequestors(_scopedRequestors.get(window.getWorkbench())); }

  • private void processRequestors(List<IRequestor> requestors) {

for(Iterator<IRequestor> iter = requestors.iterator(); iter.hasNext();) { IRequestor r = iter.next(); if(r.isValid()) { r.resolveArguments(false); r.execute(); } else { iter.remove(); }

We’ll explain scope in a sec Note get(): “track” means whether changes to this value should be tracked and cause re-injection; “group” whether the requestor changes can be grouped together

slide-37
SLIDE 37

Object Supplier Gotchas

  • Object Suppliers have to be multi-threaded
  • Calls may come in from anywhere
  • Object Suppliers need to track changes if

track==true

  • And clean up any tracking if track==false
slide-38
SLIDE 38

Installing services in the context hierarchy

  • How can we install a service into the contexts?
  • Create new IEclipseContext class, override #lookup()

method, and instantiate in the right place

  • Provide E4 lifecycle manager and initialize application-

level IEclipseContext

  • Add an MContext listener

(UIEvents.Context.TOPIC_CONTEXT)

  • Provide IContextFunction-based service installer as

OSGi-level Service

slide-39
SLIDE 39

Workbench Services Installer

<?xml version="1.0" encoding="UTF-8"?> <scr:component xmlns:scr=“http://www.osgi.org/xmlns/scr/v1.1.0"

  • activate="configure">

<service> <provide interface="org.eclipse.e4.core.contexts.IContextFunction"/> </service> <property name="service.context.key" type=“String"

  • value=“com.lgc.dsaf.app.services.properties.DspAppPropertiesProvider"/>
  • <implementation class="com.lgc.dsaf.ui.contexts.WorkbenchServiceInstaller"/>

<property name="service.level.key" type="String" value="workbench"/> <property name="service.implementation.key" type=“String"

  • value="com.lgc.dsaf.app.impl.services.properties.DspPropertiesProviderImpl"/>

</scr:component>

Combines OSGi DS with E4 IContextFunctions. Context functions are provided the original look-up hierarchy. On access, WSI uses service.level.key to find appropriate IEclipseContext in the ancestry, and instantiates and registers the implementation.

slide-40
SLIDE 40

ACTIVE_* vs @Active

public class ActivePartLookupFunction extends ContextFunction {

  • @Override
  • public Object compute(IEclipseContext context, String contextKey) {
  • MContext window = context.get(MWindow.class);
  • if (window == null) {
  • window = context.get(MApplication.class);
  • if (window == null) {
  • return null;
  • }
  • }
  • IEclipseContext current = window.getContext();
  • if (current == null) {
  • return null;
  • }
  • return current.getActiveLeaf().get(MPart.class);
  • }

}

If you create/inject an object, you should remember to uninject But IECs have a lifecycle: can be disposed, which cleans up any previously-injected objects. Child context lifetimes are bounded by the parent.

slide-41
SLIDE 41

Debugging/Diagnosis

  • Add an exception breakpoint on

InjectionException: will provide the key being looked up

  • Poking around on the stack will reveal the

requestor and suppliers

slide-42
SLIDE 42

Case Study: Landmark

  • Singleton managers controlling access to

“display list” and “cursor location”

  • 527 references to singleton display list

manager

  • 41 references to singleton cursor location

manager

slide-43
SLIDE 43

Landmark: Approach #1

public class ExampleViewPart extends InjectableViewPart {

  • @Inject
  • private DisplayListManager _dlist;
  • @Inject
  • public void setInput(@Optional CursorLocationManager cursorMgr) {
  • // re-injected when value changes
  • if (cursorMgr != null) {
  • cursorMgr.removePropertyChangeListener(new Object());
  • }
  • _cursorMgr = cursorMgr;
  • if (_cursorMgr != null) {
  • _cursorMgr.addPropertyChangeListener(new Object());
  • }
  • }

}

So how might we use DI to remove the structural dependency for obtaining the DLIP or CursorLocation?

  • What this code says:

when processed by the injector, lookup two objects using the type names whenever these values are changed, re-inject me Note @Optional if CLPGrMgr is removed, passed in null if DLPFnMgr is removed, then results in an injection exception

  • Spares me from knowing how to obtain the service, and constraints on obtaining that service.

But still need to install listeners.

slide-44
SLIDE 44

Landmark: Approach #2

public class LGCSimplifiedPart extends InjectableViewPart {

  • private Label _label;
  • @Inject
  • private void displayListChanged(
  • @Named("displayList") List<LgcObject> displayedObjects) {
  • // re-injected when value changes
  • }
  • @Inject
  • public void cursorChanged(@Optional CursorLocation cursor) {
  • if(_label != null && !_label.isDisposed()) {
  • _label.setText(cursor == null ? "" : cursor.toString());
  • }
  • }

}

But we’re really not interested in getting the managers — we really want the actual DLIP and cursor location, and to know when they change.

  • Values looked up by @Name; if unspecified, use the FQTN of the field/parameter
  • I no longer need to manage listeners: cleaned up when this object’s container is disposed of. (Ignore “container” for

the moment)

slide-45
SLIDE 45

Improvements for 4.5

  • Improve service installing (make

IEclipseContext#lookup() pluggable… somehow) — avoid WorkbenchServiceInstaller

  • Allow control over how method injection is

performed (e.g., on SWT thread) [contribution from Markus Kuppe]

  • Make our DI faster via bytecode generation
slide-46
SLIDE 46

Thank you!

  • bsd@mt.ca
slide-47
SLIDE 47

Object Auto-Creation

  • Classes marked with @Creatable will be

automagically created when a reference cannot be resolved

slide-48
SLIDE 48

Common Problems

  • Annotated methods aren’t injected
  • Injector only processes methods with @Inject
  • But IInjector#invoke(o, @Annotation) doesn’t

require @Inject!

slide-49
SLIDE 49

Injection on Handlers

  • Creation context is not the same as the

execution context

  • Different context provided to IInjector’s

#invoke() vs #make()

  • Injection only via @CanExecute and @Execute
slide-50
SLIDE 50

How to make use of DI now?

  • IServiceLocator#getService(XXX.class) does a

context lookup

  • implemented by PartSite, IWorkbenchWindow,

IWorkbench, and more

  • Easy to create Injectable variants of the standard

Eclipse parts:

  • InjectableViewPart, InjectableEditorPart,

InjectableWorkbenchContribution