spring boot自动装配机制 学习笔记

spring boot自动装配机制 学习笔记

闲谈

spring,spring boot没有新的技术点,它是服务于spring框架的框架,用于快速构建springweb项目,遵守约定大于配置的原则,
前边我们通过spring的SPI机制(满足目录一致,文件名一致,key要存在并符合当前的加载)自定义了一个自己的starter组件,并在springboot项目中成功引用,通过测试,但是当时我们只是用到了最简单的制动装配,现在我们来罗列一下springboot中的自动装配机制

spring以及spring boot相关概念理解

IOC spring mvc DI DL AOP
最早spring 的bean的配置,在applicationContext.xml 中,配置bean 标签,
spring 3.0开始,由于Java5 开始支持Javaconfig ,所有spring开始支持注解的方式代替之前的大量的滥用的xml配置文件,有了spring 3.0,才有了springboot 出现的可能

约定大于配置的体现

1.maven 或gradle的目录结构,默认会以jar的方式打包,默认会有resource文件夹
2.spring-boot-starter-web:内置Tomcat、resource、templete,static
3.默认的application.properties 或yml
4.默认通过 spring.profiles.active 属性来决定运行环境时读取的配置文件
5.EnableAutoConfiguration 默认对于依赖的 starter 进行自动装载

注解的理解

1 @AutoConfiguration
2 starter
3. actuator
4. SpringBoot CLI

IOC控制反转
@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 本质上是由 3 个注解组成,分别是

  1. @Configuration //配置类注解
  2. @EnableAutoConfiguration //自动装配注解
  3. @ComponentScan//扫描注解
简单分析@Configuration
1.定义普通Javabean
//希望这个类被springj加载 xml的形式 <bean  class="com.DemoClass"
public class DemoClass {
    public void say(){
        System.out.println("say hello");
    }
}
2.定义配置类,加载普通Javabean

相当于一个applicationContext.xml(在配置的时代,我们通常会将不同的spring xml 导入到applicationContext.xml中,达到业务bean配置的分离)加载bean,同理@Configuration也可以是多个
@Configuration

public class DemoConfiguration {
    //单利,每次通过容器获取,返回同一个
    @Bean  //等同于 <bean  class="com.DemoClass" 同样的 @Scope 上也有很多属性
    public DemoClass getDemo(){
        return new DemoClass();
    }
}
3.模拟springboot 启动 run()测试加载过程
public class DemoMainTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext=new AnnotationConfigApplicationContext(DemoConfiguration.class);
        DemoClass demoClass= (DemoClass) annotationConfigApplicationContext.getBean(DemoClass.class);
        System.out.println(demoClass);
    }
}
4.测试结果
20:58:26.312 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getDemo'
com.example.demo.demoone.DemoClass@77f80c04
简单分析@ComponentScan

等同于<context:component-scan
扫描什么?
扫描 @Component 注解以及其派生出来的注解
如 @Service @Controller@Repository @RestController 为什么会扫描这些注解呢,因为他们都是Component派生出来的,属于Component系列
托管这些注解标注的类

1.创建带@Component 注解的Javabean
@Component
public class DemoClass {
    public void say(){
        System.out.println("say hello");
    }
}
2.利用@ComponentScan 扫描当前类所在的包,或者扫描指定的包下,加载bean
@ComponentScan  //默认扫描当前路径下的包
public class DemoMainTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext=new AnnotationConfigApplicationContext(DemoMainTest.class);
        DemoClass demoClass= (DemoClass) annotationConfigApplicationContext.getBean(DemoClass.class);
        System.out.println(demoClass);
    }
}
3测试结果
21:31:17.956 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'demoClass'
com.example.demo.demotow.DemoClass@4f6ee6e4
EnableAutoConfiguration 非常重要,压轴

EnableAutoConfiguration 的 主 要 作 用 其 实 就 是 帮 助springboot 应用把所有符合条件的@Configuration 配置都加载到当前 SpringBoot 创建并使用的 IoC 容器中。
为表重要,我们贴点代码,前两个都不用贴

@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})
有兴趣也了解一下@EnableWebMvc 引入了 MVC 框架在 Spring 应用中需要用到的所有
bean; @EnableScheduling 开启计划任务的支持(定时任务)

1. @Import理解

