• 第三章 SpringBoot构造流程源码分析


    3.1 SpringApplication的初始化简介

    springboot的启动非常简单,只需要一个启动类,运行main方法即可启动应用。

    @SpringBootApplication
    public class TestApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(TestApplication.class, args); // 静态方法
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    SpringApplication.run方法

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    那么我们实际上就可以这样写,来启动应用了

    @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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    但是,如果一定要这么做呢?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)
            ;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3.2 SpringApplication实例化流程

    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    deduceFromClasspath

    推断应用类型

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    获取ApplicationContextInitializer实例

    ApplicationContextInitializer接口的实例,用来在执行ConfigurableApplicationContext容器的refresh刷新方法之前,对容器的一些初始化操作。

    public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
    
    	
    	void initialize(C applicationContext);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    getSpringFactoriesInstances

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    下面这种创建方法,我们也可以直接拿来用

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    配置ApplicationContextInitializer实例

    如果要配置ApplicationContextInitializer实例有3种方式
    1- 将实现类写在META-INF/spring.factories文件中

    org.springframework.context.ApplicationContextInitializer=\
    MyApplicationContextInitializer
    
    • 1
    • 2

    2- 通过application.yml或application.properties配置,这是通过默认加载的DelegatingApplicationContextInitializer实现的

    context.initializer.classes = MyApplicationContextInitializer
    
    • 1

    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);
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    获取ApplicationListener实例

    ApplicationListener实例用于监听容器中发布的各种事件,在发布对应的事件时,做相应的处理。
    我们也可以手动使用ApplicationContext的publishEvent方法,主动的发布事件。

    public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    
    	// ApplicationListener接口的实例,作为应用的监听器,当容器发布事件时,
    	// 通过回调监听器的onApplicationEvent方法,监听器就能接收到该事件(观察者模式)
    	void onApplicationEvent(E event);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    deduceMainApplicationClass

    通过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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    3.3 SpringApplication的定制化修改

    3.3.1 基础配置

    1. 使用application.yml配置文件
    2. 手动自己new SpringApplication实例或使用SpringApplicationBuilder来构建实例

    3.3.2 配置源配置

    如果我们在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());
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
  • 相关阅读:
    【广州华锐互动】VR党建多媒体互动展厅:随时随地开展党史教育
    mybatis动态sql一对多查询
    【Flink伪分布式环境搭建及应用,Standlong(开发测试)】
    基于SE-YOLOV4的变电站断路器分合状态识别算法
    终极 C++避坑指南
    无需专线、无需固定公网IP,各地安防数据如何高效上云?
    【从零开始学习 SystemVerilog】6.5、SystemVerilog 接口—— Clocking Blocks(上)
    多路转接IO模型(poll和epoll)
    分布式系统设计策略
    woocommerce对接paypal如何进行沙盒测试?
  • 原文地址:https://blog.csdn.net/qq_16992475/article/details/126800398