【SpringBoot】【一】 加载初始化器、监听器过程详解
1 前言
本节主要讲下 SpringBoot 启动的时候,加载初始化器、监听器的过程哈。
2 加载时机
我们先来看下加载的时机,也就是什么时候加载的呢,就是我们 SpringBoot启动的时候,创建 SpringApplication的时候就会去加载的,我们看下:
@SpringBootApplication public class DemoApplication { // 我们的启动类 public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } public SpringApplication(Class<?>... primarySources) { // 调用重载方法 this(null, primarySources); } public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); // 我们的主类就是 DemoApplication.class this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); // 决定当前应用程序的容器,默认使用的是Servlet容器 this.webApplicationType = WebApplicationType.deduceFromClasspath(); // 加载所有的初始化器 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 加载所有的监听器 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); // 这个方法仅仅是找到main方法所在的类,为后面的扫包作准备 this.mainApplicationClass = deduceMainApplicationClass(); }
如上哈,可以看到加载初始化器和监听器都是会调用 getSpringFactoriesInstances 方法来记载的只是传入的类不一样哈,那么我们本节就重点看下这个方法哈。
3 getSpringFactoriesInstances
我们直接看 getSpringFactoriesInstances 方法的源码:
// type是什么?就是我们要加载的类型 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 集合保证类唯一,通过 SpringFactoriesLoader 来获取指定目录下的类信息 // 那我们重点看下这个 SpringFactoriesLoader Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); // 通过全类名和类加载器去加载类并创建实例对象出来 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); // 排序器进行排序 排序器排序就不说了哈,这玩意有几种类型的优先级等,我在AOP的时候说过了哈,大家可以看我AOP的通知器顺序也涉及到这个排序器了 AnnotationAwareOrderComparator.sort(instances); return instances; }
可以看到大致的过程:
(1)获取类加载器
(2)通过 SpringFactoriesLoader 来获取指定类型的全类名集合
(3)通过 createSpringFactoriesInstances 方法来加载类并创建类型的实例
(4)排序
那我们重点看下步骤2、3哈。
3.1 SpringFactoriesLoader.loadFactoryNames 获取指定类型的类集合
首先 SpringFactoriesLoader 这个类是 Spring的核心包里的类哈,我们进去看看:
/** * Load the fully qualified class names of factory implementations of the * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given * class loader. * <p>As of Spring Framework 5.3, if a particular implementation class name * is discovered more than once for the given factory type, duplicates will * be ignored. * @param factoryType the interface or abstract class representing the factory * @param classLoader the ClassLoader to use for loading resources; can be * {@code null} to use the default * @throws IllegalArgumentException if an error occurs while loading factory names * @see #loadFactories */ public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { ClassLoader classLoaderToUse = classLoader; // 类加载器为空的话,就用当前类的类加载器,说实话对类加载器没什么太大感觉因为这玩意感觉很底层,咱也摸不准是来区分什么的哈 有知道的麻烦告下哈 if (classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } String factoryTypeName = factoryType.getName(); // 调用 loadSpringFactories 方法 return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) { /** * static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>(); * 先从缓存中获取 key是类加载器 */ Map<String, List<String>> result = cache.get(classLoader); // 缓存中有就直接返回 if (result != null) { return result; } // 初始化Map result = new HashMap<>(); try { /** * public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; * classLoader.getResources 加载当前类加载器的META-INF/spring.factories */ Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); 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(); // 逗号分隔出来每个全类名 String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) entry.getValue()); for (String factoryImplementationName : factoryImplementationNames) { // 塞进结果中 result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()) .add(factoryImplementationName.trim()); } } } // Replace all lists with unmodifiable lists containing unique elements /** * 去重 * collectingAndThen 后边的能看懂么 就是去重后 toList收集到List中,然后将List放进 Collections.unmodifiableList的方法中,将集合变为只读的集合 */ result.replaceAll((factoryType, implementations) -> implementations.stream().distinct() .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList))); cache.put(classLoader, result); } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } return result; }
可以看到哈,其实最后就是用到了类加载器的资源定位来获取指定目录文件下的内容的哈。
3.2 createSpringFactoriesInstances 加载以及实例化类
这个方法就处在 SpringApplication中,我们来看下:
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; }
4 初始化器、监听器
说完加载过程,那我们来看下都有哪些默认的初始化器和监听器哈,从 META-INF/spring.factories 配置文件中加载的,那么这个文件在哪呢?自带有2个,分别在源码的jar包的 spring-boot-autoconfigure 项目 和 spring-boot 项目里面各有一个:
那我们进去看看。初始化器:
监听器:
ConfigFileApplicationListener: 解析application.properties/yml以及application-profile.properties/yml配置文件核心事件监听器,也是本章的重点分析对象。
AnsiOutputApplicationListener: 根据spring.output.ansi.enabled配置值配置日志打印色彩模式。
LoggingApplicationListener: 解析application.properties/yml配置文件logging开头的配置,并将配置信息初始化日志到系统。
BackgroundPreinitializer:这个监听器不处理当前的事件。
ClasspathLoggingApplicationListener:纯日志打印,启动过程中控制台debug级别的日志关键字“Application started with classpath”相关的信息就是这个监听器打印的。
DelegatingApplicationListener:application.properties/yml配置文件context.listener.classes配置的自定义监听器的代理执行者。主要工作是执行自定义配置的事件监听器。
FileEncodingApplicationListener:将application.properties/yml配置文件spring.mandatory-file-encoding配置跟System.getProperty("file.encoding")值进行忽略大小写匹配,如果匹配不上,直接报错(throw new IllegalStateException("The Java Virtual Machine has not been configured to use the desired default character encoding (" + desired + ")."))。
至于每个初始化器和监听器具体是做什么的,我们本节就不展开讲了哈,本节主要看一下加载过程哈。
5 小结
好了,关于加载初始化器、监听器我们就看到这里哈,有理解不对的地方欢迎指正哈。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了