springboot:自动装配原理和相关的底层注解

112 阅读6分钟

1.前置知识介绍:一些相关注解

1.1 @Configuration详解

1.1.1 基本使用

-   Full模式与Lite模式
-   示例  
/**
 * 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
 * 2、配置类本身也是组件,也会被加入到容器中去
 * 3、proxyBeanMethods:是否通过spring代理bean的方法,(cglib增强)
 
 * Full(proxyBeanMethods = true):
 (使用到bean时都会到容器中去检查,保证每个@Bean方法被调用多少次返回的组件都是单实例的)(默认)
 * Lite(proxyBeanMethods = false):
 (每个@Bean方法被调用多少次返回的组件都是新创建的,轻量级启动,用在没有组件依赖的情况)
 */
 
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {

    /**
     * Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
     * @return
     */
    @Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        //user组件依赖了Pet组件
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

    @Bean("tom")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}

1.1.2 最佳实战

  • 最佳实战

    • 配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
    • 配置 类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式(默认)

lite :英 [laɪt] 美 [laɪt] adj. 低热量的,清淡的(light的一种拼写方法);类似…的劣质品

1.2 @Import 导入组件

@Bean、@Component、@Controller、@Service、@Repository,它们是Spring的基本标签,在Spring Boot中并未改变它们原来的功能。

@Import({User.class, DBHelper.class})给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名

测试代码:

@Import({User.class, DBHelper.class})
//告诉SpringBoot这是一个配置类==配置文件
@Configuration(proxyBeanMethods = false)
public class MyConfig {
}
//1、返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

//...

//5、获取组件
String[] beanNamesForType = run.getBeanNamesForType(User.class);

for (String s : beanNamesForType) {
    System.out.println(s);
}

DBHelper bean1 = run.getBean(DBHelper.class);
System.out.println(bean1);

1.3 @Conditional 条件装配

条件装配:满足Conditional指定的条件,则进行组件注入

截屏2023-02-16 16.46.27.png
用@ConditionalOnMissingBean举例说明

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = "tom")//没有tom名字的Bean时,MyConfig类的Bean才能生效。
public class MyConfig {

    @Bean
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

    @Bean("tom22")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}

public static void main(String[] args) {
    //1、返回我们IOC容器
    ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

    //2、查看容器里面的组件
    String[] names = run.getBeanDefinitionNames();
    for (String name : names) {
        System.out.println(name);
    }

    boolean tom = run.containsBean("tom");
    System.out.println("容器中Tom组件:"+tom);//false

    boolean user01 = run.containsBean("user01");
    System.out.println("容器中user01组件:"+user01);//true

    boolean tom22 = run.containsBean("tom22");
    System.out.println("容器中tom22组件:"+tom22);//true

}

1.4 @ImportResource导入Spring配置文件

比如,公司使用bean.xml文件生成配置bean,然而你为了省事,想继续复用bean.xml,@ImportResource粉墨登场。 使用方法:

@ImportResource("classpath:beans.xml")
public class MyConfig {
...
}

1.5 @ConfigurationProperties的配置绑定

如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用
传统方法:

public class getProperties {
     public static void main(String[] args) throws FileNotFoundException, IOException {
         Properties pps = new Properties();
         pps.load(new FileInputStream("a.properties"));
         Enumeration enum1 = pps.propertyNames();//得到配置文件的名字
         while(enum1.hasMoreElements()) {
             String strKey = (String) enum1.nextElement();
             String strValue = pps.getProperty(strKey);
             System.out.println(strKey + "=" + strValue);
             //封装到JavaBean。
         }
     }
 }

1.5.1 @ConfigurationProperties + @Component

@ConfigurationProperties + @Component

假设有配置文件application.properties

mycar.brand=BYD
mycar.price=100000

只有在容器中的组件,才会拥有SpringBoot提供的强大功能

@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
...
}

1.5.2 @EnableConfigurationProperties + @ConfigurationProperties

springboot中的xxxautoconfiguration类就是通过这种方法将xxxproperties 注册到容器,然后通过xxxproperties 进行配置

@EnableConfigurationProperties

  1. 开启Car配置绑定功能
  2. 把这个Car这个组件自动注册到容器中
@EnableConfigurationProperties(Car.class)
public class MyConfig {
...
}
@ConfigurationProperties(prefix = "mycar")
public class Car {
...
}

2. 自动配置【源码分析】

Spring Boot应用的启动类:
@SpringBootApplication
public class MainApplication {

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

}

分析下@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 {
    ...
}

重点分析@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan

2.1 @SpringBootConfiguration

@Configuration代表当前是一个配置类。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

2.2 @ComponentScan

指定扫描哪些Spring注解。

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

重点分析@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)

2.3.1 @AutoConfigurationPackage

标签名直译为:自动配置包,指定了默认的包规则。
springboot如何把主启动类包下所有组件自动装入的原理

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)//给容器中导入一个组件
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

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

利用Registrar给容器中导入一系列组件
将指定的一个包下的所有组件导入进容器(MainApplication所在包下。

截屏2023-02-16 17.02.25.png 获取当前注解元数据的packagename:即注解所在的包名
这就是springboot如何把主启动类包下所有组件自动装入的原理

2.3.2 @Import(AutoConfigurationImportSelector.class)

装配流程:(方法调用从外到内

  1. 利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件

  2. 调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类

  3. 利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件

  4. META-INF/spring.factories位置来加载一个文件。

    • 默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件

    • spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories

2.3.2 按需装配

# 文件里面写死了spring-boot一启动就要给容器中加载的所有配置类
# spring-boot-autoconfigure-2.3.4.RELEASE.jar/META-INF/spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
...

虽然我们127个场景的所有自动配置启动的时候默认全部加载,但是xxxxAutoConfiguration按照条件装配规则(@Conditional),最终会按需配置。

AopAutoConfiguration类:

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnProperty(
    prefix = "spring.aop",
    name = "auto",
    havingValue = "true",
    matchIfMissing = true//默认为TRUE说明默认会把aop装载进容器
)
public class AopAutoConfiguration {
  
@Configuration(proxyBeanMethods = false)
//没有引入spring-aop包所以无法加载advice类,默认使用接口+实现类存在才能生效的jdk动态代理aop
@ConditionalOnClass(Advice.class)
static class AspectJAutoProxyingConfiguration {

   @Configuration(proxyBeanMethods = false)
   @EnableAspectJAutoProxy(proxyTargetClass = false)
   @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
         matchIfMissing = false)
   static class JdkDynamicAutoProxyConfiguration {

   }

   @Configuration(proxyBeanMethods = false)
   @EnableAspectJAutoProxy(proxyTargetClass = true)
   @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
         matchIfMissing = true)
   static class CglibAutoProxyConfiguration {

   }

}

3. 装配流程总结

    1. SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
    1. 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值:xxxautoconfiguration会从xxxxProperties里面读取配置的值

springboot中的xxxautoconfiguration类就是通过@EnableConfigurationProperties注解 将xxxproperties 注册到容器,然后通过xxxproperties 进行配置


@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
}

,xxxProperties和配置文件进行了绑定

@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
    1. 生效的配置类就会给容器中装配很多组件
    1. 只要容器中有这些组件,相当于这些功能就有了
    1. 定制化配置

    • 用户直接自己@Bean替换底层的组件
    • 用户去看这个组件是获取的配置文件什么值就去修改(常用)

自动扫描xxxxAutoConfiguration ---> 按需注入组件 ---> xxxxProperties里面拿值 ----> 值通过application.properties修改