TrueOctopus
2023-03-13

SpringBoot自动装配

SpringBoot自动装配原理

通过Condition、enable、import等注解实现自动装配

一、Condition

实现选择性创建Bean操作

  1. 通过坐标是否存在从而创建Bean

ClassCondition:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ClassCondition implements Condition {

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

//1.需求: 导入Jedis坐标后创建Bean
//思路:判断redis.clients.jedis.Jedis.class文件是否存在
boolean flag = true;
try {
Class<?> cls = Class.forName("redis.clients.jedis.Jedis");
} catch (ClassNotFoundException e) {
flag = false;
}
return flag;

}
}

UserConfig:

1
2
3
4
5
6
7
8
9
10
@Configuration
public class UserConfig {

@Bean
@Conditional(ClassCondition.class)
public User user(){
return new User();
}

}
  1. 将类的判断变为动态的判断

ClassCondition:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ClassCondition implements Condition {
/**
* @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象
* @param metadata 注解元对象。 可以用于获取注解定义的属性值
* @return
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

//2.需求: 导入通过注解属性值value指定坐标后创建Bean

//获取注解属性值 value
Map<String, Object> map = metadata.getAnnotationAttributes(ConditionOnClass.class.getName());
String[] value = (String[]) map.get("value");

boolean flag = true;
try {
for (String className : value) {
Class<?> cls = Class.forName(className);
}
} catch (ClassNotFoundException e) {
flag = false;
}
return flag;

}
}

ConditionOnClass

1
2
3
4
5
6
7
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ClassCondition.class)
public @interface ConditionOnClass {
String[] value();
}

UserConfig

1
2
3
4
5
6
7
8
9
10
@Configuration
public class UserConfig {

@Bean
@@ConditionOnClass("redis.clients.jedis.Jedis")
public User user(){
return new User();
}

}
  1. ConditionalOnProperty 值存在才会创建Bean
    UserConfig
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Configuration
    public class UserConfig {

    @@Bean
    @ConditionalOnProperty(name = "test",havingValue = "111")
    public User user(){
    return new User();
    }

    }
    SpringBoot自动配置中存在各类不同触发条件下的ConditionalOn*

二、Enable

动态启用某些功能,底层使用@Import注解导入配置类,实现Bean的动态加载。


SpringBoot不能直接获取第三方工程的Bean,原因是在@SpringBootApplication中存在@ComponentScan注解,其扫描的范围只有当前引导类所在的包及其子包。

  1. 使用@ComponentScan添加对应的第三方包名(不常用)
1
2
3
4
5
6
7
8
@SpringBootApplication
@ComponentScan("com.demo.config")
public class SpringbootEnableApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootEnableApplication.class, args);
}
}
  1. 可以使用@Import注解加载类,这些类都会被Spring创建,并放入IOC容器(不常用)
1
2
3
4
5
6
7
8
@SpringBootApplication
@Import(Userconfig.class)
public class SpringbootEnableApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootEnableApplication.class, args);
}
}
  1. 可以对Import注解进行封装

EnableUser

1
2
3
4
5
6
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(UserConfig.class)
public @interface EnableUser {
}

Application

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableUser
public class SpringbootEnableApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootEnableApplication.class, args);
}
}

三、@Import注解

@Import的四种用法

  1. 导入Bean
1
2
3
4
5
6
7
8
@SpringBootApplication
@Import(User.class)
public class SpringbootEnableApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootEnableApplication.class, args);
}
}
  1. 导入配置类

UserConfig 可不加@Configuration

1
2
3
4
5
6
7
8
9
10
11
12
public class UserConfig {

@Bean
public User user() {
return new User();
}

@Bean
public Role role() {
return new Role();
}
}

Application

1
2
3
4
5
6
7
8
@SpringBootApplication
@Import(UserConfig.class)
public class SpringbootEnableApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootEnableApplication.class, args);
}
}
  1. 导入ImportSelector的实现类

ImportSelector

1
2
3
4
5
6
7
8
9
10
11
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.demo.domain.User", "com.demo.domain.Role"};
// 可以写在配置文件里
}
}

Application

1
2
3
4
5
6
7
8
@SpringBootApplication
@Import(MyImportSelector.class)
public class SpringbootEnableApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootEnableApplication.class, args);
}
}
  1. 导入ImportBeanDefinitionRegistrar实现类

MyImportBeanDefinitionRegistrar

1
2
3
4
5
6
7
8
9
10
11
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
registry.registerBeanDefinition("user", beanDefinition);


}
}

Application

1
2
3
4
5
6
7
8
@SpringBootApplication
@Import({MyImportBeanDefinitionRegistrar.class})
public class SpringbootEnableApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootEnableApplication.class, args);
}
}

四、@EnableAutoConfiguration注解

在@SpringBootApplication中有注解@EnableAutoConfiguration,其中有@Import({AutoConfigurationImportSelector.class}),使用了上一节中的第三种方式

EnableAutoConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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

1
2
3
4
5
6
7
8
9
10
11
...
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
...

其中getAutoConfigurationEntry中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
}

其中getCandidateConfigurations中,通过SpringFactoriesLoader加载,加载内容在META-INF/spring.factories,没有则会报错

1
2
3
4
5
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}

比如下图所示一个spring.factories文件

fPMfa.png

其中比如RedisAutoConfiguration,通过ConditionalOnClass等条件注解控制相关Bean的加载

1
2
3
4
5
6
7
@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
...
}

  • @EnableAutoConfiguration注解内部使用@Import({AutoConfigurationImportSelector.class})加载配置类
  • 其配置文件位于META-INF/spring.factories中,当SpringBoot启动时会自动加载
  • 但是并不是所有的Bean都会被初始化,只有满足Condition条件的Bean才会被加载