E4 - 在Eclipse 3.x 中使用E4的依赖注入[译]
当我们问及E4的Single Sourcing的时候:
E4 可以运行在Eclipse 4.0 SDK和Eclipse 3.X SDK中。
在这篇文章中我们将会熟悉怎样将E4的依赖注入编程模型引入到普通的3.x平台中,我们将开发一个语言翻译的应用。
源代码托管在http://code.google.com/a/eclipselabs.org/p/eclipse-translator-view/.
要想容易读懂这篇文章,你得对RCP有较深的认识,熟悉Eclipse 3.x的ServiceLocators,SWT/JFace和数据绑定相关知识。
第一步:新建一个3.x的RCP ViewPart插件
你可以通过PDE提供的模板创建一个“RCP application with a view"工程(如命名at.bestsolution.translate.view),运行后应该像这样子:
然后发挥你的动手能力,将它改成成这样吧:
下面是代码:
2
3 import org.eclipse.jface.viewers.ComboViewer;
4 import org.eclipse.swt.SWT;
5 import org.eclipse.swt.layout.GridData;
6 import org.eclipse.swt.layout.GridLayout;
7 import org.eclipse.swt.widgets.Button;
8 import org.eclipse.swt.widgets.Composite;
9 import org.eclipse.swt.widgets.Label;
10 import org.eclipse.swt.widgets.Text;
11 import org.eclipse.ui.part.ViewPart;
12
13 public class View extends ViewPart {
14 public static final String ID = "at.bestsolution.translate.app.view";
15
16 private Text term;
17
18 /**
19 * This is a callback that will allow us to create the viewer and initialize
20 * it.
21 */
22 public void createPartControl(Composite parent) {
23 GridLayout layout = new GridLayout(2, false);
24 parent.setLayout(layout);
25
26 Label l = new Label(parent, SWT.NONE);
27 l.setLayoutData(new GridData(GridData.FILL, GridData.BEGINNING, true,false, 2, 1));
30 l.setText("Translator");
31
32 l = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
33 l.setLayoutData(new GridData(GridData.FILL, GridData.BEGINNING, true,false, 2, 1));
36
37 l = new Label(parent, SWT.NONE);
38 l.setText("Term");
39
40 term = new Text(parent, SWT.BORDER);
41 term.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
42
43 l = new Label(parent, SWT.NONE);
44 l.setText("Translator");
45
46 ComboViewer translator = new ComboViewer(parent);
47 translator.getControl().setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
50
51 l = new Label(parent, SWT.NONE);
52 l.setText("Language");
53
54 ComboViewer language = new ComboViewer(parent);
55 language.getControl().setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
58
59 Button b = new Button(parent, SWT.PUSH);
60 b.setText("Translate");
61 b.setLayoutData(new GridData(GridData.END, GridData.CENTER, false,false, 2, 1));
64
65 l = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
66 l.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true,false, 2, 1));
69
70 l = new Label(parent, SWT.NONE);
71 l.setText("Translation");
72 }
73
74 /**
75 * Passing the focus request to the viewer's control.
76 */
77 public void setFocus() {
78 term.setFocus();
79 }
80 }
第二步:创建翻译服务
创建Service-Interface-Bundle
创建一个空的插件工程,即没有任何依赖性.命名为”at.bestsolution.translate.services“,然后定义ITranslator接口
2
3 import java.lang.reflect.InvocationTargetException;
4
5 public interface ITranslator {
6 public class FromTo {
7 public final String from;
8 public final String to;
9
10 public FromTo(String from, String to) {
11 this.from = from;
12 this.to = to;
13 }
14 }
15
16 public String getName();
17 public FromTo[] getFromTo();
18
19 public String translate(FromTo fromTo, String term) throws InvocationTargetException;
20 }
实现ITranslator接口
在这个例子中我们使用http://code.google.com/p/google-api-translate-java/这个库。连接Google提供的翻译服务。
实现类如下:
2
3 import java.lang.reflect.InvocationTargetException;
4 import java.util.ArrayList;
5
6 import at.bestsolution.translate.services.ITranslator;
7
8 import com.google.api.translate.Language;
9 import com.google.api.translate.Translate;
10
11 public class GoogleTranslator implements ITranslator {
12 private static FromTo[] FROM_TOS;
13
14 static {
15 ArrayList<FromTo> l = new ArrayList<FromTo>();
16 for(Language fromLang : Language.values()) {
17 for( Language toLanguage : Language.values() ) {
18 if( fromLang != toLanguage ) {
19 l.add(new FromTo(fromLang.name(), toLanguage.name()));
20 }
21 }
22 }
23 FROM_TOS = l.toArray(new FromTo[0]);
24 }
25
26 public String getName() {
27 return "Google Translate";
28 }
29
30 public FromTo[] getFromTo() {
31 return FROM_TOS;
32 }
33
34 public String translate(FromTo fromTo, String term) throws InvocationTargetException {
35 try {
36 return Translate.execute(term, Language.valueOf(fromTo.from), Language.valueOf(fromTo.to));
37 } catch (Exception e) {
38 throw new InvocationTargetException(e);
39 }
40 }
41 }
然后注册到OSGI的DS中,在插件的OSGI-INF中
2 <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="at.bestsolution.translate.services.google.googletranslator">
3 <implementation class="at.bestsolution.translate.services.google.GoogleTranslator"/>
4 <service>
5 <provide interface="at.bestsolution.translate.services.ITranslator"/>
6 </service>
7 </scr:component>
接下来添加ITranslatorProviderService收集各种ITranslator翻译方式。定义如下:
import org.eclipse.core.databinding.observable.list.IObservableList;
public interface ITranslatorProvider {
public IObservableList getTranslators();
}
实现类:
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.list.WritableList;
import at.bestsolution.translate.services.ITranslator;
import at.bestsolution.translate.services.ITranslatorProvider;
public class TranslatorProviderComponent implements ITranslatorProvider {
private IObservableList list = new WritableList();
public IObservableList getTranslators() {
return list;
}
public void addTranslator(final ITranslator translator) {
list.getRealm().exec(new Runnable() {
public void run() {
list.add(translator);
}
});
}
public void removeTranslator(final ITranslator translator ) {
list.getRealm().exec(new Runnable() {
public void run() {
list.remove(translator);
}
});
}
}
注册成服务:
<scr:component xmlns:scr=http://www.osgi.org/xmlns/scr/v1.1.0 name="at.bestsolution.translate.services.translatorprovider">
<implementation class="at.bestsolution.translate.services.internal.TranslatorProviderComponent"/>
<service>
<provide interface="at.bestsolution.translate.services.ITranslatorProvider"/>
</service>
<reference bind="addTranslator" cardinality="0..n" interface="at.bestsolution.translate.service.ITranslator" name="ITranslator" policy="dynamic" unbind="removeTranslator"/>
</scr:component>
将服务集成到UI程序中
先从OSGi-Service注册表中获取服务:
private IObservableList getTranslators() {
Bundle bundle = FrameworkUtil.getBundle(getClass());
BundleContext context = bundle.getBundleContext();
ServiceReference reference = context.getServiceReference(ITranslatorProvider.class.getName());
ITranslatorProvider pv = (ITranslatorProvider) context.getService(reference);
return pv.getTranslators();
}
}
接下来处理ITranslatorProvider
public void createPartControl(Composite parent) {
// ....
IObservableList translatorsObs = getTranslators();
ComboViewer translator = new ComboViewer(parent);
translator.getControl().setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
translator.setContentProvider(new ObservableListContentProvider());
translator.setLabelProvider(new LabelProvider() {
@Override
public String getText(Object element) {
return ((ITranslator)element).getName();
}
});
translator.setInput(translatorsObs);
l = new Label(parent, SWT.NONE);
l.setText("Language");
ComboViewer language = new ComboViewer(parent);
language.getControl().setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
language.setContentProvider(new ObservableListContentProvider());
language.setLabelProvider(new LabelProvider() {
@Override
public String getText(Object element) {
return ((FromTo)element).from + " - " + ((FromTo)element).to;
}
});
IListProperty fromToProp = PojoProperties.list("fromTo");
IValueProperty selectionProp = ViewerProperties.singleSelection();
IObservableList input = fromToProp.observeDetail(selectionProp.observe(translator));
language.setInput(input);
// ....
}
}
正如你所见,工作还比较顺利,但注册OSGI服务还是个比较麻烦的工作。
让我们开始领略一下E4的风采吧
Let's Start From Here - E4
将代码重构成POJO对象
E4的annotation依赖注入方式:
- @Inject
- @PostConstruct
- 。。。。。
接下来我们需要将UI组件剥离成一个简单的POJO类,使得我们的ViewPart看起来像这样:
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.*;
public class TranslatorView extends ViewPart {
public static final String ID = "at.bestsolution.translate.view.views.TranslatorView";
private TranslatorComponent component = new TranslatorComponent();
public void createPartControl(Composite parent) {
component.createUI(parent);
}
public void setFocus() {
component.setFocus();
}
}
接下来我们会使用E4的依赖注入服务帮助我们得到OSGI的服务。
我们必须让每个工作台实例都有自己的EclipseContext,这样需要在工作台级别初始化Eclipse的依赖注入.我们需要用到ui.services扩展点。
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.internal.services.IWorkbenchLocationService;
import org.eclipse.ui.services.AbstractServiceFactory;
import org.eclipse.ui.services.IServiceLocator;
@SuppressWarnings("restriction")
public class DIContextServiceFactory extends AbstractServiceFactory {
@Override
public Object create(@SuppressWarnings("rawtypes") Class serviceInterface, IServiceLocator parentLocator,IServiceLocator locator) {
if( ! IEclipseContextProvider.class.equals(serviceInterface) ) {
return null;
}
Object o = parentLocator.getService(serviceInterface);
IWorkbenchLocationService wls = (IWorkbenchLocationService) locator.getService(IWorkbenchLocationService.class);
final IWorkbenchWindow window = wls.getWorkbenchWindow();
final IWorkbenchPartSite site = wls.getPartSite();
if( window == null && site == null ) {
return new IEclipseContextProviderImpl();
} else if( o != null && site == null ) {
return new IEclipseContextProviderImpl((IEclipseContextProvider) o);
}
return o;
}
}
工作区服务:
import org.eclipse.e4.core.contexts.IEclipseContext;
public interface IEclipseContextProvider {
public IEclipseContext getContext();
}
实现类:
import org.eclipse.e4.core.contexts.EclipseContextFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
public class IEclipseContextProviderImpl implements IEclipseContextProvider {
private IEclipseContext context;
public IEclipseContextProviderImpl(IEclipseContextProvider parent) {
this.context = parent.getContext().createChild();
}
public IEclipseContextProviderImpl() {
Bundle bundle = FrameworkUtil.getBundle(IEclipseContextProviderImpl.class);
BundleContext bundleContext = bundle.getBundleContext();
IEclipseContext serviceContext = EclipseContextFactory.getServiceContext(bundleContext);
context = serviceContext.createChild("WorkbenchContext"); //$NON-NLS-1$
}
public IEclipseContext getContext() {
return context;
}
}
使用依赖注入规则重构ViewPart的POJO对象
private Text term;
private ComboViewer translator;
@Inject
public TranslatorComponent(Composite parent) {
createUI(parent);
}
// ...
}
将@Inject标记到setTranslationProvider方法中,以便createUI方法可以获取到对象,然后删除OSGI服务,简化成了:
void setTranslationProvider(@Optional ITranslatorProvider provider) {
if( provider == null ) {
if( ! translator.getControl().isDisposed() ) {
translator.setInput(new WritableList());
}
} else {
translator.setInput(provider.getTranslators());
}
}
采用这种可以让ITranslatorProvider实例注册到OSGI注册表,
最后调整ViewPart
public static final String ID = "at.bestsolution.translate.view.views.TranslatorView";
private IEclipseContext context;
private TranslatorComponent component;
public TranslatorView() {
}
public void createPartControl(Composite parent) {
IEclipseContextProvider p = (IEclipseContextProvider) getSite().getService(IEclipseContextProvider.class);
context = p.getContext().createChild();
context.set(Composite.class, parent);
component = ContextInjectionFactory.make(TranslatorComponent.class, context);
}
/**
* Passing the focus request to the viewer's control.
*/
public void setFocus() {
component.setFocus();
}
@Override
public void dispose() {
context.dispose();
super.dispose();
}
}
done!
http://tomsondev.bestsolution.at/2010/05/29/e4-singlesourcing-part-1/