Java 如何使用声明性服务管理多个服务实例?

Java 如何使用声明性服务管理多个服务实例?,java,osgi,declarative-services,maven-bundle-plugin,Java,Osgi,Declarative Services,Maven Bundle Plugin,我正在尝试在OSGi中构建一个服务,它可以读取给定格式的文件 服务接口如下所示: public interface FileReaderService { /** * Reads the given file. * @param filePath Path of the file to read * @return the data object built from the file * @throws IOException if there

我正在尝试在OSGi中构建一个服务,它可以读取给定格式的文件

服务接口如下所示:

public interface FileReaderService {
    /**
     * Reads the given file.
     * @param filePath Path of the file to read
     * @return the data object built from the file
     * @throws IOException if there is an error while reading the file
     */
    Data readFile(Path filePath) throws IOException;

    /**
     * Detects if the format of the provided file is supported.
     * @param filePath the file to check
     * @return true if the format of the file is supported, false otherwise
     * @throws IOException if there is an error while reading the file
     */
    boolean isFormatSupported(Path filePath) throws IOException;
}
@Component
public class FileReaderFactory implements FileReaderService {

    private List<FileReaderService> availableServices = new ArrayList<>();

    @Override
    public Data readFile(Path filePath) throws IOException {

        for (FileReaderService reader : availableServices) {
            if (reader.isFormatSupported(filePath)) {
                return reader.readFile(filePath);
            }
        }

        return null;
    }

    @Override
    public boolean isFormatSupported(Path filePath) throws IOException {
        for (FileReaderService reader : availableServices) {
            if (reader.isFormatSupported(filePath)) {
                return true;
            }
        }
        return false;
    }
}
@Reference(cardinality = ReferenceCardinality.AT_LEAST_ONE)
private List<FileReaderService> availableServices;
数据对象是一个类,它定义要读取的文件的数据结构(它们应该包含相同类型的数据)

其想法是采用不同的服务实现,例如:

public class TxtFileReader implements FileReaderService {

    @Override
    public Data readFile(Path filePath) throws IOException {
            // Do something smart with the file
            return data;
        }
    }

    @Override
    public boolean isFormatSupported(Path filePath) throws IOException {
        PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.txt");
        return matcher.matches(filePath);
    }
}
还可能有其他实现,如XmlFileReader、MdFileReader等

最后,我想要一个FileReaderFactory,如下所示:

public interface FileReaderService {
    /**
     * Reads the given file.
     * @param filePath Path of the file to read
     * @return the data object built from the file
     * @throws IOException if there is an error while reading the file
     */
    Data readFile(Path filePath) throws IOException;

    /**
     * Detects if the format of the provided file is supported.
     * @param filePath the file to check
     * @return true if the format of the file is supported, false otherwise
     * @throws IOException if there is an error while reading the file
     */
    boolean isFormatSupported(Path filePath) throws IOException;
}
@Component
public class FileReaderFactory implements FileReaderService {

    private List<FileReaderService> availableServices = new ArrayList<>();

    @Override
    public Data readFile(Path filePath) throws IOException {

        for (FileReaderService reader : availableServices) {
            if (reader.isFormatSupported(filePath)) {
                return reader.readFile(filePath);
            }
        }

        return null;
    }

    @Override
    public boolean isFormatSupported(Path filePath) throws IOException {
        for (FileReaderService reader : availableServices) {
            if (reader.isFormatSupported(filePath)) {
                return true;
            }
        }
        return false;
    }
}
@Reference(cardinality = ReferenceCardinality.AT_LEAST_ONE)
private List<FileReaderService> availableServices;
@组件
公共类FileReaderFactory实现FileReaderService{
private List availableServices=new ArrayList();
@凌驾
公共数据读取文件(路径文件路径)引发IOException{
for(文件阅读器服务阅读器:可用服务){
if(reader.isFormatSupported(filePath)){
返回reader.readFile(文件路径);
}
}
返回null;
}
@凌驾
公共布尔值isFormatSupported(路径文件路径)引发IOException{
for(文件阅读器服务阅读器:可用服务){
if(reader.isFormatSupported(filePath)){
返回true;
}
}
返回false;
}
}
我希望DS在工厂列表中动态注入FileReaderServices。根据提供的服务数量,我将支持(或不支持)给定的文件格式

