osgi - 根据参数获取具体的服务实现

标签 osgi sling

在我的 Sling 应用程序中,我有呈现文档、页面和内容节点的数据。我们主要以 HTML 形式提供这些文档,但现在我希望有一个 servlet 将这些文档以 PDF 和 PPT 形式提供服务。

基本上,我考虑过实现工厂模式:在我的 servlet 中,根据请求的扩展(pdf 或 ppt),我将从 DocumentBuilderFactory 获得正确的 DocumentBuilder 实现,即 PdfDocumentBuilder 或 PptDocumentBuilder。

所以首先我有这个:

public class PlanExportBuilderFactory {

  public PlanExportBuilder getBuilder(String type) {
    PlanExportBuilder builder = null;
    switch (type) {
      case "pdf":
        builder = new PdfPlanExportBuilder();
        break;
      default: 
        logger.error("Unsupported plan export builder, type: " + type);
    }
    return builder;
  }
}

在 servlet 中:

@Component(metatype = false)
@Service(Servlet.class)
@Properties({ 
  @Property(name = "sling.servlet.resourceTypes", value = "myApp/document"), 
  @Property(name = "sling.servlet.extensions", value = { "ppt", "pdf" }),
  @Property(name = "sling.servlet.methods", value = "GET") 
})
public class PlanExportServlet extends SlingSafeMethodsServlet {

  @Reference
  PlanExportBuilderFactory builderFactory;

  @Override
  protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {

    Resource resource = request.getResource();

    PlanExportBuilder builder = builderFactory.getBuilder(request.getRequestPathInfo().getExtension());
  }
}    

但问题是,在构建器中我想引用其他服务来访问 Sling 资源,而使用此解决方案,它们不受绑定(bind)。

我研究了 OSGi 的服务工厂,但根据我的理解,您可以使用它们来配置相同服务的不同实现。

然后我发现你可以通过命名来获得具体的实现,或者使用属性和过滤器。

所以我最终得到了这个:

public class PlanExportBuilderFactory {

  @Reference(target = "(builderType=pdf)")
  PlanExportBuilder pdfPlanExportBuilder;

  public PlanExportBuilder getBuilder(String type) {
    PlanExportBuilder builder = null;
    switch (type) {
      case "pdf":
        return pdfPlanExportBuilder;
      default: 
        logger.error("Unsupported plan export builder, type: " + type);
    }
    return builder;
  }

}

定义“builderType”属性的构建器:

// AbstractPlanExportBuilder implements PlanExportBuilder interface
@Component
@Service(value=PlanExportBuilder.class)
public class PdfPlanExportBuilder extends AbstractPlanExportBuilder {

  @Property(name="builderType", value="pdf")

  public PdfPlanExportBuilder() {
    planDocument = new PdfPlanDocument();
  }
}

我想知道这是否是检索有关 OSGi 良好实践的 PDF 生成器实现的好方法。

编辑 1

根据彼得的回答,我尝试添加多个引用,但对于菲利克斯来说,它似乎不起作用:

 @Reference(name = "planExportBuilder", cardinality = ReferenceCardinality.MANDATORY_MULTIPLE, policy = ReferencePolicy.DYNAMIC)
private Map<String, PlanExportBuilder> builders = new ConcurrentHashMap<String, PlanExportBuilder>();

protected final void bindPlanExportBuilder(PlanExportBuilder b, Map<String, Object> props) {
  final String type = PropertiesUtil.toString(props.get("type"), null);
  if (type != null) {
    this.builders.put((String) props.get("type"), b);
  }
}

protected final void unbindPlanExportBuilder(final PlanExportBuilder b, Map<String, Object> props) {
  final String type = PropertiesUtil.toString(props.get("type"), null);
  if (type != null) {
    this.builders.remove(type);
  }
}

我收到这些错误:

@Reference(builders) : Missing method bind for reference planExportBuilder
@Reference(builders) : Something went wrong: false - true - MANDATORY_MULTIPLE
@Reference(builders) : Missing method unbind for reference planExportBuilder

这里的 Felix 文档 http://felix.apache.org/documentation/subprojects/apache-felix-maven-scr-plugin/scr-annotations.html#reference对于绑定(bind)方法来说:

The default value is the name created by appending the reference name to the string bind. The method must be declared public or protected and take single argument which is declared with the service interface type

因此,据此,我知道它不能与 Felix 一起使用,因为我正在尝试传递两个参数。但是,我在这里找到了一个示例,它似乎与您的建议相匹配,但我无法使其工作:https://github.com/Adobe-Consulting-Services/acs-aem-samples/blob/master/bundle/src/main/java/com/adobe/acs/samples/services/impl/SampleMultiReferenceServiceImpl.java

编辑2 只需将引用移至类上方即可使其工作:

@References({
  @Reference(
    name = "planExportBuilder",
    referenceInterface = PlanExportBuilder.class,
    policy = ReferencePolicy.DYNAMIC,
    cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE)
})
public class PlanExportServlet extends SlingSafeMethodsServlet {

最佳答案

工厂是邪恶的:-)主要原因当然是通常使用的令人讨厌的类加载技巧,但也因为它们往往具有全局知识。一般来说,您希望能够使用新的 DocumentBuilder 添加 bundle ,然后该类型应该可用。

因此,更面向 OSGi 的解决方案是使用服务属性。这可能看起来像:

@Component( property=HTTP_WHITEBOARD_FILTER_REGEX+"=/as")
public class DocumentServlet {

  final Map<String,DocBuilder>        builders = new ConcurrentHashMap<>();

  public void doGet( HttpServletRequest rq, HttpServletResponse rsp ) 
                           throws IOException, ServletException {

    InputStream in = getInputStream( rq.getPathInfo() );
    if ( in == null ) 
      ....

    String type = toType( rq.getPathInfo(), rq.getParameter("type") );

    DocBuilder docbuilder = builders.get( type );
    if ( docbuilder == null)
       ....

    docbuilder.convert( type, in, rsp.getOutputStream() );
 }

 @Reference( cardinality=MULTIPLE, policy=DYNAMIC )
 void addDocBuilder( DocBuilder db, Map<String,Object> props ) {
    docbuilders.put(props.get("type"), db );
 }

 void removeDocBuilder(Map<String,Object> props ) {
    docbuilders.remove(props.get("type"));
 }

}

DocBuilder 可能看起来像:

@Component( property = "type=ppt-pdf" )
public class PowerPointToPdf implements DocBuilder {
    ...
}

关于osgi - 根据参数获取具体的服务实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37948232/

相关文章:

java - 使用正确的 "sling:match"进行 SSL 配置

java - 启动 bundle org.eclipse.jetty.osgi.boot 时出错(Eclipse 中的 OSGi 框架)

java - 以编程方式更改 OSGi 包导入

jsp - 使用c :forEach over sling:listChildren

发布中的身份验证机制

java - Sling Rewriter 更改静态资源 URL

java - 重写 Sling DefaultGetServlet.java

java - Eclipse Mars : The import org. osgi无法解析

mysql - 事务回滚在 karaf 中不起作用

java - 运行 OSGI 框架时出现 bundle 异常