SharedEntityManagerCreator's EntityManager proxies are fully serializable now (SPR-6684)

This commit is contained in:
Juergen Hoeller 2010-02-01 14:48:18 +00:00
parent df54c8613d
commit db71811c5a
5 changed files with 125 additions and 43 deletions

View File

@ -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();

View File

@ -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();
}
}

View File

@ -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));
}
}

View File

@ -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 {

View File

@ -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);