Springboot自动加载配置的原理解析

目录
  • 1、springboot自动配置的原理初探
  • 2. 补充扩展(解释为什么引用的包都报红错了,项目还能启动)
  • 3、又一个问题
  • 总结

1、springboot自动配置的原理初探

以下注解都在springboot的自动化配置包中:spring-boot-autoconfigure。读者朋友可以跟着一下步骤走一遍,应该对自动配置就有一定的认知了。

1.springboot程序的入口是在启动类,该类有个关键注解SpringBootApplication

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    //略……
}

2.打开SpringBootApplication注解,上面有个关键注解EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    //……
}

3.EnableAutoConfiguration上有个@Import(AutoConfigurationImportSelector.class),注意AutoConfigurationImportSelector,

@Import作用创建一个AutoConfigurationImportSelector的bean对象,并且加入IoC容器

	//org.springframework.boot.autoconfigure.AutoConfigurationImportSelector
//此处只贴了关键方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
				+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

4.AutoConfigurationImportSelector类中的getCandidateConfigurations方法代码如上,其调用了SpringFactoriesLoader的loadFactoryNames方法,来获取

configurations,此configurations列表其实就是要被自动花配置的类。SpringFactoriesLoader的两个重要方法如下:

//org.springframework.core.io.support.SpringFactoriesLoader
//只贴了两个关键方法
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

//此方法返回的是即将要被自动化配置的类的全限定类名,是从META-INF/spring.factories配置的,配置文件中有个org.springframework.boot.autoconfigure.EnableAutoConfiguration 其后面可配置多个想被自动花配置的类
    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullab等le 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 ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));//META-INF/spring.factories
			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);
		}
	}

5.举例分析,我们在spring.factories中可以看到org.springframework.boot.autoconfigure.EnableAutoConfiguration后有一个org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,说明springboot希望redis能够自动化配置。接着我们打开RedisAutoConfiguration源码查看。此处我故意没复制源码,用的截图,可以看到截图直接有报错,编译错误,错误的原因是我们还没添加spring-boot-starter-data-redis的依赖。**这里有个问题,为什么明明代码都报错,Cannot resolve symbol xxx(未找到类),但是我们的项目依然可以启动?不信你建立一个简单的springboot项目,只添加web依赖,手动打开RedisAutoConfiguration,发现是报红错的,但是你启动项目,发现没任何问题,why??**这个问题后面再解答,先接着看自动配置的问题。

6.先把RedisAutoConfiguration源码复制出来方便我写注释,上面用截图主要是让大家看到报错

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(name = "redisTemplate")
	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

	@Bean
	@ConditionalOnMissingBean
	public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

}

看源码可知RedisAutoConfiguration上有一个Configuration和ConditionalOnClass注解,先分析这两个。首先Configuration注解,代表这是个Java config配置类,和spring配置bean的xml文件是一个作用,都是用来实例化bean的,**但是注意还有个@ConditionalOnClass(RedisOperations.class)注解,这个注解的作用是当RedisOperations.class这个类被找到后才会生效,如果没找到此类,那么整个RedisAutoConfiguration就不会生效。**所以当我们引入了redis的依赖,springboot首先会通过RedisAutoConfiguration的方法redisTemplate给我们设置一个默认的redis配置,当然这个方法上也有个注解@ConditionalOnMissingBean(name = "redisTemplate"),就是当我们没有手动配redisTemplate这个bean它才会调用这个默认的方法,注入一个redisTemplate到IoC容器,所以一般情况我们都是手动配置这个redisTemplate,方便我们设置序列化器,如下:

@Configuration
public class RedisConfig {

    /**
     * 设置 redisTemplate 的序列化设置
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 1.创建 redisTemplate 模版
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 2.关联 redisConnectionFactory
        template.setConnectionFactory(redisConnectionFactory);
        // 3.创建 序列化类
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        // 4.设置可见度
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 5.启动默认的类型
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        // 6.序列化类,对象映射设置
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 7.设置 value 的转化格式和 key 的转化格式
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}

RedisAutoConfiguration上还有一下两个注解,作用是从配置文件读取redis相关的信息,ip、端口、密码等

@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })

2. 补充扩展(解释为什么引用的包都报红错了,项目还能启动)

所有的@Condition注解(包括衍生的)其实都对应一个具体的实现,这个实现类里面有个判断方法叫做matches,返回的是个布尔类型判断值。

打开ConditionalOnClass源码如下,其Conditional注解传递的是个OnClassCondition.class,这就其对应的判断类,也就是说,当我们使用ConditionalOnClass注解时,其实际上调用的是OnClassCondition来判断的

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {

	/**
	 * The classes that must be present. Since this annotation is parsed by loading class
	 * bytecode, it is safe to specify classes here that may ultimately not be on the
	 * classpath, only if this annotation is directly on the affected component and
	 * <b>not</b> if this annotation is used as a composed, meta-annotation. In order to
	 * use this annotation as a meta-annotation, only use the {@link #name} attribute.
	 * @return the classes that must be present
	 */
	Class<?>[] value() default {};

