死磕Spring,什么是SPI机制,对SpringBoot自动装配有什么帮助

如果没时间看的话在这里直接看总结

1. SPI是一个机制流程由三个组件构成

  • ServiceLoader就是ClassLoader
  • Service是接口作为文件在META-INF/services目录下的名称
  • ServiceProvider是接口的实现类作为文件在META-INF/services目录下的内容

2. SPI执行流程

  • ServiceLoader通过classpath路径加载指定的Service文件然后使用里面合适的内容ServiceProvider

一、Java SPI的概念和术语

SPIService Provider Interface基于ClassLoader发现并加载服务机制
SPI由三个组件构成Service、Service Provider、ServiceLoader

  • Service是一个公开的接口或抽象类定义了一个抽象的功能模块文件名称
  • Service Provider是Service的实现类文件内容
  • ServiceLoader是SPI机制中的核心组件负责在运行时发现并加载Service Provider
    在这里插入图片描述

二、看看Java SPI是如何诞生的

  1. 在Java SPI出现之前Class.forName要自己根据需求写驱动类
    在这里插入图片描述

  2. JDBC要求Driver实现类在类加载的时候能将自身的实例对象自动注册到DriverManager中从而加载数据库驱动。
    在这里插入图片描述

  3. Java SPI逐渐融入JDBC
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

三、Java SPI应该如何应用

  1. 规范的配置文件
    在这里插入图片描述
    在这里插入图片描述
  2. Service Provider类必须具备无参的默认构造方法
    在这里插入图片描述
    在JDBC中的对应实现
    在这里插入图片描述
  3. 保证能加载到配置文件和Service Provider类
    在这里插入图片描述
    在JDBC中的对应实现
    在这里插入图片描述
    总结上述除了导包需要自己动手以外其他的手续都是导包之后Java SPI自动完成的

四、从0开始手撸一个SPI的应用实例

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总体流程
在这里插入图片描述

五、SpringBoot自动装配

参考视频每一帧都是干货15分钟的视频花2小时看
参考文章springboot自动装配到底是什么意思
参考文章建立META-INF/spring.factories文件的意义何在
参考文章springboot自动装配原理-以redis为例
参考文章聊聊 SpringBoot 自动装配原理
参考文章spring.factories 文件的位置

1. 手动装配Redis实例

  • 加入pom依赖
<dependency>
	<groupId>org.springframework.data</groupId>
	<artifactId>spring-data-redis</artifactId>
	<version>2.0.9.RELEASE</version>
</dependency>

<dependency>
	<groupId>redis.clients</groupId>
	<artifactId>jedis</artifactId>
	<version>2.9.0</version>
</dependency>
  • 配置xml的bean的配置
 //配置连接池
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="minIdle" value="10"></property>
        <property name="maxTotal" value="20"></property>
    </bean>
    
    //配置连接工厂
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="47.104.128.12"></property>
        <property name="password" value="123456"></property>
        <property name="database" value="0"></property>
        <property name="poolConfig" ref="poolConfig"></property>
    </bean>


    //配置 redisTemplate 模版类
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory"  ref="jedisConnectionFactory"/>
        <!--如果不配置Serializer那么存储的时候缺省使用String如果用User类型存储那么会提示错误User can't cast to String  -->
        <property name="keySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <property name="valueSerializer">
            <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
        </property>
        <property name="hashKeySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <property name="hashValueSerializer">
         <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
        </property>
    </bean>
  • 导入配置
    @ImportResource(locations = “classpath:beans.xml”) 可以导入xml的配置文件

2. SpringBoot自动配置Redis实例

  • 引入依赖
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 配置Redis服务器
spring:
	redis:
		database:0
		host:127.0.0.1
		port:6379
		password:123456
  • 直接使用RedisTemplate或StringRedisTemplate
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
  • 提出问题自动配置
  • 我们除了通过maven引入一个starter外其他什么也没有做但是呢SpringBoot就自动完成了Redis的配置将相关的Bean对象注册到IOC容器中了。那么SpringBoot是如何做到这一点的呢这就是这篇博客所要说明的问题了。

