sb真正的做到了按照程序猿的思维来启动服务,但是封装的越好,底层的原理更加的难以掌握,
先起个引子,将boot的启动流程搞清楚,boot如何在spring的基础上做到一键启动。
通过main方法启动的时候,可以加入任意自己的代码,如下
public static void main(String[] args) {
MetaCache.init(CURIE_NAMESPACE);
SpringApplication.run(ImportExportApplication.class, args);
}
其实这也算是一个扩展点,在run之间,我直接将项目里面所有的entity,通过第三方包进行扫描,将meta信息进行了缓存,方便后面的直接的使用。
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified source using default settings.
* @param primarySource the primary source to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Class<?>[] { primarySource }, args);
}
如上,通过注释发现是个静态的static helper方法,primarySource 其实就是个简单的Class对象,
等于说我们可以加载任何的一个Class对象作为primary source去启动
一般来说在作为primary source上面我们会通过注解来加载服务需要的信息,如下
@SpringBootApplication()
@EnableAsync
@ComponentScan(basePackages=PackageInfo.APP_BASE_PACKAGE)
@EntityScan(PackageInfo.APP_BASE_PACKAGE)
@EnableDiscoveryClient
@EnableFeignClients
@EnableMetrics
@EnableRateLimit
@EnableEncryptableProperties
@EnableSleuth
@EnableMonitor
public class ImportExportApplication
好接着往下看
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified sources using default settings and user supplied arguments.
* @param primarySources the primary sources to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
我们通过primarySources构造了一个SpringApplication,通过名字我们可以看到,在primarySources中包含了SpringApplication需要的信息。
我们还是先看SpringApplication这个类的介绍
/**
SpringApplication是用来引导和启动一个通过main方法的spring的应用
- Class that can be used to bootstrap and launch a Spring application from a Java main
默认情况下,SpringApplication会通过以下的步骤来引导你的服务 - method. By default class will perform the following steps to bootstrap your
- application:
- <ul>
1 创建一个合适的 ApplicationContext 实例,当然通过你的项目信息(如何的构建这个很关键) - <li>Create an appropriate {@link ApplicationContext} instance (depending on your
- classpath)</li>
2 注册一个 CommandLinePropertySource ,暴露出command line arguments 这样你可以通过
command line的方式来注册相关的properties信息 - <li>Register a {@link CommandLinePropertySource} to expose command line arguments as
- Spring properties</li>
3 然后将第一步构建的ApplicationContext refresh下(这个就是springioc的主要内容了),将项目中注册的单例bean全部的加载到第一步创建的 ApplicationContext 中 - <li>Refresh the application context, loading all singleton beans</li>
4 Trigger所有的CommandLineRunner bean了,这也是个hook - <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) throws Exception {
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) throws Exception {
- SpringApplication application = new SpringApplication(MyApplication.class);
- // ... customize application settings here
- application.run(args)
- }
- </pre>
以下忽略,因为spring加载bean的方式很多,推荐使用@Configuration的方式来加载bean,相当于以前的xml文件config化,这样加载出来的bean可以保证都是单例的
- {@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").
**/
接着往下看
/**
* Create a new {@link SpringApplication} instance. The application context will load
等于说创建的SpringApplication加载的bean都与primarySources息息相关
* beans from the specified primary sources (see {@link SpringApplication class-level}
在run之前,可以做一些定制化的功能
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #SpringApplication(ResourceLoader, Class...)
* @see #setSources(Set)
*/
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #setSources(Set)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
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();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
先来看下,boot是如何的判断我们是哪种webApplicationType的,
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
如上很简单,通过判断当前的classpath下是否有相关的class,如如果classparth下有DispatcherServlet这个类,那基本就是个servlet服务了。
ok,下面是最关键的了
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
其实这两句代码,一句是拿到所有的ApplicationContextInitializer.class对象,一个是拿到所有的ApplicationListener.class对象,由于现在spring还没有启动,更不用说通过context的getBean去拿取了,那肯定在某个地方记录了这些ApplicationContextInitializer.class和ApplicationListener.class的信息,无非两种方法,1 hardcode在代码里面,直接的去拿。(感觉不怎么好,毕竟我加一个就要修改源码吗)2 放在一个配置文件里面,大家去配置文件里面拿,貌似这个方法可以。
上面的重要的就是 getSpringFactoriesInstances(ApplicationContextInitializer.class)这个方法。
第二篇,在讲下如何的 getSpringFactoriesInstances(ApplicationContextInitializer.class)。
网友评论