	/**
	 * The classes names that must be present.
	 * @return the class names that must be present.
	 */
	String[] name() default {};

}

ConditionalOnClass类图如下,它继承了condition接口

打开Condition接口如下,查看注释,注释中有说明 **条件判断是在bean定义即将注册到容器之前进行的,**看过springIoC源码的同学应该知道,spring创建一个对象的过程是当服务启动后,先读取xml配置文件(或者通过注解),根据配置文件先定义一个BeanDefinition,然后把这个bean给放到容器(在spring中实际就是一个Map),然后在根据bean定义,通过反射创建真正的对象。反射会触发类加载,当condition条件不满足时,根据如下注释可知,bean定义后续都被拦截了,连注册都不行,所以自然就不可能通过反射创建对象,不反射自然不会触发类加载,不触发类加载那么RedisAutoConfiguration当然啊不会加载,它不加载,那么即使它里面引用了一个不存在的类也不会有啥问题。

上面说的很绕,表达的不是很好,要想看懂以上部分需要掌握两方面的知识:

  • 类加载原理,推荐看周志明老师的《深入理解JVM虚拟机》
  • spring IoC容器创建bean的原理,推荐《spring揭秘》,详细看看IoC部分

3、又一个问题

spring-boot-autoconfigure.jar这个包中的RedisAutoConfiguration都报红色错误了,那么spring官方是怎么打包出来spring-boot-autoconfigure.jar的??怎么给我们提供了一个报错的包呢

//TODO

总结

