SpringBoot深入浅出分析初始化器

如有错误,望指正;

SpringBoot可以有三种方式定义初始化器,来为容器中增加自定义的对象,具体如下:

1、定义在spring.factories文件中,被SpringFactoriesLoader发现注册;

在resources下建立META-INF文件夹,新建spring.factories文件,添加自定义的初始化器:
org.springframework.context.ApplicationContextInitializer=com.mooc.sb2.initializer.InitializerOne

2、SpringApplication初始化完成后手动添加;

SpringApplication springApplication = new SpringApplication(SbApplication.class);
springApplication.addInitializers(new InitializerTwo());
springApplication.run(args)

3、定义成环境变量,被DelegatingApplicationContextInitializer所发现注册(优先级最高)

application.properties中增加一项配置:
context.initializer.classes=com.mooc.sb2.initializer.ThirdInitializer

下面从代码的角度,来看下这三种方式是如何加载的

对于第一种方法,可以跟进

SpringApplication.run(SpringBootApplication.class, args);
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
	return run(new Class<?>[] { primarySource }, args);
}

继续进入这个run方法:

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	return new SpringApplication(primarySources).run(args);
}

还有一个run方法,我们继续进入:

这个run方法代码比较长,这里我们关注的是这部分:

exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);

我们进入这个getSpringFactoriesInstances方法,如下:

	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);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

我们先看这个loadFactoryNames方法,它调用了loadSpringFactories方法来获取固定位置的配置信息:

loadSpringFactories的重点部分代码如下:

        try {
			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;
		}

我们可以发现一个常量的定义:FACTORIES_RESOURCE_LOCATION,而它的值,就是

"META-INF/spring.factories"

这里就解释了我们为什么要自己建一个配置文件在固定的目录下,这样springboot就成功读取到了自定义的初始化器,加载到了cache中;下面我们回到SpringApplication.java中,继续跟进getSpringFactoriesInstances方法。

在获取到了初始化器的全限定名后,我们继续看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;
	}

主要使用了反射手段,来完成初始化器的实例化工作;在获取了实例列表之AnnotationAwareOrderComparator.sort(instances);会将实例按照@Order的注解顺序进行排序,在这里不进行详述;

第二种初始化器是通过手动添加

第三种是通过DelegatingApplicationContextInitializer的initialize方法来调用的,首先,我们先看这个方法的代码:

	@Override
	public void initialize(ConfigurableApplicationContext context) {
		ConfigurableEnvironment environment = context.getEnvironment();
		List<Class<?>> initializerClasses = getInitializerClasses(environment);
		if (!initializerClasses.isEmpty()) {
			applyInitializerClasses(context, initializerClasses);
		}
	}

其中,我们重点关注getInitializerClasses()方法

	private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
		String classNames = env.getProperty(PROPERTY_NAME);
		List<Class<?>> classes = new ArrayList<>();
		if (StringUtils.hasLength(classNames)) {
			for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
				classes.add(getInitializerClass(className));
			}
		}
		return classes;
	}

这里的常量:

PROPERTY_NAME= "context.initializer.classes";

也就是我们配置文件的key,value值会被以‘,’为分界线进行分割,来获得每个所需的系统初始化器的全限定名,并通过BeanUtis工具来进行初始化;

另:为什么DelegatingApplicationContextInitializer加载的初始化器是优先于其他方式执行呢?这是因为SpringApplication的run方法中的prepareContext方法会调用applyInitializers方法,applyInitializers方法的for循环会调用getInitializers方法来加载所有的初始化器,而DelegatingApplicationContextInitializer的Order=0,因此优先级最高,会被最先加载;

