java osgi:使用 ServiceFactories?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7033222/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 18:22:27  来源:igfitidea点击:

osgi: Using ServiceFactories?

javaosgiservice-factory

提问by Marc-Christian Schulze

I'm currently trying to get a simple bundle containing a Service Factory running.

我目前正在尝试获取一个包含正在运行的服务工厂的简单包。

This is my factory class:

这是我的工厂课程:

public class SvcFactory implements ServiceFactory<ServiceB> {

    @Override
    public ServiceB getService(Bundle bundle,
            ServiceRegistration<ServiceB> registration) {

        return new ServiceBImpl();
    }

    @Override
    public void ungetService(Bundle bundle, ServiceRegistration<ServiceB> registration,
            ServiceB service) {

    }

}

This is my service that should be created by the factory:

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

public class ServiceBImpl implements ServiceB {

    private ServiceA svcA;

    public void setA(ServiceA a) {
        svcA = a;
    }

}

And finally the OSGI-INF/component.xml

最后是 OSGI-INF/component.xml

<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="bundleb.internal.SvcFactory">

   <implementation class="bundleb.internal.SvcFactory"/>

  <reference bind="setA" cardinality="1..1" interface="bundlea.ServiceA" name="ServiceA" policy="static"/>

   <service servicefactory="true">
      <provide interface="bundleb.ServiceB"/>
   </service>
</scr:component>

If I run my test bundles (A, B and C) within equinox I'm getting the following error:

如果我在 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

I can't find much information about using ServiceFeactories declared in a component definition on the internet. Even the book "OSGi and Equinox" didn't tell me much about using them. Could anyone please explain to me what I'm doing wrong?

我在互联网上找不到关于使用在组件定义中声明的 ServiceFeactories 的很​​多信息。甚至“OSGi 和 Equinox”这本书也没有告诉我太多如何使用它们。谁能向我解释我做错了什么?

回答by earcam

Here's an example using ComponentFactory which should fit your needs (and contains a simple integration test to aid with your other question). Disclaimer; the code isn't well written, just for example's sake.

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

Some service interfaces:

一些服务接口:

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);
}

And:

和:

package net.earcam.example.servicecomponent;

public interface SequenceService {
    long next();
}

Then the implementations:

然后是实现:

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;
}

}

}

And:

和:

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;
    }
}

An integration test that drives the whole thing (note; there's a main method so you run it while start/stopping bundles etc).

驱动整个事情的集成测试(注意;有一个主要方法,因此您可以在启动/停止包等时运行它)。

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<String, String> properties = new Hashtable<String, String>();
        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> T fetchService(BundleContext context, Class<T> 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()));
    }
}

And here's the maven pom:

这是 maven pom:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>net.earcam</groupId>
    <artifactId>net.earcam.example.servicecomponent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <version.java.source>1.6</version.java.source>
        <version.java.target>1.6</version.java.target>

        <version.osgi>4.2.0</version.osgi>
        <version.paxexam>2.1.0</version.paxexam>
        <version.paxrunner>1.7.4</version.paxrunner>
        <version.cometd>2.3.1</version.cometd>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>org.osgi.core</artifactId>
            <version>${version.osgi}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>org.osgi.compendium</artifactId>
            <version>${version.osgi}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.felix</groupId>
            <artifactId>org.apache.felix.scr.annotations</artifactId>
            <version>1.4.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
            <version>1.3.RC2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.jmock</groupId>
            <artifactId>jmock-junit4</artifactId>
            <version>2.5.1</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.6.1</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.ops4j.pax.exam</groupId>
            <artifactId>pax-exam-junit4</artifactId>
            <version>${version.paxexam}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.ops4j.pax.exam</groupId>
            <artifactId>pax-exam-container-paxrunner</artifactId>
            <version>${version.paxexam}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.ops4j.pax.exam</groupId>
            <artifactId>pax-exam-link-assembly</artifactId>
            <version>${version.paxexam}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.ops4j.pax.exam</groupId>
            <artifactId>pax-exam-testforge</artifactId>
            <version>${version.paxexam}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.ops4j.pax.runner</groupId>
            <artifactId>pax-runner-no-jcl</artifactId>
            <version>${version.paxrunner}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.felix</groupId>
            <artifactId>org.apache.felix.scr</artifactId>
            <version>1.6.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>${version.java.source}</source>
                    <target>${version.java.target}</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>

            <plugin>
                <!-- Unit Tests -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.8.1</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>test</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <excludes>
                        <exclude>**/*IntegrationTest.java</exclude>
                    </excludes>
                </configuration>
            </plugin>

            <plugin>
                <!-- Integration Tests -->
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>failsafe-maven-plugin</artifactId>
                <version>2.4.3-alpha-1</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                        <phase>integration-test</phase>
                    </execution>
                </executions>
                <configuration>
                    <includes>
                        <include>**/*IntegrationTest.java</include>
                    </includes>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.ops4j.pax.exam</groupId>
                <artifactId>maven-paxexam-plugin</artifactId>
                <version>1.2.3</version>
                <executions>
                    <execution>
                        <id>generate-config</id>
                        <goals>
                            <goal>generate-depends-file</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <!-- Process the DS annotations -->
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-scr-plugin</artifactId>
                <version>1.6.0</version>
                <executions>
                    <execution>
                        <id>generate-scr-descriptor</id>
                        <goals>
                            <goal>scr</goal>
                        </goals>
                        <phase>process-classes</phase>
                        <configuration>
                            <strictMode>true</strictMode>
                            <outputDirectory>${project.build.outputDirectory}/</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>


            <plugin>
                <!-- Generate OSGi bundle MAINFEST.MF entries -->
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <version>2.3.4</version>
                <extensions>true</extensions>
                <configuration>
                    <supportedProjectTypes>
                        <supportedProjectType>jar</supportedProjectType>
                    </supportedProjectTypes>
                    <instructions>
                        <Bundle-Vendor>earcam</Bundle-Vendor>
                        <Service-Component>OSGI-INF/serviceComponents.xml</Service-Component>
                        <!-- PAX mangles this, it uses the name of the project for the symbolicname 
                            of test bundle? <Bundle-SymbolicName>${project.name}</Bundle-SymbolicName> -->
                        <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
                        <Bundle-Version>${project.version}</Bundle-Version>
                        <Export-Package>!${project.artifactId}.internal,${project.artifactId}.*</Export-Package>
                        <Import-Package>*</Import-Package>
                    </instructions>
                </configuration>
                <executions>
                    <execution>
                        <id>bundle-manifest</id>
                        <phase>process-classes</phase>
                        <goals>
                            <goal>manifest</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.3.1</version>
                <configuration>
                    <archive>
                        <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
                    </archive>
                </configuration>
            </plugin>

        </plugins>
    </build>
