springboot自动装配

SpringBoot的自动装配简化了配置,提供了快速构建Spring项目的能力。本文深入探讨了自动装配的原理,包括启动器、主程序的@SpringBootApplication注解、配置文件的约定、条件装配等,并详细解析了自动装配过程中@import、配置文件配合注解以及spring.factories的角色。
摘要由CSDN通过智能技术生成

SpringBoot 自动装配

SpringBoot 项目省略了许多配置文件的编写,目的是让开发者可以快速构建一个Spring项目,简化了Spring项目的搭建和开发的配置过程,从而使开发者不用再重复的编写重复的配置代码。

  • 不再由开发者编写大多数配置文件意味着部分灵活性的丢失,但是减少了开发者需要做出决定的数量
1、开箱即用
1.1、pom.xml

再pom.xml文件中我们发现当前项目有一个父工程

<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>2.4.5</version>
   <relativePath/> <!-- lookup parent from repository -->
</parent>

当我们进去之后可以看见这个父工程中做了许多配置安装了许多插件之类,而且这个工程还有一个父工程 spring-boot-dependencies

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.4.5</version>
</parent>

这个工程管理着SpringBoot项目的核心依赖。我们不需要手动的去添加依赖因为大部分的依赖已经在父项目存在并且不需要去确认它的版本和兼容性问题这些SpringBoot已经帮我们做好了

2、启动器

spring-boot-starter-..

像这样的我们依赖我们把它称为启动器,它对应着一套相关的依赖,是SpringBoot的启动场景(项目所需要的场景)如果我们使用SpringBoot是在web项目使用

我们只需要导入web启动器就行了,会自动引入所有web需要的所有依赖。

  • SpringBoot会将相关的依赖集成成为一个个的启动器,当我们需要在不同场景使用时,添加相关的启动器就好了
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>
3、主程序

当我们新建一个SpringBoot项目在项目下会存在一个跟项目名称对应的xxxAplication.class

@SpringBootApplication
public class xxxApplication {
   public static void main(String[] args) {
      SpringApplication.run(HellospringbootApplication.class, args);
   }
}

这个类是SpringBoot自动装配实现的核心,代码很简洁,因为功能的实现都丢在注解里了

3.1、@SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan

除去四个元注解

@SpringBootConfiguration

  • @AliasFor(
        annotation = Configuration.class
    )
    

    看到这我在想它是不是@Configuration起了别名 大概就是标记某个类是SpringBoot配置类

@EnableAutoConfiguration

里面有两个注解

@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})

@Import({AutoConfigurationImportSelector.class})

导入了AutoConfigurationImportSelector.class到Ioc容器中

AutoConfigurationImportSelector.class中的方法getAutoConfigurationEntry可以让我们获取所有的配置

在这里插入图片描述

getAutoConfigurationEntry会传入this.getSpringFactoriesLoaderFactoryClass()

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

会导入使用了@EnableAutoConfiguration类同级包所需要的所有资源

3.2、是否加载全部的配置

如果如果我们不是web项目而是否有必要去加载web项目所需要的配置呢?当然不需要我们都很清楚加载不需要的东西是资源的浪费。

我们在 依赖包 spring-boot-autoconfigure.jar下MTE/INFO/spring.factories 任意点开一个配置类

@ConditionalXXX

  • 该类注解所有条件被满足该配置才会被加载
  • 我们添加启动器 是不是就是在满足 Conditional注解的条件 达到开箱即用
2、约定大于配置

为了让结构不混乱,我们需要约定,SpringBoot中有一些东西是大家都需要遵守的,application.ymal文件的位置、项目启动时扫描的Bean…这些都是我们的约定

2.1、springboot配置文件的路径

  • 项目根目录下的config文件夹中

  • 项目根目录下

  • 类路径下的config文件夹中

  • 类路径下

2.2、springboot项目启动时扫描bean的范围

  • 由自动装配分析可知
  • @EnableAutoConfiguration 注解标记的类所在的包及其同级包

深入理解自动装配

1.spring配置演进
  • spring2.5 *.xml

  • spring3.0 java配置类

  • springboot 注解自动装配

使用java类配合注解完成项目的配置

//取代mybatis-config.xml 文件


@Configuration
public class MybatisConfig {

    <bean id="JdbcDataSource">
//    <dataSource type="POOLED">
//                <property name="username"value="${jdbc.username}"/>
//                <property name="password"value="${jdbc.password}"/>
//     </dataSource>
    </bean/>
        
//    使用配置类取代配置文件
    @Bean
    public DataSource JdbcDataSource() {
        return JdbcDataSource();
    }
//    我们可以在这里写很多bean 把配置文件中的一一取代
//    ............
//    例子可能不对 就是返回bean 而不同xml <bean></bean> 标签去定义
//    方法名需要和 bean 的ID对应
}
  • AnnotationConfigApplicationContext
