分析SpringBoot自动装配原理

11 篇文章 0 订阅
5 篇文章 0 订阅

无论是Spring Cloud Netflix还会Spring Cloud Alibaba,都是基于SpringBoot这个微服务框架来构建的,所以SpringBoot对于大家来说还是十分重要的。

1、Spring 是什么?

对于Spring框架而言,我们接触比较深的应该是SpringMVC和Spring。而Spring的核心在于IOC控制反转和DI依赖注入。而这些的使用需要我们去配置大量的XML,过程十分繁琐。
SpringBoot就是为了帮助使用Spring框架的开发者快速高效的构建一个基于Spring框架以及Spring 生态体系的应用解决方案。它是对** “约定优于配置” **理念的最佳实践。因此它是一个服务于框架的框架,可以帮助我省去很多繁琐的配置。

2、约定优于配置的体现

1、Maven的目录结构
    默认有resources文件存放配置文件。
    默认打包方式为jar。
2、Spring-Boot-Srarter-Web中默认包含SpringMVC相关依赖以及内置的Tomcat容器,使得构建一个web应用更加简单。
3、默认提供application.properties/yml文件。
4、默认通过Spring.profiles.active属性来觉得运行环境时读取的配置文件。
5、EnableAutoConfiguration默认对于依赖的starter进行自动装载。

SpringBoot项目的标志性注解就是@SpringBootApplication因此,我们要想要了解SpringBoot当然需要从它开始

3、@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}
)}
)

SpringBootApplication本质上是又3个注解组成

  1. @SpringBootConfiguration
  2. @EnableAutoConfiguration
  3. @ComponentScan

我们直接使用这个三个注解其实也可以启动SpringBoot应用,只是每次配置三个注解比较麻烦,因此SpringBoot帮我们整合了他们。

3.1 @SpringBootConfiguration

这个注解你点开源码看实质上就是一个@Configuration,对于这个注解使用我们应该都不会陌生,它是JavaConfig形式的基于SpringIOC容器的配置类使用的一个注解。因为SpringBoot本身就是一个Spring应用,所以通过这个这个注解来加载IOC容器的配置也很正常。因此在启动类中标注了此注解,说明它是一个IOC的配置类。
在传统意义上的Spring应用都是基于XML形式来配置Bean的依赖关系,然后通过Spring容器在启动的时候,把Bean进行初始化,如果Bean之间存在依赖关系,则Fenix这些已经在IOC容器中的Bean根据依赖关系进行组装。
到了JAVA5中,引入了Annotations这个特性,Spring框架也推出了基于Java代码和Annotation元信息的依赖关系绑定描述的方式,也就是JavaConfig。
从Spring3开始,spring就支持了两种bean的配置方式,一种是基于xml,另一种就是JavaConfig任何一个标注了@Configuration的java类定义都是一个JavaConfig配置类。而在这个配置类中,任何标注了@Bean的方法,它的返回值都会作为Bean定义注册到Spring的IOC容器,方法名默认成为这个bean的id。
@Configuration简单使用:
创建一个实体类

//希望这个类被Spring托管
public class DemoClass {
    public void say(){
        System.out.println("Say hello Ccc");
    }
}

在以前我们需要在<bean class = “com.xxx.DemoClass”>来进行注册Bean
在后来我们使用注解配置

@Configuration //表示当前是一个配置类
public class ConfigurationDemo {
    //单例的
    @Bean
    public DemoClass demoClass(){
        return new DemoClass();
    }
}

我们声明一个配置类去进行Bean的注入,然而Bean注入不是每次都会去创建一个对象,因为在Spring中他默认是单例的。因为@Scope()注解中默认是单例的。我在之前的文章也介绍了这个注解,有兴趣的朋友可以了解一下。跳转
写个方法简单测试下

public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ConfigurationDemo.class);
        DemoClass demoClass = applicationContext.getBean(DemoClass.class); //DL
        demoClass.say();
    }
//可以在日志中看到DemoClass的注入和最后类中的方法打印。
3.2 @ComponentScan

ComponentSccan我接触的很频繁,它相当于xml中的<context:component-scan>。主要作用是扫描到指定路径下的标识了需要装配的类,自动装配到Spring的IOC容器中。
例如:@Component、@Repository、@Service、@Controller这些注解标识的类。
ComponentSccan会默认扫描单签package下所有的加了相关注解的类注入到IOC容器中。
我们修改一下上述的例子:

1.去掉ConfigurationDemo中Bean的创建
2.在DemoClass类中使用上述4个注解中一个这里使用@Service

//希望这个类被Spring托管
@Service
public class DemoClass {
    public void say(){
        System.out.println("Say hello Ccc");
    }
}

3.在main方法的类上添加@ComponentScan注解

@ComponentScan
public class ConfigurationMain {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ConfigurationMain.class);
        String[] defNames = applicationContext.getBeanDefinitionNames();
        for (String defName : defNames) {
            System.out.println(defName);
        }
    }
}
//你会发现带有@Service注解的DemoClass同样也注入了。
3.3 @EnableAutoConfiguration

在我们了解到上面两个注解之后我们再来探讨一下此注解,从名字上不难猜出 开始自动配置。对于SpringBoot来说它才是意义最大的。
你会见到很多类似EnableXxxxxBxxx格式的注解。那么Enable又是什么?
在Spring3.1版本中,提供了一系列的@Enable开头的注解,Enable是在JavaConfig上进一步的完善,使得用户在使用Spring相关的框架时,避免配置大量的代码从而降低使用难度。
例如:
@EnableWebMvc:此注解会引入MVC框架在Spring应用中需要用到的所有bean

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})

@EnableScheduling:开始计划任务的支持。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({SchedulingConfiguration.class})
@Documented

打开这些Enable开头的注解,你会发现每一个带有Enable开头的注解都会存在一个@Import的注解。
例如在@EnableAutoConfiguration中

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})

我们先来探讨一下Import注解的作用。

3.3.1@Import

在xml文件中会存在一个<import resource>形式的标签,就不难猜出它的意义,因为在JjavaConfig中所表达的意义是一样的。请看:

public class OtherBean {} //创建一个Bean
//------------------------------------------
//在创建一个配置类注入Bean
@Configuration
public class OtherConfig {
    @Bean
    public OtherBean otherBean(){
        return  new OtherBean();
    }
}
public class DefaultBean {} //创建一个Bean
//---------------------------------------------
//创建一个配置类
@Configuration
//使用Import注入上述中的Bean对象
@Import(OtherConfig.class) //也可以单独注入OtherBean.class  
public class SpringConfig {
    @Bean
    public DefaultBean defaultBean(){
        return new DefaultBean();
    }
}

说白了就是可以注入外部资源。

我们在回到@EnableAutoConfiguration。它的作用就是帮助SpringBoot应用把所有符合条件的@Configuration配置都加载到当前SpringBoot的IOC容器中。
我们观察一下它的@Import({AutoConfigurationImportSelector.class})注解
那么AutoConfigurationImportSelector 是什么?
在上述案例中使用@Import注入的简单案例之外,他还可以支持另外两种配置。
1、上述案例中的配置 使用普通的Bean进行注入
2、实现ImportSelector接口进行动态注入。
3、实现ImportBeanDefinitionRegistrar接口接口进行动态注入(此处的实现在@AutoConfigurationPackage中)。
很显然AutoConfigurationImportSelector就是进行动态注入的ImportSelector接口实现
我们找到AutoConfigurationImportSelector的类图:顶层接口就是ImportSelector!
在这里插入图片描述

而在ImportSelector接口定义的方法selectImports()中返回的数组(类的全类名)都会被纳入到 spring容器中。
我们定位到AutoConfigurationImportSelector类中的selectImports方法,本质上来说其实EnableAutoConfiguration会帮助SpringBoot应用把所有符合@Configuration配置都加载到倩倩的SpringBoot创建的IOC容器,而在这里借助了Spring框架提供的工具类SpringFactoriesLoader的支持,以及用到的Spring提供的条件注解@Conditional,选择性加载需要bean。
首先SpringFactoriesLoader的作用是从classpath/META-INF/spring.factories文件中,根据key来加载到对应的类到IOC容器中。
在AutoConfigurationImportSelector执行中,会先扫描到spring-autoconfiguration-metadata.properties 文件,最后在扫描到spring.factories时,会结合前面的元数据进行过滤?为什么需要过滤?因为很多的@Configuration是依托于其他的框架来加载的,如果当前的classpath环境下没有相关联的依赖,那么意味着这些类没有必要加载,所以通过这种方式过滤可以有效的减少@Configuration类的数量从而降低启动时间。
我们来看看SpringBoot中的配置:在这里插入图片描述
在SpringBoot启动时会如果没有条件注解配置情况下它会默认载入改key下所有的value。很显然我们不需要用到这里全部。

