SpringBoot自动装配原理 通过Condition、enable、import等注解实现自动装配
一、Condition 实现选择性创建Bean操作
通过坐标是否存在从而创建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(); } }
将类的判断变为动态的判断
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(); } }
ConditionalOnProperty 值存在才会创建BeanUserConfig 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注解,其扫描的范围只有当前引导类所在的包及其子包。
使用@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); } }
可以使用@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); } }
可以对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的四种用法
导入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); } }
导入配置类
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); } }
导入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); } }
导入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文件
其中比如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才会被加载