慕课网SpringBoot源码教程学习总结
一、系统初始化器
类名:ApplicationContextInitializer
介绍:Spring容器刷新之前执行的一个回调函数
作用:向SpringBoot容器中注册属性
使用:继承接口自定义实现
1、自定义初始化器
实现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=cn.nupt.sb.initializer.FirstInitializer -
修改Application启动类
public static void main(String[] args) { // SpringApplication.run(SpringbootApplication.class, args); SpringApplication springApplication = new SpringApplication(SpringbootApplication.class); springApplication.addInitializers(new SecondInitializer()); springApplication.run(args); } -
修改application.properties文件
context.initializer.classes=cn.nupt.sb.initializer.ThirdInitializer
2、工厂加载机制解析
SpringFactoriesLoader介绍
- 框架内部使用的通用工厂加载机制
- 从classpath下多个jar包特定的位置读取文件并初始化类
- 文件内容必须是kv形式
- key是全限定名(抽象类|接口),value是实现,多个实现使用
,分隔
SpringFactoriesLoader作用
- SpringBoot框架从类路径jar包中读取特定文件实现扩展类的载入
loadFactories流程
(1)启动类执行
(2)查找缓存,存在返回
(3)不存在,读取指定资源文件
(4)构造properties对象
(5)获取指定key对应value值
(6)逗号分隔value
(7)保存结果到缓存
(8)依次实例化结果对象
(9)对结果对象排序
(10)返回
源码解析:
进入springboot启动类
SpringApplication.run(SpringbootApplication.class, args);
进入run方法,在SpringApplication初始化器方法中获取初始化器:
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();
}
进入getSpringFactoriesInstances方法
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
//通过SpringFactoriesLoader.loadFactoryNames方法获取所有初始化器类名
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//通过反射方式获取初始化器实例
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
//根据初始化器设置的order进行排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
进入SpringFactoriesLoader.loadFactoryNames方法:
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//查找缓存,如果存在key为classLoader的值,直接返回
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
//读取指定资源文件,即META-INF/spring.factories文件
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
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);
}
}
3、系统初始化器解析
作用:
- 上下文刷新即refresh方法前调用
- 用来编码设置一些属性变量通常用在web环境中
- 可以通过order接口进行排序
调用流程:
run()框架启动--》prepareContext()上下文准备--》applyInitializers()调用系统初始化器--》遍历调用初始化器
实现原理:
- 定义在spring.factories文件中被SpringfactoriesLoader发现注册
- SpringApplication初始化完毕后手动添加
- 定义成环境变量被DelegatingApplicationContextInitializer发现注册
4、面试题
(1)介绍下SpringFactoriesLoader?
答:SpringFactoriesLoader是SpringBoot提供的通用工厂加载机制,用于实现扩展类的载入。
(2)SpringFactoriesLoader如何加载工厂类?
答:从classpath下多个jar包特定的位置读取文件并初始化类,将kv形式的内容读取为Properties,然后依次进行遍历,排序。
(3)系统初始化器的作用?
答:SpringBoot容器的一个回调接口,通过它向容器定义属性。
(4)系统初始化器调用时机?
答:是在SpringBoot启动时的run方法中的prepareContex方法中调用的。
(5)如何自定义实现系统初始化器?
答:三种方式,(1)Application启动类中添加(2)spring.factories文件中添加配置(3)application.properties文件添加配置。
(6)自定义实现系统初始化器有哪些注意事项?
答:order排序和失效问题。使用第三种方式时,通过context.initializer.classes配置的初始化器是由DelegatingApplicationContextInitializer调用的,而该系统初始化器则是通过SpringFactoriesLoader加载的,其order为0,最先执行initialize方法,在该方法内,按照order注解值排序,然后逐个调用initialize方法。因此通过context.initializer.classes配置的初始化器是最先加载的。
private static final String PROPERTY_NAME = "context.initializer.classes";
private int order = 0;
@Override
public void initialize(ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
List<Class<?>> initializerClasses = getInitializerClasses(environment);
if (!initializerClasses.isEmpty()) {
applyInitializerClasses(context, initializerClasses);
}
}
private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
//获取context.initializer.classes属性值
String classNames = env.getProperty(PROPERTY_NAME);
List<Class<?>> classes = new ArrayList<>();
if (StringUtils.hasLength(classNames)) {
for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
//获取初始化器Class对象
classes.add(getInitializerClass(className));
}
}
return classes;
}
private void applyInitializerClasses(ConfigurableApplicationContext context, List<Class<?>> initializerClasses) {
Class<?> contextClass = context.getClass();
List<ApplicationContextInitializer<?>> initializers = new ArrayList<>();
for (Class<?> initializerClass : initializerClasses) {
//通过反射方式实例化初始化器
initializers.add(instantiateInitializer(contextClass, initializerClass));
}
//按照order注解值对初始化器排序,并依次调用ApplicationContextInitializer接口实现方法
applyInitializers(context, initializers);
}