等价于 xml配置的 <import resource .../>
import 就是将多个分开的配置合并在一个配置中。与在JavaConfig 中该注解所表达的意义是一样的。
@Import可以配置三种不同的 class

1. 第一种基于普通 bean 或者带有
     @Configuration 的 bean 进行诸如
2. 实现 ImportSelector 接口进行动态注入     将方法返回的字符串数组作为bean注入到容器中
3. 实现 ImportBeanDefinitionRegistrar 接口进行动态注入   自定义注册bean
2. ImportSelector理解

上面的代码@Import({AutoConfigurationImportSelector.class}),是因为AutoConfigurationImportSelector实现了importSelector接口,其实现借助了Spring 框架提供的一个工具类 SpringFactoriesLoader 的支持,以及用到了 Spring 提供的条件注解@ConditionalOnXXXXX(*.class) 等同于spring-autoconfiguration-metadata.properties 中的条件 XXX.ConditionalOnMissingClass=,和过滤‘exclude’ 选择性的针对需要加载的 bean 进行条件过滤

SpringFactoriesLoader理解

加载器,它其实和java 中的 SPI 机制的原理是一样,不过它比 SPI 更好的点,在于不会一次性加载所有的类,而是根据 key 进行加载。首先, SpringFactoriesLoader 的作用是从classpath/META-INF/spring.factories 文件中,根据 key 来加载对应的类到 spring IoC 容器中。
AutoConfigurationImportSelector借助它完成类的加载,它的key 是 org.springframework.boot.autoconfigure.EnableAutoConfiguration ,对应N个字符串,SpringFactoriesLoader 加载后,逐一处理
如有以下的values

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\

SpringFactoriesLoader 这个类我们也可以直接用,搞一些我们自己特殊东西

AutoConfigurationImportSelector 理解

会先扫描 spring-autoconfiguration-metadata.properties文件,最后在扫描 spring.factories 对应的类时,会结合前面的元数据进行过滤,为什么要过滤呢? 原因是很多的@Configuration 其实是依托于其他的框架来加载的,如果当前的 classpath 环境下没有相关联的依赖,则意味着这些类没必要进行加载,所以,通过这种条件过滤可以有效的减少@configuration 类的数量从而降低SpringBoot 的启动时间

Conditional 和Conditional系列的 理解

Conditional 有两种方式,可以在 spring-autoconfiguration-metadata.properties 中定义,如com.test.BeanConfiguration.ConditionalOnMissingClass=com.alibaba.fastjson.JSON
也可以直接使用@ConditionalOnMissingClass("com.alibaba.fastjson.JSON") 这种注解,他们是等价的

Conditions 描述

@ConditionalOnBean 在存在某个 bean 的时候
@ConditionalOnMissingBean 不存在某个 bean 的时候
@ConditionalOnClass 当前 classpath 可以找到某个类型的类时
@ConditionalOnMissingClass 当前 classpath 不可以找到某个类型的类时
@ConditionalOnResource 当前 classpath 是否存在某个资源文件
@ConditionalOnProperty 当前 jvm 是否包含某个系统属性为某个值
@ConditionalOnWebApplication 当前 spring context 是否是 web 应用程序
3. ImportBeanDefinitionRegistrar 理解

和ImportSelector 功能类似,也支持条件注入,此方法可以对装配的类做一个过滤处理
主要工作的方法是
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)

5@AutoConfigurationPackage

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};

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

}
最新的版本中,这个注解,导入了Registrar.class,它是ImportBeanDefinitionRegistrar的一个子类,之前的版本中是直接导入ImportBeanDefinitionRegistrar.class

通过@Configuration装配Bean
1创建now包,并创建简单Java bean
public class BeanOne {
}
2.创建other 包,并创建另一个简单Java bean
public class BeanTow {
}
3.now包创建配置类
@Configuration
public class BeanConfiguration {
    @Bean
    public BeanOne getBeanOne(){
        return new BeanOne();
    }
}
4.now包创建main类验证加载
public class DemoMainTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext=new AnnotationConfigApplicationContext(BeanConfiguration.class);
        String [] defNames=annotationConfigApplicationContext.getBeanDefinitionNames();
        for (String  str:defNames) {
            System.out.println(str);
        }

    }
}
5.返回结果
beanConfiguration
getBeanOne

没有加载到BeanTow 怎么才能加载到呢,在other包中创建 配置类,有import能导入@Configuration 注解的类,进行加入

通过@Import(OtherConfiguration.class) 装配别的路径下@Configuration注解的Bean
6.在 5 的基础上other包中创建 配置类
@Configuration
public class OtherConfiguration {
    @Bean
    public BeanTow getBeanTow(){
        return new BeanTow();
    }
}
7.在now包的配置类中@ import()导入 other 包中的配置类OtherConfiguration
@Configuration
@Import(OtherConfiguration.class)
public class BeanConfiguration {
    @Bean
    public BeanOne getBeanOne(){
        return new BeanOne();
    }
}
8.验证other 包 中的类有没有被自动装配

返回结果,加载成功

beanConfiguration
com.example.demo.demnthree.other.OtherConfiguration
getBeanTow
getBeanOne

通过ImportBeanDefinitionRegistrar 和 ImportSelector 装配Bean实战

1. 简单的三个Javabean
public class AttachService {
}
public class CacheService {
}
public class LoggerService {
}

2.自定义配置类 ImportSelector
public class CacheImportSelector implements ImportSelector {
    //AnnotationMetadata 注解中的元数据
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        //获取@EnableDefineService注解的元信息也就是 exclude = {LoggerService.class}
        Map<String,Object> attributes= annotationMetadata.getAnnotationAttributes(EnableDefineService.class.getName());
        //可以返回一些信息,暂时写死
        System.out.println(attributes.get("exclude").getClass().getName());
        //排除逻辑的判断,即不加载哪个类,可以将此放入到配置文件中
        if(attributes.get("exclude")==null){
            //将CacheService托管给spring ioc
            return new String[] {CacheService.class.getName()};
        }
        else{
             //将CacheService和AttachService托管给spring ioc
            return new String[] {CacheService.class.getName(),AttachService.class.getName()}; 
           
        }
        //将扩展点放入到配置文件中,SP扩展点相当于从所有resource/MATE-INF/spring.fatories配置文件中,
        //实现这个要依赖类似于SpringFactoriesLoader 的使用
        //        return new String[] {"org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration",
       //         "org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration",
       //         "org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration"};
//总而言之,只能根据元数据,或者condition 做过条件滤处理,返回一个需要加载的 类的字符串数组,由spring来加载
    }
}
3.自定义配置类 LoggerDefinetionRegister 扩展点
public class LoggerDefinetionRegister implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
          Class beanClass =LoggerService.class;
        RootBeanDefinition beanDefinition =new RootBeanDefinition(beanClass);//封装成BeanDefinition
        //相当于<bean name=“XX”
        String name= StringUtils.uncapitalize(beanClass.getSimpleName());//首字母小写
         //可以自定义bean name 与CacheImportSelector 不同name,而且直接就能装配了,而CacheImportSelector 只能  过滤,返回字符串数组,由spring装配
        registry.registerBeanDefinition(name,beanDefinition);
    }
}
4.自定义注解中,导入CacheImportSelector.class,LoggerDefinetionRegister.class

前边说过@import
2. 实现 ImportSelector 接口进行动态注入
3. 实现 ImportBeanDefinitionRegistrar 接口进行动态注入

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({CacheImportSelector.class,LoggerDefinetionRegister.class}) //导入的不是Configuration,而是ImportSelector和ImportBeanDefinitionRegistrar
public @interface EnableDefineService {
    //配置一些方法
    Class<?>[] exclude() default {};
}
测试注解,并添加 exclude进行条件装配,
@SpringBootApplication
@EnableDefineService(exclude = {LoggerService.class})
public class EnableDefineMain {
    public static void main(String[] args) {
        ConfigurableApplicationContext ca= SpringApplication.run(EnableDefineMain.class,args);
        String[] names=ca.getBeanDefinitionNames();
        for (String s:names) {
            System.out.println(s);
        }
    }
}

运行结果

com.example.demo.demofour.CacheService
com.example.demo.demofour.AttachService
org.springframework.boot.autoconfigure.AutoConfigurationPackages
loggerService

将类的加载条件放到配置文件中去实战

请查看 手把手 自定义XXX-spring-boot-starter组件章节

本次学习结束,之前我们已经自定义了starter
下一章是进行自定义多数据库连接

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值