Spring Boot使用了多种方式来实现自动配置,其中DeferredImportSelector
接口是这些机制之一。
DeferredImportSelector
是ImportSelector
的一个扩展,它允许延迟导入配置类直到所有@Configuration
类都被处理完毕。这对于某些自动配置类需要在应用程序上下文的创建过程中的后期阶段才能做出决定的场景很有用。
为什么这么说呢?因为springboot导入的自动装配类大多都有@Conditional
注解来判断这个装配类所依赖的类在当前路径下存不存在,@Conditional有很多种,有的只需要判断当前项目路径下有没有该类(比如:@ConditionalOnClass
),有的需要判断当前spring容器中有没有该类的Bean比如:(@ConditionalOnBean
)。
所以,当情况为后者时,使用@Conditional
注解时必须确保我们导入的Bean已经存在了。所以就会延迟导入。那些加在启动类上的@EnableXXX注解就是这样的,在注解内部会用@Import导入一个标记类的Bean,然后再在自动装配类上@ConditionalOnBean注解判断有没有该Bean。这样就可以用@EnableXXX注解来控制是否导入自动装配类
下面我们分别举例:
举例1、
我举一个第一种情况的例子,假如我们项目中要用到Mybatis,那我们要导入mybatis
的依赖和mybatis-spring-boot
的依赖。当然,这两个依赖被springboot整合了
在mybatis-springboot的依赖中就会有一个自动装配类:MybatisAutoConfiguration
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
的意思就是当类路径下存在SqlSessionFactory
和SqlSessionFactoryBean
这两个类时,配置才会被激活,相应的Bean才会被创建。只要存在即可,不需要让他成为容器的Bean。因为这两个类都是mybatis包下的,我们总不能在这两个类上加一个@Component注解吧!所以只需要判断存在即可。所以只要我们导入了mybatis的依赖,这个判断就会生效。
举例2
这个例子就是我们自定义starter
新增一个标记类ConfigMarker
public class ConfigMarker{}
新增@EnableRegisterServer
注解,利用@Import注解导入ConfigMarker到Bean容器
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({ConfigMarker.class}) //将ConfigMarker添加到容器中
public @interface EnableRegisterServer{}
改造HttpAutoConfiguration
,新增条件注解@ConditionalOnBean(Marker.class)
@Configuration
@EnableConfigurationProperties(HttpProperties.class)
@ConditionalOnBean(Marker.class) //只有当Marker类的Bean在容器中存在时,当前自动配置类才会生效
public class HttpAutoConfiguration {@Resourceprivate HttpProperties properties; // 使用配置// 在Spring上下文中创建一个对象@Beanpublic HttpClient init() {HttpClient client = new HttpClient();String url = properties.getUrl();client.setUrl(url);return client;}}
测试一下,在另外一个springboot项目中导入当前工程starter,在主启动类上添加注解@EnableRegisterServer
大功告成。
如果不在主启动类上添加@EnableRegisterServer
注解,当前的标注类Marker
就不会被导入。就不会满足@ConditionalOnBean(Marker.class)
注解的条件,HttpAutoConfiguration
自动配置类也就不会被导入了。
这时候再使用:
@Resourceprivate HttpClient httpClient;
程序就会报错,因为spring容器中根本就不存在这个这个Bean。
所以,就通过@EnableRegisterServer
注解给自动装配提供了一个开关。