OSGI 环境中的依赖注入

Posted

技术标签:

【中文标题】OSGI 环境中的依赖注入【英文标题】:Dependency Injection in OSGI environments 【发布时间】:2012-06-25 05:43:31 【问题描述】:

先介绍一下背景:

我正在开发一些基于 Apache Sling 的 webapp 原型代码,它基于 OSGI 并在 Apache Felix 上运行。尽管我认为我现在已经掌握了大多数概念,但我对 OSGI 还是比较陌生。然而,令我困惑的是,我一直无法找到一个“完整的”依赖注入 (DI) 框架。我已经使用声明式服务 (DS) 成功地使用了基本的 DI。但我的理解是 DS 是用来引用的——我该怎么说呢? -- OSGI 将服务和组件注册在一起。为此它工作得很好,但我个人使用像Guice 这样的DI 框架将整个对象图连接在一起并将对象放在正确的范围内(例如@RequestScoped@SessionScoped)。然而,我看过的 OSGI 特定框架似乎都不支持这个概念。

我已经开始阅读有关OSGI blueprints 和iPOJO 的信息,但这些框架似乎更关心将OSGI 服务连接在一起,而不是提供完整的DI 解决方案。我不得不承认我还没有做过任何样品,所以我的印象可能不正确。

作为 Guice 的扩展,我已经尝试过 Peaberry,但是我发现文档很难找到,而且虽然我得到了基本的 DI 工作,但很多 guice-servlet 的高级功能(自动注入过滤器、servlet等)根本不起作用。

所以,我的问题如下:

    声明式服务与 Guice 或 Spring 等“传统”DI 相比如何?他们是解决同一个问题还是针对不同的问题? 到目前为止,我看到的所有 OSGI 特定解决方案都缺少 DI 范围的概念。例如,Guice + guice-servlet 具有请求范围的依赖关系,这使得编写 Web 应用程序非常干净和容易。我只是在文档中错过了这一点,还是这些框架都没有涵盖这些问题? JSR 330 和基于 OSGI 的 DI 是两个不同的世界吗?例如 iPOJO 带来的 own annotations 和 Felix SCR Annotations 似乎是一个完全不同的世界。 有人有构建基于 OSGI 的系统和 DI 的经验吗?甚至可能在 github 上有一些示例代码? 是否有人同时使用 Guice 和 iPOJO 等不同的技术,或者这只是一个疯狂的想法?

抱歉,这个问题太长了。

非常感谢任何反馈。


更新

范围注入:范围注入是一种有用的机制,可以自动注入来自特定生命周期的对象。例如,您的某些代码依赖于作为 servlet 过滤器的一部分创建的 Hibernate 会话对象。通过标记依赖关系,容器将自动重建对象图。也许只是有不同的方法?

JSR 330 vs DS:从您所有出色的答案中,我看到这是两件不同的事情。这就提出了一个问题,当在 OSGI 上下文中使用时,如何处理使用 JSR 330 注释的第三方库和框架?什么是好方法?在 Bundle 中运行 JSR 330 容器?

感谢您的所有回答,您非常有帮助!

【问题讨论】:

相反:感谢您提出一个措辞良好(因此“长”)的问题:-) 【参考方案1】:

我在当前项目中使用 osgi 和 DI,我选择了 Gemini 蓝图,因为它是 SPRING DYNAMIC MODULES 的第二个版本,基于此信息,我建议您阅读 Spring Dynamic Modules in Action。这本书将帮助你理解一些部分和要点如何构建架构以及它为什么好:)

【讨论】:

您好,Sergii,感谢您的建议。我一定会检查 Spring Dynamic Modules。不知道我是怎么错过的! ;) 从第二个版本开始读取 eclipse gemini bluprint eclipse.org/gemini/blueprint 如果你想使用 Spring DM 那么使用新的 Gemini。旧的 spring DM 在 OSGi 中存在一些严重的类加载问题,因为它与原始 spring 保持了太多的兼容性。另一种选择是白羊座蓝图,它比双子座更轻量级,但功能更少。见gnodet.blogspot.de/2010/03/…【参考方案2】:

我可以推荐 Bnd,如果你使用 Eclipse IDE,也可以使用 Bndtools。这样您就可以避免在 XML 中描述 DS 并改用注释。 DI 有一个特殊的Reference 注释。这个还有一个过滤器,您可以在其中仅引用特殊的服务子集。

【讨论】:

Bnd 是 Robert 提到的 maven-scr-plugin 在后台使用的。 是的,这些我已经用过了。我使用 Apache Felix 的 BND 插件自动生成 XML 文件。【参考方案3】:

总体方法

使用 Apache Sling 进行依赖注入的最简单方法是使用 maven-scr-plugin 。

