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

This commit is contained in:
Juergen Hoeller 2009-05-07 22:29:55 +00:00
parent 4ccb352aac
commit 266a65982d
26 changed files with 582 additions and 167 deletions

View File

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

View File

@ -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 <code>null</code>; or
* if the <code>targetBeanName</code> consists wholly of whitespace
*/
public DefaultScopedObject(ConfigurableBeanFactory beanFactory, String targetBeanName) {
Assert.notNull(beanFactory, "BeanFactory must not be null");

View File

@ -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 <code>writeReplace()</code> method must be visible to the class
* being serialized.)
* <p>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) {

View File

@ -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.
*
* <p>Such TargetSources must run in a {@link BeanFactory}, as it needs to
* call the <code>getBean</code> 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 <code>writeReplace()</code> method must be visible to the class
* being serialized.)
* <p>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);
}
}
}

View File

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

View File

@ -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<T>
}
/**
* 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 {

View File

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

View File

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

View File

@ -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<String, Reference<DefaultListableBeanFactory>> serializableFactories =
new ConcurrentHashMap<String, Reference<DefaultListableBeanFactory>>();
/** 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<DefaultListableBeanFactory>(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;
}
}
}

View File

@ -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.
* <p>Note that the parent shouldn't be changed: It should only be set outside

View File

@ -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.
* <p>Called by both <code>close()</code> 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<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
for (String beanName : new LinkedHashSet<String>(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;

View File

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

View File

@ -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 <code>getResource</code> 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);
}
/**

View File

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

View File

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

View File

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

View File

@ -13,6 +13,11 @@
<bean id="testBean" class="test.beans.TestBean"/>
<bean id="singletonScoped" class="test.beans.TestBean">
<aop:scoped-proxy/>
<property name="name" value="Rob Harrop"/>
</bean>
<bean id="requestScoped" class="test.beans.TestBean" scope="request">
<aop:scoped-proxy/>
<property name="name" value="Rob Harrop"/>

View File

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

View File

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

View File

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

View File

@ -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<PortletRequest>() {
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<PortletSession>() {
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<PortletRequest>, 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<PortletSession>, 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";
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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<ServletRequest>() {
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<HttpSession>() {
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<ServletRequest>, 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<HttpSession>, 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.
*/