SpringBoot基础自动装配原理

Posted 烟锁迷城

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot基础自动装配原理相关的知识,希望对你有一定的参考价值。

目录

1、SpringBoot项目构建

1.1、官网生成

1.2、IDE在线模板生成

2、常见配置

2.1、入口类和相关注解

2.2、Banner

2.3、常规配置

2.4、日志

2.5、profile环境切换

2.6、静态资源

3、核心原理

3.1、自动装配

1、搭配@Configuration注解使用

2、实现ImportSelector接口

3、实现ImportBeanDefinitionRegistrar接口


SpringBoot是为了简化开发而出现的技术。

1、SpringBoot项目构建

1.1、官网生成

https://start.spring.io/

1.2、IDE在线模板生成

在IDEA等编译工具中,也有对应的生成方法。

2、常见配置

2.1、入口类和相关注解

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

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

}

在SpringBoot的启动类SpringApplication中的run方法里,可以看到,整个run方法返回的类就是ConfigurableApplicationContext,这个类继承了ApplicationContext。

在run方法内,refreshContext(ConfigurableApplicationContext context)方法完成对context的内部构建,启动时,它会执行,最终还是调用refresh()方法,这一点上和Spring框架IOC的初始化没有区别。

@SpringBootApplication注解内容如下,IOC在初始化时一定会加载这个注解。

//使用范围,类
@Target({ElementType.TYPE})
//作用域,运行时生效
@Retention(RetentionPolicy.RUNTIME)
//该注解会被API抽取
@Documented
//可以被继承
@Inherited
//以上四个注解为元注解
//相当于@Configuration
@SpringBootConfiguration
//自动装配
@EnableAutoConfiguration
//读取指定文件
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication

可以看到,这是一个集合注解,其中包含很多注解内容

  1. 使用范围,类,@Target({ElementType.TYPE})
  2. 作用域,运行时生效,@Retention(RetentionPolicy.RUNTIME)
  3. 该注解会被API抽取,@Documented
  4. 可以被继承,@Inherited
  5. @SpringBootConfiguration,其内部代码显示其实这就是一个@Configuration注解
  6. @EnableAutoConfiguration,自动装配
  7. @ComponentScan,扫描文件,目前的默认配置是扫描@SpringBootApplication注解标注下同级的包以及子包

2.2、Banner

在resources文件夹下增加文件“banner.txt”,就可以将启动图案替换为该文件中的图案。

2.3、常规配置

在SpringBoot中提供两种配置文件,applicationContext.properties和applicationContext.yml。这两种文件的作用是一样的,但是语法格式会有所不同。

  1. server.port=8081,将访问端口改为8081
  2. server.servlet.context-path=/spring,设置访问路径,/spring
  3. 如果想要自定义配置属性,可以根据对应语法进行书写,然后使用@Value注解进行获取,如:@Value("${自定义数据}")
  4. 自定义配置属性也可以被加载到一个Bean中,然后托管给IOC。使用注解@ConfigurationProperties,此时自定义属性必须有一个前缀,该注解会将所有带有这个前缀的配置加载进来。@ConfigurationProperties(prefix = "前缀"),前缀后的数据名称需要和bean的数据名称保持一致,不然无法找到

2.4、日志

SpringBoot默认支持Logback,依赖已经被包含进去。

生效需要在配置文件中设置日志文件路径和日志输出级别

logging.file.path=d;/log
logging.level.org.springframework.web=DEBUG

或者直接使用logback.xml进行配置

2.5、profile环境切换

在SpringBoot中进行环境切换是比较简单的,只需要预先配置好对应环境的文件,比如生产环境配置:application-dev.properties,测试环境配置:application-dev.properties,然后在主配置中增加配置:

spring.profiles.active=dev

这样就可以决定引用哪一个配置文件。

2.6、静态资源

在创建SpringBoot项目时,resources文件夹下的static文件夹存放静态文件,template文件夹下存放动态文件。

3、核心原理

3.1、自动装配

@SpringBootApplication注解是一个复合注解,在其中起到自动装配作用的,是@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 {};
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}

除了四个自定义注解必须的元注解,就是@Import注解起到特殊的作用。

