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
指定为组件的实现类。