SpringBoot源码剖析(一)——系统初始化器

系列文章目录

SpringBoot源码剖析专栏



前言

实现系统初始化器、分析SpringBoot源码和系统初始化器的原理来介绍SpringBoot中的系统初始化器
参考资料:https://www.imooc.com/
参考文章:https://blog.csdn.net/qq_34886352/article/details/104949485

一、系统初始化器实现

系统初始化器的类名是ApplicationContextInitializer,官方描述为Spring容器刷新之前执行的一个回调函数,其作用是向SpringBoot容器中注册属性,使用方法为继承接口自定义实现。

下面通过三种方式自定义实现系统初始化器

方法一:spring.factories文件注入

//新建一个类,实现ApplicationContextInitializer接口
@Order(1)
public class FirstInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        //从上下文获取到程序环境
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        //创建需要注入的属性
        Map<String,Object> map = new HashMap<>();
        map.put("key1","value1");
        //将属性封装成配置项
        MapPropertySource mapPropertySource = new MapPropertySource("firstInitializer",map);
        //添加到项目环境配置的最末端
        environment.getPropertySources().addLast(mapPropertySource);
        //控制台输出提示
        System.out.println("run firstInitializer");
    }
}
#在resources的META-INF目录下的spring.factories文件中添加初始化器的实现类的完整路径
org.springframework.context.ApplicationContextInitializer=com.mooc.sb2.initializer.FirstInitializer

方法二:在SpringApplication中手动注入

@Order(2)
public class SecondInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        Map<String,Object> map = new HashMap<>();
        map.put("key2","value2");
        MapPropertySource mapPropertySource = new MapPropertySource("secondInitializer",map);
        environment.getPropertySources().addLast(mapPropertySource);
        System.out.println("run secondInitializer");
    }
}

修改启动类中的内容

@SpringBootApplication
@MapperScan("com.mooc.sb2.mapper")
public class Sb2Application {
    public static void main(String[] args) {
        //启动
        SpringApplication springApplication = new SpringApplication(Sb2Application.class);
        springApplication.addInitializers(new SecondInitializer());
        springApplication.run(args);
    }
}

方法三:在application.properties中进行注册

@Order(3)
public class ThirdInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        Map<String,Object> map = new HashMap<>();
        map.put("key3","value3");
        MapPropertySource mapPropertySource = new MapPropertySource("ThirdInitializer",map);
        environment.getPropertySources().addLast(mapPropertySource);
        System.out.println("run ThirdInitializer");
    }
}
#在resources文件中新建一个application.properties文件,配置下面信息
context.initializer.classes=com.mooc.sb2.initializer.ThirdInitializer

测试

//创建service
@Component
public class TestService implements ApplicationContextAware {
    private ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    public String test(){
        return applicationContext.getEnvironment().getProperty("key3");
    }
}
//在controller中新建test方法
@RequestMapping("test")
    @ResponseBody
    public String test(){
        return testService.test();
    }

结果:
在这里插入图片描述

总结

  • 自定义的系统初始化器都要实现ApplicationContextInitializer接口
  • Order值越小越先执行
  • application.properties中定义的优先于其他方式

自定义的系统初始化器如何被Spring容器识别并加载到Spring容器内的?==》SpringFactoriesLoader

二、SpringFactoriesLoader

SpringFactoriesLoader类的主要作用是通过类路径下的MEAT-INF/spring.factories文件获取工厂类接口的实现类,初始化并保存在缓存中,以供Springboot启动过程中各个阶段的调用。
SpringFactoriesLoader介绍

2.1 源码剖析

首先进入SpringBoot启动类

@SpringBootApplication
@MapperScan("com.mooc.sb2.mapper")
public class Sb2Application {
    public static void main(String[] args) {
    	//进入run方法
        SpringApplication.run(Sb2Application.class, args);
    }
}

在这里插入图片描述

	public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		//进入同名run方法
		return run(new Class<?>[] { primarySource }, args);
	}

在这里插入图片描述

	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		//1、初始化SpringApplication类
		//2、run方法
		//而上面已经说了,系统初始化器的作用是从MEAT-INF/spring.factories中获取工厂接口的实现类,
		//初始化并保存在缓存中,以供SpringBoot启动过程中使用
		//因此只要看SpringApplication构造方法
		//进入SpringApplication构造方法
		return new SpringApplication(primarySources).run(args);
	}

