From 266a65982d5651e735ed3e968f820a2fdffa50c1 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 7 May 2009 22:29:55 +0000 Subject: [PATCH] Servlet/Portlet ApplicationContexts use a specific id based on servlet/portlet name; DefaultListableBeanFactory references are serializable now when initialized with an id; scoped proxies are serializable now, for web scopes as well as for singleton beans; injected request/session references are serializable proxies for the current request now --- .../resources/changelog.txt | 10 ++ .../aop/scope/DefaultScopedObject.java | 8 +- .../AbstractBeanFactoryBasedTargetSource.java | 25 +---- .../AbstractPrototypeBasedTargetSource.java | 35 +++++- .../test/util/SerializationTestUtils.java | 28 +++-- .../factory/config/AbstractFactoryBean.java | 5 +- .../factory/support/AbstractBeanFactory.java | 4 +- .../beans/factory/support/AutowireUtils.java | 58 +++++++++- .../support/DefaultListableBeanFactory.java | 73 ++++++++++++- .../ConfigurableApplicationContext.java | 7 +- .../support/AbstractApplicationContext.java | 9 +- ...AbstractRefreshableApplicationContext.java | 10 ++ .../support/GenericApplicationContext.java | 10 +- .../aop/scope/ScopedProxyTests.java | 31 +++++- .../ComponentScanParserScopedProxyTests.java | 24 +++-- .../util/SerializationTestUtils.java | 60 +++-------- ...ceHandlerScopeIntegrationTests-context.xml | 5 + ...NamespaceHandlerScopeIntegrationTests.java | 29 +++-- .../test/util/SerializationTestUtils.java | 100 ++++++++++++++++++ .../web/portlet/FrameworkPortlet.java | 1 + .../PortletApplicationContextUtils.java | 61 +++++++---- .../web/servlet/FrameworkServlet.java | 1 + .../util/SerializationTestUtils.java | 65 ++++++++++++ .../ServletAnnotationControllerTests.java | 27 +++-- .../web/context/ContextLoader.java | 1 + .../support/WebApplicationContextUtils.java | 62 +++++++---- 26 files changed, 582 insertions(+), 167 deletions(-) create mode 100644 org.springframework.integration-tests/src/test/java/test/util/SerializationTestUtils.java create mode 100644 org.springframework.web.servlet/src/test/java/org/springframework/util/SerializationTestUtils.java diff --git a/build-spring-framework/resources/changelog.txt b/build-spring-framework/resources/changelog.txt index f1c47dc6c0..a747bea726 100644 --- a/build-spring-framework/resources/changelog.txt +++ b/build-spring-framework/resources/changelog.txt @@ -3,6 +3,16 @@ SPRING FRAMEWORK CHANGELOG http://www.springsource.org +Changes in version 3.0.0.RC1 (2009-06-10) +----------------------------------------- + +* Servlet/Portlet ApplicationContexts use a specific id based on servlet/portlet name +* DefaultListableBeanFactory references are serializable now when initialized with an id +* scoped proxies are serializable now, for web scopes as well as for singleton beans +* injected request/session references are serializable proxies for the current request now +* ReloadableResourceBundleMessageSource correctly calculates filenames for all locales now + + Changes in version 3.0.0.M3 (2009-05-06) ---------------------------------------- diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/scope/DefaultScopedObject.java b/org.springframework.aop/src/main/java/org/springframework/aop/scope/DefaultScopedObject.java index ea541fb0a7..77c3a107ab 100644 --- a/org.springframework.aop/src/main/java/org/springframework/aop/scope/DefaultScopedObject.java +++ b/org.springframework.aop/src/main/java/org/springframework/aop/scope/DefaultScopedObject.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2006 the original author or authors. + * Copyright 2002-2009 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,8 @@ package org.springframework.aop.scope; +import java.io.Serializable; + import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.util.Assert; @@ -32,7 +34,7 @@ import org.springframework.util.Assert; * @see org.springframework.beans.factory.BeanFactory#getBean * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#destroyScopedBean */ -public class DefaultScopedObject implements ScopedObject { +public class DefaultScopedObject implements ScopedObject, Serializable { private final ConfigurableBeanFactory beanFactory; @@ -43,8 +45,6 @@ public class DefaultScopedObject implements ScopedObject { * Creates a new instance of the {@link DefaultScopedObject} class. * @param beanFactory the {@link ConfigurableBeanFactory} that holds the scoped target object * @param targetBeanName the name of the target bean - * @throws IllegalArgumentException if either of the parameters is null; or - * if the targetBeanName consists wholly of whitespace */ public DefaultScopedObject(ConfigurableBeanFactory beanFactory, String targetBeanName) { Assert.notNull(beanFactory, "BeanFactory must not be null"); diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/target/AbstractBeanFactoryBasedTargetSource.java b/org.springframework.aop/src/main/java/org/springframework/aop/target/AbstractBeanFactoryBasedTargetSource.java index f8a0e3a50d..2bd3e3b44c 100644 --- a/org.springframework.aop/src/main/java/org/springframework/aop/target/AbstractBeanFactoryBasedTargetSource.java +++ b/org.springframework.aop/src/main/java/org/springframework/aop/target/AbstractBeanFactoryBasedTargetSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 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. @@ -157,29 +157,6 @@ public abstract class AbstractBeanFactoryBasedTargetSource this.beanFactory = other.beanFactory; } - /** - * Replaces this object with a SingletonTargetSource on serialization. - * Protected as otherwise it won't be invoked for subclasses. - * (The writeReplace() method must be visible to the class - * being serialized.) - *

