001/* 002 * Copyright 2012-2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot; 018 019import java.lang.reflect.Constructor; 020import java.security.AccessControlException; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.HashMap; 026import java.util.HashSet; 027import java.util.LinkedHashSet; 028import java.util.List; 029import java.util.Map; 030import java.util.Properties; 031import java.util.Set; 032 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035 036import org.springframework.beans.BeanUtils; 037import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader; 038import org.springframework.beans.factory.support.BeanDefinitionRegistry; 039import org.springframework.beans.factory.support.BeanNameGenerator; 040import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; 041import org.springframework.boot.Banner.Mode; 042import org.springframework.boot.diagnostics.FailureAnalyzers; 043import org.springframework.context.ApplicationContext; 044import org.springframework.context.ApplicationContextInitializer; 045import org.springframework.context.ApplicationListener; 046import org.springframework.context.ConfigurableApplicationContext; 047import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; 048import org.springframework.context.annotation.AnnotationConfigApplicationContext; 049import org.springframework.context.annotation.AnnotationConfigUtils; 050import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; 051import org.springframework.context.support.AbstractApplicationContext; 052import org.springframework.context.support.GenericApplicationContext; 053import org.springframework.core.GenericTypeResolver; 054import org.springframework.core.annotation.AnnotationAwareOrderComparator; 055import org.springframework.core.env.CommandLinePropertySource; 056import org.springframework.core.env.CompositePropertySource; 057import org.springframework.core.env.ConfigurableEnvironment; 058import org.springframework.core.env.Environment; 059import org.springframework.core.env.MapPropertySource; 060import org.springframework.core.env.MutablePropertySources; 061import org.springframework.core.env.PropertySource; 062import org.springframework.core.env.SimpleCommandLinePropertySource; 063import org.springframework.core.env.StandardEnvironment; 064import org.springframework.core.io.DefaultResourceLoader; 065import org.springframework.core.io.Resource; 066import org.springframework.core.io.ResourceLoader; 067import org.springframework.core.io.support.SpringFactoriesLoader; 068import org.springframework.util.Assert; 069import org.springframework.util.ClassUtils; 070import org.springframework.util.ObjectUtils; 071import org.springframework.util.ReflectionUtils; 072import org.springframework.util.StopWatch; 073import org.springframework.util.StringUtils; 074import org.springframework.web.context.WebApplicationContext; 075import org.springframework.web.context.support.StandardServletEnvironment; 076 077/** 078 * Classes that can be used to bootstrap and launch a Spring application from a Java main 079 * method. By default class will perform the following steps to bootstrap your 080 * application: 081 * 082 * <ul> 083 * <li>Create an appropriate {@link ApplicationContext} instance (depending on your 084 * classpath)</li> 085 * <li>Register a {@link CommandLinePropertySource} to expose command line arguments as 086 * Spring properties</li> 087 * <li>Refresh the application context, loading all singleton beans</li> 088 * <li>Trigger any {@link CommandLineRunner} beans</li> 089 * </ul> 090 * 091 * In most circumstances the static {@link #run(Object, String[])} method can be called 092 * directly from your {@literal main} method to bootstrap your application: 093 * 094 * <pre class="code"> 095 * @Configuration 096 * @EnableAutoConfiguration 097 * public class MyApplication { 098 * 099 * // ... Bean definitions 100 * 101 * public static void main(String[] args) throws Exception { 102 * SpringApplication.run(MyApplication.class, args); 103 * } 104 * } 105 * </pre> 106 * 107 * <p> 108 * For more advanced configuration a {@link SpringApplication} instance can be created and 109 * customized before being run: 110 * 111 * <pre class="code"> 112 * public static void main(String[] args) throws Exception { 113 * SpringApplication app = new SpringApplication(MyApplication.class); 114 * // ... customize app settings here 115 * app.run(args) 116 * } 117 * </pre> 118 * 119 * {@link SpringApplication}s can read beans from a variety of different sources. It is 120 * generally recommended that a single {@code @Configuration} class is used to bootstrap 121 * your application, however, any of the following sources can also be used: 122 * 123 * <ul> 124 * <li>{@link Class} - A Java class to be loaded by {@link AnnotatedBeanDefinitionReader} 125 * </li> 126 * <li>{@link Resource} - An XML resource to be loaded by {@link XmlBeanDefinitionReader}, 127 * or a groovy script to be loaded by {@link GroovyBeanDefinitionReader}</li> 128 * <li>{@link Package} - A Java package to be scanned by 129 * {@link ClassPathBeanDefinitionScanner}</li> 130 * <li>{@link CharSequence} - A class name, resource handle or package name to loaded as 131 * appropriate. If the {@link CharSequence} cannot be resolved to class and does not 132 * resolve to a {@link Resource} that exists it will be considered a {@link Package}.</li> 133 * </ul> 134 * 135 * @author Phillip Webb 136 * @author Dave Syer 137 * @author Andy Wilkinson 138 * @author Christian Dupuis 139 * @author Stephane Nicoll 140 * @author Jeremy Rickard 141 * @author Craig Burke 142 * @author Michael Simons 143 * @author Ethan Rubinson 144 * @see #run(Object, String[]) 145 * @see #run(Object[], String[]) 146 * @see #SpringApplication(Object...) 147 */ 148public class SpringApplication { 149 150 /** 151 * The class name of application context that will be used by default for non-web 152 * environments. 153 */ 154 public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context." 155 + "annotation.AnnotationConfigApplicationContext"; 156 157 /** 158 * The class name of application context that will be used by default for web 159 * environments. 160 */ 161 public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework." 162 + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext"; 163 164 private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet", 165 "org.springframework.web.context.ConfigurableWebApplicationContext" }; 166 167 /** 168 * Default banner location. 169 */ 170 public static final String BANNER_LOCATION_PROPERTY_VALUE = SpringApplicationBannerPrinter.DEFAULT_BANNER_LOCATION; 171 172 /** 173 * Banner location property key. 174 */ 175 public static final String BANNER_LOCATION_PROPERTY = SpringApplicationBannerPrinter.BANNER_LOCATION_PROPERTY; 176 177 private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless"; 178 179 private static final Log logger = LogFactory.getLog(SpringApplication.class); 180 181 private final Set<Object> sources = new LinkedHashSet<Object>(); 182 183 private Class<?> mainApplicationClass; 184 185 private Banner.Mode bannerMode = Banner.Mode.CONSOLE; 186 187 private boolean logStartupInfo = true; 188 189 private boolean addCommandLineProperties = true; 190 191 private Banner banner; 192 193 private ResourceLoader resourceLoader; 194 195 private BeanNameGenerator beanNameGenerator; 196 197 private ConfigurableEnvironment environment; 198 199 private Class<? extends ConfigurableApplicationContext> applicationContextClass; 200 201 private boolean webEnvironment; 202 203 private boolean headless = true; 204 205 private boolean registerShutdownHook = true; 206 207 private List<ApplicationContextInitializer<?>> initializers; 208 209 private List<ApplicationListener<?>> listeners; 210 211 private Map<String, Object> defaultProperties; 212 213 private Set<String> additionalProfiles = new HashSet<String>(); 214 215 /** 216 * Create a new {@link SpringApplication} instance. The application context will load 217 * beans from the specified sources (see {@link SpringApplication class-level} 218 * documentation for details. The instance can be customized before calling 219 * {@link #run(String...)}. 220 * @param sources the bean sources 221 * @see #run(Object, String[]) 222 * @see #SpringApplication(ResourceLoader, Object...) 223 */ 224 public SpringApplication(Object... sources) { 225 initialize(sources); 226 } 227 228 /** 229 * Create a new {@link SpringApplication} instance. The application context will load 230 * beans from the specified sources (see {@link SpringApplication class-level} 231 * documentation for details. The instance can be customized before calling 232 * {@link #run(String...)}. 233 * @param resourceLoader the resource loader to use 234 * @param sources the bean sources 235 * @see #run(Object, String[]) 236 * @see #SpringApplication(ResourceLoader, Object...) 237 */ 238 public SpringApplication(ResourceLoader resourceLoader, Object... sources) { 239 this.resourceLoader = resourceLoader; 240 initialize(sources); 241 } 242 243 @SuppressWarnings({ "unchecked", "rawtypes" }) 244 private void initialize(Object[] sources) { 245 if (sources != null && sources.length > 0) { 246 this.sources.addAll(Arrays.asList(sources)); 247 } 248 this.webEnvironment = deduceWebEnvironment(); 249 setInitializers((Collection) getSpringFactoriesInstances( 250 ApplicationContextInitializer.class)); 251 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); 252 this.mainApplicationClass = deduceMainApplicationClass(); 253 } 254 255 private boolean deduceWebEnvironment() { 256 for (String className : WEB_ENVIRONMENT_CLASSES) { 257 if (!ClassUtils.isPresent(className, null)) { 258 return false; 259 } 260 } 261 return true; 262 } 263 264 private Class<?> deduceMainApplicationClass() { 265 try { 266 StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); 267 for (StackTraceElement stackTraceElement : stackTrace) { 268 if ("main".equals(stackTraceElement.getMethodName())) { 269 return Class.forName(stackTraceElement.getClassName()); 270 } 271 } 272 } 273 catch (ClassNotFoundException ex) { 274 // Swallow and continue 275 } 276 return null; 277 } 278 279 /** 280 * Run the Spring application, creating and refreshing a new 281 * {@link ApplicationContext}. 282 * @param args the application arguments (usually passed from a Java main method) 283 * @return a running {@link ApplicationContext} 284 */ 285 public ConfigurableApplicationContext run(String... args) { 286 StopWatch stopWatch = new StopWatch(); 287 stopWatch.start(); 288 ConfigurableApplicationContext context = null; 289 FailureAnalyzers analyzers = null; 290 configureHeadlessProperty(); 291 SpringApplicationRunListeners listeners = getRunListeners(args); 292 listeners.starting(); 293 try { 294 ApplicationArguments applicationArguments = new DefaultApplicationArguments( 295 args); 296 ConfigurableEnvironment environment = prepareEnvironment(listeners, 297 applicationArguments); 298 Banner printedBanner = printBanner(environment); 299 context = createApplicationContext(); 300 analyzers = new FailureAnalyzers(context); 301 prepareContext(context, environment, listeners, applicationArguments, 302 printedBanner); 303 refreshContext(context); 304 afterRefresh(context, applicationArguments); 305 listeners.finished(context, null); 306 stopWatch.stop(); 307 if (this.logStartupInfo) { 308 new StartupInfoLogger(this.mainApplicationClass) 309 .logStarted(getApplicationLog(), stopWatch); 310 } 311 return context; 312 } 313 catch (Throwable ex) { 314 handleRunFailure(context, listeners, analyzers, ex); 315 throw new IllegalStateException(ex); 316 } 317 } 318 319 private ConfigurableEnvironment prepareEnvironment( 320 SpringApplicationRunListeners listeners, 321 ApplicationArguments applicationArguments) { 322 // Create and configure the environment 323 ConfigurableEnvironment environment = getOrCreateEnvironment(); 324 configureEnvironment(environment, applicationArguments.getSourceArgs()); 325 listeners.environmentPrepared(environment); 326 if (!this.webEnvironment) { 327 environment = new EnvironmentConverter(getClassLoader()) 328 .convertToStandardEnvironmentIfNecessary(environment); 329 } 330 return environment; 331 } 332 333 private void prepareContext(ConfigurableApplicationContext context, 334 ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, 335 ApplicationArguments applicationArguments, Banner printedBanner) { 336 context.setEnvironment(environment); 337 postProcessApplicationContext(context); 338 applyInitializers(context); 339 listeners.contextPrepared(context); 340 if (this.logStartupInfo) { 341 logStartupInfo(context.getParent() == null); 342 logStartupProfileInfo(context); 343 } 344 345 // Add boot specific singleton beans 346 context.getBeanFactory().registerSingleton("springApplicationArguments", 347 applicationArguments); 348 if (printedBanner != null) { 349 context.getBeanFactory().registerSingleton("springBootBanner", printedBanner); 350 } 351 352 // Load the sources 353 Set<Object> sources = getSources(); 354 Assert.notEmpty(sources, "Sources must not be empty"); 355 load(context, sources.toArray(new Object[sources.size()])); 356 listeners.contextLoaded(context); 357 } 358 359 private void refreshContext(ConfigurableApplicationContext context) { 360 refresh(context); 361 if (this.registerShutdownHook) { 362 try { 363 context.registerShutdownHook(); 364 } 365 catch (AccessControlException ex) { 366 // Not allowed in some environments. 367 } 368 } 369 } 370 371 private void configureHeadlessProperty() { 372 System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty( 373 SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless))); 374 } 375 376 private SpringApplicationRunListeners getRunListeners(String[] args) { 377 Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; 378 return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances( 379 SpringApplicationRunListener.class, types, this, args)); 380 } 381 382 private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) { 383 return getSpringFactoriesInstances(type, new Class<?>[] {}); 384 } 385 386 private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, 387 Class<?>[] parameterTypes, Object... args) { 388 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 389 // Use names and ensure unique to protect against duplicates 390 Set<String> names = new LinkedHashSet<String>( 391 SpringFactoriesLoader.loadFactoryNames(type, classLoader)); 392 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, 393 classLoader, args, names); 394 AnnotationAwareOrderComparator.sort(instances); 395 return instances; 396 } 397 398 @SuppressWarnings("unchecked") 399 private <T> List<T> createSpringFactoriesInstances(Class<T> type, 400 Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, 401 Set<String> names) { 402 List<T> instances = new ArrayList<T>(names.size()); 403 for (String name : names) { 404 try { 405 Class<?> instanceClass = ClassUtils.forName(name, classLoader); 406 Assert.isAssignable(type, instanceClass); 407 Constructor<?> constructor = instanceClass 408 .getDeclaredConstructor(parameterTypes); 409 T instance = (T) BeanUtils.instantiateClass(constructor, args); 410 instances.add(instance); 411 } 412 catch (Throwable ex) { 413 throw new IllegalArgumentException( 414 "Cannot instantiate " + type + " : " + name, ex); 415 } 416 } 417 return instances; 418 } 419 420 private ConfigurableEnvironment getOrCreateEnvironment() { 421 if (this.environment != null) { 422 return this.environment; 423 } 424 if (this.webEnvironment) { 425 return new StandardServletEnvironment(); 426 } 427 return new StandardEnvironment(); 428 } 429 430 /** 431 * Template method delegating to 432 * {@link #configurePropertySources(ConfigurableEnvironment, String[])} and 433 * {@link #configureProfiles(ConfigurableEnvironment, String[])} in that order. 434 * Override this method for complete control over Environment customization, or one of 435 * the above for fine-grained control over property sources or profiles, respectively. 436 * @param environment this application's environment 437 * @param args arguments passed to the {@code run} method 438 * @see #configureProfiles(ConfigurableEnvironment, String[]) 439 * @see #configurePropertySources(ConfigurableEnvironment, String[]) 440 */ 441 protected void configureEnvironment(ConfigurableEnvironment environment, 442 String[] args) { 443 configurePropertySources(environment, args); 444 configureProfiles(environment, args); 445 } 446 447 /** 448 * Add, remove or re-order any {@link PropertySource}s in this application's 449 * environment. 450 * @param environment this application's environment 451 * @param args arguments passed to the {@code run} method 452 * @see #configureEnvironment(ConfigurableEnvironment, String[]) 453 */ 454 protected void configurePropertySources(ConfigurableEnvironment environment, 455 String[] args) { 456 MutablePropertySources sources = environment.getPropertySources(); 457 if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) { 458 sources.addLast( 459 new MapPropertySource("defaultProperties", this.defaultProperties)); 460 } 461 if (this.addCommandLineProperties && args.length > 0) { 462 String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; 463 if (sources.contains(name)) { 464 PropertySource<?> source = sources.get(name); 465 CompositePropertySource composite = new CompositePropertySource(name); 466 composite.addPropertySource(new SimpleCommandLinePropertySource( 467 name + "-" + args.hashCode(), args)); 468 composite.addPropertySource(source); 469 sources.replace(name, composite); 470 } 471 else { 472 sources.addFirst(new SimpleCommandLinePropertySource(args)); 473 } 474 } 475 } 476 477 /** 478 * Configure which profiles are active (or active by default) for this application 479 * environment. Additional profiles may be activated during configuration file 480 * processing via the {@code spring.profiles.active} property. 481 * @param environment this application's environment 482 * @param args arguments passed to the {@code run} method 483 * @see #configureEnvironment(ConfigurableEnvironment, String[]) 484 * @see org.springframework.boot.context.config.ConfigFileApplicationListener 485 */ 486 protected void configureProfiles(ConfigurableEnvironment environment, String[] args) { 487 environment.getActiveProfiles(); // ensure they are initialized 488 // But these ones should go first (last wins in a property key clash) 489 Set<String> profiles = new LinkedHashSet<String>(this.additionalProfiles); 490 profiles.addAll(Arrays.asList(environment.getActiveProfiles())); 491 environment.setActiveProfiles(profiles.toArray(new String[profiles.size()])); 492 } 493 494 private Banner printBanner(ConfigurableEnvironment environment) { 495 if (this.bannerMode == Banner.Mode.OFF) { 496 return null; 497 } 498 ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader 499 : new DefaultResourceLoader(getClassLoader()); 500 SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter( 501 resourceLoader, this.banner); 502 if (this.bannerMode == Mode.LOG) { 503 return bannerPrinter.print(environment, this.mainApplicationClass, logger); 504 } 505 return bannerPrinter.print(environment, this.mainApplicationClass, System.out); 506 } 507 508 /** 509 * Strategy method used to create the {@link ApplicationContext}. By default this 510 * method will respect any explicitly set application context or application context 511 * class before falling back to a suitable default. 512 * @return the application context (not yet refreshed) 513 * @see #setApplicationContextClass(Class) 514 */ 515 protected ConfigurableApplicationContext createApplicationContext() { 516 Class<?> contextClass = this.applicationContextClass; 517 if (contextClass == null) { 518 try { 519 contextClass = Class.forName(this.webEnvironment 520 ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS); 521 } 522 catch (ClassNotFoundException ex) { 523 throw new IllegalStateException( 524 "Unable create a default ApplicationContext, " 525 + "please specify an ApplicationContextClass", 526 ex); 527 } 528 } 529 return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass); 530 } 531 532 /** 533 * Apply any relevant post processing the {@link ApplicationContext}. Subclasses can 534 * apply additional processing as required. 535 * @param context the application context 536 */ 537 protected void postProcessApplicationContext(ConfigurableApplicationContext context) { 538 if (this.beanNameGenerator != null) { 539 context.getBeanFactory().registerSingleton( 540 AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, 541 this.beanNameGenerator); 542 } 543 if (this.resourceLoader != null) { 544 if (context instanceof GenericApplicationContext) { 545 ((GenericApplicationContext) context) 546 .setResourceLoader(this.resourceLoader); 547 } 548 if (context instanceof DefaultResourceLoader) { 549 ((DefaultResourceLoader) context) 550 .setClassLoader(this.resourceLoader.getClassLoader()); 551 } 552 } 553 } 554 555 /** 556 * Apply any {@link ApplicationContextInitializer}s to the context before it is 557 * refreshed. 558 * @param context the configured ApplicationContext (not refreshed yet) 559 * @see ConfigurableApplicationContext#refresh() 560 */ 561 @SuppressWarnings({ "rawtypes", "unchecked" }) 562 protected void applyInitializers(ConfigurableApplicationContext context) { 563 for (ApplicationContextInitializer initializer : getInitializers()) { 564 Class<?> requiredType = GenericTypeResolver.resolveTypeArgument( 565 initializer.getClass(), ApplicationContextInitializer.class); 566 Assert.isInstanceOf(requiredType, context, "Unable to call initializer."); 567 initializer.initialize(context); 568 } 569 } 570 571 /** 572 * Called to log startup information, subclasses may override to add additional 573 * logging. 574 * @param isRoot true if this application is the root of a context hierarchy 575 */ 576 protected void logStartupInfo(boolean isRoot) { 577 if (isRoot) { 578 new StartupInfoLogger(this.mainApplicationClass) 579 .logStarting(getApplicationLog()); 580 } 581 } 582 583 /** 584 * Called to log active profile information. 585 * @param context the application context 586 */ 587 protected void logStartupProfileInfo(ConfigurableApplicationContext context) { 588 Log log = getApplicationLog(); 589 if (log.isInfoEnabled()) { 590 String[] activeProfiles = context.getEnvironment().getActiveProfiles(); 591 if (ObjectUtils.isEmpty(activeProfiles)) { 592 String[] defaultProfiles = context.getEnvironment().getDefaultProfiles(); 593 log.info("No active profile set, falling back to default profiles: " 594 + StringUtils.arrayToCommaDelimitedString(defaultProfiles)); 595 } 596 else { 597 log.info("The following profiles are active: " 598 + StringUtils.arrayToCommaDelimitedString(activeProfiles)); 599 } 600 } 601 } 602 603 /** 604 * Returns the {@link Log} for the application. By default will be deduced. 605 * @return the application log 606 */ 607 protected Log getApplicationLog() { 608 if (this.mainApplicationClass == null) { 609 return logger; 610 } 611 return LogFactory.getLog(this.mainApplicationClass); 612 } 613 614 /** 615 * Load beans into the application context. 616 * @param context the context to load beans into 617 * @param sources the sources to load 618 */ 619 protected void load(ApplicationContext context, Object[] sources) { 620 if (logger.isDebugEnabled()) { 621 logger.debug( 622 "Loading source " + StringUtils.arrayToCommaDelimitedString(sources)); 623 } 624 BeanDefinitionLoader loader = createBeanDefinitionLoader( 625 getBeanDefinitionRegistry(context), sources); 626 if (this.beanNameGenerator != null) { 627 loader.setBeanNameGenerator(this.beanNameGenerator); 628 } 629 if (this.resourceLoader != null) { 630 loader.setResourceLoader(this.resourceLoader); 631 } 632 if (this.environment != null) { 633 loader.setEnvironment(this.environment); 634 } 635 loader.load(); 636 } 637 638 /** 639 * The ResourceLoader that will be used in the ApplicationContext. 640 * @return the resourceLoader the resource loader that will be used in the 641 * ApplicationContext (or null if the default) 642 */ 643 public ResourceLoader getResourceLoader() { 644 return this.resourceLoader; 645 } 646 647 /** 648 * Either the ClassLoader that will be used in the ApplicationContext (if 649 * {@link #setResourceLoader(ResourceLoader) resourceLoader} is set, or the context 650 * class loader (if not null), or the loader of the Spring {@link ClassUtils} class. 651 * @return a ClassLoader (never null) 652 */ 653 public ClassLoader getClassLoader() { 654 if (this.resourceLoader != null) { 655 return this.resourceLoader.getClassLoader(); 656 } 657 return ClassUtils.getDefaultClassLoader(); 658 } 659 660 /** 661 * Get the bean definition registry. 662 * @param context the application context 663 * @return the BeanDefinitionRegistry if it can be determined 664 */ 665 private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) { 666 if (context instanceof BeanDefinitionRegistry) { 667 return (BeanDefinitionRegistry) context; 668 } 669 if (context instanceof AbstractApplicationContext) { 670 return (BeanDefinitionRegistry) ((AbstractApplicationContext) context) 671 .getBeanFactory(); 672 } 673 throw new IllegalStateException("Could not locate BeanDefinitionRegistry"); 674 } 675 676 /** 677 * Factory method used to create the {@link BeanDefinitionLoader}. 678 * @param registry the bean definition registry 679 * @param sources the sources to load 680 * @return the {@link BeanDefinitionLoader} that will be used to load beans 681 */ 682 protected BeanDefinitionLoader createBeanDefinitionLoader( 683 BeanDefinitionRegistry registry, Object[] sources) { 684 return new BeanDefinitionLoader(registry, sources); 685 } 686 687 /** 688 * Refresh the underlying {@link ApplicationContext}. 689 * @param applicationContext the application context to refresh 690 */ 691 protected void refresh(ApplicationContext applicationContext) { 692 Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext); 693 ((AbstractApplicationContext) applicationContext).refresh(); 694 } 695 696 /** 697 * Called after the context has been refreshed. 698 * @param context the application context 699 * @param args the application arguments 700 */ 701 protected void afterRefresh(ConfigurableApplicationContext context, 702 ApplicationArguments args) { 703 callRunners(context, args); 704 } 705 706 private void callRunners(ApplicationContext context, ApplicationArguments args) { 707 List<Object> runners = new ArrayList<Object>(); 708 runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); 709 runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); 710 AnnotationAwareOrderComparator.sort(runners); 711 for (Object runner : new LinkedHashSet<Object>(runners)) { 712 if (runner instanceof ApplicationRunner) { 713 callRunner((ApplicationRunner) runner, args); 714 } 715 if (runner instanceof CommandLineRunner) { 716 callRunner((CommandLineRunner) runner, args); 717 } 718 } 719 } 720 721 private void callRunner(ApplicationRunner runner, ApplicationArguments args) { 722 try { 723 (runner).run(args); 724 } 725 catch (Exception ex) { 726 throw new IllegalStateException("Failed to execute ApplicationRunner", ex); 727 } 728 } 729 730 private void callRunner(CommandLineRunner runner, ApplicationArguments args) { 731 try { 732 (runner).run(args.getSourceArgs()); 733 } 734 catch (Exception ex) { 735 throw new IllegalStateException("Failed to execute CommandLineRunner", ex); 736 } 737 } 738 739 private void handleRunFailure(ConfigurableApplicationContext context, 740 SpringApplicationRunListeners listeners, FailureAnalyzers analyzers, 741 Throwable exception) { 742 try { 743 try { 744 handleExitCode(context, exception); 745 listeners.finished(context, exception); 746 } 747 finally { 748 reportFailure(analyzers, exception); 749 if (context != null) { 750 context.close(); 751 } 752 } 753 } 754 catch (Exception ex) { 755 logger.warn("Unable to close ApplicationContext", ex); 756 } 757 ReflectionUtils.rethrowRuntimeException(exception); 758 } 759 760 private void reportFailure(FailureAnalyzers analyzers, Throwable failure) { 761 try { 762 if (analyzers != null && analyzers.analyzeAndReport(failure)) { 763 registerLoggedException(failure); 764 return; 765 } 766 } 767 catch (Throwable ex) { 768 // Continue with normal handling of the original failure 769 } 770 if (logger.isErrorEnabled()) { 771 logger.error("Application startup failed", failure); 772 registerLoggedException(failure); 773 } 774 } 775 776 /** 777 * Register that the given exception has been logged. By default, if the running in 778 * the main thread, this method will suppress additional printing of the stacktrace. 779 * @param exception the exception that was logged 780 */ 781 protected void registerLoggedException(Throwable exception) { 782 SpringBootExceptionHandler handler = getSpringBootExceptionHandler(); 783 if (handler != null) { 784 handler.registerLoggedException(exception); 785 } 786 } 787 788 private void handleExitCode(ConfigurableApplicationContext context, 789 Throwable exception) { 790 int exitCode = getExitCodeFromException(context, exception); 791 if (exitCode != 0) { 792 if (context != null) { 793 context.publishEvent(new ExitCodeEvent(context, exitCode)); 794 } 795 SpringBootExceptionHandler handler = getSpringBootExceptionHandler(); 796 if (handler != null) { 797 handler.registerExitCode(exitCode); 798 } 799 } 800 } 801 802 private int getExitCodeFromException(ConfigurableApplicationContext context, 803 Throwable exception) { 804 int exitCode = getExitCodeFromMappedException(context, exception); 805 if (exitCode == 0) { 806 exitCode = getExitCodeFromExitCodeGeneratorException(exception); 807 } 808 return exitCode; 809 } 810 811 private int getExitCodeFromMappedException(ConfigurableApplicationContext context, 812 Throwable exception) { 813 if (context == null || !context.isActive()) { 814 return 0; 815 } 816 ExitCodeGenerators generators = new ExitCodeGenerators(); 817 Collection<ExitCodeExceptionMapper> beans = context 818 .getBeansOfType(ExitCodeExceptionMapper.class).values(); 819 generators.addAll(exception, beans); 820 return generators.getExitCode(); 821 } 822 823 private int getExitCodeFromExitCodeGeneratorException(Throwable exception) { 824 if (exception == null) { 825 return 0; 826 } 827 if (exception instanceof ExitCodeGenerator) { 828 return ((ExitCodeGenerator) exception).getExitCode(); 829 } 830 return getExitCodeFromExitCodeGeneratorException(exception.getCause()); 831 } 832 833 SpringBootExceptionHandler getSpringBootExceptionHandler() { 834 if (isMainThread(Thread.currentThread())) { 835 return SpringBootExceptionHandler.forCurrentThread(); 836 } 837 return null; 838 } 839 840 private boolean isMainThread(Thread currentThread) { 841 return ("main".equals(currentThread.getName()) 842 || "restartedMain".equals(currentThread.getName())) 843 && "main".equals(currentThread.getThreadGroup().getName()); 844 } 845 846 /** 847 * Returns the main application class that has been deduced or explicitly configured. 848 * @return the main application class or {@code null} 849 */ 850 public Class<?> getMainApplicationClass() { 851 return this.mainApplicationClass; 852 } 853 854 /** 855 * Set a specific main application class that will be used as a log source and to 856 * obtain version information. By default the main application class will be deduced. 857 * Can be set to {@code null} if there is no explicit application class. 858 * @param mainApplicationClass the mainApplicationClass to set or {@code null} 859 */ 860 public void setMainApplicationClass(Class<?> mainApplicationClass) { 861 this.mainApplicationClass = mainApplicationClass; 862 } 863 864 /** 865 * Returns whether this {@link SpringApplication} is running within a web environment. 866 * @return {@code true} if running within a web environment, otherwise {@code false}. 867 * @see #setWebEnvironment(boolean) 868 */ 869 public boolean isWebEnvironment() { 870 return this.webEnvironment; 871 } 872 873 /** 874 * Sets if this application is running within a web environment. If not specified will 875 * attempt to deduce the environment based on the classpath. 876 * @param webEnvironment if the application is running in a web environment 877 */ 878 public void setWebEnvironment(boolean webEnvironment) { 879 this.webEnvironment = webEnvironment; 880 } 881 882 /** 883 * Sets if the application is headless and should not instantiate AWT. Defaults to 884 * {@code true} to prevent java icons appearing. 885 * @param headless if the application is headless 886 */ 887 public void setHeadless(boolean headless) { 888 this.headless = headless; 889 } 890 891 /** 892 * Sets if the created {@link ApplicationContext} should have a shutdown hook 893 * registered. Defaults to {@code true} to ensure that JVM shutdowns are handled 894 * gracefully. 895 * @param registerShutdownHook if the shutdown hook should be registered 896 */ 897 public void setRegisterShutdownHook(boolean registerShutdownHook) { 898 this.registerShutdownHook = registerShutdownHook; 899 } 900 901 /** 902 * Sets the {@link Banner} instance which will be used to print the banner when no 903 * static banner file is provided. 904 * @param banner The Banner instance to use 905 */ 906 public void setBanner(Banner banner) { 907 this.banner = banner; 908 } 909 910 /** 911 * Sets the mode used to display the banner when the application runs. Defaults to 912 * {@code Banner.Mode.CONSOLE}. 913 * @param bannerMode the mode used to display the banner 914 */ 915 public void setBannerMode(Banner.Mode bannerMode) { 916 this.bannerMode = bannerMode; 917 } 918 919 /** 920 * Sets if the application information should be logged when the application starts. 921 * Defaults to {@code true}. 922 * @param logStartupInfo if startup info should be logged. 923 */ 924 public void setLogStartupInfo(boolean logStartupInfo) { 925 this.logStartupInfo = logStartupInfo; 926 } 927 928 /** 929 * Sets if a {@link CommandLinePropertySource} should be added to the application 930 * context in order to expose arguments. Defaults to {@code true}. 931 * @param addCommandLineProperties if command line arguments should be exposed 932 */ 933 public void setAddCommandLineProperties(boolean addCommandLineProperties) { 934 this.addCommandLineProperties = addCommandLineProperties; 935 } 936 937 /** 938 * Set default environment properties which will be used in addition to those in the 939 * existing {@link Environment}. 940 * @param defaultProperties the additional properties to set 941 */ 942 public void setDefaultProperties(Map<String, Object> defaultProperties) { 943 this.defaultProperties = defaultProperties; 944 } 945 946 /** 947 * Convenient alternative to {@link #setDefaultProperties(Map)}. 948 * @param defaultProperties some {@link Properties} 949 */ 950 public void setDefaultProperties(Properties defaultProperties) { 951 this.defaultProperties = new HashMap<String, Object>(); 952 for (Object key : Collections.list(defaultProperties.propertyNames())) { 953 this.defaultProperties.put((String) key, defaultProperties.get(key)); 954 } 955 } 956 957 /** 958 * Set additional profile values to use (on top of those set in system or command line 959 * properties). 960 * @param profiles the additional profiles to set 961 */ 962 public void setAdditionalProfiles(String... profiles) { 963 this.additionalProfiles = new LinkedHashSet<String>(Arrays.asList(profiles)); 964 } 965 966 /** 967 * Sets the bean name generator that should be used when generating bean names. 968 * @param beanNameGenerator the bean name generator 969 */ 970 public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) { 971 this.beanNameGenerator = beanNameGenerator; 972 } 973 974 /** 975 * Sets the underlying environment that should be used with the created application 976 * context. 977 * @param environment the environment 978 */ 979 public void setEnvironment(ConfigurableEnvironment environment) { 980 this.environment = environment; 981 } 982 983 /** 984 * Returns a mutable set of the sources that will be added to an ApplicationContext 985 * when {@link #run(String...)} is called. 986 * @return the sources the application sources. 987 * @see #SpringApplication(Object...) 988 */ 989 public Set<Object> getSources() { 990 return this.sources; 991 } 992 993 /** 994 * The sources that will be used to create an ApplicationContext. A valid source is 995 * one of: a class, class name, package, package name, or an XML resource location. 996 * Can also be set using constructors and static convenience methods (e.g. 997 * {@link #run(Object[], String[])}). 998 * <p> 999 * NOTE: sources defined here will be used in addition to any sources specified on 1000 * construction. 1001 * @param sources the sources to set 1002 * @see #SpringApplication(Object...) 1003 */ 1004 public void setSources(Set<Object> sources) { 1005 Assert.notNull(sources, "Sources must not be null"); 1006 this.sources.addAll(sources); 1007 } 1008 1009 /** 1010 * Sets the {@link ResourceLoader} that should be used when loading resources. 1011 * @param resourceLoader the resource loader 1012 */ 1013 public void setResourceLoader(ResourceLoader resourceLoader) { 1014 Assert.notNull(resourceLoader, "ResourceLoader must not be null"); 1015 this.resourceLoader = resourceLoader; 1016 } 1017 1018 /** 1019 * Sets the type of Spring {@link ApplicationContext} that will be created. If not 1020 * specified defaults to {@link #DEFAULT_WEB_CONTEXT_CLASS} for web based applications 1021 * or {@link AnnotationConfigApplicationContext} for non web based applications. 1022 * @param applicationContextClass the context class to set 1023 */ 1024 public void setApplicationContextClass( 1025 Class<? extends ConfigurableApplicationContext> applicationContextClass) { 1026 this.applicationContextClass = applicationContextClass; 1027 if (!isWebApplicationContext(applicationContextClass)) { 1028 this.webEnvironment = false; 1029 } 1030 } 1031 1032 private boolean isWebApplicationContext(Class<?> applicationContextClass) { 1033 try { 1034 return WebApplicationContext.class.isAssignableFrom(applicationContextClass); 1035 } 1036 catch (NoClassDefFoundError ex) { 1037 return false; 1038 } 1039 } 1040 1041 /** 1042 * Sets the {@link ApplicationContextInitializer} that will be applied to the Spring 1043 * {@link ApplicationContext}. 1044 * @param initializers the initializers to set 1045 */ 1046 public void setInitializers( 1047 Collection<? extends ApplicationContextInitializer<?>> initializers) { 1048 this.initializers = new ArrayList<ApplicationContextInitializer<?>>(); 1049 this.initializers.addAll(initializers); 1050 } 1051 1052 /** 1053 * Add {@link ApplicationContextInitializer}s to be applied to the Spring 1054 * {@link ApplicationContext}. 1055 * @param initializers the initializers to add 1056 */ 1057 public void addInitializers(ApplicationContextInitializer<?>... initializers) { 1058 this.initializers.addAll(Arrays.asList(initializers)); 1059 } 1060 1061 /** 1062 * Returns read-only ordered Set of the {@link ApplicationContextInitializer}s that 1063 * will be applied to the Spring {@link ApplicationContext}. 1064 * @return the initializers 1065 */ 1066 public Set<ApplicationContextInitializer<?>> getInitializers() { 1067 return asUnmodifiableOrderedSet(this.initializers); 1068 } 1069 1070 /** 1071 * Sets the {@link ApplicationListener}s that will be applied to the SpringApplication 1072 * and registered with the {@link ApplicationContext}. 1073 * @param listeners the listeners to set 1074 */ 1075 public void setListeners(Collection<? extends ApplicationListener<?>> listeners) { 1076 this.listeners = new ArrayList<ApplicationListener<?>>(); 1077 this.listeners.addAll(listeners); 1078 } 1079 1080 /** 1081 * Add {@link ApplicationListener}s to be applied to the SpringApplication and 1082 * registered with the {@link ApplicationContext}. 1083 * @param listeners the listeners to add 1084 */ 1085 public void addListeners(ApplicationListener<?>... listeners) { 1086 this.listeners.addAll(Arrays.asList(listeners)); 1087 } 1088 1089 /** 1090 * Returns read-only ordered Set of the {@link ApplicationListener}s that will be 1091 * applied to the SpringApplication and registered with the {@link ApplicationContext} 1092 * . 1093 * @return the listeners 1094 */ 1095 public Set<ApplicationListener<?>> getListeners() { 1096 return asUnmodifiableOrderedSet(this.listeners); 1097 } 1098 1099 /** 1100 * Static helper that can be used to run a {@link SpringApplication} from the 1101 * specified source using default settings. 1102 * @param source the source to load 1103 * @param args the application arguments (usually passed from a Java main method) 1104 * @return the running {@link ApplicationContext} 1105 */ 1106 public static ConfigurableApplicationContext run(Object source, String... args) { 1107 return run(new Object[] { source }, args); 1108 } 1109 1110 /** 1111 * Static helper that can be used to run a {@link SpringApplication} from the 1112 * specified sources using default settings and user supplied arguments. 1113 * @param sources the sources to load 1114 * @param args the application arguments (usually passed from a Java main method) 1115 * @return the running {@link ApplicationContext} 1116 */ 1117 public static ConfigurableApplicationContext run(Object[] sources, String[] args) { 1118 return new SpringApplication(sources).run(args); 1119 } 1120 1121 /** 1122 * A basic main that can be used to launch an application. This method is useful when 1123 * application sources are defined via a {@literal --spring.main.sources} command line 1124 * argument. 1125 * <p> 1126 * Most developers will want to define their own main method and call the 1127 * {@link #run(Object, String...) run} method instead. 1128 * @param args command line arguments 1129 * @throws Exception if the application cannot be started 1130 * @see SpringApplication#run(Object[], String[]) 1131 * @see SpringApplication#run(Object, String...) 1132 */ 1133 public static void main(String[] args) throws Exception { 1134 SpringApplication.run(new Object[0], args); 1135 } 1136 1137 /** 1138 * Static helper that can be used to exit a {@link SpringApplication} and obtain a 1139 * code indicating success (0) or otherwise. Does not throw exceptions but should 1140 * print stack traces of any encountered. Applies the specified 1141 * {@link ExitCodeGenerator} in addition to any Spring beans that implement 1142 * {@link ExitCodeGenerator}. In the case of multiple exit codes the highest value 1143 * will be used (or if all values are negative, the lowest value will be used) 1144 * @param context the context to close if possible 1145 * @param exitCodeGenerators exist code generators 1146 * @return the outcome (0 if successful) 1147 */ 1148 public static int exit(ApplicationContext context, 1149 ExitCodeGenerator... exitCodeGenerators) { 1150 Assert.notNull(context, "Context must not be null"); 1151 int exitCode = 0; 1152 try { 1153 try { 1154 ExitCodeGenerators generators = new ExitCodeGenerators(); 1155 Collection<ExitCodeGenerator> beans = context 1156 .getBeansOfType(ExitCodeGenerator.class).values(); 1157 generators.addAll(exitCodeGenerators); 1158 generators.addAll(beans); 1159 exitCode = generators.getExitCode(); 1160 if (exitCode != 0) { 1161 context.publishEvent(new ExitCodeEvent(context, exitCode)); 1162 } 1163 } 1164 finally { 1165 close(context); 1166 } 1167 } 1168 catch (Exception ex) { 1169 ex.printStackTrace(); 1170 exitCode = (exitCode == 0 ? 1 : exitCode); 1171 } 1172 return exitCode; 1173 } 1174 1175 private static void close(ApplicationContext context) { 1176 if (context instanceof ConfigurableApplicationContext) { 1177 ConfigurableApplicationContext closable = (ConfigurableApplicationContext) context; 1178 closable.close(); 1179 } 1180 } 1181 1182 private static <E> Set<E> asUnmodifiableOrderedSet(Collection<E> elements) { 1183 List<E> list = new ArrayList<E>(); 1184 list.addAll(elements); 1185 Collections.sort(list, AnnotationAwareOrderComparator.INSTANCE); 1186 return new LinkedHashSet<E>(list); 1187 } 1188 1189}