SharedEntityManagerCreator's EntityManager proxies are fully serializable now (SPR-6684)
This commit is contained in:
parent
df54c8613d
commit
db71811c5a
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -16,6 +16,9 @@
|
|||
|
||||
package org.springframework.orm.jpa;
|
||||
|
||||
import java.io.NotSerializableException;
|
||||
import java.io.ObjectStreamException;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
|
@ -37,6 +40,9 @@ import org.apache.commons.logging.LogFactory;
|
|||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
|
@ -73,8 +79,8 @@ import org.springframework.util.CollectionUtils;
|
|||
* @see LocalContainerEntityManagerFactoryBean
|
||||
*/
|
||||
public abstract class AbstractEntityManagerFactoryBean implements
|
||||
FactoryBean<EntityManagerFactory>, BeanClassLoaderAware, InitializingBean, DisposableBean,
|
||||
EntityManagerFactoryInfo, PersistenceExceptionTranslator {
|
||||
FactoryBean<EntityManagerFactory>, BeanClassLoaderAware, BeanFactoryAware, BeanNameAware,
|
||||
InitializingBean, DisposableBean, EntityManagerFactoryInfo, PersistenceExceptionTranslator, Serializable {
|
||||
|
||||
/** Logger available to subclasses */
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
|
@ -95,9 +101,15 @@ public abstract class AbstractEntityManagerFactoryBean implements
|
|||
|
||||
private ClassLoader beanClassLoader = getClass().getClassLoader();
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
private String beanName;
|
||||
|
||||
/** Raw EntityManagerFactory as returned by the PersistenceProvider */
|
||||
public EntityManagerFactory nativeEntityManagerFactory;
|
||||
|
||||
private EntityManagerFactoryPlusOperations plusOperations;
|
||||
|
||||
private EntityManagerFactory entityManagerFactory;
|
||||
|
||||
|
||||
|
|
@ -254,6 +266,14 @@ public abstract class AbstractEntityManagerFactoryBean implements
|
|||
return this.beanClassLoader;
|
||||
}
|
||||
|
||||
public void setBeanFactory(BeanFactory beanFactory) {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
public void setBeanName(String name) {
|
||||
this.beanName = name;
|
||||
}
|
||||
|
||||
|
||||
public final void afterPropertiesSet() throws PersistenceException {
|
||||
if (this.jpaVendorAdapter != null) {
|
||||
|
|
@ -317,14 +337,13 @@ public abstract class AbstractEntityManagerFactoryBean implements
|
|||
ifcs.addAll(ClassUtils.getAllInterfacesForClassAsSet(emf.getClass(), this.beanClassLoader));
|
||||
}
|
||||
ifcs.add(EntityManagerFactoryInfo.class);
|
||||
EntityManagerFactoryPlusOperations plusOperations = null;
|
||||
if (getJpaDialect() != null && getJpaDialect().supportsEntityManagerFactoryPlusOperations()) {
|
||||
plusOperations = getJpaDialect().getEntityManagerFactoryPlusOperations(emf);
|
||||
this.plusOperations = getJpaDialect().getEntityManagerFactoryPlusOperations(emf);
|
||||
ifcs.add(EntityManagerFactoryPlusOperations.class);
|
||||
}
|
||||
return (EntityManagerFactory) Proxy.newProxyInstance(
|
||||
this.beanClassLoader, ifcs.toArray(new Class[ifcs.size()]),
|
||||
new ManagedEntityManagerFactoryInvocationHandler(emf, this, plusOperations));
|
||||
new ManagedEntityManagerFactoryInvocationHandler(this));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -390,25 +409,67 @@ public abstract class AbstractEntityManagerFactoryBean implements
|
|||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Serialization support
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
Object invokeProxyMethod(Method method, Object[] args) throws Throwable {
|
||||
if (method.getDeclaringClass().isAssignableFrom(EntityManagerFactoryInfo.class)) {
|
||||
return method.invoke(this, args);
|
||||
}
|
||||
else if (method.getDeclaringClass().equals(EntityManagerFactoryPlusOperations.class)) {
|
||||
return method.invoke(this.plusOperations, args);
|
||||
}
|
||||
Object retVal = method.invoke(this.nativeEntityManagerFactory, args);
|
||||
if (retVal instanceof EntityManager) {
|
||||
EntityManager rawEntityManager = (EntityManager) retVal;
|
||||
retVal = ExtendedEntityManagerCreator.createApplicationManagedEntityManager(rawEntityManager, this);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
protected Object writeReplace() throws ObjectStreamException {
|
||||
if (this.beanFactory != null && this.beanName != null) {
|
||||
return new SerializedEntityManagerFactoryBeanReference(this.beanFactory, this.beanName);
|
||||
}
|
||||
else {
|
||||
throw new NotSerializableException("EntityManagerFactoryBean does not run within a BeanFactory");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Minimal bean reference to the surrounding AbstractEntityManagerFactoryBean.
|
||||
* Resolved to the actual AbstractEntityManagerFactoryBean instance on deserialization.
|
||||
*/
|
||||
private static class SerializedEntityManagerFactoryBeanReference implements Serializable {
|
||||
|
||||
private final BeanFactory beanFactory;
|
||||
|
||||
private final String lookupName;
|
||||
|
||||
public SerializedEntityManagerFactoryBeanReference(BeanFactory beanFactory, String beanName) {
|
||||
this.beanFactory = beanFactory;
|
||||
this.lookupName = BeanFactory.FACTORY_BEAN_PREFIX + beanName;
|
||||
}
|
||||
|
||||
private Object readResolve() {
|
||||
return this.beanFactory.getBean(this.lookupName, AbstractEntityManagerFactoryBean.class);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dynamic proxy invocation handler proxying an EntityManagerFactory to
|
||||
* return a proxy EntityManager if necessary from createEntityManager()
|
||||
* methods.
|
||||
*/
|
||||
private static class ManagedEntityManagerFactoryInvocationHandler implements InvocationHandler {
|
||||
private static class ManagedEntityManagerFactoryInvocationHandler implements InvocationHandler, Serializable {
|
||||
|
||||
private final EntityManagerFactory targetEntityManagerFactory;
|
||||
private final AbstractEntityManagerFactoryBean entityManagerFactoryBean;
|
||||
|
||||
private final EntityManagerFactoryInfo entityManagerFactoryInfo;
|
||||
|
||||
private final EntityManagerFactoryPlusOperations entityManagerFactoryPlusOperations;
|
||||
|
||||
public ManagedEntityManagerFactoryInvocationHandler(EntityManagerFactory targetEmf,
|
||||
EntityManagerFactoryInfo emfInfo, EntityManagerFactoryPlusOperations entityManagerFactoryPlusOperations) {
|
||||
|
||||
this.targetEntityManagerFactory = targetEmf;
|
||||
this.entityManagerFactoryInfo = emfInfo;
|
||||
this.entityManagerFactoryPlusOperations = entityManagerFactoryPlusOperations;
|
||||
public ManagedEntityManagerFactoryInvocationHandler(AbstractEntityManagerFactoryBean emfb) {
|
||||
this.entityManagerFactoryBean = emfb;
|
||||
}
|
||||
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
|
|
@ -421,20 +482,7 @@ public abstract class AbstractEntityManagerFactoryBean implements
|
|||
// Use hashCode of EntityManagerFactory proxy.
|
||||
return System.identityHashCode(proxy);
|
||||
}
|
||||
else if (method.getDeclaringClass().isAssignableFrom(EntityManagerFactoryInfo.class)) {
|
||||
return method.invoke(this.entityManagerFactoryInfo, args);
|
||||
}
|
||||
else if (method.getDeclaringClass().equals(EntityManagerFactoryPlusOperations.class)) {
|
||||
return method.invoke(this.entityManagerFactoryPlusOperations, args);
|
||||
}
|
||||
|
||||
Object retVal = method.invoke(this.targetEntityManagerFactory, args);
|
||||
if (retVal instanceof EntityManager) {
|
||||
EntityManager rawEntityManager = (EntityManager) retVal;
|
||||
retVal = ExtendedEntityManagerCreator.createApplicationManagedEntityManager(
|
||||
rawEntityManager, this.entityManagerFactoryInfo);
|
||||
}
|
||||
return retVal;
|
||||
return this.entityManagerFactoryBean.invokeProxyMethod(method, args);
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
throw ex.getTargetException();
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@
|
|||
|
||||
package org.springframework.orm.jpa;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
|
@ -123,7 +126,7 @@ public abstract class SharedEntityManagerCreator {
|
|||
* transactional EntityManager, if any; else, it will fall back
|
||||
* to a newly created EntityManager per operation.
|
||||
*/
|
||||
private static class SharedEntityManagerInvocationHandler implements InvocationHandler {
|
||||
private static class SharedEntityManagerInvocationHandler implements InvocationHandler, Serializable {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
|
|
@ -131,11 +134,15 @@ public abstract class SharedEntityManagerCreator {
|
|||
|
||||
private final Map properties;
|
||||
|
||||
private final ClassLoader proxyClassLoader;
|
||||
private transient volatile ClassLoader proxyClassLoader;
|
||||
|
||||
public SharedEntityManagerInvocationHandler(EntityManagerFactory target, Map properties) {
|
||||
this.targetFactory = target;
|
||||
this.properties = properties;
|
||||
initProxyClassLoader();
|
||||
}
|
||||
|
||||
private void initProxyClassLoader() {
|
||||
if (this.targetFactory instanceof EntityManagerFactoryInfo) {
|
||||
this.proxyClassLoader = ((EntityManagerFactoryInfo) this.targetFactory).getBeanClassLoader();
|
||||
}
|
||||
|
|
@ -254,6 +261,13 @@ public abstract class SharedEntityManagerCreator {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
|
||||
// Rely on default serialization, just initialize state after deserialization.
|
||||
ois.defaultReadObject();
|
||||
// Initialize transient fields.
|
||||
initProxyClassLoader();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -24,12 +24,14 @@ import javax.persistence.FlushModeType;
|
|||
import javax.persistence.NoResultException;
|
||||
import javax.persistence.Query;
|
||||
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.orm.jpa.domain.DriversLicense;
|
||||
import org.springframework.orm.jpa.domain.Person;
|
||||
import org.springframework.test.annotation.ExpectedException;
|
||||
import org.springframework.test.annotation.NotTransactional;
|
||||
import org.springframework.test.annotation.Repeat;
|
||||
import org.springframework.test.annotation.Timed;
|
||||
import org.springframework.util.SerializationTestUtils;
|
||||
|
||||
/**
|
||||
* Integration tests for LocalContainerEntityManagerFactoryBean.
|
||||
|
|
@ -252,4 +254,12 @@ public abstract class AbstractContainerEntityManagerFactoryIntegrationTests
|
|||
}
|
||||
}
|
||||
|
||||
public void testCanSerializeProxies() throws Exception {
|
||||
// just necessary because of AbstractJpaTests magically cloning the BeanFactory
|
||||
((DefaultListableBeanFactory) getApplicationContext().getBeanFactory()).setSerializationId("emf-it");
|
||||
|
||||
assertNotNull(SerializationTestUtils.serializeAndDeserialize(entityManagerFactory));
|
||||
assertNotNull(SerializationTestUtils.serializeAndDeserialize(sharedEntityManager));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -18,7 +18,6 @@ package org.springframework.orm.jpa;
|
|||
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.EntityTransaction;
|
||||
|
|
@ -30,12 +29,14 @@ import javax.persistence.spi.PersistenceUnitTransactionType;
|
|||
|
||||
import org.easymock.MockControl;
|
||||
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver;
|
||||
import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
|
||||
import org.springframework.util.SerializationTestUtils;
|
||||
|
||||
/**
|
||||
* @author Rod Johnson
|
||||
|
|
@ -74,6 +75,13 @@ public class LocalContainerEntityManagerFactoryBeanTests extends AbstractEntityM
|
|||
|
||||
assertNotSame("EMF must be proxied", mockEmf, emf);
|
||||
assertTrue(emf.equals(emf));
|
||||
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
bf.setSerializationId("emf-bf");
|
||||
bf.registerSingleton("emf", cefb);
|
||||
cefb.setBeanFactory(bf);
|
||||
cefb.setBeanName("emf");
|
||||
assertNotNull(SerializationTestUtils.serializeAndDeserialize(emf));
|
||||
}
|
||||
|
||||
public void testApplicationManagedEntityManagerWithoutTransaction() throws Exception {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -56,7 +56,7 @@ import org.springframework.util.SerializationTestUtils;
|
|||
*/
|
||||
public class PersistenceInjectionTests extends AbstractEntityManagerFactoryBeanTests {
|
||||
|
||||
public void testPrivatePersistenceContextField() {
|
||||
public void testPrivatePersistenceContextField() throws Exception {
|
||||
GenericApplicationContext gac = new GenericApplicationContext();
|
||||
gac.getDefaultListableBeanFactory().registerSingleton("entityManagerFactory", mockEmf);
|
||||
gac.registerBeanDefinition("annotationProcessor",
|
||||
|
|
@ -73,6 +73,9 @@ public class PersistenceInjectionTests extends AbstractEntityManagerFactoryBeanT
|
|||
"&" + FactoryBeanWithPersistenceContextField.class.getName());
|
||||
assertNotNull(bean.em);
|
||||
assertNotNull(bean2.em);
|
||||
|
||||
assertNotNull(SerializationTestUtils.serializeAndDeserialize(bean.em));
|
||||
assertNotNull(SerializationTestUtils.serializeAndDeserialize(bean2.em));
|
||||
}
|
||||
|
||||
public void testPrivateVendorSpecificPersistenceContextField() {
|
||||
|
|
@ -111,8 +114,7 @@ public class PersistenceInjectionTests extends AbstractEntityManagerFactoryBeanT
|
|||
|
||||
public void testPublicExtendedPersistenceContextSetterWithSerialization() throws Exception {
|
||||
DummyInvocationHandler ih = new DummyInvocationHandler();
|
||||
Object mockEm = (EntityManager) Proxy.newProxyInstance(
|
||||
getClass().getClassLoader(), new Class[] {EntityManager.class}, ih);
|
||||
Object mockEm = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] {EntityManager.class}, ih);
|
||||
mockEmf.createEntityManager();
|
||||
emfMc.setReturnValue(mockEm, 1);
|
||||
emfMc.replay();
|
||||
|
|
@ -141,7 +143,7 @@ public class PersistenceInjectionTests extends AbstractEntityManagerFactoryBeanT
|
|||
}
|
||||
|
||||
public void testPublicExtendedPersistenceContextSetterWithEntityManagerInfoAndSerialization() throws Exception {
|
||||
Object mockEm = (EntityManager) Proxy.newProxyInstance(
|
||||
Object mockEm = Proxy.newProxyInstance(
|
||||
getClass().getClassLoader(), new Class[] {EntityManager.class}, new DummyInvocationHandler());
|
||||
MockControl emfMc = MockControl.createControl(EntityManagerFactoryWithInfo.class);
|
||||
EntityManagerFactoryWithInfo mockEmf = (EntityManagerFactoryWithInfo) emfMc.getMock();
|
||||
|
|
@ -175,7 +177,7 @@ public class PersistenceInjectionTests extends AbstractEntityManagerFactoryBeanT
|
|||
}
|
||||
|
||||
public void testPublicExtendedPersistenceContextSetterWithOverriding() {
|
||||
EntityManager mockEm2 = (EntityManager) MockControl.createControl(EntityManager.class).getMock();
|
||||
EntityManager mockEm2 = MockControl.createControl(EntityManager.class).getMock();
|
||||
|
||||
GenericApplicationContext gac = new GenericApplicationContext();
|
||||
gac.getDefaultListableBeanFactory().registerSingleton("entityManagerFactory", mockEmf);
|
||||
|
|
|
|||
Loading…
Reference in New Issue