With this implementation of this method, there is no need to mark - * non-serializable fields in this class or subclasses as transient. - */ - protected Object writeReplace() throws ObjectStreamException { - if (logger.isDebugEnabled()) { - logger.debug("Disconnecting TargetSource [" + this + "]"); - } - try { - // Create disconnected SingletonTargetSource. - return new SingletonTargetSource(getTarget()); - } - catch (Exception ex) { - logger.error("Cannot get target for disconnecting TargetSource [" + this + "]", ex); - throw new NotSerializableException( - "Cannot get target for disconnecting TargetSource [" + this + "]: " + ex); - } - } - @Override public boolean equals(Object other) { diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java b/org.springframework.aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java index 605b29d3a0..6ce2bca6ec 100644 --- a/org.springframework.aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java +++ b/org.springframework.aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java @@ -16,6 +16,9 @@ package org.springframework.aop.target; +import java.io.NotSerializableException; +import java.io.ObjectStreamException; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanFactory; @@ -23,8 +26,8 @@ import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.config.ConfigurableBeanFactory; /** - * Base class for dynamic TargetSources that can create new prototype bean - * instances to support a pooling or new-instance-per-invocation strategy. + * Base class for dynamic {@link TargetSource} implementations that create new prototype + * bean instances to support a pooling or new-instance-per-invocation strategy. * *

Such TargetSources must run in a {@link BeanFactory}, as it needs to * call the getBean method to create a new prototype instance. @@ -83,4 +86,32 @@ public abstract class AbstractPrototypeBasedTargetSource extends AbstractBeanFac } } + + //--------------------------------------------------------------------- + // Serialization support + //--------------------------------------------------------------------- + + /** + * Replaces this object with a SingletonTargetSource on serialization. + * Protected as otherwise it won't be invoked for subclasses. + * (The writeReplace() method must be visible to the class + * being serialized.) + *