到此这篇关于SpringBoot深入浅出分析初始化器的文章就介绍到这了,更多相关SpringBoot初始化器内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 浅谈SpringBoot中的Bean初始化方法 @PostConstruct

    目录 注解说明 代码示例 注解示例 错误示例 正确示例 SpringBoot @PostConstruct虽好,也要慎用 1 问题的产生 2 案例模拟 3 总结 注解说明 使用注解: @PostConstruct 效果:在Bean初始化之后(构造方法和@Autowired之后)执行指定操作.经常用在将构造方法中的动作延迟. 备注:Bean初始化时候的执行顺序: 构造方法 -> @Autowired -> @PostConstruct 代码示例 注解示例 @Component public cl

  • springboot 启动时初始化数据库的步骤

    问题描述 在spring-boot启动时,希望能执行相应的sql文件来初始化数据库. 使用配置文件初始化数据库 可以在spring-boot的配置文件application.yml中设置要初始化的sql文件.这是最简单的方法,只需要添加属性就可以实现. 首先设置spring.datasource.initialization-mode=always表示任何类型数据库都进行数据库初始化,默认情况下,spring-boot会自动加载data.sql或data-${platform}.sql文件来初始

  • SpringBoot项目启动时如何读取配置以及初始化资源

    介绍   在开发过程中,我们有时候会遇到非接口调用而出发程序执行任务的一些场景,比如我们使用quartz定时框架通过配置文件来启动定时任务时,或者一些初始化资源场景等触发的任务执行场景. 方法一:注解 方案   通过使用注解@Configuration和@Bean来初始化资源,配置文件当然还是通过@Value进行注入. @Configuration:用于定义配置类,可替换xml配置文件,被注解的类内部一般是包含了一个或者多个@Bean注解的方法. @Bean:产生一个Bean对象,然后将Bean

  • 浅谈SpringBoot资源初始化加载的几种方式

    目录 一.问题 二.资源初始化 一.问题 在平时的业务模块开发过程中,难免会需要做一些全局的任务.缓存.线程等等的初始化工作,那么如何解决这个问题呢?方法有多种,但具体又要怎么选择呢? 二.资源初始化 1.既然要做资源的初始化,那么就需要了解一下springboot启动过程(这里大体说下启动过程,详细:https://www.jb51.net/article/133648.htm) 按照前面的分析,Spring-boot容器启动流程总体可划分为2部分: 执行注解:扫描指定范围下的bean.载入自

  • 详解SpringBoot程序启动时执行初始化代码

    因项目集成了Redis缓存部分数据,需要在程序启动时将数据加载到Redis中,即初始化数据到Redis. 在SpringBoot项目下,即在容器初始化完毕后执行我们自己的初始化代码. 第一步:创建实现ApplicationListener接口的类 package com.stone; import com.stone.service.IPermissionService; import org.springframework.context.ApplicationListener; import

  • springboot中项目启动时实现初始化方法加载参数

    目录 springboot项目启动,初始化方法加载参数 1.@PostConstruct说明 2.@PreDestroy说明 第一种:注解@PostConstruct 第二种:实现CommandLineRunner接口 第三种:springboot的启动类 springboot初始化参数顺序 spring初始化参数顺序为 springboot项目启动,初始化方法加载参数 今天我看到项目中用到了 @PostConstruct 这个注解,之前没看到过,特地查了一下, 1.@PostConstruct

  • Spring中Bean的加载与SpringBoot的初始化流程详解

    目录 前言 第一章 Spring中Bean的一些简单概念 1.1 SpingIOC简介 1.2 BeanFactory 1.2.1 BeanDefinition 1.2.2 BeanDefinitionRegistry 1.2.3 BeanFactory结构图 1.3 ApplicationContext 第二章 SpringBoot的初始化流程 2.1 准备阶段 2.2 运行阶段 2.2.1 监听器分析 2.2.2 refreshContext 2.3 总结 前言 一直对它们之间的关系感到好奇

  • SpringBoot深入浅出分析初始化器

    如有错误,望指正: SpringBoot可以有三种方式定义初始化器,来为容器中增加自定义的对象,具体如下: 1.定义在spring.factories文件中,被SpringFactoriesLoader发现注册: 在resources下建立META-INF文件夹,新建spring.factories文件,添加自定义的初始化器:org.springframework.context.ApplicationContextInitializer=com.mooc.sb2.initializer.Ini

  • SpringBoot 过滤器、拦截器、监听器对比及使用场景分析

    一.关系图理解 二.区别 1.过滤器 过滤器是在web应用启动的时候初始化一次, 在web应用停止的时候销毁 可以对请求的URL进行过滤, 对敏感词过滤 挡在拦截器的外层 实现的是 javax.servlet.Filter 接口 ,是 Servlet 规范的一部分 在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后 依赖Web容器 会多次执行 过滤器简介 过滤器的英文名称为 Filter, 是 Servlet 技术中最实用的技术.如同它的名字一样,过滤器

  • 详谈springboot过滤器和拦截器的实现及区别

    前言 springmvc中有两种很普遍的AOP实现: 1.过滤器(Filter) 2.拦截器(Interceptor) 本篇面对的是一些刚接触springboot的人群 所以主要讲解filter和interceptor的简单实现和它们之间到底有什么区别 (一些复杂的功能我会之后发出文章,请记得关注) Filter的简单实现 字面意思:过滤器就是过滤的作用,在web开发中过滤一些我们指定的url 那么它能帮我们过滤什么呢? 那功能可就多了: 比如过拦截掉我们不需要的接口请求 修改请求(reques

  • SpringBoot使用flyway初始化数据库

    概述 Flyway这款数据库版本工具就算大家没有使用过但也略有耳闻了,SpringBoot对该款工具进行集成的框架可以让我们在启动SpringBoot应用时自动去找SQL版本文件进行比对执行,但在迁移或初始化时往往还是需要先手动进行下数据库的初始化配置,否则会把Unknown database的异常. 为了减少这一步所以个人就以SpringBoot的方式编码在项目的启动时自动进行数据库的初始化,然后再执行版本文件. 自动建库实现步骤 具体思路 SpringBoot的配置项都会有相应的Proper

  • springboot过滤器和拦截器的实例代码

    拦截器与过滤器 在讲Spring boot之前,我们先了解一下过滤器和拦截器.这两者在功能方面很类似,但是在具体技术实现方面,差距还是比较大的.在分析两者的区别之前,我们先理解一下AOP的概念,AOP不是一种具体的技术,而是一种编程思想.在面向对象编程的过程中,我们很容易通过继承.多态来解决纵向扩展. 但是对于横向的功能,比如,在所有的service方法中开启事务,或者统一记录日志等功能,面向对象的是无法解决的.所以AOP--面向切面编程其实是面向对象编程思想的一个补充.而我们今天讲的过滤器和拦

  • springboot+mybatis-plus基于拦截器实现分表的示例代码

    目录 前言 一.设计思路 二.实现思路 三.代码实现 接口描述 核心组成部分 1.本地线程工具类 2.注解部分 3.拦截器实现 四.测试 后记 前言 最近在工作遇到数据量比较多的情况,单表压力比较大,crud的操作都受到影响,因为某些原因,项目上没有引入sharding-jdbc这款优秀的分表分库组件,所以打算简单写一个基于mybatis拦截器的分表实现 一.设计思路 在现有的业务场景下,主要实现的目标就是表名的替换,需要解决的问题有 如何从执行的方法中,获取对应的sql并解析获取当前执行的表名

  • 深入浅出分析C++ string底层原理

    目录 一.深浅拷贝 浅拷贝: 深拷贝 二.string迭代器原理 三.string的传统写法 1.构造实现 2.其他接口 一.深浅拷贝 浅拷贝: 在实现string时要是不实先string拷贝构造,会自动生成一个拷贝构造函数,但是他只是一个浅拷贝.两个string对象指向同一个地址,在两个对象调用析构函数是,前一个对象调用的析构函数已经释放了这个地址的内从,而后一个会重复释放该块空间,导致出错. 会触发断点,然后报错. class string { public: /*string() :_st

  • SpringBoot 过滤器与拦截器实例演示

       SpringBoot中的过滤器拦截器操作与springmvc中的几乎一样所以这里也不过多介绍了,下面举两个简单的栗子演示一下 1.过滤器         1 创建过滤器类LoginFilter,实现servlet包下的Filter接口(包不要导错),加入注解WebFilter package com.example.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.

  • 深入浅出分析Java 类和对象

    目录 一.什么是类 二.Java的类和C语言的结构体异同 三.类和类的实例化 类的声明 实例化的对象,成员遵循默认值规则 类的实例化 静态属性(静态成员变量) 四.构造方法 创建构造方法 this 一.什么是类 类(Class)是面向对象程序设计(OOP,Object-Oriented Programming)实现信息封装的基础.类是一种用户自定义的引用数据类型,也称类类型.每个类包含数据说明和一组操作数据或传递消息的函数,类的实例称为对象 类的实质是一种引用数据类型,类似于 byte,shor

  • SpringBoot实现过滤器拦截器的耗时对比

    目录 过滤器的方式 拦截器的方式 三种方式 下面为大家一一对应 过滤器的方式 拦截器的方式 过滤器的方式 这种方式简单点 但是可配置性不高 注意:一定得扫描到spring容器中 创建一个类 实现 filter接口 init:该方法是对filter对象进行初始化的方法,仅在容器初始化filter对象结束后被调用一次,参数FilterConfig可以获得filter的初始化参数: doFilter:可以对request和response进行<u>预处理</u>.其中FilterChai

随机推荐