因此,我的问题是:

1) 这对DS可行吗

2) 如果是,如何使用DS注释

3) 如果没有,你会怎么做

谢谢


编辑:我尝试了Christian的解决方案,但没有成功。完整代码可在此处下载:

用@Reference注释列表:

@Reference(service = FileReaderService.class)
private List<FileReaderService> availableServices;
@Reference(service=FileReaderService.class)
私有列表可用服务;
您将需要DS 1.3才能正常工作。最新的felix scr版本支持这一点


我不建议将FileReaderFactory导出为FileReaderService,因为它可能导致递归。

正如Christian Schneider所指出的,解决方案是使用@Reference注释

然而,当我按照Karaf的回答使用它时,Karaf确实崩溃了:

Cannot register Component
org.osgi.service.component.ComponentException: Component org.test.reader.service.factory.FileReaderFactory validation failed: Field value type must not be set for unary field references.
通过查看Felix SCR源代码,我在org.apache.Felix.SCR.impl.metadata.ReferenceMetadata类中找到了导致异常的行:

// field value type
if ( !m_isMultiple )
{
    // value type must not be specified for unary references
    if ( m_field_collection_type != null )
    {
        throw componentMetadata.validationFailure( "Field value type must not be set for unary field references." );
    }
}
Felix SCR似乎不高兴,因为组件XML中的属性m_field_collection_type设置为“service”(即不为null),而没有指定基数

因此,我改变了我的注释如下:

public interface FileReaderService {
    /**
     * Reads the given file.
     * @param filePath Path of the file to read
     * @return the data object built from the file
     * @throws IOException if there is an error while reading the file
     */
    Data readFile(Path filePath) throws IOException;

    /**
     * Detects if the format of the provided file is supported.
     * @param filePath the file to check
     * @return true if the format of the file is supported, false otherwise
     * @throws IOException if there is an error while reading the file
     */
    boolean isFormatSupported(Path filePath) throws IOException;
}
@Component
public class FileReaderFactory implements FileReaderService {

    private List<FileReaderService> availableServices = new ArrayList<>();

    @Override
    public Data readFile(Path filePath) throws IOException {

        for (FileReaderService reader : availableServices) {
            if (reader.isFormatSupported(filePath)) {
                return reader.readFile(filePath);
            }
        }

        return null;
    }

    @Override
    public boolean isFormatSupported(Path filePath) throws IOException {
        for (FileReaderService reader : availableServices) {
            if (reader.isFormatSupported(filePath)) {
                return true;
            }
        }
        return false;
    }
}
@Reference(cardinality = ReferenceCardinality.AT_LEAST_ONE)
private List<FileReaderService> availableServices;
@Reference(基数=ReferenceCardinality.至少一个)
私有列表可用服务;
为了测试应用程序,我还添加了一个factory service consumer,它可以正常工作

对于感兴趣的用户,可在以下位置获取固定源代码:

不幸的是,我使用的是最新版本的karaf,它与Felix SCR 1.8.2一起提供。据我所知,它与DS 1.3不兼容。我同意您的建议,即阻止工厂本身成为FileReaderService。在DS的早期版本中,像下面这样的方法可以工作吗@引用public void addService(final FileReaderService serviceToAdd){availableServices.add(serviceToAdd);}最新的karaf支持scr 2。对于旧版本,您需要一个bind和unbind方法。但我无法编译它。我的项目依赖于org.osgi.compendium版本5。我错过了什么?对于DS 1.3,您需要OSGi规范版本6。请参见我的示例: