0%

springboot的Spi机制

springboot的Spi机制

​ 在与Springboot打交道的过程中,应该大家对starter都不会陌生,springboot中的各种starter其实就是利用了springbootSPI机制。

​ 在springboot中,会默认扫描Applicatoin启用类及其子包里面的配置类Bean(比如标记@Configuration@Component@Server等)然后进行初始化,那么如果你是为别人提供二方包,三方包的库,如果你的需要加载的类路径跟他的不一样,那么根据Springbootbean加载机制是不会加载到的。

主工程:

1
2
3
4
├── src
│ └── main
│ └── cn.com.xiaocainiaoya
│ ├── Application.java

二方库A:

1
2
3
4
├── src
│ └── main
│ └── cn.com.xiaocainiaoya
│ ├── MybatisXmlReloadConfig.java

二方库B:

1
2
3
4
├── src
│ └── main
│ └── cn.xiaocainiaoya
│ ├── MybatisXmlReloadConfig.java

这时只有二方库A可以正确加载MybatisXmlReloadConfig这个bean,因为它的路径在主工程Application的包以及子包下,那么就出现所有的二方库、三方库都需要与主工程的类路径一致?

这里Springboot就提供了Spi的机制用来获取二方库,三方库中需要加载的Bean对象。

注意:一定要确保二方库、三方库的类路径不在主工程Application下的情况,在考虑SPI逻辑,我之前就掉入一个陷阱,因为我的二方库的类路径与主工程一致,但是那个时候我的spring.factories指向的路径是错的,导致一度怀疑自己理解的SPI有出入。

二方库、三方库类路径与主工程启动类路径不一致的情况下!!!(再次强调!)如果使得主工程可以加载到对应库中的bean对象。

  1. 创建META-INF/spring.factories文件。这个文件中是k-v的结构,一个key对应多个逗号分隔的value。对于这个文件来说,可以配置的key有很多,比如有EnableAutoConfigurationApplicationContextInitializerApplicationListener

    1
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.xiaocainiaoya.MyImportTest
  2. 在主工程启动时,会加载META-INF/spring.factories文件,对这个文件内指向的bean对象进行逐个加载。

主要源码在AutoConfigurationImportSelector#getCandidateConfigurations中。

1
2
3
4
5
6
7
8
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
return configurations;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}

注意:在Springboot2.7之后引入了新的SPI配置方式。

  1. 创建META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件。这个文件的文件名是固定,文件内容是value,各个value之间通过回车分隔。

    1
    com.bosssoft.xiaocainiaoya.MyImportTest

主要源码同样是在AutoConfigurationImportSelector#getCandidateConfigurations中进行了兼容。

1
2
3
4
5
6
7
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
// 兼容
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
return configurations;
}

@AutoConfiguration

​ 在Springboot中可以直接使用@AutoConfiguration来启用SPI机制,这是一个组合注解,通过@AliasFor注解将值传递给@Configuration@AutoConfigureBefore@AutoConfigureAfter三个注解。

注:使用这个注解需要配合META-INF/spring中设置org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中配置全限定类名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore
@AutoConfigureAfter
public @interface AutoConfiguration {

@AliasFor(annotation = Configuration.class)
String value() default "";

@AliasFor(annotation = AutoConfigureBefore.class, attribute = "value")
Class<?>[] before() default {};

@AliasFor(annotation = AutoConfigureBefore.class, attribute = "name")
String[] beforeName() default {};

@AliasFor(annotation = AutoConfigureAfter.class, attribute = "value")
Class<?>[] after() default {};

@AliasFor(annotation = AutoConfigureAfter.class, attribute = "name")
String[] afterName() default {};

}

org.springframework.boot.autoconfigure.AutoConfiguration.imports

1
cn.com.xiaocainiaoya.config.MyAutoConfiguration

注:我之前进入一个误区,以为组合注解标记在某个类上就表明这个类拥有组合注解中的相关能力,其实这是错误的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* AutoConfiguration测试
*
* @author xiaocainiao
* @date 2023/5/10 10:11:00
*/
@AutoConfiguration()
//@Configuration
public class MyAutoConfiguration {

@Bean
public House house() {
return new House();
}
}

​ 这里我创建了类MyAutoConfiguration,标记了@AutoConfiguration注解,虽然这个注解是组合注解,也就是说实际上这个类也被@AutoConfiguration注解中的@Configuration标记,但是启动之后发现,在没有写META-INF/spring文件的情况下是不会加载,所以这里要解释一下,注解仅仅是标记了某个类,注解要起到什么作用取决于获取这个注解的动作的代码逻辑是如何。换句话说,标记了@AutoConfiguration实际上是可以具备@Configuration能力(不写META-INF/spring文件就可以被加载到容器中),但是获取这个注解的处理端可以选择不赋予这种能力。感觉还是没有说情况,文字表述不能很清楚的表达,上代码!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 当扫描到MyAutoConfiguration类需要进行容器化处理时肯定是需要进入某个处理器来进行处理
// 通过类对象来获取到对应标记的注解(这时其实只获取到@AutoConfiguration)
Annotation[] annotations = MyAutoConfiguration.class.getAnnotations();
// 1.如果我的处理代码,仅仅是判断这个annotations中存不存在@Configuration来进行容器化,那么这个类就不会进行容器化。
// 2.如果我的处理代码,是如下代码,获取到当前类上的注解之后,逐个获取对应的组合注解中是否存在Configuration注解,再进行对应的处理,那么这个注解就会具备@Configuration的能力
// 所以在使用组合注解的情况下,主要是看处理注解的地方的判断方式,而不是组合注解上是否存在某注解!!
Annotation[] annotations = MyAutoConfiguration.class.getAnnotations();
for(Annotation annotation : annotations){
Configuration configuration = annotation.annotationType().getAnnotation(Configuration.class);
if(ObjectUtils.isNotEmpty(configuration)){
break;
}
}
// hutool中有提供方法可以快速获取组合注解中的注解
Configuration[] candidates = AnnotationUtil.getCombinationAnnotations(field, Configuration.class);
-------------本文结束感谢您的阅读-------------