在这里插入图片描述

	public SpringApplication(Class<?>... primarySources) {
		//点this查看
		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();
		//通过getSpringFactoriesInstances来获取系统初始化器的实现
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

在这里插入图片描述

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		//进入同名方法
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}
	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		//获取类加载器
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		//获得所有实现类的全路径名
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		//创建实现类的实例
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		//将上面获取到的所有类根据@Order进行排序
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

getSpringFactoriesInstances中有三个核心的方法
①loadFactoryNames:获取所有实现类的全路径名
②createSpringFactoriesInstances:创建实现类的实例
③sort:将上面获取到的所有类根据@Order进行排序

loadFactoryNames
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		//获取到所需要的工厂类的名字
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}
	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		//首先看是否存在缓存
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration<URL> urls = (classLoader != null ?
			//public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
			//在上文的实现方法一中正是在META-INF/spring.factories中注入系统初始化器的全路径名
			//但是这里并不是只有这一个spring.factories文件,它会获取所有jar包中的META-INF/spring.factories
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			//因此这里循环遍历所有的META-INF/spring.factories文件
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				//转化为kv形式即properties类型
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					//获取key值,例如在上文方法一中,就是org.springframework.context.ApplicationContextInitializer
					String factoryTypeName = ((String) entry.getKey()).trim();
					//将value的值按照','逗号进行分割
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			//将结果先保存到缓存中再进行返回
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

createSpringFactoriesInstances
	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;
	}

2.2 总结

在getSpringFactoriesInstances中分为三步

  1. loadFactoryNames:获取所有实现类的全路径名
    • 获取工厂类的名字
    • 加载工厂类
      • 看是否存在缓存==》存在就直接返回
      • 获取所有jar包中的META-INF/spring.factories文件
      • 遍历文件转为properties对象
      • 获取key、value值并用’,'将value值分割
      • 将结果存入缓存并返回
  2. createSpringFactoriesInstances:创建实现类的实例
    • 根据全路径名依次实例化对象
  3. sort:将上面获取到的所有类根据@Order进行排序
    • 结果排序

流程图

三、自定义系统初始化器原理解析

方法一:spring.factories文件注入

	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		//在run方法启动时,才真正开始管理springBoot的生命周期
		//进入run方法中
		return new SpringApplication(primarySources).run(args);
	}

run方法中的调用点

//参考博客:https://blog.csdn.net/qq_34886352/article/details/104949485
public ConfigurableApplicationContext run(String... args) {
    //创建一个计时器,用来记录SpringBoot的运行时间
   StopWatch stopWatch = new StopWatch();
   //启动计时器
   stopWatch.start();
   //配置应用上下文的控制器,可进行一些基础配置的操作,设置上下文ID,设置父应用上下文,添加监听器和刷新容器相关的操作等
   ConfigurableApplicationContext context = null;
   Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
   // 配置属性
   configureHeadlessProperty();
   //获取监听器  从路径MEAT-INF/spring.factories中找到所有的SpringApplicationRunListener
   SpringApplicationRunListeners listeners = getRunListeners(args);
   //监视器启动!
   listeners.starting();
   try {
       //封装一下环境
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      // 准备环境
      ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
      configureIgnoreBeanInfo(environment);
      //打印banner  就是每次启动输出的SpringBoot的图标
      Banner printedBanner = printBanner(environment);
      //创建应用程序上下文,常用的有2种  web环境的和普通环境的,根据条件不同,生成的结果不一样
      context = createApplicationContext();
      exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
      //准备上下文,下面那个就是刷新上下文
      //ApplicationContextInitializer接口在刷新上下文(refreshContext)之前起作用
      //就是这里没跑了,点进去
      prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      //刷新上下文
      refreshContext(context);
      //2次刷新
      afterRefresh(context, applicationArguments);
      //计时器停止
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
      listeners.started(context);
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
   }

   try {
      listeners.running(context);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
   }
   return context;
}

进入prepareContext方法

	private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		//传入的参数就是之前获取的web环境的应用程序上下文,进入!!
   		//ApplicationContextInitializer也就是从这里要开始起作用了
		applyInitializers(context);
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[0]));
		listeners.contextLoaded(context);
	}

applyInitializers方法

protected void applyInitializers(ConfigurableApplicationContext context) {
        //getInitializers() 就是简单的拿出initializers属性,然后根据order属性进行排序
		for (ApplicationContextInitializer initializer : getInitializers()) {
           //这里会获取实现ApplicationContextInitializer接口时填写的泛型
           //如果记不得了 ,全文搜索一下“FirstInitializer”,可以看到我们实现ApplicationContextInitializer时填写的泛型正是“ConfigurableApplicationContext”
			Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
					ApplicationContextInitializer.class);
           //判断填入的泛型是否为ConfigurableApplicationContext类或者是它的子类,如果不是停止运行,并报错
			Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
            //这里调用了ApplicationContextInitializer接口强制实现的initialize方法
            //在FirstInitializer,实现接口的同时,重写了initialize方法
			initializer.initialize(context);
		}
	}

运行至此,就可以打印出"run firstInitializer"

方法二:在SpringApplication中手动注入

在SpringBoot启动类中直接addInitializers

SpringApplication springApplication = new SpringApplication(Sb2Application.class);
springApplication.addInitializers(new SecondInitializer());
springApplication.run(args);

进入addInitializers方法

	public void addInitializers(ApplicationContextInitializer<?>... initializers) {
		//其实就是手动把系统初始化器加入到集和中
		this.initializers.addAll(Arrays.asList(initializers));
	}

在上文中,工厂加载机制中会调用到setInitializers,然后在SpringBoot运行时加载系统初始化器过程中会getInitializers
在这里插入图片描述
在这里插入图片描述

方法三:在application.properties中进行注册

这种方式是通过DelegatingApplicationContextInitializer类来实现,在这个类中,定义了 private int order = 0; 即通过这种方法实现的优先级最高

	@Override
	public void initialize(ConfigurableApplicationContext context) {
		ConfigurableEnvironment environment = context.getEnvironment();
		//环境变量中查找initializer所有类的类型
		List<Class<?>> initializerClasses = getInitializerClasses(environment);
		if (!initializerClasses.isEmpty()) {
			applyInitializerClasses(context, initializerClasses);
		}
	}

getInitializerClasses:获取application.properties中的配置信息

private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) { 
        //从环境变量(我们的配置文件)中查找一个叫PROPERTY_NAME的属性
        //PROPERTY_NAME是个常量,值为“context.initializer.classes”
        //全局搜索context.initializer.classes,看看出现在哪了,就是配置文件中的属性
		String classNames = env.getProperty(PROPERTY_NAME);
		List<Class<?>> classes = new ArrayList<>();
		if (StringUtils.hasLength(classNames)) {
           //将设置的值根据“,”切割  所以我们配置多个ApplicationContextInitializer实现类的时候需要用“,”隔开
			for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
              //getInitializerClass同名方法,方法的重载,根据字符查找对应的类,并判断是否为ApplicationContextInitializer的实现类
              //然后添加到返回对象中
				classes.add(getInitializerClass(className));
			}
		}
		return classes;
	}

applyInitializerClasses

private void applyInitializerClasses(ConfigurableApplicationContext context, List<Class<?>> initializerClasses) {
		Class<?> contextClass = context.getClass();
		List<ApplicationContextInitializer<?>> initializers = new ArrayList<>();
		for (Class<?> initializerClass : initializerClasses) {
           //instantiateInitializer方法是使用BeanUtils工具类实例化ApplicationContextInitializer的实现类
           //将实例添加到集合中
			initializers.add(instantiateInitializer(contextClass, initializerClass));
		}
        //调用这些实例 跳转到步骤4
		applyInitializers(context, initializers);
	}
private void applyInitializers(ConfigurableApplicationContext context,
      List<ApplicationContextInitializer<?>> initializers) {
   //根据order属性进行排序   
   initializers.sort(new AnnotationAwareOrderComparator());
   //循环刚刚的实例集合
   for (ApplicationContextInitializer initializer : initializers) {
       //调用ApplicationContextInitializer的接口强制实现的initialize方法
      //完成ApplicationContextInitializer实现类的起作用
      initializer.initialize(context);
   }
}

总结

在这里插入图片描述
在这里插入图片描述

四、面试题总结

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值