public static void main(String[] args) {
    //传入配置类 
    AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(MybatisConfig.class);
    context.getBean("s");
    //同 ClassPathApplicationContext 功能一致
}
2.BeanDefinition
  • AbstractBeanDefinition

    • 存储着可以完全描述一个bean的信息(这里只列出部分)

    • public static final String SCOPE_DEFAULT = "";
      public static final int AUTOWIRE_NO = 0;
      public static final int AUTOWIRE_BY_NAME = 1;
      public static final int AUTOWIRE_BY_TYPE = 2;
      public static final int AUTOWIRE_CONSTRUCTOR = 3;
      /** @deprecated */
      //........
      //有 scope class ....等
        @Nullable
          private String scope;
      
       @Nullable
          private volatile Object beanClass;
      
  • BeanFactoryPostProcessor

    • bean工厂后置处理器 (在BeanDefinition 被预实例化之前 可以对beanDefinition 做出修改 )

    • @FunctionalInterface
      public interface BeanFactoryPostProcessor {
          void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException;
      }
      //我们可以自定义自己的 完成一些功能
      
  • BeanPostProcessor

    • 后置处理器 (能够让我们在bean 被实例化之后对bean 做出修改)

    • public interface BeanPostProcessor {
          @Nullable
          default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
              return bean;
          }
          //应该是预实例之后
      
          @Nullable
          default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
              return bean;
          }
          //见名知意
      }
      
3.通过注解标注bean的装载过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MMVz3CWi-1624531396316)(C:\Users\Xingcl\AppData\Roaming\Typora\typora-user-images\image-20210624142235201.png)]

4.自动装配
4.1、@import
  • 导入普通组件(类)把该组件装入到容器中

    • @Import({ExampleBeanA.class})
      @Configuration
      public class ExampleConfig {
          public ExampleConfig() {
              System.out.println("Example1 被装载");
          }
      }
      
  • 导入Bean定义注册器 (实现了ImportBeanDefinitionRegister接口的组件)

ImportBeanDefinitionRegister

public interface ImportBeanDefinitionRegistrar {
    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
        this.registerBeanDefinitions(importingClassMetadata, registry);
    }

    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    }
}
  • 自定义Bean定义注册器

    • public class MyBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
      //    @Override
      //    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
      //        System.out.println("----------------");
      //    }
      
          @Override
          public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
              RootBeanDefinition rootBeanDefinition=new RootBeanDefinition(ExampleBeanB.class);
              //一种beanDefinition
              registry.registerBeanDefinition("ExampleBeanB",rootBeanDefinition);
          }
      }
      
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lF73be6P-1624531396319)(C:\Users\Xingcl\AppData\Roaming\Typora\typora-user-images\image-20210624144900799.png)]

  • 导入实现了ImportSelector的组件

    • public class MyImportSelector implements ImportSelector {
          @Override
          public Predicate<String> getExclusionFilter() {
              return null;
          }
      
          @Override
          public String[] selectImports(AnnotationMetadata annotationMetadata) {
              //返回需要注册BeanDefinition 的全限定类名 可以一次导入多个
              return new String[]{"com.lxc.hellospringboot.pojo.ExampleBeanA","com.lxc.hellospringboot.pojo.ExampleBeanB"};
          }
      }
      

@Import 注解在SpringBoot 的自动装配中起到了重要的作用

@Configuration 本质也是一个@Component注解 在被注册到容器中时

  • 如果被 Configuration所标注 那么回去找类中 被@Bean 注解所标注的方法 以方法名为id 把他们返回值 的BeanDefinition注册

    • @Configuration
      public class ExampleConfig {
          public ExampleConfig() {
      
          }
      
          @Bean
          public ExampleBeanFunC exampleBeanFunC()
          {
              return new ExampleBeanFunC();
          }
      
          @Bean
          public ExampleBeanFunD exampleBeanFunD()
          {
              return new ExampleBeanFunD();
          }
      }
      

在这里插入图片描述

4.2、配置文件配合注解半自动装配
  • 配置文件

    • com.lxc.hellospringboot.annotation.LxcEnableAutoConfig=com.lxc.hellospringboot.config.ExampleConfigB
      
  • ImportSelector

    • public class MyImportSelector implements ImportSelector {
          @Override
          public Predicate<String> getExclusionFilter() {
              return null;
          }
      
          @Override
          public String[] selectImports(AnnotationMetadata annotationMetadata) {
              Properties properties=PropertiesReader.getProperties("MyProperties.properties");
              ArrayList<String>candidateLists=new ArrayList<>();
              //拿取配置文件
              Set<Object> objects = properties.keySet();
              for (Object object : objects) {
                  String property = properties.getProperty(String.valueOf(object));
                  candidateLists.add(property);
              }
              //返回需要注册BeanDefinition 的全限定类名 可以一次导入多个
              return candidateLists.toArray(new String[candidateLists.size()]);
          }
      }
      
  • propertise 读取工具

    • //读取配置文件
      public class PropertiesReader {
          public static Properties getProperties(String path)
          {
              Properties properties=null;
              try {
                  Resource resource=new ClassPathResource("MyProperties.properties");
                   properties = PropertiesLoaderUtils.loadProperties(resource);
      
              } catch (IOException e) {
                  e.printStackTrace();
              }
              return properties;
          }
      }
      
  • 以注解为键 拿到配置文件中对应的值(需要注册的BeanDefinition的全限定类名)返回注册

