Skip to content

Commit

Permalink
Refine SpringApplication source types
Browse files Browse the repository at this point in the history
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
  • Loading branch information
philwebb committed May 16, 2017
1 parent 302f038 commit 889d43d
Show file tree
Hide file tree
Showing 12 changed files with 94 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ public SpringApplicationLauncher(ClassLoader classLoader) {
* @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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ private void run(String[] args) throws Exception {
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public void compileAndRun() throws Exception {
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);
Expand All @@ -125,8 +125,8 @@ public void stop() {
}
}

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) + "'");
Expand All @@ -148,18 +148,18 @@ private class RunThread extends Thread {

private final Object monitor = new Object();

private final Object[] compiledSources;
private final Class<?>[] compiledSources;

private Object applicationContext;

/**
* 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public void systemPropertyOverridesEnvironmentVariable() {
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);

Expand All @@ -88,7 +88,7 @@ public void sourcesDefaultPropertiesAndArgsAreUsedToLaunch() throws Exception {
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) {
Expand Down Expand Up @@ -129,7 +129,7 @@ public static class TestSpringApplication {

private static String[] args;

public TestSpringApplication(Object[] sources) {
public TestSpringApplication(Class<?>[] sources) {
TestSpringApplication.sources = sources;
}

Expand Down
8 changes: 4 additions & 4 deletions spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package sample.xml;

import java.util.Collections;

import sample.xml.service.HelloWorldService;

import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -24,6 +26,8 @@

public class SampleSpringXmlApplication implements CommandLineRunner {

private static final String CONTEXT_XML = "classpath:/META-INF/application-context.xml";

@Autowired
private HelloWorldService helloWorldService;

Expand All @@ -33,7 +37,9 @@ public void run(String... args) {
}

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -137,17 +148,6 @@ protected SpringApplication getSpringApplication() {
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="
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@
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;
Expand All @@ -92,7 +91,7 @@
* <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">
Expand Down Expand Up @@ -122,18 +121,13 @@
*
* {@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
Expand All @@ -152,9 +146,9 @@
* @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 {

Expand Down Expand Up @@ -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;

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

/**
Expand All @@ -271,17 +265,12 @@ public SpringApplication(Object... primarySources) {
* {@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();
Expand Down Expand Up @@ -1131,12 +1120,12 @@ public void setEnvironment(ConfigurableEnvironment environment) {
* 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);
}

Expand All @@ -1147,24 +1136,24 @@ public void addPrimarySources(Collection<Object> additionalPrimarySources) {
* 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);
}
Expand Down Expand Up @@ -1284,9 +1273,9 @@ public Set<ApplicationListener<?>> getListeners() {
* @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);
}

/**
Expand All @@ -1296,7 +1285,7 @@ public static ConfigurableApplicationContext run(Object primarySource,
* @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);
}
Expand All @@ -1307,14 +1296,14 @@ public static ConfigurableApplicationContext run(Object[] primarySources,
* 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);
}

/**
Expand Down
Loading

0 comments on commit 889d43d

Please sign in to comment.