未注入 OSGI 服务 JPA PersistenceContext

Posted

技术标签:

【中文标题】未注入 OSGI 服务 JPA PersistenceContext【英文标题】:OSGI service JPA PersistenceContext not injected 【发布时间】:2012-11-21 05:28:37 【问题描述】:

我有一个核心库,它有一个接口,我想在 Fuse ESB(Apache ServiceMix 和 Karaf)中将其公开为 OSGI 服务。目标是允许其他捆绑包使用它。该服务使用 JPA (OpenJPA) 和 Spring。以下是界面:

public interface PatientService 
    public Patient find(Integer id);

和班级:

@Repository
public class PatientServiceJpaImpl implements PatientService 
    @PersistenceContext(unitName="psu")
    private EntityManager entityManager;

    @Override
    public Patient find(Integer id) 
        return entityManager.find(Patient.class, id);
    

以下是META-INF/spring/beans.xml的缩写:

<beans xmlns="http://www.springframework.org/schema/beans" ...>
    <context:annotation-config />
    <context:component-scan base-package="..." />

    <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="emf" />
    </bean>

    <bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="psu" />
        <property name="jpaVendorAdapter" ref="jpaAdapter" />
        <property name="dataSource" ref="dataSource" />
    </bean>

    <bean id="jpaAdapter" class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter" />

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="$database.driver" />
        <property name="url" value="$database.url" />
        <property name="username" value="$database.username" />
        <property name="password" value="$database.password" />
    </bean>

    <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
</beans>

还有META-INF/persistence.xml(也缩写):

<persistence xmlns="http://java.sun.com/xml/ns/persistence" ...>
    <persistence-unit name="psu" transaction-type="RESOURCE_LOCAL">
        <class>...</class>
</persistence>

在非 OSGi 环境中,一切正常。它使用 felix maven-bundle-plugin,所以为了创建 OSGi 服务,我添加了以下OSGI-INF/blueprint/osgi-context.xml

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0
    http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">

    <bean id="patientService" class="com.test.service.PatientServiceJpaImpl" />
    <service id="osgiPatientService" ref="patientService" interface="com.test.service.PatientService" />

</blueprint>

bundle 部署成功,服务注册成功。问题是当PatientService被另一个bundle引用时,实体管理器还没有被注入,因此在find(Integer id)方法中抛出了NullPointerException。以下是消费者META-INF/spring/consumer-context.xml的sn-p:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:osgi="http://www.springframework.org/schema/osgi"
xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://cxf.apache.org/jaxws
    http://cxf.apache.org/schemas/jaxws.xsd
    http://www.springframework.org/schema/osgi
    http://www.springframework.org/schema/osgi/spring-osgi.xsd">

    <bean id="patientServiceImpl" class="com.test.ws.PatientWebServiceImpl" >
        <property name="patientService">
            <osgi:reference interface="com.test.service.PatientService"/>
        </property>
    </bean>

    ...

</beans>

需要明确的是,PatientService 被注入到消费者包中,但实体管理器没有被注入到提供者包中。此外,由于启动原始服务时出现以下日志输出,因此持久性单元似乎不是问题:

125  psu  TRACE  [SpringOsgiExtenderThread-14] openjpa.Runtime - org.apache.openjpa.persistence.PersistenceProviderImpl@24a5031d creating container org.apache.openjpa.persistence.EntityManagerFactoryImpl@4d6f77b6 for PU psu.

为了了解发生了什么,我在PatientServiceJpaImpl 类的构造函数中记录了对象内存引用和堆栈跟踪。构造函数被调用了两次(创建了两个不同的对象):

    第一个输出似乎源自 osgi 容器,从 org.apache.felix 开始,或多或少以 org.apache.aries.blueprint 结束。

    第二个输出似乎源自 spring 框架,从 org.springframework.osgi 开始,或多或少以 org.springframework.beans.BeanUtils 结束。

当调用消费者服务时,它所拥有的引用是对蓝图实例化对象的引用,该对象没有注入实体管理器。同样从日志中可以看出,持久化单元是在PatientServiceJpaImpl对象的蓝图实例化之后实例化的。