在这里博主也简单写了一个案例来帮助大家更好的理解一下。
首先展示一下动态注入的方式
1.首先我们定义一个@EnableDefineService注解来模仿@EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited //表示是否允许被继承
@Import({LoggerDefinitionRegistrar.class,CacheImportSelector.class})
public @interface EnableDefineService {
    Class<?>[] exclude() default {};
}

这里的**@Import({LoggerDefinitionRegistrar.class,CacheImportSelector.class})**我将上述中
2、实现ImportSelector接口进行动态注入。
3、实现ImportBeanDefinitionRegistrar接口接口进行动态注入(此处的实现在@AutoConfigurationPackage中)。
两种实现方式进行整合到一个注解中,减少一下演示代码。
2.创建两个需要注入类CacheService和LoggerService一个通过方式2注入一个通过方式3注入

public class CacheService {
}
public class LoggerService {
}

3.创建CacheImportSelector类实现ImportSelector接口。

public class CacheImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        //AnnotationMetadata 注解元数据 例如:@Bean(name="xxx") name就是元数据了
        //读取元信息
        Map<String,Object> maps = annotationMetadata.getAnnotationAttributes(EnableDefineService.class.getName());
        //这里简单返回固定对象。
        return new String[]{CacheService.class.getName()};
    }
}

4.创建LoggerDefinitionRegistrar实现ImportBeanDefinitionRegistrar

public class LoggerDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Class beanClass = LoggerService.class; //拿到类
        RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass); //进行包装
        String name = StringUtils.uncapitalize(beanClass.getSimpleName()); //将类名首字母小写
        registry.registerBeanDefinition(name,beanDefinition);
    }
}

写一个测试类

@SpringBootApplication
//模仿@EnableAutoConfiguration的注解
@EnableDefineService
public class EnableDemoMain {
    public static void main(String[] args) {
        ConfigurableApplicationContext ca = SpringApplication.run(EnableDemoMain.class,args);
        System.out.println(ca.getBean(CacheService.class));
        System.out.println(ca.getBean(LoggerService.class));
    }
}

你会发现加上@EnableDefineService就会成功注入这两个对象信息。

条件过滤案例
我们选择有条件的进行过滤掉我们需要的Bean对象
1.另外创建一个Maven工程
里面的类

//bean对象
public class Core {
    public String study(){
        System.out.println("good good study day day up");
        return "www.cxhorange.com";
    }
}
//配置类
@Configuration
public class Config {
    @Bean
    public Core core(){
        return new Core();
    }
}
//过滤条件
public class TestClass {
}

我们模仿SpringBoot的内容,在resources下创建一个META-INF目录并创建两个文件
1.spring-autoconfigure-metadata.properties 空文件加就行
2.spring.factories:key为springboot中的key,value指向我们类名。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ccc.core.Config
我们文章中也提到了在SpringBoot中AutoConfigurationImportSelector会依次扫描这两个文件。
如果不存在条件过滤那么springBoot会默认加载spring.factories文件中该key下所有类
打个jar包
在这里插入图片描述
复制maven地址
在这里插入图片描述
随便在一个SpringBoot应用中进行测试。
导入上面的maven依赖

		<dependency>
            <groupId>com.ccc.core</groupId>
            <artifactId>ccc-core</artifactId>
            <version>1.0-SNAPSHOT</version>
       </dependency>

测试

@SpringBootApplication
public class FouthMain {
    public static void main(String[] args) {
        ConfigurableApplicationContext ca = SpringApplication.run(FouthMain.class,args);
        System.out.println(ca.getBean(Core.class).study());
    }
}

输出内容
在这里插入图片描述

修改maven工程中的spring-autoconfigure-metadata.properties 文件
加上内容
com.ccc.core.Config.ConditionalOnClass = com.ccc.core.TestClass
在进行打包测试 你会发现同样注入成功,我们如果将value替换成com.ccc.core.Test 在进行打包测试你会发现报错了
在这里插入图片描述
原因是在我们maven中并没有创建Test这个类。

另外关于条件注解几种类型

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

以上便是本人对于SpringBoot原理的总结,如果能够帮助到一些人便是再好不过,如果您在浏览中发现了错误,也希望您能批评指正,在此感谢!

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈橙橙丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值