1.Springboot的自动装配
1.1 组件装配
Spring Framework
本身有一个IOC
容器,该容器会统一管理其中的Bean
对象,Bean
对象可以理解为组件,要理解组件装配,首先要理解组件的概念。
1.1.1 组件
基于Spring Framework
的应用在整合第三方技术时,要把第三方框架中的核心API
配置到Spring Framework
的配置文件或注解配置类中,以供Spring Framework
统一管理。此处的配置是关键,通过编写XML
配置文件或注解配置类,将第三方框架中的核心API
以对象的形式注册到IOC
容器中。这些核心API
对象会在适当的位置发挥其作用,以支撑项目的正常运行。IOC
容器中的核心API
对象本身就是一个个的bean
对象,即组件;将核心API
配置到XML
配置文件或注解配置类的行为称为组件装配。
Spring Framework
本身只有-一种组件装配方式,即手动装配,而Spring Boot
基于原生的手动装配,通过模块装配+条件装配+SPI机制,可以完美实现组件的自动装配。手动装配指的是开发者在项目中通过编写XML
配置文件、注解配置类、配合特定等方式将所需的组件注册到IOC
容器中, 即ApplicationContext
中。Springboot
中的核心特性是组件的自动装配,自动装配的核心是,本应该由开发者编写的配置,转为框架自动根据项目中整合的场景依赖,合理地做出判断并装配合适的Bean
到IOC
容器中。
1.2 Spring Framework 的模块装配
1.2.1 @Import注解
可以参考 Springboot
核心注解和基本配置解读_山河亦问安的博客-CSDN博客
1.2.2 BeanDefinition
BeanDefinition
是定义 Bean
的配置元信息接口,这里举例通过BeanDefinition
来进行Bean
对象的创建。代码如下:
DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory(); BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class).addPropertyValue("username", "admin"); defaultListableBeanFactory.registerBeanDefinition("user01",beanDefinitionBuilder.getBeanDefinition()); User user01 = defaultListableBeanFactory.getBean("user01", User.class); System.out.println(user01); RootBeanDefinition beanDefinition = new RootBeanDefinition(User.class); //构造参数 ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues(); constructorArgumentValues.addIndexedArgumentValue(0, "root"); beanDefinition.setConstructorArgumentValues(constructorArgumentValues); defaultListableBeanFactory.registerBeanDefinition("user02", beanDefinition); User user2 = defaultListableBeanFactory.getBean("user02", User.class); System.out.println(user2);
1.3 Spring Framework 的条件装配
1.3.1 @Profile
@Profile
注解可以标注在组件上,当一个配置属性激活的时候,它才会起作用。Profile
提供了一种基于环境的配置,根据当前项目的不同运行的环境,可以动态的注册与当前运行环境匹配的组件。
使用@Profile:
@Profile("test") public class User { private String username; }
为ApplicationContext
设置Profile
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(); annotationConfigApplicationContext.getEnvironment().setActiveProfiles("test"); annotationConfigApplicationContext.register(User.class); annotationConfigApplicationContext.refresh(); User bean = annotationConfigApplicationContext.getBean(User.class); System.out.println(bean);
AnnotationConfigApplicationContext
在创建对象的时候,如果直接传入了配置类,则会立即初始化IOC
容器,在不传入配置类的情况下,内部不会执行初始化逻辑,而是要等到手动调用其refresh
方法后才会初始化IOC
容器,在初始化的过程中会顺便将环境配置一并处理。
1.3.2 @Conditional
可以参考 Springboot
核心注解和基本配置解读_山河亦问安的博客-CSDN博客
判断是否存在的条件判断类:
public class MyConditional implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return context.getBeanFactory().containsBeanDefinition(Admin.class.getName()); } }
上面的matches
方法中使用BeanDefinition
而不是Bean
做判断,这是因为考虑的是当条件匹配时Admin
对象可能尚未创建,导致条件匹配出现偏差。
1.3.3 MetaData元数据接口(补充)
元数据是关于数据的数据,元数据是数据的描述和上下文,它有助于组织,查找,理解和使用数据。
AnnotatedTypeMetadata
这个接口表示的是注解元素(AnnotatedElement
)的元数据,只要能在上面标注注解的元素都属于注解元素。
public interface AnnotatedTypeMetadata { // 此元素是否标注有此注解,annotationName:注解全类名 boolean isAnnotated(String annotationName); //取得指定类型注解的所有的属性 - 值(k-v) // annotationName:注解全类名 // classValuesAsString:若是true表示 Class用它的字符串的全类名来表示。这样可以避免Class被提前加载 @Nullable Map<String, Object> getAnnotationAttributes(String annotationName); @Nullable Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString); }
AnnotationMetadata
它是ClassMetadata
和AnnotatedTypeMetadata
的子接口,具有两者共同能力,并且新增了访问注解的相关方法。可以简单理解为它是对注解的抽象。
public interface AnnotationMetadata extends ClassMetadata, AnnotatedTypeMetadata { //拿到当前类上所有的注解的全类名(注意是全类名) Set<String> getAnnotationTypes(); // 拿到指定的注解类型 //annotationName:注解类型的全类名 Set<String> getMetaAnnotationTypes(String annotationName); // 是否包含指定注解 (annotationName:全类名) boolean hasAnnotation(String annotationName); //这个厉害了,用于判断注解类型自己是否被某个元注解类型所标注 //依赖于AnnotatedElementUtils#hasMetaAnnotationTypes boolean hasMetaAnnotation(String metaAnnotationName); // 类里面只有有一个方法标注有指定注解,就返回true //getDeclaredMethods获得所有方法, AnnotatedElementUtils.isAnnotated是否标注有指定注解 boolean hasAnnotatedMethods(String annotationName); // 返回所有的标注有指定注解的方法元信息。注意返回的是MethodMetadata 原理基本同上 Set<MethodMetadata> getAnnotatedMethods(String annotationName); }
MethodMetadata
public interface MethodMetadata extends AnnotatedTypeMetadata { //方法名 String getMethodName(); //定义方法的类全限定名 String getDeclaringClassName(); //返回的值全限定名 String getReturnTypeName(); }
MetadataReader
public interface MetadataReader { /** * 返回class文件的IO资源引用 */ Resource getResource(); /** * 为基础class读取基本类元数据,返回基础类的元数据。 */ ClassMetadata getClassMetadata(); /** *为基础类读取完整的注释元数据,包括注释方法的元数据。返回基础类的完整注释元数据 */ AnnotationMetadata getAnnotationMetadata(); }
MetadataReaderFactory
MetadataReaderFactory
接口 ,MetadataReader
的工厂接口。允许缓存每个MetadataReader的元数据集。
public interface MetadataReaderFactory { /** * 根据class名称创建MetadataReader */ MetadataReader getMetadataReader(String className) throws IOException; /** * 根据class的Resource创建MetadataReader */ MetadataReader getMetadataReader(Resource resource) throws IOException; }
1.4 SPI机制
Spring
中的SPI
相较于JDK
原生的SPI
更加的高级实用,因为它不仅限于接口或者抽象类,可以是任何一个类,接口和注解。在Springboot
中大量用到SPI
机制加载自动配置类和特殊组件。
声明SPI
文件
将SPI
文件放在项目的META-INF
目录下,且文件名必须为spring.factories
。代码如下:
com.example.demo.entity.User=\ com.example.demo.entity.User
spring.factories
中被检索的类,接口和注解的全限定名作为key
,具体要检索的类的全限定名作为value
,多个类之间用逗号进行分割。
测试获取
List<User> users = SpringFactoriesLoader.loadFactories(User.class, Demo2Application.class.getClassLoader()); for (User user : users) { System.out.println(user); }
1.5 Springboot 的装配机制
Springboot
中的自动装配其实就是模块装配+条件装配+SPI机制的组合使用
@SpringbootApplication
的组成可以参考 Springboot
核心注解和基本配置解读_山河亦问安的博客-CSDN博客
这里主要讲解@EnableAutoConfiguration
,点开@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 {}; }
@AutoConfigurationPackage
注解本身组合了一个@Impor
t注解
@Import({AutoConfigurationPackages.Registrar.class})
@AutoConfiguration
注解所做的是将主启动类所在的包记录下来,注册到AutoConfigurationPackages
中。在Springboot 2.3.0
版本后,注解中多了两个属性,可以手动指定应用的根包路径,如下:
String[] basePackages() default {}; Class<?>[] basePackageClasses() default {};
简单介绍了@AutoConfigurationPackage
注解本身,下面把目标转移到导人的内部类
AutoConfigurationPackages.Registrar.class
上。 Registrar
本身是一ImportBeanDefinitionRegistrar
,它的作用是以编程式向IOC
容器中注册 bean
对象,而Registrar
要注册的对象实际上是默认主启动类所在的包路径(也就是@AutoConfiqurationPackage
注解要记录的根包)。Registrar
代码如下:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { Registrar() { } public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0])); } }
debug
如下:
注意register
方法的第二个参数,它利用PackageImports
导出了一组包名.而包名的来源是一个AnnotationMetadata
,这个AnnotationMetadata
本质上可以理解为AutoConfiaurationPackage
注解本身。换句话说,这个 PackageImports
类提取出@AutoConfigurationPackage
注解中定义的两个属性( baserackages
与basePackageClasses
)。实际上源码也是如此,它做了一个约定大于配置的设计,PackageImports
的构造方法中会先提取注解中的这两个属性,如果两个属性都没有定义会提取主启动类所在的包名。
@Import({AutoConfigurationImportSelector.class}):
将AutoConfigurationImportSelector
这个类导入到spring
容器中,AutoConfigurationImportSelector
可以帮助springboot
应用将所有符合条件的@Configuration
配置都加载到当前SpringBoot
创建并使用的IOC
容器(ApplicationContext
)中。主干逻辑只有三步:加载自动配置类,移除被去掉的自动配置类,封装Entry
返回。这里加载自动配置类的动作就是利用 Spring Framework
的 SPI
机制.从spring.factories
中提取出所有@EnableAutoConfiguration
对应的配置值。可以通过三种途径移除被去掉的自动配置类:@springBootApplication
或@EnableAutoConfiguration
注解的exclude
、excludeName
属性,以及全局配置文件的spring.autoconfigure.exclude
属性配置。底层源码会提取出这三个位置配置的自动配置类并移除。代码如下:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } else { //加载注解属性配置 AnnotationAttributes attributes = this.getAttributes(annotationMetadata); //加载自动配置类 List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes); //获取显式配置了要移除的自动配置类 configurations = this.removeDuplicates(configurations); Set<String> exclusions = this.getExclusions(annotationMetadata, attributes); this.checkExcludedClasses(configurations, exclusions); //移除要删除的自动配置类 configurations.removeAll(exclusions); configurations = this.getConfigurationClassFilter().filter(configurations); this.fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); } }
springboot
底层实现自动配置的步骤是:
1.@SpringBootApplication
起作用;
2.@EnableAutoConfiguration
;
3.@AutoConfigurationPackage
:这个组合注解主要是@Import(AutoConfigurationPackages.Registrar.class)
,它通过将Registrar
类导入到容器中,而Registrar
类作用是扫描主配置类同级目录以及子包,并将相应的组件导入到springboot
创建管理的容器中;
4.@Import(AutoConfigurationImportSelector.class)
:它通过将AutoConfigurationImportSelector
类导入到容器中,AutoConfigurationImportSelector
类作用是通过selectImports
方法实现将配置类信息交给SpringFactory
加载器进行一系列的容器创建过程。