我已经对这个问题进行了很长时间的搜索和修改,但我已经没有想法了。具有讽刺意味的是,它实际上在某个时候有效,但我做了很多改变才能让它发挥作用,以至于它是一个我无法成功退出的老鼠窝。

为什么没有在蓝图托管对象中注入持久化上下文?任何想法将不胜感激。谢谢。

【问题讨论】:

【参考方案1】:

我不确定这是否可行,因为您正在将 spring 与蓝图混合在一起。我有一个仅基于蓝图的工作应用程序,我很高兴。对于您的用例,我建议至少为您的 JPA 部分使用蓝图。您仍然可以使用 spring-dm 将 jpa 类用作服务。

<blueprint default-activation="eager"
xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jpa="http://aries.apache.org/xmlns/jpa/v1.0.0" xmlns:tx="http://aries.apache.org/xmlns/transactions/v1.0.0">

<bean id="patientService" class="com.test.service.PatientServiceJpaImpl" >
    <jpa:context property="em" unitname="dn1" />
<tx:transaction method="*" value="Required" />
</bean>
<service id="osgiPatientService" ref="patientService" interface="com.test.service.PatientService" />
</blueprint>

您的PatientServiceJPAImpl 我将更改为不包含任何注释。

public class PatientServiceJpaImpl implements PatientService 
    protected EntityManager em;

    @Override
    public Patient find(Integer id) 
        return em.find(Patient.class, id);
    

【讨论】:

感谢您的回复。这个库也将被其他开发人员在非 OSGI 环境中使用。有没有办法同时维护这两种解决方案?换句话说,在蓝图环境中保留“PatientServiceJpaImpl”注释会产生什么影响(如果有)? 在这种情况下,请确保您不是通过蓝图而是使用 spring-dm 导出服务。如果您必须使用 spring-dm 到 spring-dm 并通过蓝图引用您的服务,则两者的混合将不起作用。 我最初是通过在 spring 上下文 xml 文件中定义 osgi 服务来尝试的。但是,在使用 felix maven-bundle-plugin 构建捆绑包后,我没有在 MANIFEST-MF 中看到 Export-Service 声明,并且部署捆绑包并没有注册服务。我决定不跟进,因为 Spring DM 似乎已经停产。是否可以在 karaf 中使用 spring-dm(我正在使用 Fuse ESB 发行版)?如果是这样,那么在未来这样做是否有意义?谢谢。 Karaf 在 1.2.1 版本中确实支持 Spring-dm,所以是的,这可能是您的选择。我不会依赖 maven-bundle-plugin 来为 export-service 声明正确解释 spring xml。我会试一试并使用它。 我太聪明了,因为我认为如果没有出口服务声明,该服务将不可用。我通过使用 spring dm 让它工作(实际上,保险丝分发带有安装的 spring-dm 1.2.1 包)。再次感谢。【参考方案2】:

处理这个问题的一个好方法是使用Gemini JPA。使用 Gemini JPA,您的 JPA 包将根据您的 persistence.xml 中的配置自动公开一个 EntityManagerFactory OSGi 服务。因此,在 JPA 客户端包中,蓝图只需要使用指定持久性单元名称的过滤条件导入此服务。

这是这些行的一个小实现:mvc-osgi

如果 EMF 服务不适合您,您可以使用 Gemini JPA 包公开的 EntityManagerFactoryBuilder 服务来手动创建 EMF。这需要调用“createEntityManagerFactory”。

【讨论】:

以上是关于未注入 OSGI 服务 JPA PersistenceContext的主要内容,如果未能解决你的问题,请参考以下文章

Equinox OSGi + JPA Eclipselink + PAX JDBC 驱动适配器

JPA 与 OSGi 示例在 serviceReference 中返回 null

OSGI、Servlets 和 JPA hello world / 教程 / 示例

在 Wildfly 中用于模块间服务注入的 OSGI 替代方案是啥?

使用 OSGi 将组件注入 POJO

Flyway 与 JPA + OSGi 的集成