2. 自动配置一切从注解@SpringBootApplicaiton说起

  • @SpringBootApplication注解
    在这里插入图片描述
  • 下面我们逐步分析@EnableAutoConfiguration的自动配置
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
 
    Class<?>[] exclude() default {};
 
    String[] excludeName() default {};
}

AutoConfigurationImportSelector.class的selectImports方法

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata){
	if(!isEnabled(annotationMetadata))
		return NO_IMPORTS;
	AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
			.loadMetadata(this.beanClassLoader);
	//SpringBoot自动配置的入口方法
	AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
			autoConfigurationMetadata, annotationErtadata);
	return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
  • selectImports方法中引用的getAutoConfigurationEntry
protected AutoConfigurationEntry getAutoConfigurationEntry(
		AutoConfigurationMetadata autoConfigurationMetadata,
		AnnotationMetadata annotationMetadata){
	//1. 获取annotationMetadata的注解@EnableAutoConfiguration的属性
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
	//2. 从资源文件Spring.factories中获取EnableAutoConfiguration对应的所有的类
	List<String> configurations = getCandidateConfigurations(
		annotationMetadata, attributes);
	//3. 通过在注解@EnableAutoConfiguration设置exclude的相关属性可以排除指定的自动配置类
	Set<String> exclusions = getExclusions(anntationMetadata, attributes);
	checkExcludedClasses(configurations, exclusions);
	configurations.removeAll(exclusions);
	//4. 根据注解@Conditional来判断是否需要排除某些自动配置类
	configurations filter = filter(configurations, autoConfigurationMetadata);
	//5. 触发AutoConfiguration导入的相关事件
	fireAutoCOnfigurationImportEvents(configurations, exclusions);
	return new AutofigurationEntry(configurations, exclusions);
}
  • getAutoConfigurationEntry引用的getCandidateConfigurations
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, 
		AnnotationAttributes attributes){
	//通过SpringFactories机制从配置文件Spring.factories中找出所有的自动配置类
	List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
			EnableAutoConfiguration.class, getBeanClassLoader());
	Assert.notEmpty(configurations,"No auto configuration classes found");
	return configurations;
}

SpringFactoriesLoader.loadFactoryNames方法调用loadSpringFactories方法从所有的jar包中读取META-INF/spring.factories文件信息。

	// 参数
 	// Class<?> factoryType需要被加载的工厂类的class
 	// ClassLoader classLoader类加载器
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			// 若没传入类加载器使用该本类的类加载器
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		// class.getName():获取该类的全类限定名字
		String factoryTypeName = factoryType.getName();
		// loadSpringFactories(classLoaderToUse) 返回是Map
		// Map.getOrDefault(A,B): A为Key从Map中获取Value若Value为Null则返回B 当作返回值
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}

loadSpringFactories方法调用ClassLoader.getSystemResources获取META-INF/spring.factories文件

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap result = (MultiValueMap)cache.get(classLoader);
        if(result != null) {
            return result;
        } else {
            try {
                Enumeration ex = classLoader != null?classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result1 = new LinkedMultiValueMap();
 
                while(ex.hasMoreElements()) {
                    URL url = (URL)ex.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();
 
                    while(var6.hasNext()) {
                        Entry entry = (Entry)var6.next();
                        List factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String)entry.getValue()));
                        result1.addAll((String)entry.getKey(), factoryClassNames);
                    }
                }
 
                cache.put(classLoader, result1);
                return result1;
            } catch (IOException var9) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var9);
            }
        }
    }

下面是spring-boot-autoconfigure这个jar中spring.factories文件部分内容选择带有EnableAutoConfiguration自动配置类。

org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
 
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition
 
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\

六、Spring SPI机制与Spring Factories机制做对比

  • 联系Spring Factories自动装配借用了SPI机制SPI机制本身就是一种思想不是特定的技术。
  • 区别如下
    在这里插入图片描述

七、这里是给我自己提个醒

META-IF/spring.factories是在Maven引入的Jar包中每一个Jar都有自己META-IF/spring.factories所以SpringBoot是去每一个Jar包里面寻找META-IF/spring.factories而不是我的项目中存在META-IF/spring.factories当然也可以存在但是我项目的META-IF/spring.factories肯定没有类似以下这些东西
在这里插入图片描述

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Spring