@Import注解主要起到注入外部Class的作用,在Spring中,能将Bean导入IOC的方法有很多,比如:

  1. 基于xml配置文件Bean标签
  2. 基于xml配置文件@Component
  3. 基于jiava配置类@Configuration和@Bean
  4. 基于java配置类,@ComponentScan+@Component
  5. FactoryBean接口getObject
  6. @Import注解

@Import注解的使用有三种。

1、搭配@Configuration注解使用

@Configuration
@Import({User.class})
public class JavaConfig {
}

这种写法可以直接将类注入到IOC容器中,但需要注意的是,这种写法是写死在程序中的。

2、实现ImportSelector接口

可以将一个类实现ImportSelector接口的selectImports方法,在这个方法内返回类的名字。使用@Import注解在自定义注解上,引入继承ImportSelector的类,即可将需要的类加入IOC中。

public class MyImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{User.class.getName()};
    }
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({MyImportSelector.class})
public @interface EnableMyDefine {
}
@EnableMyDefine
public class Main {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String name : beanDefinitionNames) {
            System.out.println(name);
        }
    }
}

这种写法就更加灵活

3、实现ImportBeanDefinitionRegistrar接口

可以实现接口ImportBeanDefinitionRegistrar的方法registerBeanDefinitions,在这个方法中,有一个IOC的注册器,只要在其中完成注册,就可以实现加载,其他文件代码和第二种差别不大。

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                        BeanDefinitionRegistry registry) {
        RootBeanDefinition user = new RootBeanDefinition(User.class);
        registry.registerBeanDefinition("user", user);
    }
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({MyImportBeanDefinitionRegistrar.class})
public @interface EnableMyDefine {
}
@EnableMyDefine
public class Main {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String name : beanDefinitionNames) {
            System.out.println(name);
        }
    }
}

了解到这些,就可以看这几个@Import注解的作用了

在注解@EnableAutoConfiguration中,可以看到对于@Import注解中引入的AutoConfigurationImportSelector根据命名来看就是实现了ImportSelector接口,那么可以看一下代码

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

直接看selectImport方法,在正常流程下,会执行getAutoConfigurationEntry中。List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);这句代码将加载特定的配置文件spring.factories。路径为:/org/springframework/boot/spring-boot-autoconfigure/2.5.5/spring-boot-autoconfigure-2.5.5

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(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.getConfigurationClassFilter().filter(configurations);
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
    }
}

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

这个位于org.springframework对应jar的配置文件spring.factories决定到底加载那些类。

 后面的代码主要是为了对这些类做出一些过滤,最后把需要装配的类返回去。

阶段总结:实现ImportSelector接口的类可以根据springboot的jar包自动装配配置文件对指定的类进行加载,最后注入到IOC中,但是因为读取配置文件的原因,只能将第一方的类加载进去,第三方的无法被感知到,所以第三方jar也会带有一个一样的spring.factories文件来告诉IOC需要加载的类的路径。

那么既然自动装配装配了这么多类,有一些类其实并没有被依赖,那么没有被依赖的类是怎么过滤掉的呢?这就需要条件进行判断了。

在spring.factories同级目录下有一个文件spring-autoconfigure-metadata.properties,其内容大致为:

可以看到,里面有一些带有condition的配置,这是条件加载,只有在满足某些条件时才会将其装配到IOC中。 

于是整个自动装配原理为:

  1. 在SpringBoot项目启动时,会加载@SpringBootApplication注解。
  2. 在@SpringBootApplication注解内,有@EnableAutoConfiguration注解
  3. 在@EnableAutoConfiguration注解中,有@Import注解
  4. @Import注解实现了ImportSelector接口的selectImports方法
  5. 加载META-INF/spring-autoconfigure-metadata.properties文件,根据其条件过滤需要加载的文件内容。
  6. 加载META-INF/spring.factories文件,根据配置类路径加载需要装配的类。
  7. 根据名称,配置文件等进行加载过滤,返回需要装配的类名,完成自动装配

以上是关于SpringBoot基础自动装配原理的主要内容,如果未能解决你的问题,请参考以下文章

程序员必备技能之SpringBoot的自动装配原理,很详细,建议收藏!!!

SpringBoot自动装配原理详细讲解(清楚 明白)

SpringBoot自动装配原理详细讲解(清楚 明白)

spring boot自动装配原理

SpringBoot学习探究Springboot自动装配

SpringBoot自动装配原理