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

Get a specific service implementation based on a parameter

在我的 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 资源,而使用此解决方案,它们不受约束。

我查看了带有 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

根据 Peter 的回答,我尝试添加多个引用,但对于 Felix,它似乎不起作用:

 @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 对绑定方法说:

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 只需将引用移到 class 上方即可使其工作:

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

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

因此,一个更面向 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 {
    ...
}