到此这篇关于Springboot自动加载配置原理的文章就介绍到这了,更多相关Springboot自动加载配置原理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot内部外部配置文件加载顺序解析

    内部配置加载顺序 SpringBoot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件 –file:./config/ –file:./ –classpath:/config/ –classpath:/ 优先级由高到底,高优先级的配置会覆盖低优先级的配置: SpringBoot会从这四个位置全部加载主配置文件:互补配置:还可以通过spring.config.location来改变默认的配置文件位置 项

  • 简单了解springboot加载配置文件顺序

    1.目录结构 Application属性文件,按优先级排序,位置高的将覆盖位置 当前项目目录下的一个/config子目录 当前项目目录 项目的resources即一个classpath下的/config包 项目的resources即classpath根路径(root) 二.读取顺序 如果在不同的目录中存在多个配置文件,它的读取顺序是: 1.config/application.properties(项目根目录中config目录下) 2.config/application.yml 3.appli

  • 浅谈SpringBoot2.4 配置文件加载机制大变化

    前言 Spring Boot 2.4.0.M2刚刚发布,它对 application.properties 和 application.yml 文件的加载方式进行重构.如果应用程序仅使用单个 application.properties 或 application.yml 作为配置文件,那么可能感受不到任何区别.但是如果您的应用程序使用更复杂的配置(例如,Spring Cloud 配置中心等),则需要来了解更改的内容以及原因. 为什么要进行这些更改 随着最新版本 Spring Boot 发布,S

  • SpringBoot配置文件的加载位置实例详解

    springboot采纳了建立生产就绪spring应用程序的观点. Spring Boot优先于配置的惯例,旨在让您尽快启动和运行.在一般情况下,我们不需要做太多的配置就能够让spring boot正常运行.在一些特殊的情况下,我们需要做修改一些配置,或者需要有自己的配置属性. SpringBoot启动会扫描以下位置的application.yml或者 application.properties文件作为SpringBoot的默认配置文件. -file:./config/    -file:./

  • springboot配置文件的加载顺序解析

    这篇文章主要介绍了springboot配置文件的加载顺序解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 springboot启动时会扫描一下位置的application.properties或者application.yml文件作为默认配置文件: file:./config/ file:./ classpath:/config/ classpath:/ 以上是按照优先级从高到低的顺序,所有位置的文件都会被加载,高优先级配置会覆盖低优先级配置

  • Springboot自动加载配置的原理解析

    目录 1.springboot自动配置的原理初探 2. 补充扩展(解释为什么引用的包都报红错了,项目还能启动) 3.又一个问题 总结 1.springboot自动配置的原理初探 以下注解都在springboot的自动化配置包中:spring-boot-autoconfigure.读者朋友可以跟着一下步骤走一遍,应该对自动配置就有一定的认知了. 1.springboot程序的入口是在启动类,该类有个关键注解SpringBootApplication @Target(ElementType.TYPE

  • SpringBoot扩展外部化配置的原理解析

    Environment实现原理 在基于SpringBoot开发的应用中,我们常常会在application.properties.application-xxx.properties.application.yml.application-xxx.yml等配置文件中设置一些属性值,然后通过@Value.@ConfigurationProperties等注解获取,或者采用编码的方式通过Environment获取. # application.properties my.config.appId=d

  • PHP框架自动加载类文件原理详解

    描述:公司项目PHP用作中间转发层(接收http请求,用 socket跟c++做通信),由于代码没有用到框架,这些东西自然就是之前的人自己写的.最近需要对这个底层进行优化,于是便看了下这部分的代码. 目的:这块代码的主要作用是把主目录下的所有插件类一次性全部加载进来.当使用尚未被定义的类(class)和接口(interface)时自动去加载.通过注册自动加载器,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类. 实现方法:主要用到PHP函数__autoload() 详细: error_

  • springboot+idea热启动设置方法(自动加载)

    springboot+idea热启动 1.在pom.xml中引入jar包 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> 然后在build/plugins/plugin中设置

  • PHP从零开始打造自己的MVC框架之类的自动加载实现方法详解

    本文实例讲述了PHP从零开始打造自己的MVC框架之类的自动加载实现方法.分享给大家供大家参考,具体如下: 前面介绍了MVC框架的入口文件,接下来我们希望完成一个"自动加载类"的功能,我们把这个功能放到Imooc这个基础类当中. core\imooc.php: <?php namespace core; class Imooc { public static $classMap = array(); static public function run() { p('ok'); $

  • SpringBoot加载外部依赖过程解析

    这篇文章主要介绍了SpringBoot加载外部依赖过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 背景 公司一个项目的大数据平台进行改造,之前使用Structured Streaming作为实时计算框架,需要替换为替换为Kafka Streams,并使用SpringBoot包装,使其可以纳入微服务体系. 然而由于之前并没有接触过SpringFramework相关技术,并且项目工期较为紧张,因此只好花了2天时间看了看Spring和Spri

  • CodeIgniter配置之autoload.php自动加载用法分析

    本文实例分析了CodeIgniter配置之autoload.php自动加载用法.分享给大家供大家参考,具体如下: CodeIgniter带了自动加载的功能,可以全局加载类库.模型.配置.语言包等,对于需要全局使用的功能相当方便. 例如:有个全局函数写在app_helper.php中,需要全局加载这个函数,只需设置autoload.php: 复制代码 代码如下: $autoload['helper'] = array('app'); 接下来,所有的地方都可以使用了,配置.模型等配置相似.但方便的同

  • PHP面向对象自动加载机制原理与用法分析

    本文实例讲述了PHP面向对象自动加载机制原理与用法.分享给大家供大家参考,具体如下: 在学习PHP的面向对象的时候,会知道很多"语法糖",也就是魔术方法.有一个加自动加载的魔术方法,叫:__autoload(); 先看一段代码 <?php function __autoload($classname) { $filename = "./". $classname .".php"; include_once($filename); } new

  • PHP 自动加载类原理与用法实例分析

    本文实例讲述了PHP 自动加载类原理与用法.分享给大家供大家参考,具体如下: 类的自动加载 (Autoloading Classes) 在编写面向对象(OOP) 程序时,很多开发者为每个类新建一个 PHP 文件. 这会带来一个烦恼:每个脚本的开头,都需要包含(include)一个长长的列表(每个类都有个文件). 在 PHP 5 中,已经不再需要这样了. spl_autoload_register() 函数可以注册任意数量的自动加载器,当使用尚未被定义的类(class)和接口(interface)

  • PHP 命名空间和自动加载原理与用法实例分析

    本文实例讲述了PHP 命名空间和自动加载原理与用法.分享给大家供大家参考,具体如下: PHP 命名空间 php5.3 之后引入了命名空间的特性,从本质上讲,命名空间就是一个容器,你可以将类.函数和变量放在其中,在命名空间中,你可以无条件地访问这些项,在命名空间之外,必须导入或引用命名空间,才能访问它所包含的项. 声明命名空间 namespace my; require_one 'outputter3.php'; class outputter { // 输出数据 public function

随机推荐