提交 a7293d29 编写于 作者: C Chris Beams

Introduce ApplicationContextInitializer interface

Designed primarily for use in conjunction with web applications
to provide a convenient mechanism for configuring the container
programmatically.

ApplicationContextInitializer implementations are specified through the
new "contextInitializerClasses" servlet context parameter, then detected
and invoked by ContextLoader in its customizeContext() method.

In any case, the semantics of ApplicationContextInitializer's
initialize(ConfigurableApplicationContext) method require that
invocation occur *prior* to refreshing the application context.

ACI implementations may also implement Ordered/PriorityOrdered and
ContextLoader will sort instances appropriately prior to invocation.

Specifically, this new support provides a straightforward way to
programmatically access the container's Environment for the purpose
of adding, removing or otherwise manipulating PropertySource objects.

See Javadoc for further details.

Also note that ApplicationContextInitializer semantics overlap to
some degree with Servlet 3.0's notion of ServletContainerInitializer
classes. As Spring 3.1 development progresses, we'll likely see
these two come together and further influence one another.
上级 71e60f45
/*
* Copyright 2002-2011 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.context;
/**
* Callback interface for initializing a Spring {@link ConfigurableApplicationContext}
* prior to being {@linkplain ConfigurableApplicationContext#refresh() refreshed}.
*
* <p>{@code ApplicationContextInitializer} processors are encouraged to detect
* whether Spring's {@link org.springframework.core.Ordered Ordered} or
* {@link org.springframework.core.PriorityOrdered PriorityOrdered} interfaces are also
* implemented and sort instances accordingly if so prior to invocation.
*
* @author Chris Beams
* @since 3.1
* @see org.springframework.web.context.ContextLoader#customizeContext
*/
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
/**
* Initialize the given application context.
* @param applicationContext the application to configure
*/
void initialize(C applicationContext);
}
......@@ -63,6 +63,18 @@ public abstract class PropertySource<T> {
this.source = source;
}
/**
* Create a new {@code PropertySource} with the given name and with a new {@code Object}
* instance as the underlying source.
* <p>Often useful in testing scenarios when creating
* anonymous implementations that never query an actual source, but rather return
* hard-coded values.
*/
@SuppressWarnings("unchecked")
public PropertySource(String name) {
this(name, (T) new Object());
}
/**
* Return the name of this {@code PropertySource}
*/
......
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2011 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,15 +16,24 @@
package org.springframework.web.context;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.FileNotFoundException;
import java.io.IOException;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.beans.BeansException;
import org.springframework.beans.TestBean;
import org.springframework.beans.factory.BeanCreationException;
......@@ -33,9 +42,14 @@ import org.springframework.beans.factory.LifecycleBean;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.mock.web.MockServletContext;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.SimpleWebApplicationContext;
......@@ -100,6 +114,39 @@ public final class ContextLoaderTests {
assertEquals("customizeContext() should have been called.", expectedContents, buffer.toString());
}
@Test
public void testContextLoaderListenerWithRegisteredContextConfigurer() {
MockServletContext sc = new MockServletContext("");
sc.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM,
"org/springframework/web/context/WEB-INF/ContextLoaderTests-acc-context.xml");
sc.addInitParameter(ContextLoader.CONTEXT_INITIALIZER_CLASSES_PARAM,
StringUtils.arrayToCommaDelimitedString(
new Object[]{TestContextInitializer.class.getName(), TestWebContextInitializer.class.getName()}));
ContextLoaderListener listener = new ContextLoaderListener();
listener.contextInitialized(new ServletContextEvent(sc));
WebApplicationContext wac = ContextLoaderListener.getCurrentWebApplicationContext();
TestBean testBean = wac.getBean(TestBean.class);
assertThat(testBean.getName(), equalTo("testName"));
assertThat(wac.getServletContext().getAttribute("initialized"), notNullValue());
}
@Test
public void testContextLoaderListenerWithUnkownContextConfigurer() {
MockServletContext sc = new MockServletContext("");
// config file doesn't matter. just a placeholder
sc.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM,
"/org/springframework/web/context/WEB-INF/empty-context.xml");
sc.addInitParameter(ContextLoader.CONTEXT_INITIALIZER_CLASSES_PARAM,
StringUtils.arrayToCommaDelimitedString(new Object[]{UnknownContextInitializer.class.getName()}));
ContextLoaderListener listener = new ContextLoaderListener();
try {
listener.contextInitialized(new ServletContextEvent(sc));
fail("expected exception");
} catch (IllegalArgumentException ex) {
assertTrue(ex.getMessage().contains("not assignable"));
}
}
@Test
public void testContextLoaderWithDefaultContextAndParent() throws Exception {
MockServletContext sc = new MockServletContext("");
......@@ -257,4 +304,33 @@ public final class ContextLoaderTests {
}
}
private static class TestContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
environment.getPropertySources().addFirst(new PropertySource<Object>("testPropertySource") {
@Override
public Object getProperty(String key) {
return "name".equals(key) ? "testName" : null;
}
});
}
}
private static class TestWebContextInitializer implements ApplicationContextInitializer<ConfigurableWebApplicationContext> {
public void initialize(ConfigurableWebApplicationContext applicationContext) {
ServletContext ctx = applicationContext.getServletContext(); // type-safe access to servlet-specific methods
ctx.setAttribute("initialized", true);
}
}
private static interface UnknownApplicationContext extends ConfigurableApplicationContext {
void unheardOf();
}
private static class UnknownContextInitializer implements ApplicationContextInitializer<UnknownApplicationContext> {
public void initialize(UnknownApplicationContext applicationContext) {
applicationContext.unheardOf();
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="org.springframework.beans.TestBean">
<constructor-arg value="${name}"/>
</bean>
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2011 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,24 +17,33 @@
package org.springframework.web.context;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.access.BeanFactoryLocator;
import org.springframework.beans.factory.access.BeanFactoryReference;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.access.ContextSingletonBeanFactoryLocator;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Performs the actual initialization work for the root application context.
......@@ -78,14 +87,23 @@ public class ContextLoader {
/**
* Config param for the root WebApplicationContext implementation class to
* use: "<code>contextClass</code>"
* use: {@value}
* @see #determineContextClass(ServletContext)
* @see #createWebApplicationContext(ServletContext, ApplicationContext)
*/
public static final String CONTEXT_CLASS_PARAM = "contextClass";
/**
* Name of servlet context parameter (i.e., "<code>contextConfigLocation</code>")
* that can specify the config location for the root context, falling back
* to the implementation's default otherwise.
* Config param for which {@link ApplicationContextInitializer} classes to use
* for initializing the web application context: {@value}
* @see #customizeContext(ServletContext, ConfigurableWebApplicationContext)
*/
public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";
/**
* Name of servlet context parameter (i.e., {@value}) that can specify the
* config location for the root context, falling back to the implementation's
* default otherwise.
* @see org.springframework.web.context.support.XmlWebApplicationContext#DEFAULT_CONFIG_LOCATION
*/
public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
......@@ -308,18 +326,73 @@ public class ContextLoader {
}
}
/**
* Return the {@link ApplicationContextInitializer} implementation classes to use
* if any have been specified by {@link #CONTEXT_INITIALIZER_CLASSES_PARAM}.
* @param servletContext current servlet context
* @see #CONTEXT_INITIALIZER_CLASSES_PARAM
*/
@SuppressWarnings("unchecked")
protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
determineContextInitializerClasses(ServletContext servletContext) {
String classNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>();
if (classNames != null) {
for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
try {
Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
Assert.isAssignable(ApplicationContextInitializer.class, clazz,
"class [" + className + "] must implement ApplicationContextInitializer");
classes.add((Class<ApplicationContextInitializer<ConfigurableApplicationContext>>)clazz);
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load context configurer class [" + className + "]", ex);
}
}
}
return classes;
}
/**
* Customize the {@link ConfigurableWebApplicationContext} created by this
* ContextLoader after config locations have been supplied to the context
* but before the context is <em>refreshed</em>.
* <p>The default implementation is empty but can be overridden in subclasses
* to customize the application context.
* <p>The default implementation {@linkplain #determineContextInitializerClasses(ServletContext)
* determines} what (if any) context initializer classes have been specified through
* {@linkplain #CONTEXT_INITIALIZER_CLASSES_PARAM context init parameters} and
* {@linkplain ApplicationContextInitializer#initialize invokes each} with the
* given web application context.
* <p>Any {@link Ordered} {@code ApplicationContextInitializer} will be sorted
* appropriately.
* @param servletContext the current servlet context
* @param applicationContext the newly created application context
* @see #createWebApplicationContext(ServletContext, ApplicationContext)
* @see #CONTEXT_INITIALIZER_CLASSES_PARAM
* @see ApplicationContextInitializer#initialize(ConfigurableApplicationContext)
*/
protected void customizeContext(
ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) {
protected void customizeContext(ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) {
List<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerInstances =
new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>();
for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass :
determineContextInitializerClasses(servletContext)) {
Class<?> contextClass = applicationContext.getClass();
Class<?> initializerContextClass =
GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
Assert.isAssignable(initializerContextClass, contextClass, String.format(
"Could not add context initializer [%s] as its generic parameter [%s] " +
"is not assignable from the type of application context used by this " +
"context loader [%s]", initializerClass.getName(), initializerContextClass, contextClass));
initializerInstances.add(BeanUtils.instantiateClass(initializerClass));
}
OrderComparator.sort(initializerInstances);
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : initializerInstances) {
initializer.initialize(applicationContext);
}
}
/**
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册