您当前的位置: 首页 >  spring

分析SpringBoot自动装配原理

发布时间:2020-04-28 12:13:48 ,浏览量:5

无论是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 在后来我们使用注解配置

@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中的。主要作用是扫描到指定路径下的标识了需要装配的类,自动装配到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文件中会存在一个形式的标签,就不难猜出它的意义,因为在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 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原理的总结,如果能够帮助到一些人便是再好不过,如果您在浏览中发现了错误,也希望您能批评指正,在此感谢!

关注
打赏
1688896170
查看更多评论

暂无认证

  • 5浏览

    0关注

    115984博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录

0.0547s