您可以注释您的 java 类,然后在构建时调用 SCR 插件,可以作为 Maven 插件,也可以作为 Ant 任务。

例如,要注册一个 servlet,您可以执行以下操作:

@Component // signal that it's OSGI-managed
@Service(Servlet.class) // register as a Servlet service
public class SampleServlet implements Servlet    
   @Reference SlingRepository repository; // get a reference to the repository    

具体答案

声明式服务与 Guice 或 Spring 等“传统”DI 相比如何?他们是解决同一个问题还是针对不同的问题?

他们解决了同样的问题——依赖注入。但是(见下文)它们的构建还考虑了服务可以随时出现或消失的动态系统。

到目前为止,我看到的所有 OSGI 特定解决方案都缺少 DI 范围的概念。例如,Guice + guice-servlet 具有请求范围的依赖关系,这使得编写 Web 应用程序非常干净和容易。我只是在文档中错过了这一点,还是这些框架中的任何一个都没有涵盖这些问题?

我在 SCR 世界中没有看到任何添加会话范围或请求范围服务的方法。但是,SCR 是一种通用方法,可以在更具体的层处理范围。

由于您使用的是 Sling,我认为几乎不需要会话范围或请求范围的绑定,因为 Sling 为每个请求都内置了为当前用户适当创建的对象。

一个很好的例子是 JCR 会话。它是使用正确的权限自动构建的,实际上它是一个请求范围的 DAO。 Sling resourceResolver 也是如此。

如果您发现自己需要每个用户的工作,最简单的方法是让服务接收 JCR Session 或 Sling ResourceResolver 并使用它们来执行您需要的工作。结果将根据当前用户的权限自动调整,无需任何额外工作。

JSR 330 和基于 OSGI 的 DI 是两个不同的世界吗?例如 iPOJO 带来了自己的注释,而 Felix SCR 注释似乎是一个完全不同的世界。

是的,它们是不同的。您应该记住,虽然 Spring 和 Guice 更主流,但 OSGi 服务更复杂,支持更多用例。在 OSGi 中,捆绑包(以及隐含的服务)可以随时来来去去。

这意味着当您的组件依赖于刚刚变得不可用的服务时,您的组件将被停用。或者当您收到一个组件列表(例如,Servlet 实现)并且其中一个被停用时,您会收到通知。据我所知,Spring 和 Guice 都不支持这一点,因为它们的接线是静态的。

这是 OSGi 为您提供的极大灵活性。

有人有构建基于 OSGI 的系统和 DI 的经验吗?甚至是 github 上的一些示例代码?

Sling Samples SVN repository 中有大量样本。你应该在那里找到大部分你需要的东西。

是否有人将 Guice 和 iPOJO 等不同的技术一起使用,或者这只是一个疯狂的想法?

如果您的框架配置了 JSR 330 注释,那么在运行时使用 Guice 或 Spring 或任何适合您的方式配置它们是有意义的。然而,正如 Neil Bartlett 所指出的,这不会跨捆绑工作。

【讨论】:

完全同意 Robert 的观点,如果您已经在使用 Sling,则无需添加额外的层。与 OSGi 服务组件运行时相比,Spring 或 Guice 可能会添加一些额外的功能,但更少的移动部分通常会转化为更易于调试和维护的系统。我建议阅读 OSGi in Action 一书 (manning.com/hall),以全面了解 Sling 中使用的标准 OSGi 服务带来了什么,开箱即用。 至于 Sling 示例,我建议查看 Slingbucks 示例,它在使用 SCR 服务和注释的方式上相当简单和“现代” - 它位于 svn.apache.org/repos/asf/sling/trunk/samples/slingbucks 。我还为我的 ApacheCon“OSGi for mere mortals”演讲创建了一个简单的 RESTful 服务器示例,您可以在 github.com/bdelacretaz/OSGi-for-mere-mortals 找到它,并在 Sling 之外使用类似的技术。 哇,首先,感谢您的精心回答!我将尝试通过更多更新来更新原始问题,cmets 似乎不是进行大量更新的正确位置。 正如 Robert 所提到的,范围界定方法是特定于域的。因此,它们应该是特定领域框架(例如 Web 框架)和通过相关上下文的 API 的一部分。在 Sling 的情况下,这将是 SlingHttpServletRequest(以及响应)接口,它使您可以访问请求周围的所有内容(即预构建的资源解析器 + 与请求用户进行身份验证的会话)。如果需要,请将其传递给需要那些特定于请求的对象的服务。 恕我直言,将这种范围界定方法与 DI 混合只会导致过多的“神奇”代码。【参考方案4】:

我有一些使用 Aries Blueprint 构建应用程序的经验。它有一些关于 OSGi 服务和配置管理支持的非常好的特性。

