Refine SpringApplication source types

Update `SpringApplication` so that the `run` methods and constructors
now require `Class<?>` arguments, rather than `Objects`. String based
sources can still be loaded, but must now be set on the `getSources()`
collections. `Package` and `Resource` types are no longer directly
supported.

This change should help IDEs offer better content assist, and will
help integrations with alternative languages such as Ceylon.

Users currently passing in Class references or using the
`spring.main.sources` property should not be affected by this change. If
an XML resource is being used, some refactoring may be required (see the
changes to `SampleSpringXmlApplication` in this commit).

Fixes gh-9170
pull/5920/merge
Phillip Webb 8 years ago
parent 302f038e84
commit 889d43ddc4

@ -56,12 +56,12 @@ public class SpringApplicationLauncher {
* @return The application's {@code ApplicationContext}
* @throws Exception if the launch fails
*/
public Object launch(Object[] sources, String[] args) throws Exception {
public Object launch(Class<?>[] sources, String[] args) throws Exception {
Map<String, Object> defaultProperties = new HashMap<>();
defaultProperties.put("spring.groovy.template.check-template-location", "false");
Class<?> applicationClass = this.classLoader
.loadClass(getSpringApplicationClassName());
Constructor<?> constructor = applicationClass.getConstructor(Object[].class);
Constructor<?> constructor = applicationClass.getConstructor(Class[].class);
Object application = constructor.newInstance((Object) sources);
applicationClass.getMethod("setDefaultProperties", Map.class).invoke(application,
defaultProperties);

@ -51,7 +51,7 @@ public final class PackagedSpringApplicationLauncher {
new SpringApplicationLauncher(classLoader).launch(getSources(classLoader), args);
}
private Object[] getSources(URLClassLoader classLoader) throws Exception {
private Class<?>[] getSources(URLClassLoader classLoader) throws Exception {
Enumeration<URL> urls = classLoader.getResources("META-INF/MANIFEST.MF");
while (urls.hasMoreElements()) {
URL url = urls.nextElement();

@ -98,7 +98,7 @@ public class SpringApplicationRunner {
synchronized (this.monitor) {
try {
stop();
Object[] compiledSources = compile();
Class<?>[] compiledSources = compile();
monitorForChanges();
// Run in new thread to ensure that the context classloader is setup
this.runThread = new RunThread(compiledSources);
@ -125,8 +125,8 @@ public class SpringApplicationRunner {
}
}
private Object[] compile() throws IOException {
Object[] compiledSources = this.compiler.compile(this.sources);
private Class<?>[] compile() throws IOException {
Class<?>[] compiledSources = this.compiler.compile(this.sources);
if (compiledSources.length == 0) {
throw new RuntimeException(
"No classes found in '" + Arrays.toString(this.sources) + "'");
@ -148,7 +148,7 @@ public class SpringApplicationRunner {
private final Object monitor = new Object();
private final Object[] compiledSources;
private final Class<?>[] compiledSources;
private Object applicationContext;
@ -156,10 +156,10 @@ public class SpringApplicationRunner {
* Create a new {@link RunThread} instance.
* @param compiledSources the sources to launch
*/
RunThread(Object... compiledSources) {
RunThread(Class<?>... compiledSources) {
super("runner-" + (runnerCounter++));
this.compiledSources = compiledSources;
if (compiledSources.length != 0 && compiledSources[0] instanceof Class) {
if (compiledSources.length != 0) {
setContextClassLoader(((Class<?>) compiledSources[0]).getClassLoader());
}
setDaemon(true);

@ -73,7 +73,7 @@ public class SpringApplicationLauncherTests {
public void sourcesDefaultPropertiesAndArgsAreUsedToLaunch() throws Exception {
System.setProperty("spring.application.class.name",
TestSpringApplication.class.getName());
Object[] sources = new Object[0];
Class<?>[] sources = new Class<?>[0];
String[] args = new String[0];
new SpringApplicationLauncher(getClass().getClassLoader()).launch(sources, args);
@ -88,7 +88,7 @@ public class SpringApplicationLauncherTests {
private Set<String> launch() {
TestClassLoader classLoader = new TestClassLoader(getClass().getClassLoader());
try {
new TestSpringApplicationLauncher(classLoader).launch(new Object[0],
new TestSpringApplicationLauncher(classLoader).launch(new Class<?>[0],
new String[0]);
}
catch (Exception ex) {
@ -129,7 +129,7 @@ public class SpringApplicationLauncherTests {
private static String[] args;
public TestSpringApplication(Object[] sources) {
public TestSpringApplication(Class<?>[] sources) {
TestSpringApplication.sources = sources;
}

@ -398,10 +398,10 @@ The `Application.java` file would declare the `main` method, along with the basi
[[using-boot-configuration-classes]]
== Configuration classes
Spring Boot favors Java-based configuration. Although it is possible to call
`SpringApplication.run()` with an XML source, we generally recommend that your primary
source is a `@Configuration` class. Usually the class that defines the `main` method
is also a good candidate as the primary `@Configuration`.
Spring Boot favors Java-based configuration. Although it is possible to use
`SpringApplication` with an XML sources, we generally recommend that your primary
source is a single `@Configuration` class. Usually the class that defines the `main`
method is also a good candidate as the primary `@Configuration`.
TIP: Many Spring configuration examples have been published on the Internet that use XML
configuration. Always try to use the equivalent Java-based configuration if possible.

@ -16,6 +16,8 @@
package sample.xml;
import java.util.Collections;
import sample.xml.service.HelloWorldService;
import org.springframework.beans.factory.annotation.Autowired;
@ -24,6 +26,8 @@ import org.springframework.boot.SpringApplication;
public class SampleSpringXmlApplication implements CommandLineRunner {
private static final String CONTEXT_XML = "classpath:/META-INF/application-context.xml";
@Autowired
private HelloWorldService helloWorldService;
@ -33,7 +37,9 @@ public class SampleSpringXmlApplication implements CommandLineRunner {
}
public static void main(String[] args) throws Exception {
SpringApplication.run("classpath:/META-INF/application-context.xml", args);
SpringApplication application = new SpringApplication();
application.setSources(Collections.singleton(CONTEXT_XML));
application.run(args);
}
}

@ -91,9 +91,20 @@ public class SpringBootContextLoader extends AbstractContextLoader {
@Override
public ApplicationContext loadContext(MergedContextConfiguration config)
throws Exception {
Class<?>[] configClasses = config.getClasses();
String[] configLocations = config.getLocations();
Assert.state(
!ObjectUtils.isEmpty(configClasses)
|| !ObjectUtils.isEmpty(configLocations),
"No configuration classes "
+ "or locations found in @SpringApplicationConfiguration. "
+ "For default configuration detection to work you need "
+ "Spring 4.0.3 or better (found " + SpringVersion.getVersion()
+ ").");
SpringApplication application = getSpringApplication();
application.setMainApplicationClass(config.getTestClass());
application.setSources(getSources(config));
application.addPrimarySources(Arrays.asList(configClasses));
application.getSources().addAll(Arrays.asList(configLocations));
ConfigurableEnvironment environment = new StandardEnvironment();
if (!ObjectUtils.isEmpty(config.getActiveProfiles())) {
setActiveProfiles(environment, config.getActiveProfiles());
@ -137,17 +148,6 @@ public class SpringBootContextLoader extends AbstractContextLoader {
return new SpringApplication();
}
private Set<Object> getSources(MergedContextConfiguration mergedConfig) {
Set<Object> sources = new LinkedHashSet<>();
sources.addAll(Arrays.asList(mergedConfig.getClasses()));
sources.addAll(Arrays.asList(mergedConfig.getLocations()));
Assert.state(!sources.isEmpty(), "No configuration classes "
+ "or locations found in @SpringApplicationConfiguration. "
+ "For default configuration detection to work you need "
+ "Spring 4.0.3 or better (found " + SpringVersion.getVersion() + ").");
return sources;
}
private void setActiveProfiles(ConfigurableEnvironment environment,
String[] profiles) {
EnvironmentTestUtils.addEnvironment(environment, "spring.profiles.active="

@ -65,7 +65,6 @@ import org.springframework.core.env.PropertySource;
import org.springframework.core.env.SimpleCommandLinePropertySource;
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.core.io.support.SpringFactoriesLoader;
import org.springframework.util.Assert;
@ -92,7 +91,7 @@ import org.springframework.web.context.support.StandardServletEnvironment;
* <li>Trigger any {@link CommandLineRunner} beans</li>
* </ul>
*
* In most circumstances the static {@link #run(Object, String[])} method can be called
* In most circumstances the static {@link #run(Class, String[])} method can be called
* directly from your {@literal main} method to bootstrap your application:
*
* <pre class="code">
@ -122,18 +121,13 @@ import org.springframework.web.context.support.StandardServletEnvironment;
*
* {@link SpringApplication}s can read beans from a variety of different sources. It is
* generally recommended that a single {@code @Configuration} class is used to bootstrap
* your application, however, any of the following sources can also be used:
*
* your application, however, you may also set {@link #getSources() sources} from:
* <ul>
* <li>{@link Class} - A Java class to be loaded by {@link AnnotatedBeanDefinitionReader}
* </li>
* <li>{@link Resource} - An XML resource to be loaded by {@link XmlBeanDefinitionReader},
* or a groovy script to be loaded by {@link GroovyBeanDefinitionReader}</li>
* <li>{@link Package} - A Java package to be scanned by
* {@link ClassPathBeanDefinitionScanner}</li>
* <li>{@link CharSequence} - A class name, resource handle or package name to loaded as
* appropriate. If the {@link CharSequence} cannot be resolved to class and does not
* resolve to a {@link Resource} that exists it will be considered a {@link Package}.</li>
* <li>The fully qualified class name to be loaded by
* {@link AnnotatedBeanDefinitionReader}</li>
* <li>The location of an XML resource to be loaded by {@link XmlBeanDefinitionReader}, or
* a groovy script to be loaded by {@link GroovyBeanDefinitionReader}</li>
* <li>The name of a package to be scanned by {@link ClassPathBeanDefinitionScanner}</li>
* </ul>
*
* Configuration properties are also bound to the {@link SpringApplication}. This makes it
@ -152,9 +146,9 @@ import org.springframework.web.context.support.StandardServletEnvironment;
* @author Michael Simons
* @author Madhura Bhave
* @author Brian Clozel
* @see #run(Object, String[])
* @see #run(Object[], String[])
* @see #SpringApplication(Object...)
* @see #run(Class, String[])
* @see #run(Class[], String[])
* @see #SpringApplication(Class...)
*/
public class SpringApplication {
@ -214,9 +208,9 @@ public class SpringApplication {
private static final Log logger = LogFactory.getLog(SpringApplication.class);
private Set<Object> primarySources;
private Set<Class<?>> primarySources;
private Set<Object> sources = new LinkedHashSet<>();
private Set<String> sources = new LinkedHashSet<>();
private Class<?> mainApplicationClass;
@ -256,12 +250,12 @@ public class SpringApplication {
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param primarySources the primary bean sources
* @see #run(Object, String[])
* @see #SpringApplication(ResourceLoader, Object...)
* @see #run(Class, String[])
* @see #SpringApplication(ResourceLoader, Class...)
* @see #setSources(Set)
*/
public SpringApplication(Object... primarySources) {
initialize(primarySources);
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
/**
@ -271,17 +265,12 @@ public class SpringApplication {
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param primarySources the primary bean sources
* @see #run(Object, String[])
* @see #SpringApplication(ResourceLoader, Object...)
* @see #run(Class, String[])
* @see #setSources(Set)
*/
public SpringApplication(ResourceLoader resourceLoader, Object... primarySources) {
this.resourceLoader = resourceLoader;
initialize(primarySources);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void initialize(Object[] primarySources) {
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = deduceWebApplication();
@ -1131,12 +1120,12 @@ public class SpringApplication {
* should consider using {@link #getSources()}/{@link #setSources(Set)} rather than
* this calling method.
* @param additionalPrimarySources the additional primary sources to add
* @see #SpringApplication(Object...)
* @see #SpringApplication(Class...)
* @see #getSources()
* @see #setSources(Set)
* @see #getAllSources()
*/
public void addPrimarySources(Collection<Object> additionalPrimarySources) {
public void addPrimarySources(Collection<Class<?>> additionalPrimarySources) {
this.primarySources.addAll(additionalPrimarySources);
}
@ -1147,24 +1136,24 @@ public class SpringApplication {
* Sources set here will be used in addition to any primary sources set in the
* constructor.
* @return the application sources.
* @see #SpringApplication(Object...)
* @see #SpringApplication(Class...)
* @see #getAllSources()
*/
public Set<Object> getSources() {
public Set<String> getSources() {
return this.sources;
}
/**
* Set additional sources that will be used to create an ApplicationContext. A source
* can be: a class, class name, package, package name, or an XML resource location.
* can be: a class name, package name, or an XML resource location.
* <p>
* Sources set here will be used in addition to any primary sources set in the
* constructor.
* @param sources the application sources to set
* @see #SpringApplication(Object...)
* @see #SpringApplication(Class...)
* @see #getAllSources()
*/
public void setSources(Set<Object> sources) {
public void setSources(Set<String> sources) {
Assert.notNull(sources, "Sources must not be null");
this.sources = new LinkedHashSet<>(sources);
}
@ -1284,9 +1273,9 @@ public class SpringApplication {
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Object primarySource,
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Object[] { primarySource }, args);
return run(new Class<?>[] { primarySource }, args);
}
/**
@ -1296,7 +1285,7 @@ public class SpringApplication {
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Object[] primarySources,
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
@ -1307,14 +1296,14 @@ public class SpringApplication {
* argument.
* <p>
* Most developers will want to define their own main method and call the
* {@link #run(Object, String...) run} method instead.
* {@link #run(Class, String...) run} method instead.
* @param args command line arguments
* @throws Exception if the application cannot be started
* @see SpringApplication#run(Object[], String[])
* @see SpringApplication#run(Object, String...)
* @see SpringApplication#run(Class[], String[])
* @see SpringApplication#run(Class, String...)
*/
public static void main(String[] args) throws Exception {
SpringApplication.run(new Object[0], args);
SpringApplication.run(new Class<?>[0], args);
}
/**

@ -73,7 +73,7 @@ public class SpringApplicationBuilder {
private final AtomicBoolean running = new AtomicBoolean(false);
private final Set<Object> sources = new LinkedHashSet<>();
private final Set<Class<?>> sources = new LinkedHashSet<>();
private final Map<String, Object> defaultProperties = new LinkedHashMap<>();
@ -85,7 +85,7 @@ public class SpringApplicationBuilder {
private boolean configuredAsChild = false;
public SpringApplicationBuilder(Object... sources) {
public SpringApplicationBuilder(Class<?>... sources) {
this.application = createSpringApplication(sources);
}
@ -97,7 +97,7 @@ public class SpringApplicationBuilder {
* @return The {@link org.springframework.boot.SpringApplication} instance
* @since 1.1.0
*/
protected SpringApplication createSpringApplication(Object... sources) {
protected SpringApplication createSpringApplication(Class<?>... sources) {
return new SpringApplication(sources);
}
@ -176,7 +176,7 @@ public class SpringApplicationBuilder {
* @param sources the sources for the application (Spring configuration)
* @return the child application builder
*/
public SpringApplicationBuilder child(Object... sources) {
public SpringApplicationBuilder child(Class<?>... sources) {
SpringApplicationBuilder child = new SpringApplicationBuilder();
child.sources(sources);
@ -205,7 +205,7 @@ public class SpringApplicationBuilder {
* @param sources the sources for the application (Spring configuration)
* @return the parent builder
*/
public SpringApplicationBuilder parent(Object... sources) {
public SpringApplicationBuilder parent(Class<?>... sources) {
if (this.parent == null) {
this.parent = new SpringApplicationBuilder(sources).web(false)
.properties(this.defaultProperties).environment(this.environment);
@ -245,7 +245,7 @@ public class SpringApplicationBuilder {
* @param sources the sources for the application (Spring configuration)
* @return the new sibling builder
*/
public SpringApplicationBuilder sibling(Object... sources) {
public SpringApplicationBuilder sibling(Class<?>... sources) {
return runAndExtractParent().child(sources);
}
@ -258,7 +258,7 @@ public class SpringApplicationBuilder {
* parent
* @return the new sibling builder
*/
public SpringApplicationBuilder sibling(Object[] sources, String... args) {
public SpringApplicationBuilder sibling(Class<?>[] sources, String... args) {
return runAndExtractParent(args).child(sources);
}
@ -273,23 +273,13 @@ public class SpringApplicationBuilder {
return this;
}
/**
* Add more sources to use in this application.
* @param sources the sources to add
* @return the current builder
*/
public SpringApplicationBuilder sources(Object... sources) {
this.sources.addAll(new LinkedHashSet<>(Arrays.asList(sources)));
return this;
}
/**
* Add more sources (configuration classes and components) to this application.
* @param sources the sources to add
* @return the current builder
*/
public SpringApplicationBuilder sources(Class<?>... sources) {
this.sources.addAll(new LinkedHashSet<Object>(Arrays.asList(sources)));
this.sources.addAll(new LinkedHashSet<>(Arrays.asList(sources)));
return this;
}

@ -47,7 +47,7 @@ import org.springframework.web.context.WebApplicationContext;
* <p>
* To configure the application either override the
* {@link #configure(SpringApplicationBuilder)} method (calling
* {@link SpringApplicationBuilder#sources(Object...)}) or make the initializer itself a
* {@link SpringApplicationBuilder#sources(Class...)}) or make the initializer itself a
* {@code @Configuration}. If you are using {@link SpringBootServletInitializer} in
* combination with other {@link WebApplicationInitializer WebApplicationInitializers} you
* might also want to add an {@code @Ordered} annotation to configure a specific startup
@ -126,7 +126,8 @@ public abstract class SpringBootServletInitializer implements WebApplicationInit
+ "configure method or add an @Configuration annotation");
// Ensure error pages are registered
if (this.registerErrorPageFilter) {
application.getSources().add(ErrorPageFilterConfiguration.class);
application.addPrimarySources(
Collections.singleton(ErrorPageFilterConfiguration.class));
}
return run(application);
}

@ -46,7 +46,7 @@ public class OverrideSourcesTests {
@Test
public void beanInjectedToMainConfiguration() {
this.context = SpringApplication.run(new Object[] { MainConfiguration.class },
this.context = SpringApplication.run(new Class<?>[] { MainConfiguration.class },
new String[] { "--spring.main.web_environment=false" });
assertThat(this.context.getBean(Service.class).bean.name).isEqualTo("foo");
}
@ -54,7 +54,7 @@ public class OverrideSourcesTests {
@Test
public void primaryBeanInjectedProvingSourcesNotOverridden() {
this.context = SpringApplication.run(
new Object[] { MainConfiguration.class, TestConfiguration.class },
new Class<?>[] { MainConfiguration.class, TestConfiguration.class },
new String[] { "--spring.main.web_environment=false",
"--spring.main.sources=org.springframework.boot.OverrideSourcesTests.MainConfiguration" });
assertThat(this.context.getBean(Service.class).bean.name).isEqualTo("bar");

@ -160,7 +160,7 @@ public class SpringApplicationTests {
public void sourcesMustNotBeNull() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("PrimarySources must not be null");
new SpringApplication((Object[]) null).run();
new SpringApplication((Class<?>[]) null).run();
}
@Test
@ -576,20 +576,22 @@ public class SpringApplicationTests {
@Test
public void loadSources() throws Exception {
Object[] sources = { ExampleConfig.class, "a", TestCommandLineRunner.class };
Class<?>[] sources = { ExampleConfig.class, TestCommandLineRunner.class };
TestSpringApplication application = new TestSpringApplication(sources);
application.getSources().add("a");
application.setWebApplicationType(WebApplicationType.NONE);
application.setUseMockLoader(true);
this.context = application.run();
Set<Object> allSources = application.getAllSources();
assertThat(allSources.toArray()).isEqualTo(sources);
assertThat(allSources).contains(ExampleConfig.class, TestCommandLineRunner.class,
"a");
}
@Test
public void wildcardSources() {
Object[] sources = {
"classpath:org/springframework/boot/sample-${sample.app.test.prop}.xml" };
TestSpringApplication application = new TestSpringApplication(sources);
TestSpringApplication application = new TestSpringApplication();
application.getSources().add(
"classpath:org/springframework/boot/sample-${sample.app.test.prop}.xml");
application.setWebApplicationType(WebApplicationType.NONE);
this.context = application.run();
}
@ -603,7 +605,7 @@ public class SpringApplicationTests {
@Test
public void runComponents() throws Exception {
this.context = SpringApplication.run(
new Object[] { ExampleWebConfig.class, Object.class }, new String[0]);
new Class<?>[] { ExampleWebConfig.class, Object.class }, new String[0]);
assertThat(this.context).isNotNull();
}
@ -970,12 +972,12 @@ public class SpringApplicationTests {
private Banner.Mode bannerMode;
TestSpringApplication(Object... sources) {
super(sources);
TestSpringApplication(Class<?>... primarySources) {
super(primarySources);
}
TestSpringApplication(ResourceLoader resourceLoader, Object... sources) {
super(resourceLoader, sources);
TestSpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
super(resourceLoader, primarySources);
}
public void setUseMockLoader(boolean useMockLoader) {

Loading…
Cancel
Save