springboot的启动非常简单,只需要一个启动类,运行main方法即可启动应用。
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args); // 静态方法
}
}
run方法是SpringApplication中的静态方法,
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
// 将入口类 包装成 一个数组, 意思是我们可以传入多个这样的primarySource
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
// 这样就看的很明显了, 仅仅2个步骤
// 第一步: 先创建了一个 SpringApplication 实例, 并且传入了 入口类 (数组类型)
// 第二步: 调用 SpringApplication实例 的run方法
return new SpringApplication(primarySources).run(args);
}
那么我们实际上就可以这样写,来启动应用了
@SpringBootApplication(scanBasePackages = "com.zzhua.test")
public class TestApplication {
public static void main(String[] args) throws IOException {
// SpringApplication.run(TestApplication.class, args);
// 这样写的好处是, 我们可以在运行run方法前, 自由的设置属性了,比较灵活, 但没特殊需求的话, 没必要这样做
SpringApplication springApplication = new SpringApplication();
springApplication.addPrimarySources(Arrays.asList(TestApplication.class));
springApplication.run(args);
}
}
但是,如果一定要这么做呢?springboot还提供了一个SpringApplicationBuilder,所以也可以这样写
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) throws IOException {
// SpringApplication.run(TestApplication.class, args);
new SpringApplicationBuilder()
.bannerMode(Banner.Mode.OFF)
.sources(new Class[]{TestApplication.class})
.run(args)
;
}
new SpringApplication(primarySources).run(args)分为2步骤,本篇只探讨SpringApplication实例化部分,run部分后面再分析。
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 默认传入的resourceLoader是null,资源加载器默认使用DefaultResourceLoader
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 对 primarySources 去重
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 从类路径上, 使用判断 是否存在 指定的类, 比如: DispatcherServlet[SERVLET]、DispatcherHandler(REACTIVE)
// 就是通过ClassUtils.isPresent(xxxClass)加载类, 能加载到就存在, 加载失败就不存在
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 通过SpringFactoriesLoader加载spring.factories文件中, ApplicationContextInitializer全类名所对应配置的初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 通过SpringFactoriesLoader加载spring.factories文件中, ApplicationListener全类名所对应配置的初始化器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 推断主类(第一个main方法所在的类)
this.mainApplicationClass = deduceMainApplicationClass();
}
推断应用类型
static WebApplicationType deduceFromClasspath() {
// 存在"org.springframework.web.reactive.DispatcherHandler"
// 不存在"org.springframework.web.servlet.DispatcherServlet"
// 不存在"org.glassfish.jersey.servlet.ServletContainer"
// 则为: REACTIVE
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
// "javax.servlet.Servlet",
// "org.springframework.web.context.ConfigurableWebApplicationContext"
// 上面2个必须都存在, 才是SERVLET, 否则就不是web应用
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
ApplicationContextInitializer接口的实例,用来在执行ConfigurableApplicationContext容器的refresh刷新方法之前,对容器的一些初始化操作。
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
void initialize(C applicationContext);
}
getSpringFactoriesInstances(ApplicationContextInitializer.class);
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
// 第二个参数表示, 使用type类中指定构造参数类型的构造器,来实例化
// 其实还有第三个参数, 它是可变数组, 所以这里没写, 表示传入的构造参数
// 这里,显然是获取无参构造器来实例化, 所以也没传入构造参数
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// 使用SpringFactoriesLoader加载spring.factories文件指定类型全类名所对应的配置的名称
// 具体加载过程见:https://www.processon.com/view/link/61f0c54ee0b34d616d516e16
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 使用反射创建对应的实例
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 对这些实例按照@Orser注解或Order接口的值来进行排序,值越小排序越靠前
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
下面这种创建方法,我们也可以直接拿来用
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
// 反射加载类
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
// 获取指定构造参数类型的构造器
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
// 反射创建实例
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
如果要配置ApplicationContextInitializer实例有3种方式
1- 将实现类写在META-INF/spring.factories文件中
org.springframework.context.ApplicationContextInitializer=\
MyApplicationContextInitializer
2- 通过application.yml或application.properties配置,这是通过默认加载的DelegatingApplicationContextInitializer实现的
context.initializer.classes = MyApplicationContextInitializer
3- 可以用我们前面提到的SpringApplication实例来添加
@SpringBootApplication(scanBasePackages = "com.zzhua.test")
public class TestApplication {
public static void main(String[] args) throws IOException {
SpringApplication.run(TestApplication.class, args);
SpringApplication springApplication = new SpringApplication();
springApplication.addPrimarySources(Arrays.asList(TestApplication.class));
// 主动添加
springApplication.addInitializers(new MyApplicationContextInitializer);
springApplication.run(args);
}
}
ApplicationListener实例用于监听容器中发布的各种事件,在发布对应的事件时,做相应的处理。
我们也可以手动使用ApplicationContext的publishEvent方法,主动的发布事件。
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
// ApplicationListener接口的实例,作为应用的监听器,当容器发布事件时,
// 通过回调监听器的onApplicationEvent方法,监听器就能接收到该事件(观察者模式)
void onApplicationEvent(E event);
}
通过new一个运行时异常,获取栈,找到第一个main方法所在的类就是主类
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
如果我们在springboot应用中,仍然想使用xml配置文件,那又该怎么做呢?
在SpringApplication中,有个sources属性,可以传入包名、类名、xml文件
private Set<String> sources = new LinkedHashSet<>();
public Set<Object> getAllSources() {
Set<Object> allSources = new LinkedHashSet<>();
if (!CollectionUtils.isEmpty(this.primarySources)) {
allSources.addAll(this.primarySources);
}
if (!CollectionUtils.isEmpty(this.sources)) {
allSources.addAll(this.sources);
}
return Collections.unmodifiableSet(allSources);
}
private void prepareContext(){
// ...
Set<Object> sources = getAllSources();
load(context, sources.toArray(new Object[0]));
// ...
}
# load方法会调用到 BeanDefinitionLoader的load, 去载入对应的资源
private int load(Object source) {
Assert.notNull(source, "Source must not be null");
if (source instanceof Class<?>) {
return load((Class<?>) source);
}
if (source instanceof Resource) {
return load((Resource) source);
}
if (source instanceof Package) {
return load((Package) source);
}
if (source instanceof CharSequence) {
return load((CharSequence) source);
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}