如果您搜索一些出色的示例,请查看 Apache Karaf 的代码,该代码使用蓝图进行所有布线。 见http://svn.apache.org/repos/asf/karaf/

我的网站上还有一些 Blueprint 和 Apache Karaf 的教程: http://www.liquid-reality.de/display/liquid/Karaf+Tutorials

在您使用嵌入式 felix 的环境中,它会有点不同,因为您没有 Karaf 的管理功能,但您只需要安装相同的包,它应该可以很好地工作。

【讨论】:

【参考方案5】:

我只想为 Robert 的出色回答添加更多信息,尤其是关于 JSR330 和 DS。

声明式服务、蓝图、iPOJO 和其他 OSGi“组件模型”主要用于注入 OSGi 服务。这些比常规依赖项更难处理,因为它们可以随时来来去去,包括响应外部事件(例如网络断开)或用户操作(例如删除捆绑包)。因此,所有这些组件模型都在纯依赖注入框架之上提供了一个额外的生命周期层。

这是 DS 注释与 JSR330 不同的主要原因...... JSR330 没有提供足够的语义来处理生命周期。例如,他们什么也没说:

什么时候应该注入依赖? 当依赖项当前不可用时我们应该怎么做(即它是可选的还是强制的)? 当我们正在使用的服务消失时,我们应该怎么做? 我们能否动态地从一个服务实例切换到另一个实例? 等等……

不幸的是,因为组件模型主要集中在服务上——即,包之间的链接——它们在内部的依赖关系的连接方面比较简陋。 bundle(尽管 Blueprint 确实为此提供了一些支持)。

使用现有的 DI 框架来连接包内的依赖项应该没有问题。例如,我有一个客户使用 Guice 来连接一些声明式服务组件的内部部件。但是我倾向于质疑这样做的价值,因为如果您在包中需要 DI,这表明您的包可能太大且不连贯。

请注意,不要使用传统的 DI 框架在包之间连接组件,这一点非常重要。如果 DI 框架需要从另一个 bundle 访问一个类,那么其他 bundle 必须公开其实现细节,这破坏了我们在 OSGi 中寻求的封装。

【讨论】:

尼尔您好,感谢您的回答。我的观察与您的相似,OSGI DI 框架主要关注 OSGI 服务,而不是连接对象图。我询问 JSR330 DI 的原因是我们已经有很多使用它们的代码,所以最简单的方法是放入 Guice 并在包有一个注入器。然而,那么问题就变成了如何通过/作为 OSGI 服务公开 Guice 创建的实例? 连接 OSGi 服务连接对象图...该图跨越模块边界。如果您要使用 DS 之类的东西,那么您可以完全在 DS 组件中创建一个 Guice“模块”(术语冲突!)。【参考方案6】:

在这里遇到类似的架构问题 - 正如罗伯特在上面的回答中提到的那样:

如果您发现自己需要按用户工作,最简单的方法是 具有接收 JCR 会话或 Sling ResourceResolver 的服务 并使用它们来执行您需要的工作。结果将是 自动调整当前用户的权限,无需 任何额外的努力。

据此推断(以及我目前正在编码的内容),一种方法是将@param resourceResolver 添加到任何@Service 方法中,以便您可以传递适当的请求范围对象以在执行链中使用。

具体来说,我们有一个 XXXXService / XXXXDao 层,从 XXXXServlet / XXXXViewHelper / JSP 等效项调用。因此,通过 OSGI @Service 注释管理所有这些组件,我们可以轻松地连接整个堆栈。

这里的缺点是您需要使用ResourceResolverSessions 参数乱扔您的界面设计。

最初我们尝试将ResourceResolverFactory注入到DAO层,这样我们就可以很方便地通过工厂随意访问会话。但是,我们在层次结构中的多个点与会话交互,并且每个请求多次。这导致了会话关闭异常。

有没有办法可靠地获取每个请求 ResourceResolver 而无需将其传递给每个服务方法?

使用服务层上的请求范围注入,您可以改为将 ResourceResolver 作为构造函数 arg 传递并使用实例变量。当然,这里的缺点是您必须考虑请求范围与原型范围的服务代码并相应地分开。

这似乎是一个常见问题,您希望将关注点分离到服务/dao 代码中,将 JCR 交互留在 DAO 中,类似于 Hibernate,您如何轻松获取每个请求 @ 987654332@执行回购操作?

【讨论】:

以上是关于OSGI 环境中的依赖注入的主要内容,如果未能解决你的问题,请参考以下文章

DI依赖注入环境

Spring依赖注入

Spring5依赖注入(DI)

Spring入门-----7依赖注入之set注入

译 Node.js 中的依赖注入

在依赖注入环境(如 Spring Boot)中创建设计模式是不是无用?