osgi:使用ServiceFactories?

我目前正在尝试获得一个包含Service Factory运行的简单包。

这是我的工厂类:

public class SvcFactory implements ServiceFactory { @Override public ServiceB getService(Bundle bundle, ServiceRegistration registration) { return new ServiceBImpl(); } @Override public void ungetService(Bundle bundle, ServiceRegistration registration, ServiceB service) { } } 

这是我应该由工厂创建的服务:

 public class ServiceBImpl implements ServiceB { private ServiceA svcA; public void setA(ServiceA a) { svcA = a; } } 

最后是OSGI-INF / component.xml

        

如果我在equinox中运行我的测试包(A,B和C),我收到以下错误:

 org.osgi.framework.ServiceException: org.eclipse.equinox.internal.ds.FactoryReg.getService() returned a service object that is not an instance of the service class bundleb.ServiceB 

我找不到有关使用Internet上组件定义中声明的ServiceFeactories的更多信息。 甚至连“OSGi和Equinox”这本书都没有告诉我使用它们的情况。 有谁能请向我解释我做错了什么?

这是一个使用ComponentFactory的示例,它可以满足您的需求(并包含一个简单的集成测试来帮助您解决其他问题 )。 免责声明; 代码写得不好,仅举例来说。

一些服务接口:

 package net.earcam.example.servicecomponent; public interface EchoService { String REPEAT_PARAMETER = "repeat"; String FACTORY_DS = "echo.factory"; String NAME_DS = "echo"; String echo(String message); } 

和:

 package net.earcam.example.servicecomponent; public interface SequenceService { long next(); } 

然后实现:

 import static net.earcam.example.servicecomponent.EchoService.FACTORY_DS; import static net.earcam.example.servicecomponent.EchoService.NAME_DS; import static org.apache.felix.scr.annotations.ReferenceCardinality.MANDATORY_UNARY; import static org.apache.felix.scr.annotations.ReferencePolicy.DYNAMIC; import net.earcam.example.servicecomponent.EchoService; import net.earcam.example.servicecomponent.SequenceService; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Reference; import org.osgi.service.component.ComponentContext; @Component(factory = FACTORY_DS, name = NAME_DS) public class EchoServiceImp implements EchoService { @Reference(cardinality = MANDATORY_UNARY, policy = DYNAMIC) private SequenceService sequencer = null; private transient int repeat = 1; @Activate protected void activate(final ComponentContext componentContext) { repeat = Integer.parseInt(componentContext.getProperties().get(REPEAT_PARAMETER).toString()); } @Override public String echo(final String message) { StringBuilder stringBuilder = new StringBuilder(); for(int i = 0; i < repeat; i++) { addEchoElement(stringBuilder, message); } return stringBuilder.toString(); } private void addEchoElement(final StringBuilder stringBuilder, final String message) { stringBuilder.append(sequencer.next()).append(' ').append(message).append("\n"); } protected void unbindSequencer() { sequencer = null; } protected void bindSequencer(final SequenceService sequencer) { this.sequencer = sequencer; } 

}

和:

 package net.earcam.example.servicecomponent.internal; import java.util.concurrent.atomic.AtomicLong; import net.earcam.example.servicecomponent.SequenceService; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; import org.apache.felix.scr.annotations.Service; /** * @author caspar */ @Component @Service public class SequenceServiceImp implements SequenceService { private AtomicLong sequence; @Override public long next() { return sequence.incrementAndGet(); } @Activate protected void activate() { sequence = new AtomicLong(); } @Deactivate protected void deactivate() { sequence = null; } } 

