springboot
一、Spring Boot的价值
Spring Boot并不是一个新的技术框架,其主要作用就是简化Spring应用的开发,开发者只需要通过少量的代码就可以创建一个产品级的Spring应用,而达到这一目的最核心的思想就是“约定优于配置(Convention overConfiguration )“
1.1 如何理解约定优于配置
约定优于配置(Convention Over Configuration)是一种软件设计范式,目的在于减少配置的数量或者降低理解难度,从而提升开发效率,需要注意的是,它并不是一种新的思想,实际上从我们开始接触Java依赖,就会发现很多地方都有这种思想的体现。比如,数据库中表名的设计对应到Java中实体类的名字,就是一种约定我们可以从这个实体类的名字知道它对应数据库中哪张表。再比如,每个公司都会有自己的开发规范,开发者按照开发规范可以在一定程度上减少Bug的数量,增加可读性和可维护性
在SpringBoot中,约定优于配置的思想主要体现在以下方面(包括但不限于):
- Maven目录结构的约定。
- Spring Boot默认的配置文件及配置文件中配置属性的约定
- 对于SpringMVC的依赖,自动依赖内置的Tomcat容器
- 对于Starter组件自动完成装配。
1.2 Spring Boot的核心
Spring Boot是基于Spring Framework体系来构建的,所以它并没有什么新的东西,但是要想学好SpringBoot,必须知道它的核心:
- Starter组件,提供开箱即用的组件。
- 自动装配,自动根据上下文完成Bean的装配。
- Actuator,Spring Boot应用的监控
- Spring Boot CLI,基于命令行工具快速构建Spring Boot应用
其中,最核心的部分应该是自动装配,Starter组件的核心部分也是基于自动装配来实现的。由于本书并不是专门写SpringBoot的,所以笔者在后续章节中只会重点分析自动装配的原理。
在本节中,从Spring的起源,到SpringXML配置文件时代的问题,接着引出JavaConfig的方式实现无配置化注入的解决方案。可以看到,在整个发展过程中,Bean的装载方式在形式上发生了变化,但是本质问题仍然没有解决,直到Spring Boot出现。然后我们简单分析了Spring Boot的优势以及核心之所以花这么多笔墨去串联整个过程,是因为在笔者看来,比使用技术更重要的是了解技术的产生背景,这将有助于提高和改变技术人的思维方式;
二、Spring Boot自动装配的原理
在Spring Boot中,不得不说的一个点是自动装配,它是Starter的基础,也是Spring Boot的核心,那么什么叫自动装配呢?或者说什么叫装配呢?
简单来说,就是自动将Bean装配到IoC容器中,接下来,我们通过一个Spring Boot整合Redis的例子来了解一下自动装配;
在这个案例中,我们并没有通过XML形式或者注解形式把RedisTemplate注入IoC容器中,但是在HelloController中却可以直接使用@Autowired来注入redisTemplate实例,这就说明,IoC容器中已经存在RedisTemplate。这就是Spring Boot的自动装配机制。
在往下探究其原理前,可以大胆猜测一下,如何只添加一个Starter依赖,就能完成该依赖组件相关Bean的自动注入?不难猜出,这个机制的实现一定基于某种约定或者规范,只要Starter组件符合Spring Boot中自动装配约定的规范,就能实现自动装配。
2.1自动装配的实现
自动装配在Spring Boot中是通过@EnableAutoConfiguration注解来开启的,这个注解的声明在启动类注解@SpringBootApplication内;
这里简单和大家讲解一下@Enable注解。其实Spring 31版本就已经支持@Enable注解了,它的主要作用把相关组件的Bean装配到IoC容器中,@Enable注解对JavaConfig的进一步完善,为使用Spring Framework的开发者减少了配置代码量,降低了使用的难度。比如常见的@Enable注解有@EnableWebMvc.@EnableScheduling等。
在前面的章节中讲过,如果基于JavaConfig的形式来完成Bean的装载,则必须要使用@Configuration注解及@Bean注解。而@Enable本质上就是针对这两个注解的封装,所以大家如果仔细关注过这些注解,就不难发现这些注解中都会携带一个@Import注解,比如@EnableScheduling
因此,使用@Enable注解后,Spring会解析到@Import导入的配置类,从而根据这个配置类中的描述来实现Bean的装配。大家思考一下,@EnableAutoConfiguration的实现原理是不是也一样呢?
2.2 AutoConfigurationImportSelector
AutoConfigurationImportSelector实现了ImportSelector,它只有一个selectImports抽象方法,并目返回一个String数组,在这个数组中可以指定需要装配到IoC容器的类,当在@Import中导入一个ImportSelectol的实现类之后,会把该实现类中返回的Class名称都装载到IoC容器中;
AutoConfigurationImportSelector实现了ImportSelector,它只有一个selectImports抽象方法,并且返回一个String数组,在这个数组中可以指定需要装配到IoC容器的类,当在@Import中导入一个ImportSelector的实现类之后,会把该实现类中返回的Class名称都装载到IoC容器中。
和@Configuration不同的是,ImportSelector可以实现批量装配,并且还可以通过逻辑处理来实现Bean的选择性装配,也就是可以根据上下文来决定哪些类能够被IC容初始化。接下来通过一个简单的例子带大家了解ImportSelector的使用。
- 首先创建两个类,我们需要把这两个类装配到IoC容器中
- 创建一个ImportSelector的实现类,在实现类中把定义的两个Bean加入String数组,这意味着这两个Bean会装配到IoC容器中。
- 我们可以自定义一个类似的注解,通过@Import导入GpImportSelector
- - 创建一个启动类,在启动类上使用@EnableAutolmport注解后,即可通过ca.getBean从IoC容器
中得到FirstClass对象实例;
*这种实现方式相比@Import(Confiquration.cass)的好处在于装配的灵活性还可以实现批量。比如GpImportSelector还可以直接在String数组中定义多个Configuration类,由于一个配置类代表的是某一个技术组件中批量的Bean声明,所以在自动装配这个过程中只需要扫描到指定路径下对应的配置类即可。
2.3 自动装配原理分析;
基于前面章节的分析可以猜想到,自动装配的核心是扫描约定目录下的文件进行解析,解析完成之后把得到的Configuration配置类通过ImportSelector进行导入,从而完成Bean的自动装配过程那么接下来我们通过分析AutoConfigurationImportSelector的实现来求证这个猜想是否正确
定位到AutoConfigurationImportSelector中的selectImports方法,它是ImportSelector接口的实现,这个方法中主要有两个功能:
- AutoConfiqurationMetadataLoader.loadMetadata从META-INF/spring-autoconfigure-metadata,properties中加载自动装配的条件元数据,简单来说就是只有满足条件的Bean才能够进行装配
- 收集所有符合条件的配置类autoConfigurationEntry.getConfigurations(),完成自动装配
需要注意的是,在AutoConfigurationImportSelector中不执行selectImports方法,而是通过ConfigurationClassPostProcessor中的processConfigBeanDefinitions方法来扫描和注册所有配置类的Bean,最终还是会调用getAutoConfigurationEntry方法获得所有需要自动装配的配置类。
我们重点分析一下配置类的收集方法getAutoConfigurationEntry,结合之前Starter的作用不难猜测到,这个方法应该会扫描指定路径下的文件解析得到需要装配的配置类,而这里面用到了SpringFactoriesLoader,这块内容后续随着代码的展开再来讲解。简单分析一下这段代码,它主要做几件事情:
- getAttributes获得@EnableAutoConfiguration注解中的属性excludeexcludeName等
- getCandidateConfigurations获得所有自动装配的配置类,后续会重点分析。
- removeDuplicates去除重复的配置项。
- getExclusions根据@EnableAutoConfiguration注解中配置的exclude等属性,把不需要自动装配的配置类移除。
- fireAutoConfigurationImportEvents广播事件
- 最后返回经过多层判断和过滤之后的配置类集合。
总的来说,它先获得所有的配置类,通过去重、exclude排除等操作,得到最终需要实现自动装配的配置类这里需要重点关注的是getCandidateConfigurations,它是获得配置类最核心的方法;
这里用到了SpringFactoriesLoader,它是Spring内部提供的一种约定俗成的加载方式,类似于Java中的SPI。简单来说,它会扫描casspath下的META-INF/spring.factories文件,spring.factories文件中的数据以Key=Value形式存储而SpringFactoriesLoaderloadFactoryNames会根据Key得到对应的value值。因此在这个场景中,Key对应为EnableAutoConfiguration,Value是多个配置类,也就是getCandidateConfigurations方法所返回的值
打开RabbitAutoConfiguration,可以看到,它就是一个基于JavaConfig形式的配置类
除了基本的@Configuration注解,还有一个@ConditionalOnClass注解,这个条件控制机制在这里的用途是,判断casspath下是否存在RabbitTemplate和Channel这两个类,如果是,则把当前配置类注册到IoC容器。另外,@EnableConfiqurationProperties是属性配置,也就是说我们可以按照约定在applicationproperties中配置RabbitMQ的参数,而这些配置会加载到RabbitProperties中。实际上,这些东西都是Spring本身就有的功能。
至此,自动装配的原理基本上就分析完了,简单来总结一下核心过程
- 通过@Import(AutoConfigurationImportSelector)实现配置类的导入,但是这里并不是传统意义上的单个配置类装配。
- AutoConfigurationImportSelector类实现了ImportSelector接口,重写了方法selectImports,它用于实现选择性批量配置类的装配。
- 通过Spring提供的SpringFactoriesLoader机制,扫描asspath路径下的META-INF/springfactories,读取需要实现自动装配的配置类。
- 通过条件筛选的方式,把不符合条件的配置类移除,最终完成自动装配;
2.4 @Conditional条件装配
@Conditional是Spring Framework提供的一个核心注解,这个注解的作用是提供自动装配的条件约束,般与@Configuration和@Bean配合使用。
简单来说,Spring在解析@Configuration配置类时,如果该配置类增加了@Conditional注解,那么会根据该注解配置的条件来决定是否要实现Bean的装配。
2.4.1@Conditional的使用
@Conditional的注解类声明代码如下,该注解可以接收一个Condition的数组。
Condition是一个函数式接口,提供了matches方法,它主要提供一个条件匹配规则,返回true表示可以注入Bean,反之则不注入。
我们接下来基于@Conditional实现一个条件装配的案例
- 自定义一个Condition,逻辑很简单,如果当前操作系统是Windows,则返回true,否则返回false。
- 创建一个配置类,装载一个BeanClass(自定义的一个类)
在BeanClass的bean声明方法中增加@Conditional(GpCondition.class),其中具体的条件是我们自定义的GpCondition类。
上述代码所表达的意思是,如果GpCondition类中的matchs返回true,则将BeanClass装载到SpringIoC容器中。
- 运行测试方法
在Windows环境中运行,将会输出BeanClass这个对象实例。在Linux环境中,会出现如下错误
以上就是@Conditional注解的使用方法,为Bean的装载提供了上下文的判断;
2.4.2 Spring Boot中的@Conditional
在Spring Boot中,针对@Conditional做了扩展,提供了更简单的使用形式,扩展注解如下:
- ConditionalOnBean/ConditionalOnMissingBean:容器中存在某个类或者不存在某个Bean时进行Bean装载
ConditionalOnClass/ConditionalOnMissingClass:casspath下存在指定类或者不存在指定类时进行Bean装载 - ConditionalOnCloudPlatform:只有运行在指定的云平台上才加载指定的Bean。
- ConditionalOnExpression:基于SpEl表达式的条件判断。
- ConditionalOnJava:只有运行指定版本的Java才会加载Bean。
- ConditionalOnJndi:只有指定的资源通过JNDI加载后才加载Bean。
- ConditionalOnWebApplication/ConditionalOnNotWebApplication:如果是Web应用或者不是Web应用,才加载指定的Bean。
- ConditionalOnProperty:系统中指定的对应的属性是否有对应的值
- ConditionalOnResource:要加载的Bean依赖指定资源是否存在于classpath中
- ConditionalOnSingleCandidate:只有在确定了给定Bean类的单个候选项时才会加载Bean。这些注解只需要添加到@Configuration配置类的类级别或者方法级别,然后根据每个注解的作用来传参就行。下面演示几种注解类的使用。
在applicationproperties或application,ym/文件中当gp.bean.enable=true时才会加载ConditionConfig这个Bean,如果没有匹配上也会加载,因为matchifMissing=true,默认值是false。
在classpath中如果存在gp.properties,则会加载ConditionConfig。
这些条件配置在Spring Boot的自动装配配置类中出现的频率非常高,它能够很好地为自动装配提供上下文条件判断,来让Spring决定是否装载该配置类
2.4.6 spring-autoconfigure-metadata
除了@Conditional注解类,在Spring Boot中还提供了spring-autoconfigure-metadata.properties文件来实现批量自动装配条件配置。
它的作用和@Conditional是一样的,只是将这些条件配置放在了配置文件中。下面这段配置来自spring-boot-autoconfigure.jar包中的/META-INF/spring-autoconfiqure-metadata.properties文件
同样,这种形式也是“约定优于配置”的体现,通过这种配置化的方式来实现条件过滤必须要遵循两个条件:
- 配置文件的路径和名称必须是/META-INF/spring-autoconfigure-metadata.properties。
- 配置文件中key的配置格式:自动配置类的类全路径名.条件=值
这种配置方法的好处在于,它可以有效地降低Spring Boot的启动时间,通过这种过滤方式可以减少配置类的加载数量,因为这个过滤发生在配置类的装载之前,所以它可以降低Spring Boot启动时装载Bean的耗时。
三、手写实现一个Starter
对于自动装配的原理进行分析之后,我们可以基于这个机制来实现一个Starter组件,以便加深大家对自动装配及Starter组件的理解。同时,Spring Boot官方提供的Starter并不能括所有的技术组件,在工作中,如果自己的项目需要支持Spring Boot,也需要开发Starter组件。
从Spring Boot官方提供的Starter的作用来看,Starter组件主要有三个功能:
- 涉及相关组件的Jar包依赖。
- 自动实现Bean的装配
- 自动声明并且加载application.properties文件中的属性配置
下面我们先来了解一下starter组件的命名规范
3.1Starter的命名规范
Starter的命名主要分为两类,一类是官方命名,另一类是自定义组件命名。这种命名格式并不是强制性的也是一种约定俗成的方式,可以让开发者更容易识别。
- 官方命名的格式为:spring-boot-starter-模块名称,比如spring-boot-starter-web
- 自定义命名格式为:模块名称-spring-boot-starter,比如mybatis-spring-boot-starter。
简单来说,官方命名中模块名放在最后,而自定义组件中模块名放在最前面
3.2实现基于Redis的Starter
虽然Spring Boot官方提供了spring-boot-starter-data-redis组件来实现RedisTemplate的自动装配,但是我们仍然基于前面学到的思想实现一个基于Redis简化版本的Starter组件。
- 创建一个工程,命名为redis-spring-boot-starter。
- 添加Jar包依赖,Redisson提供了在Java中操作Redis的功能,并且基于Redis的特性封装了很多可直接使用的场景,比如分布式锁。
- 定义属性类,实现在application.properties中配置Redis的连接参数,由于只是一个简单版本的Demo,所以只简单定义了一些必要参数。另外@ConfigurationProperties这个注解的作用是把当前类中的属性和配置文件(properties/yml)中的配置进行绑定,并且前缀是gp.redisson。
- 定义需要自动装配的配置类,主要就是把RedissonClient装配到IoC容器,值得注意的是@ConditionalOnClass,它表示一个条件,在当前场景中表示的是:在dasspath下存在Redisson这个类的时候,RedissonAutoConfiguration才会实现自动装配。另外,这里只演示了一种单机的配置模式,除此之外,Redisson还支持集群、主从、哨兵等模式的配置,大家有兴趣的话可以基于当前案例去扩展,建议使用config.fromYAML方式,直接加载配置完成不同模式的初始化,这会比根据不同模式的判断来实现配置化的方式更加简单。
- 在resources下创建META-INF/spring.factories文件,使得Spring Boot程序可以扫描到该文件完成自动装配,key和value对应如下:
·最后一步,使用阶段只需要做两个步骤:添加Starter依赖、设置属性配置
在application.properties中配置host和port,这个属性会自动绑定到RedissonProperties中定义的
属性上。
至此,一个非常简易的手写Starter就完成了,建议大家看完这段内容之后,基于对这块内容的理解尝试自己写一个Starter组件,以便真正掌握它的原理。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!