----------------------------------------------------------------------------------------------------------------------
Spring Boot启动过程主要方法++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
SpringApplication()
this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
SpringApplication.run()
createBootstrapContext
this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners = getSpringFactoriesInstances(SpringApplicationRunListener.class,
listeners.starting
prepareEnvironment
printBanner
context = createApplicationContext();
prepareContext(bootstrapContext, context,
applyInitializers(context);
listeners.contextPrepared
bootstrapContext.close
Set<Object> sources = getAllSources();
load(context, sources.toArray(new Object[0]));
BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
new RootBeanDefinition(ConfigurationClassPostProcessor.class);
new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
new RootBeanDefinition(EventListenerMethodProcessor.class);
new RootBeanDefinition(DefaultEventListenerFactory.class);
this.scanner = new ClassPathBeanDefinitionScanner(registry);
loader.setBeanNameGenerator(this.beanNameGenerator);
loader.setResourceLoader(this.resourceLoader);
loader.setEnvironment(this.environment);
loader.load();//此行注册了主类bean
listeners.contextLoaded
refreshContext(context);
postProcessBeanFactory
this.scanner.scan
this.reader.register
invokeBeanFactoryPostProcessors //ConfigurationClassPostProcessor回调函数解析出了注解bean,生成了beandefinition
registerBeanPostProcessors
initApplicationEventMulticaster
registerListeners
getApplicationEventMulticaster().addApplicationListener(listener);
getApplicationEventMulticaster().multicastEvent(earlyEvent);
finishBeanFactoryInitialization
beanFactory.preInstantiateSingletons();
finishRefresh
publishEvent(new ContextRefreshedEvent(this));
listeners.started
callRunners
感觉BootstrapContext存在的作用只是给SpringApplicationRunListener提供事件广播支持。而SpringApplicationRunListener用于记录Application整个启动过程。
在 prepareContext 方法中,SpringBoot会把主类注册到Spring容器中,为什么要这么做昵?主类上的注解 @SpringBootApplication 需要 ConfigurationClassPostProcessor 解析,才能发挥@Import,@ComponentScan的作用,想要 ConfigurationClassPostProcessor 处理主类的前提是主类的BeanDefinition需要在Spring容器中。
invokeBeanFactoryPostProcessors回调processor,而ConfigurationClassPostProcessor回调函数调用processConfigBeanDefinitions()函数创建了ConfigurationClassParser对象,执行parse解析出了注解bean,生成了beandefinition。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
package org.springframework.context.annotation;
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
BeanRegistrationAotProcessor, BeanFactoryInitializationAotProcessor, PriorityOrdered,
ResourceLoaderAware, ApplicationStartupAware, BeanClassLoaderAware, EnvironmentAware {
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {//这两个回调函数都会调用processConfigBeanDefinitions
processConfigBeanDefinitions(registry);
postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {//这两个回调函数都会调用processConfigBeanDefinitions
processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
。。。。。。
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
parser.parse(candidates);//此行解析出了注解bean,生成了beandefinition
----------------------------------------------------------------------------------------------------------------------
剩下的内容是最开始的时候写的,比较菜。。。。。。。
RTFSC懂得都懂。。。
呐,框子就这6行包括2行大括号。。。。。。
@SpringBootApplication
public class LearnSpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(LearnSpringbootApplication.class, args);
}
}
-----------------------------------------------------------------------------------------------------------------------
下边是@SpringBootApplication注解的源码注释。
Indicates a configuration class that declares one or more @Bean methods and also triggers auto-configuration and component scanning. This is a convenience annotation that is equivalent to declaring @SpringBootConfiguration, @EnableAutoConfiguration and @ComponentScan.
-----------------------------------------------------------------------------------------------------------------------
下边是SpringApplication类的源码注释。大意是:SpringApplication类可以用在main函数中以启动一个Spring应用,它做的操作有4步:1创建ApplicationContext实例,2读取命令行参数作为properties,3刷新context载入所有单例bean,4启动所有CommandLineRunner beans。
* Class that can be used to bootstrap and launch a Spring application from a Java main
* method. By default class will perform the following steps to bootstrap your
* application:
* <ul>
* <li>Create an appropriate {@link ApplicationContext} instance (depending on your
* classpath)</li>
* <li>Register a {@link CommandLinePropertySource} to expose command line arguments as
* Spring properties</li>
* <li>Refresh the application context, loading all singleton beans</li>
* <li>Trigger any {@link CommandLineRunner} beans</li>
* </ul>
* 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">
* @Configuration
* @EnableAutoConfiguration
* public class MyApplication {
* // ... Bean definitions
* public static void main(String[] args) {
* SpringApplication.run(MyApplication.class, args);
* }
* }
* </pre>
* <p>
* For more advanced configuration a {@link SpringApplication} instance can be created and
* customized before being run:
* <pre class="code">
* public static void main(String[] args) {
* SpringApplication application = new SpringApplication(MyApplication.class);
* // ... customize application settings here
* application.run(args)
* }
* </pre>
* {@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, you may also set {@link #getSources() sources} from:
* <ul>
* <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
* possible to set {@link SpringApplication} properties dynamically, like additional
* sources ("spring.main.sources" - a CSV list) the flag to indicate a web environment
* ("spring.main.web-application-type=none") or the flag to switch off the banner
* ("spring.main.banner-mode=off").
先看静态run方法,有2个,汗。。。:
public static
ConfigurableApplicationContext
run
(Class<?> primarySource, String... args) {
return
run(
new
Class<?>[] { primarySource }, args);
}
public static
ConfigurableApplicationContext
run
(Class<?>[] primarySources, String[] args) {
return new
SpringApplication(primarySources).run(args);
}
查资料发现原来可以有多个主类,多模块运行。。。。。。
仔细看第二个run的参数是primarySources,多个s。。。是个数组。一般单模块就是第1个run调用第2个run;而多模块就是直接调用第二个run。。。。。。
第二个静态run会new一个 SpringApplication 对象,然后加args调用对象的run。。。。。。
先说对象创建:又有2构造函数。。。跟上边一样,第1个是个壳子,用于没有传入resourceLoader情况,这是默认情况。。。
public SpringApplication(Class<?>... primarySources) {
this(null, 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 = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
WebApplicationType.deduceFromClasspath()根据Classpath下是否有相应的类来推断容器类型,默认是servlet。
接着看
getSpringFactoriesInstances()
方法
:其调用了
SpringFactoriesLoader
类的静态方法
forDefaultResourceLocation
来生成
SpringFactoriesLoader
对象并调用其
load
方法。
先看静态方法
forDefaultResourceLocation
是怎么生成
SpringFactoriesLoader
对象的,传入参数是
getClassLoader()
这实际是
Thread.currentThread().getContextClassLoader()
也就是
ApplicationClassLoader
。一通
Map
代码
。。。。。。根据传入的
(
META-INF/spring.factories
和
ApplicationClassLoader
)
最后
new
了个
SpringFactoriesLoader
对象返回。。。而
new
构造函数实际就传了
2
个成员变量值
,一个是
classloader
,一个是
loadFactoriesResource
方法生成的
Map
。。。
loadFactoriesResource
方法把本程序及依赖库的
META-INF/spring.factories
内容转换成了
Map
。
SpringFactoriesLoader
对象的
load
方法先获取
factory
名称然后实例化
:
通过
loadFactoryNames
()
和
instantiateFactory()
。
private <T> List<T> getSpringFactoriesInstances(Class<T> type, ArgumentResolver argumentResolver) {
return SpringFactoriesLoader.forDefaultResourceLocation(getClassLoader()).load(type, argumentResolver);
}
public
ClassLoader getClassLoader() {
if (this.resourceLoader != null) {
return this.resourceLoader.getClassLoader();
}
return ClassUtils.getDefaultClassLoader();
}
public static ClassLoader getDefaultClassLoader() {
ClassLoader cl = null;
try {
cl = Thread.currentThread().getContextClassLoader();
}
。。。。。。。。。。。。。。。。
}
public class SpringFactoriesLoader {
/**
* The location to look for factories.
* <p>Can be present in multiple JAR files.
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static final FailureHandler THROWING_FAILURE_HANDLER = FailureHandler.throwing();
private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
static final Map<ClassLoader, Map<String, SpringFactoriesLoader>> cache = new ConcurrentReferenceHashMap<>();
@Nullable
private final ClassLoader classLoader;
private final Map<String, List<String>> factories;
public static SpringFactoriesLoader forDefaultResourceLocation(@Nullable ClassLoader classLoader) {
return forResourceLocation(FACTORIES_RESOURCE_LOCATION, classLoader);
}
public static SpringFactoriesLoader forResourceLocation(String resourceLocation, @Nullable ClassLoader classLoader) {
Assert.hasText(resourceLocation, "'resourceLocation' must not be empty");
ClassLoader resourceClassLoader = (classLoader != null ? classLoader :
SpringFactoriesLoader.class.getClassLoader());
Map<String, SpringFactoriesLoader> loaders = cache.computeIfAbsent(
resourceClassLoader, key -> new ConcurrentReferenceHashMap<>());
return loaders.computeIfAbsent(resourceLocation, key ->
new SpringFactoriesLoader(classLoader, loadFactoriesResource(resourceClassLoader, resourceLocation)));
}
protected static Map<String, List<String>> loadFactoriesResource(ClassLoader classLoader, String resourceLocation) {
Map<String, List<String>> result = new LinkedHashMap<>();
try {
Enumeration<URL> urls = classLoader.getResources(resourceLocation);
while (urls.hasMoreElements()) {
UrlResource resource = new UrlResource(urls.nextElement());
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
properties.forEach((name, value) -> {
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) value);
List<String> implementations = result.computeIfAbsent(((String) name).trim(),
key -> new ArrayList<>(factoryImplementationNames.length));
Arrays.stream(factoryImplementationNames).map(String::trim).forEach(implementations::add);
});
}
result.replaceAll(SpringFactoriesLoader::toDistinctUnmodifiableList);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" + resourceLocation + "]", ex);
}
return Collections.unmodifiableMap(result);
}
protected SpringFactoriesLoader(@Nullable ClassLoader classLoader, Map<String, List<String>> factories) {
this.classLoader = classLoader;
this.factories = factories;
}
public <T> List<T> load(Class<T> factoryType, @Nullable ArgumentResolver argumentResolver,
@Nullable FailureHandler failureHandler) {
Assert.notNull(factoryType, "'factoryType' must not be null");
List<String> implementationNames = loadFactoryNames(factoryType);
logger.trace(LogMessage.format("Loaded [%s] names: %s", factoryType.getName(), implementationNames));
List<T> result = new ArrayList<>(implementationNames.size());
FailureHandler failureHandlerToUse = (failureHandler != null) ? failureHandler : THROWING_FAILURE_HANDLER;
for (String implementationName : implementationNames) {
T factory = instantiateFactory(implementationName, factoryType, argumentResolver, failureHandlerToUse);
if (factory != null) {
result.add(factory);
}
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
再来看SpringFactoriesLoader
对象的
loadFactoryNames
()
和
instantiateFactory()
。
先说
loadFactoryNames(factoryType)
方法:它返回了一个
SpringFactoriesLoader
成员变量
this.factories
这个
map
的
value
。这个成员变量是在类构造函数中初始化的
,就是上边那个
loadFactoriesResource
处理各个包
META-INF/spring.factories
得到的
。
。。。。。而
instantiateFactory
()
方法则会通过
FactoryInstantiator.
forClass
()
给每个
factory
创建一个
factoryInstantiator
调用
constructor.newInstance(args)
创建出一个对象。
private List<String> loadFactoryNames(Class<?> factoryType) {
return this.factories.getOrDefault(factoryType.getName(), Collections.emptyList());
}
protected <T> T instantiateFactory(String implementationName, Class<T> type,
@Nullable ArgumentResolver argumentResolver, FailureHandler failureHandler) {
try {
Class<?> factoryImplementationClass = ClassUtils.forName(implementationName, this.classLoader);
Assert.isTrue(type.isAssignableFrom(factoryImplementationClass), () ->
"Class [%s] is not assignable to factory type [%s]".formatted(implementationName, type.getName()));
FactoryInstantiator<T> factoryInstantiator = FactoryInstantiator.forClass(factoryImplementationClass);
return factoryInstantiator.instantiate(argumentResolver);
}
catch (Throwable ex) {
failureHandler.handleFailure(type, implementationName, ex);
return null;
}
}
T instantiate(@Nullable ArgumentResolver argumentResolver) throws Exception {
Object[] args = resolveArgs(argumentResolver);
if (isKotlinType(this.constructor.getDeclaringClass())) {
return KotlinDelegate.instantiate(this.constructor, args);
}
return this.constructor.newInstance(args);
}
汗!!!也就是说
SpringApplication
对象的
getSpringFactoriesInstances()
方法绕了一圈子就是
:
1.
从
META-INF/spring.factories
文件中根据传入的类
.
class
获
得对应的
map
值
(
一个
List)
。。。
2.
遍历这个
list
调用
SpringFactoriesLoader
对象的
instantiateFactory
()
方法
实例化这些
factories
。
接着看
getSpringFactoriesInstances(BootstrapRegistryInitializer.class)
获取
META-INF/spring.factories
中配置
key
为
org.springframework.boo
t.BootstrapRegistryInitializer
的数据实例化相应的
factory
。
BootstrapRegistryInitializer
是
Spring
Boot
框架中一个非常重要的类,它允许开发者通过实现
ApplicationContextInitializer
接口来编写自定义的
Bean
初始化器。通过向
ApplicationContext
注册这些自定义的初始化器,开发者可以在
Spring
Boot
应用启动时执行自定义的逻辑,实现对
Bean
的自定义初始化。。。。。。太深入了,先不管它继续往下。。。。。。
接着是
ApplicationContextInitializer
,这个
package org.springframework.context;
@FunctionalInterface
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
void initialize(C applicationContext);
}
initialize方法的形参类型是ConfigurableApplicationContext,因此可以认为ApplicationContextInitializer实际上是Spring容器初始化前ConfigurableApplicationContext的回调接口,可以对上下文环境作一些操作,如运行环境属性注册、激活配置文件等。可以实际试一下:
class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("----------MyApplicationContextInitializer---------------");
Map<String, Object> systemProperties = applicationContext.getEnvironment().getSystemProperties();
System.out.println("---------------start------------------");
systemProperties.forEach((key,value)->{
System.out.println("key:"+key+",value:"+value);
}); System.out.println("---------------end------------------");
}
}
接着是ApplicationListener,先过。。。。。。
接着是this.mainApplicationClass = deduceMainApplicationClass();deduceMainApplicationClass 主要是通过StackWalker来找到main方法,main方法所在的class就是启动类。不明觉厉。。。。。。
private Class<?> deduceMainApplicationClass() {
return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
.walk(this::findMainClass)
.orElse(null);
}
SpringApplication 对象终于构造完了。。。。。。
接着是其run(args)方法
public ConfigurableApplicationContext run(String... args) {
Startup startup = Startup.create();
if (this.registerShutdownHook) {
SpringApplication.shutdownHook.enableShutdownHookAddition();
}
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
startup.started();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), startup);
}
listeners.started(context, startup.timeTakenToStarted());
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
throw handleRunFailure(context, ex, listeners);
}
try {
if (context.isRunning()) {
listeners.ready(context, startup.ready());
}
}
catch (Throwable ex) {
throw handleRunFailure(context, ex, null);
}
return context;
}
东西比较多,先看个顺序了解全貌,不深入。。。
第1个是Startup类,貌似主要是计时。。。。。。
然后是shutdownHook,貌似是跟停应用有关。。。
然后是createBootstrapContext()方法
private DefaultBootstrapContext createBootstrapContext() {
DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));
return bootstrapContext;
}
有必要了解一下DefaultBootstrapContext里边啥玩意儿内容。。。。。。两个Map一个enents。。。
public class DefaultBootstrapContext implements ConfigurableBootstrapContext {
private final Map<Class<?>, InstanceSupplier<?>> instanceSuppliers = new HashMap<>();
private final Map<Class<?>, Object> instances = new HashMap<>();
private final ApplicationEventMulticaster events = new SimpleApplicationEventMulticaster();
跟前边构造方法中提到的BootstrapRegistryInitializer
有关。。。
然后新建一个局部变量ConfigurableApplicationContext context = null;,这是Spring应用的IoC容器,贯穿整个方法。
然后configureHeadlessProperty()设置无头模式,默认是true。。。
然后是 SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
这应该是配置监听器,前边构造函数也涉及了这个。
然后是程序运行参数ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
然后是环境ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
然后是打印Banner printedBanner = printBanner(environment);
创建IoC容器context = createApplicationContext();比较复杂,先过。。。。。。
protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextFactory.create(this.webApplicationType);
}
然后是context.(this.applicationStartup);不知道干啥的,看似不太重要。。。先过
然后三连。。。应该比较复杂,有空再研究,先把run走完。。。
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
然后是startup.started();发布SpringBoot程序已启动的事件。。。。。。
然后是listeners.started(context, startup.timeTakenToStarted());通知所有listeners
然后是callRunners(context, applicationArguments);调用ApplicationRunner和CommandLineRunner通知他们可以操作了。。。
终于。。。run()结束了
-------------------------------------------------------------------------------------------------------------------
为了更深入理解上边Springboot的源码,需要学习一些知识点和Springboot的一些手法和模式:
套壳方法:像两个run静态函数,外层参数用Class<?> primarySource内层参数用Class<?>[] primarySources,这样就有两种入口且实现相同的功能。重载方法。。。
Class<?>范型:.class对象作为Class<?>范型传递:Class<?> primarySource,这个 primarySource接收实参YourApplication.class作为对象,其实这里Class<?>换作Class也行。问:在Java中,Class<?>与Class的主要区别是什么?答:Class<?>和Class在许多情况下都可以用来表示未知的类类型。但Class<?>被认为是一个通配符泛型,表示它是安全的,而Class通常被视为一个原始类型。使用Class<?>是更加类型安全的方式,因为它告诉编译器你明确地想表示一个未知的类型,而不是简单地忽略了泛型。不使用泛型时,反射创建类时需进行强转,若类型不符会抛出ClassCastException。而泛型Class则无需强转。
Classloader对象是从哪里获得资源的:ClassLoader对象的loadResources(“path”)函数会从本程序和所有依赖jar包的path中获取资源。path加“/”是从根目录开始,不加是从classpath开始。
Map<>对象的computeIfAbsent 方法:cache.computeIfAbsent(resourceClassLoader, key -> new ConcurrentReferenceHashMap<>());完成的功能为:如果查到就返回,查不到则执行lambda表达式添加一条记录,用作登记式缓存。那么这个 ConcurrentReferenceHashMap到底有什么作用呢?ConcurrentReferenceHashMap 能指定所存放对象的引用级别默认情况下是软引用级别。(四种引用级别:强/软/弱/虚)
接口的默认实现:神仙写法。。。。。。接口里边定义了一个static final名叫DEFAULT引用了一个new实现,汗!!!
public class SpringApplication {private ApplicationContextFactory applicationContextFactory = ApplicationContextFactory.DEFAULT;......}
public interface ApplicationContextFactory {ApplicationContextFactory DEFAULT = new DefaultApplicationContextFactory();......}
class DefaultApplicationContextFactory implements ApplicationContextFactory {...…}