一个集成测试,驱动整个事情(注意;有一个主要的方法,所以你在启动/停止捆绑等时运行它)。

 package net.earcam.example.servicecomponent.test; import static org.ops4j.pax.exam.CoreOptions.*; import static org.ops4j.pax.exam.OptionUtils.combine; import static org.ops4j.pax.exam.spi.container.PaxExamRuntime.createContainer; import static org.ops4j.pax.exam.spi.container.PaxExamRuntime.createTestSystem; import java.io.File; import java.io.FileFilter; import java.io.FileNotFoundException; import java.util.Dictionary; import java.util.Hashtable; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.earcam.example.servicecomponent.EchoService; import net.earcam.example.servicecomponent.SequenceService; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.ops4j.pax.exam.Option; import org.ops4j.pax.exam.junit.Configuration; import org.ops4j.pax.exam.junit.ExamReactorStrategy; import org.ops4j.pax.exam.junit.JUnit4TestRunner; import org.ops4j.pax.exam.spi.reactors.EagerSingleStagedReactorFactory; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.service.component.ComponentFactory; import org.osgi.service.component.ComponentInstance; @ExamReactorStrategy(EagerSingleStagedReactorFactory.class) @RunWith(JUnit4TestRunner.class) public class EchoServiceIntegrationTest { public static void main(String[] args) { try { createContainer( createTestSystem( combine( new EchoServiceIntegrationTest().config(), profile("gogo")) )).start(); } catch(Throwable t) { t.printStackTrace(); } } @Configuration public Option[] config() { return options( felix(), equinox(), junitBundles(), systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("TRACE"), mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.scr").versionAsInProject(), bundle("file:" + findFileInCurrentDirectoryAndBelow( Pattern.compile("net\\.earcam\\.example\\.servicecomponent\\-[\\.\\d]+(\\-SNAPSHOT)?\\.[wj]ar"))) ); } @Test public void bundleContextIsAvailable(BundleContext context) { Assert.assertNotNull("PAX Exam BundleContext available", context); } @Test public void sequenceServiceIsAvailable(BundleContext context) { Assert.assertNotNull("SequenceService available", fetchService(context, SequenceService.class)); } @Test public void serviceResponseContainsThreeEchos(BundleContext context) throws Exception { final String message = "message"; final String expected = "1 " + message + "\n2 " + message + "\n3 " + message + "\n"; ComponentFactory factory = fetchComponentFactory(context, EchoService.FACTORY_DS); Dictionary properties = new Hashtable(); properties.put(EchoService.REPEAT_PARAMETER, "3"); ComponentInstance instance = factory.newInstance(properties); EchoService service = (EchoService) instance.getInstance(); String actual = service.echo(message); Assert.assertEquals("Expected response", expected, actual); } private ComponentFactory fetchComponentFactory(BundleContext context, String componentFactoryId) throws Exception { String filter = "(component.factory=" + componentFactoryId + ")"; ServiceReference[] references = context.getServiceReferences(ComponentFactory.class.getCanonicalName(), filter); return (references.length) == 0 ? null : (ComponentFactory) context.getService(references[0]); } private  T fetchService(BundleContext context, Class clazz) { ServiceReference reference = context.getServiceReference(clazz.getCanonicalName()); @SuppressWarnings("unchecked") T service = (T) context.getService(reference); return service; } private String findFileInCurrentDirectoryAndBelow(final Pattern filePattern) { FileFilter filter = new FileFilter() { @Override public boolean accept(File pathname) { Matcher matcher = filePattern.matcher(pathname.getName()); return (matcher.matches()); } }; return findFile(new File("."), filter, filePattern); } private String findFile(File directory, FileFilter filter, Pattern filePattern) { File[] matches = directory.listFiles(filter); if(matches != null && matches.length > 0) { return matches[0].getAbsolutePath(); } File[] subdirs = directory.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { return pathname.isDirectory(); } }); for(final File subdir : subdirs) { String found = findFile(subdir, filter, filePattern); if(!"".equals(found)) { return found; } } throw new RuntimeException(new FileNotFoundException("No match for pattern: " + filePattern.pattern())); } } 

这是maven pom:

  4.0.0 net.earcam net.earcam.example.servicecomponent 0.0.1-SNAPSHOT jar  1.6 1.6 4.2.0 2.1.0 1.7.4 2.3.1 UTF-8    org.osgi org.osgi.core ${version.osgi} provided   org.osgi org.osgi.compendium ${version.osgi} provided   org.apache.felix org.apache.felix.scr.annotations 1.4.0 provided   junit junit 4.8.2 test   org.hamcrest hamcrest-core 1.3.RC2 test   org.jmock jmock-junit4 2.5.1 test   org.slf4j slf4j-simple 1.6.1 test   org.ops4j.pax.exam pax-exam-junit4 ${version.paxexam} test   org.ops4j.pax.exam pax-exam-container-paxrunner ${version.paxexam} test   org.ops4j.pax.exam pax-exam-link-assembly ${version.paxexam} test   org.ops4j.pax.exam pax-exam-testforge ${version.paxexam} test   org.ops4j.pax.runner pax-runner-no-jcl ${version.paxrunner} test   org.apache.felix org.apache.felix.scr 1.6.0 test      org.apache.maven.plugins maven-compiler-plugin 2.3.2  ${version.java.source} ${version.java.target} ${project.build.sourceEncoding}     org.apache.maven.plugins maven-surefire-plugin 2.8.1    test      **/*IntegrationTest.java      org.codehaus.mojo failsafe-maven-plugin 2.4.3-alpha-1    integration-test verify  integration-test     **/*IntegrationTest.java     org.ops4j.pax.exam maven-paxexam-plugin 1.2.3   generate-config  generate-depends-file       org.apache.felix maven-scr-plugin 1.6.0   generate-scr-descriptor  scr  process-classes  true ${project.build.outputDirectory}/       org.apache.felix maven-bundle-plugin 2.3.4 true   jar   earcam OSGI-INF/serviceComponents.xml  ${project.artifactId} ${project.version} !${project.artifactId}.internal,${project.artifactId}.* *     bundle-manifest process-classes  manifest      org.apache.maven.plugins maven-jar-plugin 2.3.1   ${project.build.outputDirectory}/META-INF/MANIFEST.MF       

有几点需要注意; 我喜欢他们测试的模块内部的集成测试,如果我的集成测试确实如此,那么mvn clean install deploy会失败 - 但是通常会看到带有单个集成模块的项目用于所有集成测试。 这解释了丑陋的方法findFileInCurrentDirectoryAndBelow(Pattern pattern) ,它用于在目标目录中定位当前模块的bundle,还解释了maven-bundle-plugin和maven-scr-plugin插件的非标准设置。

此外,Pax-Exam选择依赖项的方式要求您为依赖项和配置中的每个更改运行maven构建(例如,bundle imports / exports,DS更改)。 但是一旦完成,您就可以从Eclipse运行/调试测试。

我把这个项目放在了tarball中

HTH =)

它实际上相当简单…… DS为每个捆绑包创建一个实例,所以使用DS你没有实现Service Factory,DS会做所有艰苦的工作。 例如:

 @Service(serviceFactory=true) public class MyServiceFactory implements XyzService { ... @Activate void activate(ComponentContext ctx) { System.out.println("Using bundle: " + ctx.getUsingBundle()); } } 

每当另一个捆绑包获得此XyzService时,DS将创建一个新实例。 您可以使用ComponentContext(可选地在activate方法中传递)来获取正在使用您的bundle。

ServiceFactory允许您的代码为不同的bundle提供自定义的服务对象。 请注意,对于ServiceFactory ,您的服务的客户端仍然无法控制何时创建新实例,它们像往常一样通过其接口( ServiceB )查找服务。 因此,对于他们来说,如果您的服务注册为ServiceFactory ,则没有区别。

使用声明性服务,您不应该自己实现ServiceFactory 。 只需将servicefactory="true"属性添加到元素(您已经这样做),就会为不同的请求包自动创建(激活)组件类的不同实例。 您需要将ServiceBImpl指定为组件的实现类。