问题
I have a core library that has an interface that I want to expose as an OSGI service in Fuse ESB (Apache ServiceMix and Karaf). The goal is to allow other bundles to use it. The service uses JPA (OpenJPA) and Spring. The following is the interface:
public interface PatientService {
public Patient find(Integer id);
}
and the class:
@Repository
public class PatientServiceJpaImpl implements PatientService {
@PersistenceContext(unitName="psu")
private EntityManager entityManager;
@Override
public Patient find(Integer id) {
return entityManager.find(Patient.class, id);
}
}
The following is an abbreviated 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>
And the META-INF/persistence.xml
(also abbreviated):
<persistence xmlns="http://java.sun.com/xml/ns/persistence" ...>
<persistence-unit name="psu" transaction-type="RESOURCE_LOCAL">
<class>...</class>
</persistence>
In a non-OSGi environment, everything works great. It uses the felix maven-bundle-plugin, so to create the OSGi service, I added the following 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>
The bundle is deployed successfully and the service is registered. The problem is that when the PatientService
is referenced from another bundle, the entity manager has not been injected, thus throwing a NullPointerException
in the find(Integer id)
method. The following is a snippet of the consumer's META-INF/spring/consumer-context.xml
:
<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>
Just to be clear, the PatientService
is injected in the consumer bundle, but the entity manager is not injected in the provider bundle. Also, it does not appear to be an issue with the persistence unit due to the following log output when starting the original service:
125 psu TRACE [SpringOsgiExtenderThread-14] openjpa.Runtime - org.apache.openjpa.persistence.PersistenceProviderImpl@24a5031d creating container org.apache.openjpa.persistence.EntityManagerFactoryImpl@4d6f77b6 for PU psu.
To get an idea what is going on, I logged the the object memory reference and stack trace in the constructor of the PatientServiceJpaImpl
class. The constructor was called twice (creating two different objects):
The first output appears to originate from the osgi container starting from
org.apache.felix
and more or less ending inorg.apache.aries.blueprint
.The second output appears to originate from the spring framework starting from
org.springframework.osgi
and more or less ending inorg.springframework.beans.BeanUtils
.
When the consumer service is called, the reference it has is to the blueprint instantiated object, which does not have an injected entity manager. Also from the logs, the persistence unit is instantiated after the blueprint instantiation of the PatientServiceJpaImpl
object.
I have searched and tinkered with this issue for quite a while and I've run out of ideas. The irony is that it actually worked at one point, but I had made so many changes to get it to work that it was a rats nest that I was not able to back out of successfully.
Why is the persistence context not injected in the blueprint managed object? Any ideas will be appreciated. Thanks.
回答1:
I'm not sure if this is going to work, cause you are mixing spring with blueprint. I have a working application based only on blueprint and I'm quite happy. For your use-case I'd suggest using blueprint at least for your JPA part. You still can use spring-dm for using the jpa classes as services.
<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>
Your PatientServiceJPAImpl
I would change to not contain any annotations.
public class PatientServiceJpaImpl implements PatientService {
protected EntityManager em;
@Override
public Patient find(Integer id) {
return em.find(Patient.class, id);
}
}
回答2:
A good way to deal with this would be to use Gemini JPA. With Gemini JPA, your JPA bundle would automatically expose an EntityManagerFactory OSGi service, based on the configurations in your persistence.xml. Thus, in the JPA client bundle, the blueprint just needs to import this service with a filter condition specifying the persistence unit name.
Here's a small implementation on those lines: mvc-osgi
If the EMF service doesn't work for you, you can use the EntityManagerFactoryBuilder service exposed by the Gemini JPA bundle to manually create the EMF. This would require calling the "createEntityManagerFactory".
来源:https://stackoverflow.com/questions/13688492/osgi-service-jpa-persistencecontext-not-injected