4.3 spring.factories 自动装配
  • 一个普通的SpringBoot(可能不普通哈哈 hello xxx)
@SpringBootApplication
public class HellospringbootApplication {
   public static void main(String[] args) {

      SpringApplication.run(HellospringbootApplication.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}
)}
)
  • @SpringBootConfiguration
//SpringBootConfiguration 
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}
//一个简单的configration
//我们可以在主启动类中使用@Bean 添加Bean
  • @ComponentScan
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
}
//指定容器要扫描的位置
/*
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
*/
//好像设置了默认值 启动类所在的包和子包(这就是启动类只能放在最外层的原因吗 )
// excludeFilters 排除策略 
  • @@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 {};
}
//像我们之前自定义的注解一样 它有导入了一个 实现了 ImportSelector 的组件
//应该在此组件内部完成了 beanDefinition 的批量装入
  • AutoConfigurationImportSelector

    • public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
            ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered 
            {}
      //开头 实现了一大堆接口 
      // DeferredImportSelector 这个接口是ImportSelector的子类
      public interface DeferredImportSelector extends ImportSelector
         
      
1.selectImports
  • @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
       if (!isEnabled(annotationMetadata)) {
          return NO_IMPORTS;
           //这个应该是没有东西可以装吧
           //annotationMetadata 注解元数据 @EnableAutoConfiguration 应该是这个吧
       }
       AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
        //调用了这个方法 getAutoConfigurationEntry() 字面意思应该是 获取自动配置入口(key?)
       return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
    
2.getAutoConfigurationEntry
  • protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
       if (!isEnabled(annotationMetadata)) {
          return EMPTY_ENTRY;
          //如果没有加这个注解是不会自动装配的
       }
       AnnotationAttributes attributes = getAttributes(annotationMetadata);
       //注解属性
       List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        //得到候选配置通过 EnableAutoConfiguration 注解 和它的属性(直接说出全限定名)  加了s 
       configurations = removeDuplicates(configurations);
        //去除重复的
       Set<String> exclusions = getExclusions(annotationMetadata, attributes);
       checkExcludedClasses(configurations, exclusions);
       configurations.removeAll(exclusions);
        //排除一些指定要排除的
       configurations = getConfigurationClassFilter().filter(configurations);
       fireAutoConfigurationImportEvents(configurations, exclusions);
        //盲猜这里是按是否导入包过滤
       return new AutoConfigurationEntry(configurations, exclusions);
    }
    
2.1、getAttributes
  • protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
       String name = getAnnotationClass().getName();
       AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
       Assert.notNull(attributes, () -> "No auto-configuration attributes found. Is " + metadata.getClassName()
             + " annotated with " + ClassUtils.getShortName(name) + "?");
       return attributes;
    }
    //返回注解的一些属性
    

2.1.1、getAnnotationClass

  • protected Class<?> getAnnotationClass() {
       return EnableAutoConfiguration.class;
    }
    //EnableAutoConfiguration 瞬间清醒有没有
    //就是拿取这个注解的一些信息
    
2.3、getCandidateConfigurations
  • protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
       List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
             getBeanClassLoader());
             //传入 EnableAutoConfiguration.class 和类加载器
             //调用了 loadFactoryNames()返回了 一个String  集合
       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;
    }
    

2.3.1、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();
        //拿到 @EnableAutoConfiguration的全限定类名
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
        //loadSpringFactories(classLoaderToUse) 应该是用类加载器拿到了一些东西 
        //getOrDefault()
        
        
        //Collections.emptyList() 
        //返回了一个空的List,但是这个List和我们平时常用的那个List是不一样的。这个方法返回的List是Collections类的一个静态内部类,它继承AbstractList后并没有实现add()、remove()等方法,因此这个返回值List并不能增加删除元素。
        //感觉像一个集合语法糖。。
        //返回将被注册的类的BeanDefinition的全限定名
    }
    

2.3.2、loadSpringFactories

  • private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        //返回值是Map<String,List<String>> 一对多?
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        //先从缓存中拿
        if (result != null) {
            return result;
        } else {
            HashMap result = new HashMap();
    		
            try {
                Enumeration urls = classLoader.getResources("META-INF/spring.factories");
    			// Enumeration? 好像跟迭代有关
                //META-INF/spring.factories 从这个路径下 (参考autoconfiguration/META-INF)
                while(urls.hasMoreElements()) {
                    //这里需要细细分析了 就是筛选过滤
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();
    
                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        String[] var10 = factoryImplementationNames;
                        int var11 = factoryImplementationNames.length;
    
                        for(int var12 = 0; var12 < var11; ++var12) {
                            String factoryImplementationName = var10[var12];
                            ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                                return new ArrayList();
                            })).add(factoryImplementationName.trim());
                        }
                    }
                }
    
                result.replaceAll((factoryType, implementations) -> {
                    return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
                });
                cache.put(classLoader, result);
                //放入缓存 减少IO 毕竟上面一个n3方的循环 多搞几次就g了
                return result;
                //返回 一个HashMap 一对多哦
                
            } catch (IOException var14) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
            }
        }
    }
    

一对多o像这样
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值