首页 > 其他分享 >【SpringBoot】【一】 加载初始化器、监听器详解

【SpringBoot】【一】 加载初始化器、监听器详解

时间:2023-05-04 16:00:57浏览次数:43  
标签:初始化 SpringBoot classLoader class 监听器 new type Class 加载

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  小结

好了,关于加载初始化器、监听器我们就看到这里哈,有理解不对的地方欢迎指正哈。

标签:初始化,SpringBoot,classLoader,class,监听器,new,type,Class,加载
From: https://www.cnblogs.com/kukuxjx/p/17371472.html

相关文章

  • Java 数组、List初始化赋值
    1数组初始化赋值//第一种初始化赋值方式String[]strs1={"1","2"};//第二种初始化赋值方式String[]strs2=newString[]{"1","2"};2List初始化赋值//第一种初始化赋值方式List<String>strList1=Arrays.asList(newString[]{"1","2"});......
  • springboot与mongodb之整合(一)
    一、添加maven依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId><version>2.6.7</version></dependency>二、配置properties文件1、mongodb无......
  • springboot 切面注解方式 记录日志
    1.定义GateOpLogimportjava.lang.annotation.*;/***操作日志记录*@authorcodefulture*/@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceGateOpLog{/***说明*/Strin......
  • springboot异常处理的通用方式2
    2、定义一个异常的枚举数组•ServerErrCodeDefine类//```java@AllArgsConstructor@GetterpublicenumServerErrCodeDefine{privateinterrCode;privateStringcode;privateHttpStatushttpStatus;privateStringmessageSourceKey;/***************************......
  • SpringBoot 集成 Shiro 简单教程
    1.前言 ApacheShiro是一个功能强大且易于使用的Java安全框架,提供了认证,授权,加密,和会话管理。Shiro有三大核心组件:Subject: 即当前用户,在权限管理的应用程序里往往需要知道谁能够操作什么,谁拥有操作该程序的权利,shiro中则需要通过Subject来提供基础的当前用户信息,Sub......
  • SpringBoot项目部署在外置Tomcat正常启动,但项目没有被加载的问题
    最近打算部署个SpringBoot项目到外置Tomcat运行,但是发现tomcat启动成功,访问却一直404,刚开始以为是Tomcat的问题,就一直在改Tomcat配置。最后发现tomcat启动时根本就没加载到项目,因为控制台没有打印"SpringBoot"的项目标志经过一番百度查找,最后发现是因为项目启动类没有继承Spring......
  • 记录一件很神奇的类型转换问题(springboot项目+echarts)
    今天博主在应付学校的实验,想要使用echarts绘制一张很简单的条形图(博主是初学者),如下(时间还未作排序) 对于横轴,我封装了一个dateList,这个datelist是用java,将数据库中date类型的数据,提取其年月拼装而成的,代码如下:Stringdate=String.valueOf(art.getArticleCreateTime().getYea......
  • java基于springboot+vue非前后端分离的网上商城购物系统、在线商城管理系统,附源码+数
    1、项目介绍java基于springboot+vue非前后端分离的网上商城购物系统、在线商城管理系统,实现管理员:首页、个人中心、用户管理、商品分类管理、商品信息管理、订单评价管理、系统管理、订单管理,用户;首页、个人中心、订单评价管理、我的收藏管理、订单管理,前台首页;首页、商品信息、......
  • vue学习 第十天(1) css高级技巧 ----CSS用户界面样式 / vertical-align属性应用
    用户界面样式 1)鼠标样式cursorli{cursor:pointer;}设置或检索在对象上移动的鼠标指针采用何种系统预定义的光标形状。 2、轮廓线outline给表单添加outline:0;或者outline:none;样式之后,就可以去掉默认的蓝色边......
  • SpringBoot定义优雅全局统一Restful API 响应框架二
    这里解决之前留下来的问题,当程序没有正常返回时候就是程序由于运行时异常导致的结果,有些异常我们可,能无法提前预知,不能正常走到我们return的R对象返回。这个时候该如何处理在SpringBoot中,可以使用@ControllerAdvice注解来启用全局异常处理。通过使用@ControllerAdvice注解,可以捕......