[bs-175] Generic dispatcher features for SpringApplication

* Move Spring.main into SpringApplication.main
* User can bind command line or application.properties into
SpringApplication
* User can provide sources dynamically with --spring.main.sources
(a CSV list of class names, package names or XML resource locations)
* One side effect was to make DocumentMatchers stateless

[#52830829]
This commit is contained in:
Dave Syer 2013-07-12 11:29:17 +01:00
parent d75c1e4956
commit 4ce6b64dce
21 changed files with 533 additions and 416 deletions

View File

@ -1,98 +0,0 @@
/*
* Copyright 2012-2013 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.autoconfigure.main;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.LogFactory;
import org.springframework.autoconfigure.EnableAutoConfiguration;
import org.springframework.bootstrap.SpringApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.SpringVersion;
import org.springframework.util.ClassUtils;
/**
* Very simple main class that can be used to launch an application from sources (class,
* package or XML). Useful for demos and testing, perhaps less for production use (where
* the {@link SpringApplication} run methods are often more convenient).
*
* @author Dave Syer
*/
@Configuration
@EnableAutoConfiguration
@ComponentScan
public abstract class Spring {
// FIXME can we delete this? is it used? does it belong here
private static ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(
true);
private static ApplicationContext context;
/**
* @return the context if there is one
*/
public static ApplicationContext getApplicationContext() {
return context;
}
/**
* A basic main that can be used to launch an application.
*
* @param args command line arguments
* @see SpringApplication#run(Object[], String[])
* @see SpringApplication#run(Object, String...)
*/
public static void main(String[] args) throws Exception {
List<String> strings = new ArrayList<String>();
List<Object> sources = new ArrayList<Object>();
for (String arg : args) {
if (ClassUtils.isPresent(arg, null)) {
sources.add(ClassUtils.forName(arg, null));
}
else if (arg.endsWith(".xml")) {
sources.add(arg);
}
else if (!scanner.findCandidateComponents(arg).isEmpty()) {
sources.add(arg);
}
else {
strings.add(arg);
}
}
if (sources.isEmpty()) {
sources.add(Spring.class);
}
context = SpringApplication.run(sources.toArray(new Object[sources.size()]),
strings.toArray(new String[strings.size()]));
LogFactory.getLog(Spring.class).info(
"Running Spring " + SpringVersion.getVersion() + " with sources: "
+ sources);
}
}

View File

@ -20,7 +20,7 @@ import org.junit.Ignore;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import org.springframework.autoconfigure.main.SimpleMainTests;
import org.springframework.bootstrap.SimpleMainTests;
import org.springframework.bootstrap.context.embedded.jetty.JettyEmbeddedServletContainerFactoryTests;
/**

View File

@ -1,66 +0,0 @@
/*
* Copyright 2012-2013 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.autoconfigure.main;
import org.junit.After;
import org.junit.Test;
import org.springframework.autoconfigure.main.Spring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.ClassUtils;
import static org.junit.Assert.assertNotNull;
/**
* Tests for {@link Spring}.
*
* @author Dave Syer
*/
public class SimpleMainTests {
private ApplicationContext context;
@After
public void close() {
if (this.context instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) this.context).close();
}
}
@Test
public void emptyApplicationContext() throws Exception {
Spring.main(new String[0]);
this.context = Spring.getApplicationContext();
assertNotNull(this.context);
}
@Test
public void basePackageScan() throws Exception {
Spring.main(new String[] { ClassUtils.getPackageName(Spring.class) });
this.context = Spring.getApplicationContext();
assertNotNull(this.context);
}
@Test
public void xmlContext() throws Exception {
Spring.main(new String[] { Spring.class.getName(),
"org/springframework/bootstrap/sample-beans.xml" });
this.context = Spring.getApplicationContext();
assertNotNull(this.context);
}
}

View File