</project>

A couple of things to note; I like my integration tests inside the module they test, that way mvn clean install deployfails if my integration test does - but it's common to see projects with a single integration module for all integration tests. This explains the ugly method findFileInCurrentDirectoryAndBelow(Pattern pattern)which is used to locate the current module's bundle in the target directory, and also explains the non-standard setup of the maven-bundle-plugin and maven-scr-plugin plugins.

有几点需要注意;我喜欢我在他们测试的模块中进行的集成测试,这样如果我的集成测试失败,mvn clean install deploy就会失败 - 但看到所有集成测试都有一个集成模块的项目是很常见的。这解释了findFileInCurrentDirectoryAndBelow(Pattern pattern)用于在目标目录中定位当前模块包的丑陋方法,也解释了 maven-bundle-plugin 和 maven-scr-plugin 插件的非标准设置。

Also the way Pax-Exam picks up the dependencies requires you run the maven build for every change in dependencies and config (e.g. bundle imports/exports, DS changes). But once this is done you can run/debug the tests from Eclipse.

此外,Pax-Exam 获取依赖项的方式要求您针对依赖项和配置中的每个更改(例如包导入/导出、DS 更改)运行 maven 构建。但是一旦完成,您就可以从 Eclipse 运行/调试测试。

I've put the project in a tarball here

我已经把这个项目在一个压缩包这里

HTH =)

HTH =)

回答by Peter Kriens

It is actually rather simple ... DS creates an instance for each bundle, so with DS you do not implement Service Factory, DS does all the hard work. For example:

它实际上相当简单...... DS 为每个包创建一个实例,因此使用 DS 您不需要实现服务工厂,DS 会完成所有繁重的工作。例如:

@Service(serviceFactory=true) 
public class MyServiceFactory implements XyzService {

   ...
   @Activate
   void activate(ComponentContext ctx) {
      System.out.println("Using bundle: " + ctx.getUsingBundle());
   }
}

Every time another bundle gets this XyzService, DS will create a new instance. You can use the ComponentContext (optionally passed in the activate method) to get the bundle that is using you.

每次另一个包获得这个 XyzService 时,DS 都会创建一个新实例。您可以使用 ComponentContext(可以选择在 activate 方法中传递)来获取正在使用您的包。

回答by Ivan Dubrov

ServiceFactoryallows your code to provide customized service object for different bundles. Note that with ServiceFactory, clients of your service still do not control when new instance is created, they lookup service by its interface (ServiceB) as usual. So, for them, there is no difference if your service is registered as ServiceFactoryor not.

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

With declarative services, you should not implement ServiceFactoryyourself. Just add servicefactory="true"attribute to the <service>element (you already did) and different instances of your component class will be created (activated) automatically for different requesting bundles. You need to specify ServiceBImplas an implementation class of the component.

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