springboot如何实现自动装配?

文章详细解释了SpringBoot中@SpringBootApplication注解的作用,它是@Configuration、@EnableAutoConfiguration和@ComponentScan的集合。同时介绍了自动配置的原理,包括如何通过spring.factories文件管理和加载配置类,以及@ConditionalOnXXX注解在控制配置加载中的应用。
摘要由CSDN通过智能技术生成

核心注解:@SpringBootApplication

springboot的入口是一个application类新建的springboot工程自带一个application类,这个类上一般标注有@SpringBootApplication注解,相信@SpringBootApplication这个注解大家或多或少都接触过。下面是@SpringBootApplication注解的部分内容:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {...}

大概可以把 @SpringBootApplication看作是 @Configuration、@EnableAutoConfiguration、@ComponentScan 注解的集合。

那么来看看这几个注解什么作用:

@Configuration:

@Configuration允许在上下文中注册额外的 bean 或导入其他配置类,这也是我们可以在启动类(Application类)注入我们想要让IOC容器管理的类(@Bean的方式)的原因。

@Configuration 注解的类可以看作是能生产让Spring IoC容器管理的Bean实例的工厂。

@Bean 注解的方法返回的对象可以被注册到spring容器中。

@ComponentScan:

扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean。如上面片段所示,容器中将排除TypeExcludeFilter和AutoConfigurationExcludeFilter。

@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 {};
}

我们可以看到里面通过@Import({AutoConfigurationImportSelector.class})引入了AutoConfigurationImportSelector类,我们进入这个类

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,ResourceLoaderAware, 
BeanFactoryAware, EnvironmentAware, Ordered {
    //成员变量

    /*调用链selectImports->AutoConfigurationEntry->getCandidateConfigurations*/
    
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            //!!!!!
            AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            // ...
            //!!!!!
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            // ...
        }
    }

    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;
    }
    // 其他方法
}

可以看到AutoConfigurationImportSelector类中存在调用链selectImports->AutoConfigurationEntry->getCandidateConfigurations,而getCandidateConfigurations方法中调用了SpringFactoriesLoader里的loadFactoryNames方法。

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }

        String factoryTypeName = factoryType.getName();
        //!!!!!
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    Map<String, List<String>> result = (Map)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        Map<String, List<String>> result = new HashMap();

        try {
            //!!!!!
            Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
            //...
        }

在loadFactoryNames中loadFactoryNames紧接着会调用类中另一个叫做loadSpringFactories的方法,绕了一大圈后我们可以发现,loadFactoryNames这个静态方法可以读取所有的jar包中META-INF/spring.factories文件,而自动配置的类就在这个文件中进行配置。

在springboot的starter的jar包中就可以找到对应的spring.factories文件

内容如下:

同样的,如果你的项目使用了mybatis,那么我们可以在依赖库中找到mybatis的starter的jar包,然后找到spring.factories文件

这个文件里面的内容如下:

# Auto Configure
# pattrtn: key=value1,value2...
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

spring.factories文件是什么,有什么作用?

结合上述两个文件,spring.factories其实就是个键值对格式的配置文件

作用:spring.factories用键值对的方式记录了所有需要加入容器的类。

springboot自动装配流程

springboot通过@Import({AutoConfigurationImportSelector.class})引入了AutoConfigurationImportSelector,EnableAutoConfigurationImportSelector的selectImports方法返回的类名,来自所有 Spring Boot Starter的 spring.factories文件内的配置信息。

这些key等于EnableAutoConfiguration的配置信息,由于spring boot应用启动时使用了EnableAutoConfiguration注解,所以EnableAutoConfiguration注解通过import注解将EnableAutoConfigurationImportSelector类实例化,并且将其selectImports方法返回的类名实例化后注册到spring容器。

注意:如果你想要实现自己的自动配置(自定义spring boot starter),就将你的类通过键值对的方式写在你的META-INF/spring.factories即可,value是你的自动配置类,key必须是org.springframework.boot.autoconfigure.EnableAutoConfiguration。

讲到这里,在额外延伸出一个问题:

spring.factories中这么多配置,每次启动都要全部加载么?

答案是:不会全部加载。

这里就不得不提到@ConditionalOnXXX 注解了,我们以mybatis为例子,下面仍然是其spring.factories文件

# Auto Configure
# pattrtn: key=value1,value2...
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

我们进入EnableAutoConfiguration里面随便一个类,比如MybatisLanguageDriverAutoConfiguration类

可以看到类被声明了一个@ConditionalOnClass({LanguageDriver.class})的注解,这个注解表示当前配置类只有在classpath中存在LanguageDriver类的情况下才会生效。这意味着如果项目依赖中不包含LanguageDriver的实现,那么此配置将被忽略。这一步进行了一遍筛选,@ConditionalOnXXX 中的所有条件都满足,该类才会生效。

Springboot 提供了其他的条件注解:

  • @ConditionalOnBean:当容器里有指定 Bean 的条件下
  • @ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下
  • @ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean
  • @ConditionalOnClass:当类路径下有指定类的条件下
  • @ConditionalOnMissingClass:当类路径下没有指定类的条件下
  • @ConditionalOnProperty:指定的属性是否有指定的值
  • @ConditionalOnResource:类路径是否有指定的值
  • @ConditionalOnExpression:基于 SpEL 表达式作为判断条件
  • @ConditionalOnJava:基于 Java 版本作为判断条件
  • @ConditionalOnJndi:在 JNDI 存在的条件下差在指定的位置
  • @ConditionalOnNotWebApplication:当前项目不是 Web 项目的条件下
  • @ConditionalOnWebApplication:当前项目是 Web 项 目的条件下

通过这些条件注解,在自定义starter时可以按需加载配置类。

  • 23
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值