With this implementation of this method, there is no need to mark + * non-serializable fields in this class or subclasses as transient. + */ + protected Object writeReplace() throws ObjectStreamException { + if (logger.isDebugEnabled()) { + logger.debug("Disconnecting TargetSource [" + this + "]"); + } + try { + // Create disconnected SingletonTargetSource. + return new SingletonTargetSource(getTarget()); + } + catch (Exception ex) { + logger.error("Cannot get target for disconnecting TargetSource [" + this + "]", ex); + throw new NotSerializableException( + "Cannot get target for disconnecting TargetSource [" + this + "]: " + ex); + } + } + } diff --git a/org.springframework.aop/src/test/java/test/util/SerializationTestUtils.java b/org.springframework.aop/src/test/java/test/util/SerializationTestUtils.java index cd7df05058..74130ff343 100644 --- a/org.springframework.aop/src/test/java/test/util/SerializationTestUtils.java +++ b/org.springframework.aop/src/test/java/test/util/SerializationTestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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,9 +16,7 @@ package test.util; -import static org.junit.Assert.*; - -import java.awt.Point; +import java.awt.*; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -28,8 +26,8 @@ import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Serializable; +import static org.junit.Assert.*; import org.junit.Test; - import test.beans.TestBean; /** @@ -41,13 +39,13 @@ import test.beans.TestBean; * @author Chris Beams */ public final class SerializationTestUtils { - + public static void testSerialization(Object o) throws IOException { OutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(o); } - + public static boolean isSerializable(Object o) throws IOException { try { testSerialization(o); @@ -57,7 +55,7 @@ public final class SerializationTestUtils { return false; } } - + public static Object serializeAndDeserialize(Object o) throws IOException, ClassNotFoundException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); @@ -69,30 +67,30 @@ public final class SerializationTestUtils { ByteArrayInputStream is = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(is); Object o2 = ois.readObject(); - return o2; } - + + @Test(expected=NotSerializableException.class) public void testWithNonSerializableObject() throws IOException { TestBean o = new TestBean(); assertFalse(o instanceof Serializable); assertFalse(isSerializable(o)); - + testSerialization(o); } - + @Test public void testWithSerializableObject() throws Exception { int x = 5; int y = 10; Point p = new Point(x, y); assertTrue(p instanceof Serializable); - + testSerialization(p); - + assertTrue(isSerializable(p)); - + Point p2 = (Point) serializeAndDeserialize(p); assertNotSame(p, p2); assertEquals(x, (int) p2.getX()); diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java index 6bb7a2aaa2..2b0cfa9b48 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -236,6 +236,9 @@ public abstract class AbstractFactoryBean } + /** + * Reflective InvocationHandler for lazy access to the actual singleton object. + */ private class EarlySingletonInvocationHandler implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index e1c5db7069..b14c87110c 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -868,12 +868,12 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); if (mbd.isSingleton() || mbd.isPrototype()) { throw new IllegalArgumentException( - "Bean name '" + beanName + "' does not correspond to an object in a Scope"); + "Bean name '" + beanName + "' does not correspond to an object in a mutable scope"); } String scopeName = mbd.getScope(); Scope scope = this.scopes.get(scopeName); if (scope == null) { - throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'"); + throw new IllegalStateException("No Scope SPI registered for scope '" + scopeName + "'"); } Object bean = scope.remove(beanName); if (bean != null) { diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java index ac19224baf..4d83d9c8e2 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -17,13 +17,18 @@ package org.springframework.beans.factory.support; import java.beans.PropertyDescriptor; +import java.io.Serializable; import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.Comparator; import java.util.Set; +import org.springframework.beans.factory.ObjectFactory; import org.springframework.util.ClassUtils; /** @@ -123,4 +128,55 @@ abstract class AutowireUtils { return false; } + /** + * Resolve the given autowiring value against the given required type, + * e.g. an {@link ObjectFactory} value to its actual object result. + * @param autowiringValue the value to resolve + * @param requiredType the type to assign the result to + * @return the resolved value + */ + public static Object resolveAutowiringValue(Object autowiringValue, Class requiredType) { + if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue) && + autowiringValue instanceof Serializable && requiredType.isInterface()) { + autowiringValue = Proxy.newProxyInstance( + requiredType.getClassLoader(), new Class[] {requiredType}, + new ObjectFactoryDelegatingInvocationHandler((ObjectFactory) autowiringValue)); + } + return autowiringValue; + } + + + /** + * Reflective InvocationHandler for lazy access to the current target object. + */ + private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable { + + private final ObjectFactory objectFactory; + + public ObjectFactoryDelegatingInvocationHandler(ObjectFactory objectFactory) { + this.objectFactory = objectFactory; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + String methodName = method.getName(); + if (methodName.equals("equals")) { + // Only consider equal when proxies are identical. + return (proxy == args[0]); + } + else if (methodName.equals("hashCode")) { + // Use hashCode of proxy. + return System.identityHashCode(proxy); + } + else if (methodName.equals("toString")) { + return this.objectFactory.toString(); + } + try { + return method.invoke(this.objectFactory.getObject(), args); + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + } + } diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index cb8dbb70f5..e50cb973f9 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -16,7 +16,11 @@ package org.springframework.beans.factory.support; +import java.io.ObjectStreamException; +import java.io.Serializable; import java.lang.annotation.Annotation; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -81,7 +85,14 @@ import org.springframework.util.StringUtils; * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader */ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory - implements ConfigurableListableBeanFactory, BeanDefinitionRegistry { + implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable { + + /** Map from serialized id to factory instance */ + private static final Map> serializableFactories = + new ConcurrentHashMap>(); + + /** Optional id for this factory, for serialization purposes */ + private String serializationId; /** Whether to allow re-registration of a different definition with the same name */ private boolean allowBeanDefinitionOverriding = true; @@ -127,6 +138,20 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } + /** + * Specify an id for serialization purposes, allowing this BeanFactory to be + * deserialized from this id back into the BeanFactory object, if needed. + */ + public void setSerializationId(String serializationId) { + if (serializationId != null) { + serializableFactories.put(serializationId, new WeakReference(this)); + } + else if (this.serializationId != null) { + serializableFactories.remove(this.serializationId); + } + this.serializationId = serializationId; + } + /** * Set whether it should be allowed to override bean definitions by registering * a different definition with the same name, automatically replacing the former. @@ -724,9 +749,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto for (Class autowiringType : this.resolvableDependencies.keySet()) { if (autowiringType.isAssignableFrom(requiredType)) { Object autowiringValue = this.resolvableDependencies.get(autowiringType); - if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) { - autowiringValue = ((ObjectFactory) autowiringValue).getObject(); - } + autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType); if (requiredType.isInstance(autowiringValue)) { result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue); break; @@ -824,4 +847,46 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto return sb.toString(); } + + //--------------------------------------------------------------------- + // Serialization support + //--------------------------------------------------------------------- + + protected Object writeReplace() throws ObjectStreamException { + if (this.serializationId != null) { + return new SerializedBeanFactoryReference(this.serializationId); + } + else { + return this; + } + } + + + /** + * Minimal id reference to the factory. + * Resolved to the actual factory instance on deserialization. + */ + private static class SerializedBeanFactoryReference implements Serializable { + + private final String id; + + public SerializedBeanFactoryReference(String id) { + this.id = id; + } + + private Object readResolve() { + Reference ref = serializableFactories.get(this.id); + if (ref == null) { + throw new IllegalStateException( + "Cannot deserialize BeanFactory with id " + this.id + ": no factory registered for this id"); + } + Object result = ref.get(); + if (result == null) { + throw new IllegalStateException( + "Cannot deserialize BeanFactory with id " + this.id + ": factory has been garbage-collected"); + } + return result; + } + } + } diff --git a/org.springframework.context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java index 6854cd1aa8..e00ff721d2 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -65,6 +65,11 @@ public interface ConfigurableApplicationContext extends ApplicationContext, Life String SYSTEM_ENVIRONMENT_BEAN_NAME = "systemEnvironment"; + /** + * Set the unique id of this application context. + */ + void setId(String id); + /** * Set the parent of this application context. *

Note that the parent shouldn't be changed: It should only be set outside diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index 212f7ec31f..0b417ac5ac 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -382,7 +382,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader catch (BeansException ex) { // Destroy already created singletons to avoid dangling resources. - beanFactory.destroySingletons(); + destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); @@ -794,7 +794,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader * destroys the singletons in the bean factory of this application context. *

Called by both close() and a JVM shutdown hook, if any. * @see org.springframework.context.event.ContextClosedEvent - * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#destroySingletons() + * @see #destroyBeans() * @see #close() * @see #registerShutdownHook() */ @@ -803,6 +803,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader if (logger.isInfoEnabled()) { logger.info("Closing " + this); } + try { // Publish shutdown event. publishEvent(new ContextClosedEvent(this)); @@ -810,15 +811,19 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader catch (Throwable ex) { logger.error("Exception thrown from ApplicationListener handling ContextClosedEvent", ex); } + // Stop all Lifecycle beans, to avoid delays during individual destruction. Map lifecycleBeans = getLifecycleBeans(); for (String beanName : new LinkedHashSet(lifecycleBeans.keySet())) { doStop(lifecycleBeans, beanName); } + // Destroy all cached singletons in the context's BeanFactory. destroyBeans(); + // Close the state of this context itself. closeBeanFactory(); + onClose(); synchronized (this.activeMonitor) { this.active = false; diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java index 2eee9bf0d2..161ecdce94 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java @@ -122,6 +122,7 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl } try { DefaultListableBeanFactory beanFactory = createBeanFactory(); + beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { @@ -134,9 +135,18 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl } } + @Override + protected void cancelRefresh(BeansException ex) { + synchronized (this.beanFactoryMonitor) { + this.beanFactory.setSerializationId(null); + } + super.cancelRefresh(ex); + } + @Override protected final void closeBeanFactory() { synchronized (this.beanFactoryMonitor) { + this.beanFactory.setSerializationId(null); this.beanFactory = null; } } diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/GenericApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/support/GenericApplicationContext.java index 11b5370f37..cc0218172c 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/GenericApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/GenericApplicationContext.java @@ -99,6 +99,7 @@ public class GenericApplicationContext extends AbstractApplicationContext implem */ public GenericApplicationContext() { this.beanFactory = new DefaultListableBeanFactory(); + this.beanFactory.setSerializationId(getId()); this.beanFactory.setParameterNameDiscoverer(new LocalVariableTableParameterNameDiscoverer()); this.beanFactory.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); } @@ -149,6 +150,12 @@ public class GenericApplicationContext extends AbstractApplicationContext implem this.beanFactory.setParentBeanFactory(getInternalParentBeanFactory()); } + @Override + public void setId(String id) { + super.setId(id); + this.beanFactory.setSerializationId(id); + } + /** * Set a ResourceLoader to use for this context. If set, the context will * delegate all getResource calls to the given ResourceLoader. @@ -219,11 +226,12 @@ public class GenericApplicationContext extends AbstractApplicationContext implem } /** - * Do nothing: We hold a single internal BeanFactory that will never + * Not much to do: We hold a single internal BeanFactory that will never * get released. */ @Override protected final void closeBeanFactory() { + this.beanFactory.setSerializationId(null); } /** diff --git a/org.springframework.context/src/test/java/org/springframework/aop/scope/ScopedProxyTests.java b/org.springframework.context/src/test/java/org/springframework/aop/scope/ScopedProxyTests.java index 291dc7f81a..8c3cfa094b 100644 --- a/org.springframework.context/src/test/java/org/springframework/aop/scope/ScopedProxyTests.java +++ b/org.springframework.context/src/test/java/org/springframework/aop/scope/ScopedProxyTests.java @@ -16,13 +16,13 @@ package org.springframework.aop.scope; -import static org.junit.Assert.*; - import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import static org.junit.Assert.*; import org.junit.Test; + import org.springframework.aop.support.AopUtils; import org.springframework.beans.ITestBean; import org.springframework.beans.TestBean; @@ -31,6 +31,7 @@ import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.io.ClassPathResource; +import org.springframework.util.SerializationTestUtils; /** * @author Rob Harrop @@ -82,6 +83,7 @@ public class ScopedProxyTests { @Test public void testJdkScopedProxy() throws Exception { XmlBeanFactory bf = new XmlBeanFactory(TESTBEAN_CONTEXT); + bf.setSerializationId("X"); SimpleMapScope scope = new SimpleMapScope(); bf.registerScope("request", scope); @@ -91,14 +93,26 @@ public class ScopedProxyTests { assertTrue(bean instanceof ScopedObject); ScopedObject scoped = (ScopedObject) bean; assertEquals(TestBean.class, scoped.getTargetObject().getClass()); + bean.setAge(101); assertTrue(scope.getMap().containsKey("testBeanTarget")); assertEquals(TestBean.class, scope.getMap().get("testBeanTarget").getClass()); + + ITestBean deserialized = (ITestBean) SerializationTestUtils.serializeAndDeserialize(bean); + assertNotNull(deserialized); + assertTrue(AopUtils.isJdkDynamicProxy(deserialized)); + assertEquals(101, bean.getAge()); + assertTrue(deserialized instanceof ScopedObject); + ScopedObject scopedDeserialized = (ScopedObject) deserialized; + assertEquals(TestBean.class, scopedDeserialized.getTargetObject().getClass()); + + bf.setSerializationId(null); } @Test - public void testCglibScopedProxy() { + public void testCglibScopedProxy() throws Exception { XmlBeanFactory bf = new XmlBeanFactory(LIST_CONTEXT); + bf.setSerializationId("Y"); SimpleMapScope scope = new SimpleMapScope(); bf.registerScope("request", scope); @@ -107,9 +121,20 @@ public class ScopedProxyTests { assertTrue(tb.getFriends() instanceof ScopedObject); ScopedObject scoped = (ScopedObject) tb.getFriends(); assertEquals(ArrayList.class, scoped.getTargetObject().getClass()); + tb.getFriends().add("myFriend"); assertTrue(scope.getMap().containsKey("scopedTarget.scopedList")); assertEquals(ArrayList.class, scope.getMap().get("scopedTarget.scopedList").getClass()); + + ArrayList deserialized = (ArrayList) SerializationTestUtils.serializeAndDeserialize(tb.getFriends()); + assertNotNull(deserialized); + assertTrue(AopUtils.isCglibProxy(deserialized)); + assertTrue(deserialized.contains("myFriend")); + assertTrue(deserialized instanceof ScopedObject); + ScopedObject scopedDeserialized = (ScopedObject) deserialized; + assertEquals(ArrayList.class, scopedDeserialized.getTargetObject().getClass()); + + bf.setSerializationId(null); } } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanParserScopedProxyTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanParserScopedProxyTests.java index d806807dd3..4cbfd9b34e 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanParserScopedProxyTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanParserScopedProxyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 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,16 +16,16 @@ package org.springframework.context.annotation; +import example.scannable.FooService; +import example.scannable.ScopedProxyTestBean; import static org.junit.Assert.*; - import org.junit.Test; + import org.springframework.aop.support.AopUtils; import org.springframework.beans.FatalBeanException; import org.springframework.beans.factory.config.SimpleMapScope; import org.springframework.context.support.ClassPathXmlApplicationContext; - -import example.scannable.FooService; -import example.scannable.ScopedProxyTestBean; +import org.springframework.util.SerializationTestUtils; /** * @author Mark Fisher @@ -54,7 +54,7 @@ public class ComponentScanParserScopedProxyTests { } @Test - public void testInterfacesScopedProxy() { + public void testInterfacesScopedProxy() throws Exception { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "org/springframework/context/annotation/scopedProxyInterfacesTests.xml"); context.getBeanFactory().registerScope("myScope", new SimpleMapScope()); @@ -62,16 +62,26 @@ public class ComponentScanParserScopedProxyTests { FooService bean = (FooService) context.getBean("scopedProxyTestBean"); // should be dynamic proxy assertTrue(AopUtils.isJdkDynamicProxy(bean)); + // test serializability + assertEquals("bar", bean.foo(1)); + FooService deserialized = (FooService) SerializationTestUtils.serializeAndDeserialize(bean); + assertNotNull(deserialized); + assertEquals("bar", deserialized.foo(1)); } @Test - public void testTargetClassScopedProxy() { + public void testTargetClassScopedProxy() throws Exception { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "org/springframework/context/annotation/scopedProxyTargetClassTests.xml"); context.getBeanFactory().registerScope("myScope", new SimpleMapScope()); ScopedProxyTestBean bean = (ScopedProxyTestBean) context.getBean("scopedProxyTestBean"); // should be a class-based proxy assertTrue(AopUtils.isCglibProxy(bean)); + // test serializability + assertEquals("bar", bean.foo(1)); + ScopedProxyTestBean deserialized = (ScopedProxyTestBean) SerializationTestUtils.serializeAndDeserialize(bean); + assertNotNull(deserialized); + assertEquals("bar", deserialized.foo(1)); } @Test diff --git a/org.springframework.context/src/test/java/org/springframework/util/SerializationTestUtils.java b/org.springframework.context/src/test/java/org/springframework/util/SerializationTestUtils.java index bdffe7578d..f42512ccb0 100644 --- a/org.springframework.context/src/test/java/org/springframework/util/SerializationTestUtils.java +++ b/org.springframework.context/src/test/java/org/springframework/util/SerializationTestUtils.java @@ -1,11 +1,21 @@ /* - * The Spring Framework is published under the terms - * of the Apache Software License. + * Copyright 2002-2009 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.springframework.util; -import java.awt.Point; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -13,20 +23,14 @@ import java.io.NotSerializableException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; -import java.io.Serializable; - -import junit.framework.TestCase; - -import org.springframework.beans.TestBean; /** * Utilities for testing serializability of objects. * Exposes static methods for use in other test cases. - * Extends TestCase only to test itself. * * @author Rod Johnson */ -public class SerializationTestUtils extends TestCase { +public class SerializationTestUtils { public static void testSerialization(Object o) throws IOException { OutputStream baos = new ByteArrayOutputStream(); @@ -55,43 +59,7 @@ public class SerializationTestUtils extends TestCase { ByteArrayInputStream is = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(is); Object o2 = ois.readObject(); - return o2; } - - public SerializationTestUtils(String s) { - super(s); - } - - public void testWithNonSerializableObject() throws IOException { - TestBean o = new TestBean(); - assertFalse(o instanceof Serializable); - - assertFalse(isSerializable(o)); - - try { - testSerialization(o); - fail(); - } - catch (NotSerializableException ex) { - // Ok - } - } - - public void testWithSerializableObject() throws Exception { - int x = 5; - int y = 10; - Point p = new Point(x, y); - assertTrue(p instanceof Serializable); - - testSerialization(p); - - assertTrue(isSerializable(p)); - - Point p2 = (Point) serializeAndDeserialize(p); - assertNotSame(p, p2); - assertEquals(x, (int) p2.getX()); - assertEquals(y, (int) p2.getY()); - } } diff --git a/org.springframework.integration-tests/src/test/java/org/springframework/aop/config/AopNamespaceHandlerScopeIntegrationTests-context.xml b/org.springframework.integration-tests/src/test/java/org/springframework/aop/config/AopNamespaceHandlerScopeIntegrationTests-context.xml index add6b7e481..4be55f0ea7 100644 --- a/org.springframework.integration-tests/src/test/java/org/springframework/aop/config/AopNamespaceHandlerScopeIntegrationTests-context.xml +++ b/org.springframework.integration-tests/src/test/java/org/springframework/aop/config/AopNamespaceHandlerScopeIntegrationTests-context.xml @@ -13,6 +13,11 @@ + + + + + diff --git a/org.springframework.integration-tests/src/test/java/org/springframework/aop/config/AopNamespaceHandlerScopeIntegrationTests.java b/org.springframework.integration-tests/src/test/java/org/springframework/aop/config/AopNamespaceHandlerScopeIntegrationTests.java index 78b24f5aab..fa5feaeed6 100644 --- a/org.springframework.integration-tests/src/test/java/org/springframework/aop/config/AopNamespaceHandlerScopeIntegrationTests.java +++ b/org.springframework.integration-tests/src/test/java/org/springframework/aop/config/AopNamespaceHandlerScopeIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 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. @@ -17,23 +17,24 @@ package org.springframework.aop.config; import static java.lang.String.format; -import static org.junit.Assert.*; -import static org.springframework.util.ClassUtils.convertClassNameToResourcePath; +import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; +import test.beans.ITestBean; +import test.beans.TestBean; +import test.util.SerializationTestUtils; + import org.springframework.aop.framework.Advised; import org.springframework.aop.support.AopUtils; import org.springframework.context.ApplicationContext; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpSession; +import org.springframework.util.ClassUtils; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.support.XmlWebApplicationContext; -import test.beans.ITestBean; -import test.beans.TestBean; - /** * Integration tests for scoped proxy use in conjunction with aop: namespace. * Deemed an integration test because .web mocks and application contexts are required. @@ -47,7 +48,7 @@ import test.beans.TestBean; public final class AopNamespaceHandlerScopeIntegrationTests { private static final String CLASSNAME = AopNamespaceHandlerScopeIntegrationTests.class.getName(); - private static final String CONTEXT = format("classpath:%s-context.xml", convertClassNameToResourcePath(CLASSNAME)); + private static final String CONTEXT = format("classpath:%s-context.xml", ClassUtils.convertClassNameToResourcePath(CLASSNAME)); private ApplicationContext context; @@ -59,6 +60,20 @@ public final class AopNamespaceHandlerScopeIntegrationTests { this.context = wac; } + @Test + public void testSingletonScoping() throws Exception { + ITestBean scoped = (ITestBean) this.context.getBean("singletonScoped"); + assertTrue("Should be AOP proxy", AopUtils.isAopProxy(scoped)); + assertTrue("Should be target class proxy", scoped instanceof TestBean); + String rob = "Rob Harrop"; + String bram = "Bram Smeets"; + assertEquals(rob, scoped.getName()); + scoped.setName(bram); + assertEquals(bram, scoped.getName()); + ITestBean deserialized = (ITestBean) SerializationTestUtils.serializeAndDeserialize(scoped); + assertEquals(bram, deserialized.getName()); + } + @Test public void testRequestScoping() throws Exception { MockHttpServletRequest oldRequest = new MockHttpServletRequest(); diff --git a/org.springframework.integration-tests/src/test/java/test/util/SerializationTestUtils.java b/org.springframework.integration-tests/src/test/java/test/util/SerializationTestUtils.java new file mode 100644 index 0000000000..609ae77e00 --- /dev/null +++ b/org.springframework.integration-tests/src/test/java/test/util/SerializationTestUtils.java @@ -0,0 +1,100 @@ +/* + * Copyright 2002-2009 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package test.util; + +import java.awt.*; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.NotSerializableException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.Serializable; + +import static org.junit.Assert.*; +import org.junit.Test; +import test.beans.TestBean; + +/** + * Utilities for testing serializability of objects. + * Exposes static methods for use in other test cases. + * Contains {@link org.junit.Test} methods to test itself. + * + * @author Rod Johnson + * @author Chris Beams + */ +public final class SerializationTestUtils { + + public static void testSerialization(Object o) throws IOException { + OutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(o); + } + + public static boolean isSerializable(Object o) throws IOException { + try { + testSerialization(o); + return true; + } + catch (NotSerializableException ex) { + return false; + } + } + + public static Object serializeAndDeserialize(Object o) throws IOException, ClassNotFoundException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(o); + oos.flush(); + baos.flush(); + byte[] bytes = baos.toByteArray(); + + ByteArrayInputStream is = new ByteArrayInputStream(bytes); + ObjectInputStream ois = new ObjectInputStream(is); + Object o2 = ois.readObject(); + return o2; + } + + + @Test(expected=NotSerializableException.class) + public void testWithNonSerializableObject() throws IOException { + TestBean o = new TestBean(); + assertFalse(o instanceof Serializable); + assertFalse(isSerializable(o)); + + testSerialization(o); + } + + @Test + public void testWithSerializableObject() throws Exception { + int x = 5; + int y = 10; + Point p = new Point(x, y); + assertTrue(p instanceof Serializable); + + testSerialization(p); + + assertTrue(isSerializable(p)); + + Point p2 = (Point) serializeAndDeserialize(p); + assertNotSame(p, p2); + assertEquals(x, (int) p2.getX()); + assertEquals(y, (int) p2.getY()); + } + +} diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/FrameworkPortlet.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/FrameworkPortlet.java index 3797f58621..a59f053ae0 100644 --- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/FrameworkPortlet.java +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/FrameworkPortlet.java @@ -340,6 +340,7 @@ public abstract class FrameworkPortlet extends GenericPortletBean ConfigurablePortletApplicationContext pac = (ConfigurablePortletApplicationContext) BeanUtils.instantiateClass(getContextClass()); + pac.setId(getPortletContext().getPortletContextName() + "." + getPortletName()); pac.setParent(parent); pac.setPortletContext(getPortletContext()); pac.setPortletConfig(getPortletConfig()); diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletApplicationContextUtils.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletApplicationContextUtils.java index 71b65c5186..9346983916 100644 --- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletApplicationContextUtils.java +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletApplicationContextUtils.java @@ -16,6 +16,7 @@ package org.springframework.web.portlet.context; +import java.io.Serializable; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; @@ -120,24 +121,8 @@ public abstract class PortletApplicationContextUtils { pc.setAttribute(PortletContextScope.class.getName(), appScope); } - beanFactory.registerResolvableDependency(PortletRequest.class, new ObjectFactory() { - public PortletRequest getObject() { - RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes(); - if (!(requestAttr instanceof PortletRequestAttributes)) { - throw new IllegalStateException("Current request is not a portlet request"); - } - return ((PortletRequestAttributes) requestAttr).getRequest(); - } - }); - beanFactory.registerResolvableDependency(PortletSession.class, new ObjectFactory() { - public PortletSession getObject() { - RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes(); - if (!(requestAttr instanceof PortletRequestAttributes)) { - throw new IllegalStateException("Current request is not a portlet request"); - } - return ((PortletRequestAttributes) requestAttr).getRequest().getPortletSession(); - } - }); + beanFactory.registerResolvableDependency(PortletRequest.class, new RequestObjectFactory()); + beanFactory.registerResolvableDependency(PortletSession.class, new SessionObjectFactory()); } /** @@ -197,4 +182,44 @@ public abstract class PortletApplicationContextUtils { } } + + /** + * Factory that exposes the current request object on demand. + */ + private static class RequestObjectFactory implements ObjectFactory, Serializable { + + public PortletRequest getObject() { + RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes(); + if (!(requestAttr instanceof PortletRequestAttributes)) { + throw new IllegalStateException("Current request is not a portlet request"); + } + return ((PortletRequestAttributes) requestAttr).getRequest(); + } + + @Override + public String toString() { + return "Current PortletRequest"; + } + } + + + /** + * Factory that exposes the current session object on demand. + */ + private static class SessionObjectFactory implements ObjectFactory, Serializable { + + public PortletSession getObject() { + RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes(); + if (!(requestAttr instanceof PortletRequestAttributes)) { + throw new IllegalStateException("Current request is not a portlet request"); + } + return ((PortletRequestAttributes) requestAttr).getRequest().getPortletSession(); + } + + @Override + public String toString() { + return "Current PortletSession"; + } + } + } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FrameworkServlet.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FrameworkServlet.java index a2a4d95db6..6a9b68aedf 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FrameworkServlet.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FrameworkServlet.java @@ -416,6 +416,7 @@ public abstract class FrameworkServlet extends HttpServletBean ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(getContextClass()); + wac.setId(getServletContext().getServletContextName() + "." + getServletName()); wac.setParent(parent); wac.setServletContext(getServletContext()); wac.setServletConfig(getServletConfig()); diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/util/SerializationTestUtils.java b/org.springframework.web.servlet/src/test/java/org/springframework/util/SerializationTestUtils.java new file mode 100644 index 0000000000..d920a3b4ff --- /dev/null +++ b/org.springframework.web.servlet/src/test/java/org/springframework/util/SerializationTestUtils.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2009 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.NotSerializableException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; + +/** + * Utilities for testing serializability of objects. + * Exposes static methods for use in other test cases. + * + * @author Rod Johnson + */ +public class SerializationTestUtils { + + public static void testSerialization(Object o) throws IOException { + OutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(o); + } + + public static boolean isSerializable(Object o) throws IOException { + try { + testSerialization(o); + return true; + } + catch (NotSerializableException ex) { + return false; + } + } + + public static Object serializeAndDeserialize(Object o) throws IOException, ClassNotFoundException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(o); + oos.flush(); + baos.flush(); + byte[] bytes = baos.toByteArray(); + + ByteArrayInputStream is = new ByteArrayInputStream(bytes); + ObjectInputStream ois = new ObjectInputStream(is); + Object o2 = ois.readObject(); + return o2; + } + +} diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java index 5b0cd45dd2..ee297ee1eb 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java @@ -18,6 +18,7 @@ package org.springframework.web.servlet.mvc.annotation; import java.io.IOException; import java.io.Writer; +import java.io.Serializable; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -72,6 +73,7 @@ import org.springframework.stereotype.Controller; import org.springframework.ui.ExtendedModelMap; import org.springframework.ui.Model; import org.springframework.ui.ModelMap; +import org.springframework.util.SerializationTestUtils; import org.springframework.util.StringUtils; import org.springframework.validation.BindingResult; import org.springframework.validation.Errors; @@ -524,7 +526,7 @@ public class ServletAnnotationControllerTests { GenericWebApplicationContext wac = new GenericWebApplicationContext(); wac.setServletContext(servletContext); RootBeanDefinition bd = new RootBeanDefinition(MyParameterDispatchingController.class); - bd.setScope(WebApplicationContext.SCOPE_REQUEST); + //bd.setScope(WebApplicationContext.SCOPE_REQUEST); wac.registerBeanDefinition("controller", bd); AnnotationConfigUtils.registerAnnotationConfigProcessors(wac); wac.getBeanFactory().registerResolvableDependency(ServletConfig.class, servletConfig); @@ -541,8 +543,8 @@ public class ServletAnnotationControllerTests { assertEquals("myView", response.getContentAsString()); assertSame(servletContext, request.getAttribute("servletContext")); assertSame(servletConfig, request.getAttribute("servletConfig")); - assertSame(session, request.getAttribute("session")); - assertSame(request, request.getAttribute("request")); + assertSame(session.getId(), request.getAttribute("sessionId")); + assertSame(request.getRequestURI(), request.getAttribute("requestUri")); request = new MockHttpServletRequest(servletContext, "GET", "/myPath.do"); response = new MockHttpServletResponse(); @@ -551,8 +553,8 @@ public class ServletAnnotationControllerTests { assertEquals("myView", response.getContentAsString()); assertSame(servletContext, request.getAttribute("servletContext")); assertSame(servletConfig, request.getAttribute("servletConfig")); - assertSame(session, request.getAttribute("session")); - assertSame(request, request.getAttribute("request")); + assertSame(session.getId(), request.getAttribute("sessionId")); + assertSame(request.getRequestURI(), request.getAttribute("requestUri")); request = new MockHttpServletRequest(servletContext, "GET", "/myPath.do"); request.addParameter("view", "other"); @@ -572,6 +574,11 @@ public class ServletAnnotationControllerTests { response = new MockHttpServletResponse(); servlet.service(request, response); assertEquals("mySurpriseView", response.getContentAsString()); + + MyParameterDispatchingController deserialized = (MyParameterDispatchingController) + SerializationTestUtils.serializeAndDeserialize(servlet.getWebApplicationContext().getBean("controller")); + assertNotNull(deserialized.request); + assertNotNull(deserialized.session); } @Test @@ -1265,13 +1272,13 @@ public class ServletAnnotationControllerTests { @Controller @RequestMapping("/myPath.do") - private static class MyParameterDispatchingController { + private static class MyParameterDispatchingController implements Serializable { @Autowired - private ServletContext servletContext; + private transient ServletContext servletContext; @Autowired - private ServletConfig servletConfig; + private transient ServletConfig servletConfig; @Autowired private HttpSession session; @@ -1288,8 +1295,8 @@ public class ServletAnnotationControllerTests { response.getWriter().write("myView"); request.setAttribute("servletContext", this.servletContext); request.setAttribute("servletConfig", this.servletConfig); - request.setAttribute("session", this.session); - request.setAttribute("request", this.request); + request.setAttribute("sessionId", this.session.getId()); + request.setAttribute("requestUri", this.request.getRequestURI()); } @RequestMapping(params = {"view", "!lang"}) diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoader.java b/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoader.java index d926fdcef0..83e9845b83 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoader.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoader.java @@ -247,6 +247,7 @@ public class ContextLoader { ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); + wac.setId(servletContext.getServletContextName()); wac.setParent(parent); wac.setServletContext(servletContext); wac.setConfigLocation(servletContext.getInitParameter(CONFIG_LOCATION_PARAM)); diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java index 771d50766f..483bbd9fc0 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java @@ -16,6 +16,7 @@ package org.springframework.web.context.support; +import java.io.Serializable; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; @@ -150,25 +151,8 @@ public abstract class WebApplicationContextUtils { sc.setAttribute(ServletContextScope.class.getName(), appScope); } - beanFactory.registerResolvableDependency(ServletRequest.class, new ObjectFactory() { - public ServletRequest getObject() { - RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes(); - if (!(requestAttr instanceof ServletRequestAttributes)) { - throw new IllegalStateException("Current request is not a servlet request"); - } - return ((ServletRequestAttributes) requestAttr).getRequest(); - } - }); - beanFactory.registerResolvableDependency(HttpSession.class, new ObjectFactory() { - public HttpSession getObject() { - RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes(); - if (!(requestAttr instanceof ServletRequestAttributes)) { - throw new IllegalStateException("Current request is not a servlet request"); - } - return ((ServletRequestAttributes) requestAttr).getRequest().getSession(); - } - }); - + beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory()); + beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory()); if (jsfPresent) { FacesDependencyRegistrar.registerFacesDependencies(beanFactory); } @@ -237,6 +221,46 @@ public abstract class WebApplicationContextUtils { } + /** + * Factory that exposes the current request object on demand. + */ + private static class RequestObjectFactory implements ObjectFactory, Serializable { + + public ServletRequest getObject() { + RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes(); + if (!(requestAttr instanceof ServletRequestAttributes)) { + throw new IllegalStateException("Current request is not a servlet request"); + } + return ((ServletRequestAttributes) requestAttr).getRequest(); + } + + @Override + public String toString() { + return "Current HttpServletRequest"; + } + } + + + /** + * Factory that exposes the current session object on demand. + */ + private static class SessionObjectFactory implements ObjectFactory, Serializable { + + public HttpSession getObject() { + RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes(); + if (!(requestAttr instanceof ServletRequestAttributes)) { + throw new IllegalStateException("Current request is not a servlet request"); + } + return ((ServletRequestAttributes) requestAttr).getRequest().getSession(); + } + + @Override + public String toString() { + return "Current HttpSession"; + } + } + + /** * Inner class to avoid hard-coded JSF dependency. */