@ -29,10 +29,14 @@ import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Loads bean definitions from underlying sources, including XML and JavaConfig. Acts as a
@ -162,13 +166,37 @@ class BeanDefinitionLoader {
if (loadedResource != null && loadedResource.exists()) {
return load(loadedResource);
}
Package packageResource = Package.getPackage(source.toString());
Package packageResource = findPackage(source);
if (packageResource != null) {
return load(packageResource);
}
throw new IllegalArgumentException("Invalid source '" + source + "'");
}
private Package findPackage(CharSequence source) {
Package pkg = Package.getPackage(source.toString());
if (pkg != null) {
return pkg;
}
try {
// Attempt to find a class in this package
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(
getClass().getClassLoader());
Resource[] resources = resolver.getResources(ClassUtils
.convertClassNameToResourcePath(source.toString()) + "/*.class");
for (Resource resource : resources) {
String className = StringUtils.stripFilenameExtension(resource
.getFilename());
load(Class.forName(source.toString() + "." + className));
break;
}
}
catch (Exception ex) {
// swallow exception and continue
}
return Package.getPackage(source.toString());
}
private boolean isComponent(Class<?> type) {
// This has to be a bit of a guess. The only way to be sure that this type is
// eligible is to make a bean definition out of it and try to instantiate it.

View File

@ -19,9 +19,9 @@ package org.springframework.bootstrap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -36,20 +36,19 @@ import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.CommandLinePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.ConfigurableWebApplicationContext;
/**
@ -133,7 +132,7 @@ public class SpringApplication {
private final Log log = LogFactory.getLog(getClass());
private Object[] sources;
private Set<Object> sources = new LinkedHashSet<Object>();
private Class<?> mainApplicationClass;
@ -169,8 +168,7 @@ public class SpringApplication {
* @see #SpringApplication(ResourceLoader, Object...)
*/
public SpringApplication(Object... sources) {
Assert.notEmpty(sources, "Sources must not be empty");
this.sources = sources;
addSources(sources);
initialize();
}
@ -185,12 +183,20 @@ public class SpringApplication {
* @see #SpringApplication(ResourceLoader, Object...)
*/
public SpringApplication(ResourceLoader resourceLoader, Object... sources) {
Assert.notEmpty(sources, "Sources must not be empty");
this.resourceLoader = resourceLoader;
this.sources = sources;
addSources(sources);
initialize();
}
private void addSources(Object[] sources) {
if (sources == null) {
return;
}
for (Object source : sources) {
this.sources.add(source);
}
}
private void initialize() {
this.webEnvironment = deduceWebEnvironment();
this.initializers = new ArrayList<ApplicationContextInitializer<?>>();
@ -228,6 +234,22 @@ public class SpringApplication {
return null;
}
/**
* A basic main that can be used to launch an application.
*
* @param args command line arguments
* @see SpringApplication#run(Object[], String[])
* @see SpringApplication#run(Object, String...)
*/
public static void main(String[] args) throws Exception {
SpringApplication.run(new Object[0], args);
}
@Configuration
protected static class EmptyConfiguration {
}
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
@ -235,29 +257,30 @@ public class SpringApplication {
* @return a running {@link ApplicationContext}
*/
public ApplicationContext run(String... args) {
applySpringApplicationInitializers();
applySpringApplicationInitializers(args);
Assert.notEmpty(this.sources, "Sources must not be empty");
if (this.showBanner) {
printBanner();
}
ApplicationContext context = createApplicationContext();
postProcessApplicationContext(context);
addPropertySources(context, args);
if (context instanceof ConfigurableApplicationContext) {
applyInitializers((ConfigurableApplicationContext) context);
}
if (this.logStartupInfo) {
logStartupInfo();
}
load(context, this.sources);
load(context, this.sources.toArray(new Object[this.sources.size()]));
refresh(context);
runCommandLineRunners(context, args);
return context;
}
private void applySpringApplicationInitializers() {
private void applySpringApplicationInitializers(String[] args) {
args = StringUtils.mergeStringArrays(this.defaultCommandLineArgs, args);
for (ApplicationContextInitializer<?> initializer : this.initializers) {
if (initializer instanceof SpringApplicationInitializer) {
((SpringApplicationInitializer) initializer).initialize(this);
((SpringApplicationInitializer) initializer).initialize(this, args);
}
}
}
@ -289,6 +312,7 @@ public class SpringApplication {
protected void logStartupInfo() {
new StartupInfoLogger(this.mainApplicationClass).log(getApplicationLog());
getApplicationLog().info("Sources: " + this.sources);
}
/**
@ -358,88 +382,6 @@ public class SpringApplication {
}
}
/**
* Add any {@link PropertySource}s to the application context environment.
* @param context the application context
* @param args run arguments
*/
protected void addPropertySources(ApplicationContext context, String[] args) {
Environment environment = context.getEnvironment();
if (environment instanceof ConfigurableEnvironment) {
ConfigurableEnvironment configurable = (ConfigurableEnvironment) environment;
if (this.addCommandLineProperties) {
// Don't use SimpleCommandLinePropertySource (SPR-10579)
PropertySource<?> propertySource = new MapPropertySource(
"commandLineArgs", mergeCommandLineArgs(
this.defaultCommandLineArgs, args));
configurable.getPropertySources().addFirst(propertySource);
}
}
}
/**
* Merge two sets of command lines, the defaults and the ones passed in at run time.
*
* @param defaults the default values
* @param args the ones passed in at runtime
* @return a new command line
*/
protected Map<String, Object> mergeCommandLineArgs(String[] defaults, String[] args) {
if (defaults == null) {
defaults = new String[0];
}
List<String> nonopts = new ArrayList<String>();
Map<String, Object> options = new LinkedHashMap<String, Object>();
for (String arg : defaults) {
if (isOptionArg(arg)) {
addOptionArg(options, arg);
}
else {
nonopts.add(arg);
}
}
for (String arg : args) {
if (isOptionArg(arg)) {
addOptionArg(options, arg);
}
else if (!nonopts.contains(arg)) {
nonopts.add(arg);
}
}
for (String key : nonopts) {
options.put(key, "");
}
return options;
}
private boolean isOptionArg(String arg) {
return arg.startsWith("--");
}
private void addOptionArg(Map<String, Object> map, String arg) {
String optionText = arg.substring(2, arg.length());
String optionName;
String optionValue = "";
if (optionText.contains("=")) {
optionName = optionText.substring(0, optionText.indexOf('='));
optionValue = optionText.substring(optionText.indexOf('=') + 1,
optionText.length());
}
else {
optionName = optionText;
}
if (optionName.isEmpty()) {
throw new IllegalArgumentException("Invalid argument syntax: " + arg);
}
map.put(optionName, optionValue);
}
/**
* Load beans into the application context.
* @param context the context to load beans into
@ -552,6 +494,13 @@ public class SpringApplication {
this.addCommandLineProperties = addCommandLineProperties;
}
/**
* @return the addCommandLineProperties
*/
public boolean isAddCommandLineProperties() {
return this.addCommandLineProperties;
}
/**
* Set some default command line arguments which can be overridden by those passed
* into the run methods.
@ -571,12 +520,45 @@ public class SpringApplication {
/**
* Sets the underlying environment that should be used when loading.
*
* @param environment the environment
*/
public void setEnvironment(ConfigurableEnvironment environment) {
this.environment = environment;
}
/**
* The environment that will be used to create the application context (can be null in
* which case a default will be provided).
*
* @return the environment
*/
public ConfigurableEnvironment getEnvironment() {
return this.environment;
}
/**
* The sources that will be used to create an ApplicationContext if this application
* {@link #run(String...)} is called.
*
* @return the sources
*/
public Set<Object> getSources() {
return this.sources;
}
/**
* The sources that will be used to create an ApplicationContext. A valid source is
* one of: a class, class name, package, package name, or an XML resource location.
* Can also be set using contructors and static convenience methods (e.g.
* {@link #run(Object[], String[])}).
*
* @param sources the sources to set
*/
public void setSources(Set<Object> sources) {
this.sources = sources;
}
/**
* Sets the {@link ResourceLoader} that should be used when loading resources.
* @param resourceLoader the resource loader
@ -660,21 +642,6 @@ public class SpringApplication {
return new SpringApplication(sources).run(args);
}
/**
* Static helper that can be used to run a {@link SpringApplication} from a script
* using the specified sources with default settings. This method is useful when
* calling this calls from a script environment that will not have a single main
* application class.
* @param sources the sources to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ApplicationContext runFromScript(Object[] sources, String[] args) {
SpringApplication application = new SpringApplication(sources);
application.setMainApplicationClass(null);
return application.run(args);
}
/**
* Static helper that can be used to exit a {@link SpringApplication} and obtain a
* code indicating success (0) or otherwise. Does not throw exceptions but should

View File

@ -25,9 +25,10 @@ package org.springframework.bootstrap;
public interface SpringApplicationInitializer {
/**
* Initialize the applcation
* Initialize the application
* @param springApplication the spring application.
* @param args the args provided on command line by caller
*/
void initialize(SpringApplication springApplication);
void initialize(SpringApplication springApplication, String[] args);
}

View File

@ -27,7 +27,7 @@ import org.springframework.bootstrap.config.YamlProcessor.MatchStatus;
*
* @author Dave Syer
*/
public final class DefaultProfileDocumentMatcher implements DocumentMatcher {
public class DefaultProfileDocumentMatcher implements DocumentMatcher {
@Override
public MatchStatus matches(Properties properties) {

View File

@ -19,7 +19,6 @@ package org.springframework.bootstrap.config;
import java.io.IOException;
import java.util.Properties;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
@ -38,9 +37,9 @@ public class PropertiesPropertySourceLoader implements PropertySourceLoader {
}
@Override
public PropertySource<?> load(Resource resource, Environment environment) {
public PropertySource<?> load(Resource resource) {
try {
Properties properties = loadProperties(resource, environment);
Properties properties = loadProperties(resource);
return new PropertiesPropertySource(resource.getDescription(), properties);
}
catch (IOException ex) {
@ -49,8 +48,7 @@ public class PropertiesPropertySourceLoader implements PropertySourceLoader {
}
}
protected Properties loadProperties(Resource resource, Environment environment)
throws IOException {
protected Properties loadProperties(Resource resource) throws IOException {
return PropertiesLoaderUtils.loadProperties(resource);
}
}

View File

@ -16,7 +16,6 @@
package org.springframework.bootstrap.config;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
@ -37,6 +36,6 @@ public interface PropertySourceLoader {
* Load the resource into a property source.
* @return a property source
*/
PropertySource<?> load(Resource resource, Environment environment);
PropertySource<?> load(Resource resource);
}

View File

@ -16,15 +16,18 @@
package org.springframework.bootstrap.config;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Properties;
import org.springframework.bootstrap.config.YamlProcessor.DocumentMatcher;
import org.springframework.bootstrap.config.YamlProcessor.MatchStatus;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
/**
* {@link DocumentMatcher} backed by {@link Environment#getActiveProfiles()}.
* {@link DocumentMatcher} backed by {@link Environment#getActiveProfiles()}. A YAML
* document matches if it contains an element "spring.profiles" (a comma-separated list)
* and one of the profiles is in the active list.
*
* @author Dave Syer
*/
@ -32,20 +35,20 @@ public class SpringProfileDocumentMatcher implements DocumentMatcher {
private static final String[] DEFAULT_PROFILES = new String[] { "default" };
private final Environment environment;
private String[] activeProfiles = new String[0];
/**
* Create a new {@link SpringProfileDocumentMatcher} instance.
* @param environment the environment
*/
public SpringProfileDocumentMatcher(Environment environment) {
Assert.notNull(environment, "Environment must not be null");
this.environment = environment;
public void addActiveProfiles(String... profiles) {
LinkedHashSet<String> set = new LinkedHashSet<String>(
Arrays.asList(this.activeProfiles));
for (String profile : profiles) {
set.add(profile);
}
this.activeProfiles = set.toArray(new String[set.size()]);
}
@Override
public MatchStatus matches(Properties properties) {
String[] profiles = this.environment.getActiveProfiles();
String[] profiles = this.activeProfiles;
if (profiles.length == 0) {
profiles = DEFAULT_PROFILES;
}

View File

@ -20,11 +20,13 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.springframework.bootstrap.config.YamlProcessor.DocumentMatcher;
import org.springframework.core.env.Environment;
import org.springframework.bootstrap.config.YamlProcessor.MatchStatus;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
/**
* Strategy to load '.yml' files into a {@link PropertySource}.
@ -49,8 +51,7 @@ public class YamlPropertySourceLoader extends PropertiesPropertySourceLoader {
}
@Override
protected Properties loadProperties(final Resource resource,
final Environment environment) throws IOException {
protected Properties loadProperties(final Resource resource) throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
if (this.matchers != null && !this.matchers.isEmpty()) {
factory.setMatchDefault(false);
@ -74,13 +75,31 @@ public class YamlPropertySourceLoader extends PropertiesPropertySourceLoader {
* which have an explicit "spring.profiles.active" value in the current active
* profiles.
*
* @param activeProfiles the active profiles to match independent of file contents
*
* @return a property source loader
*/
public static YamlPropertySourceLoader springProfileAwareLoader(
Environment environment) {
return new YamlPropertySourceLoader(
new SpringProfileDocumentMatcher(environment),
new DefaultProfileDocumentMatcher());
String[] activeProfiles) {
final SpringProfileDocumentMatcher matcher = new SpringProfileDocumentMatcher();
for (String profile : activeProfiles) {
matcher.addActiveProfiles(profile);
}
return new YamlPropertySourceLoader(matcher, new DefaultProfileDocumentMatcher() {
@Override
public MatchStatus matches(Properties properties) {
MatchStatus result = super.matches(properties);
if (result == MatchStatus.FOUND) {
Set<String> profiles = StringUtils.commaDelimitedListToSet(properties
.getProperty("spring.profiles.active", ""));
for (String profile : profiles) {
// allow document with no profile to set the active one
matcher.addActiveProfiles(profile);
}
}
return result;
}
});
}
}

View File

@ -17,20 +17,35 @@
package org.springframework.bootstrap.context.initializer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.bootstrap.config.SpringProfileDocumentMatcher;
import org.springframework.beans.PropertyValues;
import org.springframework.bootstrap.SpringApplication;
import org.springframework.bootstrap.SpringApplicationInitializer;
import org.springframework.bootstrap.bind.PropertySourcesPropertyValues;
import org.springframework.bootstrap.bind.RelaxedDataBinder;
import org.springframework.bootstrap.config.PropertiesPropertySourceLoader;
import org.springframework.bootstrap.config.PropertySourceLoader;
import org.springframework.bootstrap.config.YamlPropertySourceLoader;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.CommandLinePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
@ -44,6 +59,7 @@ import org.springframework.util.StringUtils;
* <li>classpath:config/</li>
* <li>file:./config/:</li>
* </ul>
*
* <p>
* Alternative locations and names can be specified using
* {@link #setSearchLocations(String[])} and {@link #setName(String)}.
@ -62,7 +78,8 @@ import org.springframework.util.StringUtils;
* @author Phillip Webb
*/
public class ConfigFileApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
ApplicationContextInitializer<ConfigurableApplicationContext>,
SpringApplicationInitializer, Ordered {
private static final String LOCATION_VARIABLE = "${spring.config.location}";
@ -73,24 +90,76 @@ public class ConfigFileApplicationContextInitializer implements
private int order = Integer.MIN_VALUE + 10;
private Map<String, PropertySource<?>> cached = new HashMap<String, PropertySource<?>>();
private ConversionService conversionService = new DefaultConversionService();
/**
* Creates a property source from the command line, loads additional external
* configuration, and then binds it to the application. This makes it possible to set
* things dynamically, like the sources ("spring.main.sources" - a CSV list) the flag
* to indicate a web environment ("spring.main.web_environment=true") or the flag to
* switch off the banner ("spring.main.show_banner=false").
*/
@Override
public void initialize(SpringApplication application, String[] args) {
if (application.isAddCommandLineProperties()) {
this.cached.put(CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME,
new MapPropertySource(
CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME,
extractCommandLineArgs(args)));
}
ConfigurableEnvironment environment = application.getEnvironment();
if (environment == null) {
environment = new StandardEnvironment();
}
load(environment, new DefaultResourceLoader());
// Set bean properties from command line args parameters.
PropertyValues pvs = new PropertySourcesPropertyValues(
environment.getPropertySources());
RelaxedDataBinder binder = new RelaxedDataBinder(application, "spring.main");
binder.setConversionService(this.conversionService);
binder.bind(pvs);
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
load(applicationContext.getEnvironment(), applicationContext);
}
private void load(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
List<String> candidates = getCandidateLocations();
if (this.cached
.containsKey(CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME)) {
environment
.getPropertySources()
.addFirst(
this.cached
.get(CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME));
}
// Initial load allows profiles to be activated
for (String candidate : candidates) {
load(applicationContext, candidate, null);
load(environment, resourceLoader, candidate, null);
}
// Second load for specific profiles
for (String profile : applicationContext.getEnvironment().getActiveProfiles()) {
for (String profile : environment.getActiveProfiles()) {
for (String candidate : candidates) {
load(applicationContext, candidate, profile);
load(environment, resourceLoader, candidate, profile);
}
}
}
private List<String> getCandidateLocations() {
List<String> candidates = new ArrayList<String>();
for (String searchLocation : this.searchLocations) {
for (String extension : new String[] { ".properties", ".yml" }) {
@ -100,38 +169,118 @@ public class ConfigFileApplicationContextInitializer implements
}
candidates.add(LOCATION_VARIABLE);
return candidates;
}
private void load(ConfigurableApplicationContext applicationContext, String location,
String profile) {
private void load(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
String location, String profile) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
location = environment.resolvePlaceholders(location);
String suffix = "." + StringUtils.getFilenameExtension(location);
if (StringUtils.hasLength(profile)) {
location = location.replace(suffix, "-" + profile + suffix);
}
PropertySourceLoader[] loaders = {
new PropertiesPropertySourceLoader(),
new YamlPropertySourceLoader(new SpringProfileDocumentMatcher(environment),
new ProfileSettingDocumentMatcher(environment)) };
for (PropertySourceLoader loader : loaders) {
Resource resource = applicationContext.getResource(location);
if (resource != null && resource.exists() && loader.supports(resource)) {
PropertySource<?> propertySource = loader.load(resource, environment);
MutablePropertySources propertySources = environment.getPropertySources();
if (propertySources
.contains(CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME)) {
propertySources.addAfter(
CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME,
propertySource);
} else {
propertySources.addFirst(propertySource);
}
return;
List<PropertySourceLoader> loaders = new ArrayList<PropertySourceLoader>();
loaders.add(new PropertiesPropertySourceLoader());
if (ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
loaders.add(YamlPropertySourceLoader.springProfileAwareLoader(environment
.getActiveProfiles()));
}
Resource resource = resourceLoader.getResource(location);
PropertySource<?> propertySource = getPropertySource(resource, loaders);
if (propertySource == null) {
return;
}
if (propertySource.containsProperty("spring.profiles.active")) {
Set<String> profiles = StringUtils.commaDelimitedListToSet(propertySource
.getProperty("spring.profiles.active").toString());
for (String active : profiles) {
// allow document with no profile to set the active one
environment.addActiveProfile(active);
}
}
MutablePropertySources propertySources = environment.getPropertySources();
if (propertySources
.contains(CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME)) {
propertySources.addAfter(
CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME,
propertySource);
}
else {
propertySources.addFirst(propertySource);
}
}
private PropertySource<?> getPropertySource(Resource resource,
List<PropertySourceLoader> loaders) {
String key = resource.getDescription();
if (this.cached.containsKey(key)) {
return this.cached.get(key);
}
for (PropertySourceLoader loader : loaders) {
if (resource != null && resource.exists() && loader.supports(resource)) {
PropertySource<?> propertySource = loader.load(resource);
this.cached.put(key, propertySource);
return propertySource;
}
}
return null;
}
/**
* Merge two sets of command lines, the defaults and the ones passed in at run time.
*
* @param args the ones passed in at runtime
* @return a new command line
*/
protected Map<String, Object> extractCommandLineArgs(String[] args) {
List<String> nonopts = new ArrayList<String>();
Map<String, Object> options = new LinkedHashMap<String, Object>();
for (String arg : args) {
if (isOptionArg(arg)) {
addOptionArg(options, arg);
}
else if (!nonopts.contains(arg)) {
nonopts.add(arg);
}
}
for (String key : nonopts) {
options.put(key, "");
}
return options;
}
private boolean isOptionArg(String arg) {
return arg.startsWith("--");
}
private void addOptionArg(Map<String, Object> map, String arg) {
String optionText = arg.substring(2, arg.length());
String optionName;
String optionValue = "";
if (optionText.contains("=")) {
optionName = optionText.substring(0, optionText.indexOf('='));
optionValue = optionText.substring(optionText.indexOf('=') + 1,
optionText.length());
}
else {
optionName = optionText;
}
if (optionName.isEmpty()) {
throw new IllegalArgumentException("Invalid argument syntax: " + arg);
}
map.put(optionName, optionValue);
}
public void setOrder(int order) {

View File

@ -81,7 +81,7 @@ public class LoggingApplicationContextInitializer implements
private int order = Integer.MIN_VALUE + 11;
@Override
public void initialize(SpringApplication springApplication) {
public void initialize(SpringApplication springApplication, String[] args) {
LoggingSystem.get(springApplication.getClass().getClassLoader())
.beforeInitialize();
}

View File

@ -1,60 +0,0 @@
/*
* Copyright 2012-2013 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.bootstrap.context.initializer;
import java.util.Properties;
import java.util.Set;
import org.springframework.bootstrap.config.YamlProcessor.DocumentMatcher;
import org.springframework.bootstrap.config.YamlProcessor.MatchStatus;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
/**
* A {@link DocumentMatcher} that sets the active profile if it finds a document with a
* key <code>spring.profiles.active</code>.
*
* @author Dave Syer
*
*/
public class ProfileSettingDocumentMatcher implements DocumentMatcher {
private final Environment environment;
public ProfileSettingDocumentMatcher(Environment environment) {
this.environment = environment;
}
@Override
public MatchStatus matches(Properties properties) {
if (!properties.containsKey("spring.profiles")) {
Set<String> profiles = StringUtils.commaDelimitedListToSet(properties
.getProperty("spring.profiles.active", ""));
if (this.environment instanceof ConfigurableEnvironment) {
ConfigurableEnvironment configurable = (ConfigurableEnvironment) this.environment;
for (String profile : profiles) {
// allow document with no profile to set the active one
configurable.addActiveProfile(profile);
}
}
// matches default profile
return MatchStatus.FOUND;
} else {
return MatchStatus.NOT_FOUND;
}
}
}

View File

@ -157,15 +157,14 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
private PropertySources loadPropertySources(String[] path) {
MutablePropertySources propertySources = new MutablePropertySources();
PropertySourceLoader[] loaders = { new PropertiesPropertySourceLoader(),
YamlPropertySourceLoader.springProfileAwareLoader(this.environment) };
YamlPropertySourceLoader.springProfileAwareLoader(environment.getActiveProfiles()) };
for (String location : path) {
location = this.environment.resolvePlaceholders(location);
Resource resource = this.resourceLoader.getResource(location);
if (resource != null && resource.exists()) {
for (PropertySourceLoader loader : loaders) {
if (loader.supports(resource)) {
PropertySource<?> propertySource = loader.load(resource,
this.environment);
PropertySource<?> propertySource = loader.load(resource);
propertySources.addFirst(propertySource);
}
}

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-/tmp/}spring.log}"/>
<property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${PID:- } --- %-40.40logger{39} : %m [%t]%n%wex"/>
<property name="CONSOLE_LOG_PATTERN" value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m %clr([%t]){faint}%n%wex"/>
<property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${PID:- } [%t] --- %-40.40logger{39} : %m%n%wex"/>
<property name="CONSOLE_LOG_PATTERN" value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex"/>
<conversionRule conversionWord="clr" converterClass="org.springframework.bootstrap.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.bootstrap.logging.logback.WhitespaceThrowableProxyConverter" />

View File

@ -0,0 +1,104 @@
/*
* Copyright 2012-2013 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.bootstrap;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import static org.junit.Assert.assertTrue;
/**
* Tests for {@link SpringApplication} main method.
*
* @author Dave Syer
*/
@Configuration
public class SimpleMainTests {
private PrintStream savedOutput;
private ByteArrayOutputStream output;
@Before
public void open() {
this.savedOutput = System.out;
this.output = new ByteArrayOutputStream();
System.setOut(new PrintStream(this.output));
}
@After
public void after() {
System.setOut(this.savedOutput);
System.out.println(getOutput());
}
@Test(expected = IllegalArgumentException.class)
public void emptyApplicationContext() throws Exception {
SpringApplication.main(getArgs());
assertTrue(getOutput().contains("Pre-instantiating singletons"));
}
@Test
public void basePackageScan() throws Exception {
SpringApplication.main(getArgs(ClassUtils.getPackageName(getClass())
+ ".sampleconfig"));
assertTrue(getOutput().contains("Pre-instantiating singletons"));
}
@Test
public void configClassContext() throws Exception {
SpringApplication.main(getArgs(getClass().getName()));
assertTrue(getOutput().contains("Pre-instantiating singletons"));
}
@Test
public void xmlContext() throws Exception {
SpringApplication.main(getArgs("org/springframework/bootstrap/sample-beans.xml"));
assertTrue(getOutput().contains("Pre-instantiating singletons"));
}
@Test
public void mixedContext() throws Exception {
SpringApplication.main(getArgs(getClass().getName(),
"org/springframework/bootstrap/sample-beans.xml"));
assertTrue(getOutput().contains("Pre-instantiating singletons"));
}
private String[] getArgs(String... args) {
List<String> list = new ArrayList<String>(Arrays.asList(
"--spring.main.webEnvironment=false", "--spring.main.showBanner=false"));
if (args.length > 0) {
list.add("--spring.main.sources="
+ StringUtils.arrayToCommaDelimitedString(args));
}
return list.toArray(new String[list.size()]);
}
private String getOutput() {
return this.output.toString();
}
}

View File

@ -26,10 +26,6 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
import org.springframework.bootstrap.BeanDefinitionLoader;
import org.springframework.bootstrap.CommandLineRunner;
import org.springframework.bootstrap.ExitCodeGenerator;
import org.springframework.bootstrap.SpringApplication;
import org.springframework.bootstrap.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
import org.springframework.bootstrap.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
import org.springframework.context.ApplicationContext;
@ -94,14 +90,14 @@ public class SpringApplicationTests {
public void sourcesMustNotBeNull() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Sources must not be empty");
new SpringApplication((Object[]) null);
new SpringApplication((Object[]) null).run();
}
@Test
public void sourcesMustNotBeEmpty() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Sources must not be empty");
new SpringApplication();
new SpringApplication().run();
}
@Test
@ -270,7 +266,7 @@ public class SpringApplicationTests {
application.setWebEnvironment(false);
application.setUseMockLoader(true);
application.run();
assertThat(application.getSources(), equalTo(sources));
assertThat(application.getSources().toArray(), equalTo(sources));
}
@Test
@ -341,8 +337,6 @@ public class SpringApplicationTests {
private boolean useMockLoader;
private Object[] sources;
public TestSpringApplication(Object... sources) {
super(sources);
}
@ -358,7 +352,6 @@ public class SpringApplicationTests {
@Override
protected BeanDefinitionLoader createBeanDefinitionLoader(
BeanDefinitionRegistry registry, Object[] sources) {
this.sources = sources;
if (this.useMockLoader) {
this.loader = mock(BeanDefinitionLoader.class);
}
@ -372,9 +365,6 @@ public class SpringApplicationTests {
return this.loader;
}
public Object[] getSources() {
return this.sources;
}
}
@Configuration

View File

@ -20,11 +20,15 @@ import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
@ -36,7 +40,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.bootstrap.bind.RelaxedDataBinder;
import org.springframework.beans.NotWritablePropertyException;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
@ -119,20 +123,50 @@ public class RelaxedDataBinderTests {
@Test
public void testBindNestedList() throws Exception {
TargetWithNestedList target = new TargetWithNestedList();
bind(target, "nested: bar,foo");
bind(target, "nested[0]: bar");
bind(target, "nested[1]: foo");
bind(target, "nested[0]: bar\nnested[1]: foo");
assertEquals("[bar, foo]", target.getNested().toString());
}
@Test
public void testBindNestedListCommaDelimitedONly() throws Exception {
public void testBindNestedListCommaDelimitedOnly() throws Exception {
TargetWithNestedList target = new TargetWithNestedList();
this.conversionService = new DefaultConversionService();
bind(target, "nested: bar,foo");
assertEquals("[bar, foo]", target.getNested().toString());
}
@Test
public void testBindNestedSetCommaDelimitedOnly() throws Exception {
TargetWithNestedSet target = new TargetWithNestedSet();
this.conversionService = new DefaultConversionService();
bind(target, "nested: bar,foo");
assertEquals("[bar, foo]", target.getNested().toString());
}
@Test(expected = NotWritablePropertyException.class)
public void testBindNestedReadOnlyListCommaSeparated() throws Exception {
TargetWithReadOnlyNestedList target = new TargetWithReadOnlyNestedList();
this.conversionService = new DefaultConversionService();
bind(target, "nested: bar,foo");
assertEquals("[bar, foo]", target.getNested().toString());
}
@Test
public void testBindNestedReadOnlyListIndexed() throws Exception {
TargetWithReadOnlyNestedList target = new TargetWithReadOnlyNestedList();
this.conversionService = new DefaultConversionService();
bind(target, "nested[0]: bar\nnested[1]:foo");
assertEquals("[bar, foo]", target.getNested().toString());
}
@Test
public void testBindNestedReadOnlyCollectionIndexed() throws Exception {
TargetWithReadOnlyNestedCollection target = new TargetWithReadOnlyNestedCollection();
this.conversionService = new DefaultConversionService();
bind(target, "nested[0]: bar\nnested[1]:foo");
assertEquals("[bar, foo]", target.getNested().toString());
}
@Test
public void testBindNestedMap() throws Exception {
TargetWithNestedMap target = new TargetWithNestedMap();
@ -306,6 +340,34 @@ public class RelaxedDataBinderTests {
}
}
public static class TargetWithReadOnlyNestedList {
private List<String> nested = new ArrayList<String>();
public List<String> getNested() {
return this.nested;
}
}
public static class TargetWithReadOnlyNestedCollection {
private Collection<String> nested = new ArrayList<String>();
public Collection<String> getNested() {
return this.nested;
}
}
public static class TargetWithNestedSet {
private Set<String> nested = new LinkedHashSet<String>();
public Set<String> getNested() {
return this.nested;
}
public void setNested(Set<String> nested) {
this.nested = nested;
}
}
public static class TargetWithNestedObject {
private VanillaTarget nested;

View File

@ -0,0 +1,21 @@
/*
* Copyright 2002-2013 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.
*/
/**
* Sample config for tests
*/
package org.springframework.bootstrap.sampleconfig;

View File

@ -0,0 +1 @@
main.sources: org.springframework.bootstrap.main.DispatcherMainTests