SpringBoot原理总结
- 在我们的学习springboot的时候,我们需要去了解他的原理和底层,这样有助于我们对springboot的了解和掌握,个人总结部分内容供大家参考。
一.spring的bean加载方式
- 首先我们回顾我们之前学习spring的时候加载bean的几种方式.
第一种方式使用xml的形式注册bean:
例:
<bean id=" 自定义id名" class="类路径">
<properties="类属性名" value="值"/>
<properties="类属性名" ref="引用数据类型"/>
</bean>
第二种是使用扫描包的形式bean:
在xml中配置 <context:component-scan base-package="类包名">
@Component
public class Cat {
private String name;
private int age;
}
然后在实体类上面使用对应的注解如@component即可将Bean注入ioc容器
第三种是脱离配置文件使用配置类的形式:
@Configuration
public class MyConfigruation {
@Bean
public Cat getCat(){
return new Cat();
}
}
@Configruation 代表该类是一个配置类 @Bean代表要注入ioc容器的类对象
第四种方式是使用@Import注解将Bean加载进ioc容器
@Import({Cat.class})
public class Configruation01{
}
public class Cat {
private String name;
private int age;
}
类上面无需使用@Component注解就可以将Bean加载进ioc容器
二:学习了SpringBoot后个人发现注册的方式有很多种这里只列出常见的
第五种是一种是将配置文件注册的Bean使用注解的方式加载进我们的配置类中通过配置类加载:
@ImportResource("xxx.xml")
public class Configruation01{
}
第六种是实现ImportServlet接口然后重写electorimports方法
public class ImportSelectorImpl implements ImportSelector {
@Override
//AnnotationMetadata这个类里面封装了导入他的配置类的注释信息等...
//这个方法可以根据条件动态的选择要加载的Bean
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
boolean b = importingClassMetadata.hasAnnotation("org.springframework.context.annotation.Import");
if(b==true){
return new String[]{"com.itheima.test.pojo.Cat"};
}else {
return new String[]{"com.itheima.test.pojo.Dog"};
}
}
}
然后使用注解@import将ImportSelectorImpl加载进配置类然后加载进ioc容器
@Import({ImportSelectorImpl.class})
public class Configruation01{
}
第七种是实现ImportDefinitionRegistry接口然后重写registerBeanDefinitions方法
class ImportDefinitionRegistryImpl implements ImportBeanDefinitionRegistrar{
//AnnotationMetadata这个类里面封装了导入他的配置类的注释信息等...
//这个方法可以根据条件动态的选择要加载的Bean
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//可以对我们即将注入的bean进行一系列的操作设置属性 如设置单例或多例模式等等...
BeanDefinition beanDefinition= BeanDefinitionBuilder.rootBeanDefinition(Cat.class).getBeanDefinition();
registry.registerBeanDefinition(bean的名称,beanDefinition);
}
}
然后使用注解@import将ImportSelectorImpl加载进配置类
@Import({ImportDefinitionRegistryImpl .class})
public class Configruation01{
}
第八种是实现BeanDefinitionRegistryPostProcessor接口并重写 postProcessBeanDefinitionRegistry方法
class BeanDefinitionRegistryPostProcessorImpl implements BeanDefinitionRegistryPostProcessor{
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
//可以对我们即将注入的bean进行一系列的操作设置属性 如设置单例或多例模式等等...
BeanDefinition beanDefinition= BeanDefinitionBuilder.rootBeanDefinition(Cat.class).getBeanDefinition();
registry.registerBeanDefinition(bean的名称,beanDefinition);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
//这个方法暂时不用关注
}
}
然后使用注解@import将ImportSelectorImpl加载进配置类
@Import({ImportDefinitionRegistryImpl .class,其他bean})
public class Configruation01{
}
在这里有一个点需要注意这个加载bean的方式是后加载,如果配置了registerBeanDefinition方法中注册了
同名和相同的Class对象的bean 那么在使用@import往配置类导入的过程中会覆盖ioc容器中原有的bean
第九种是应用spring上下文对象的register方法将bean加载进容器
@SpringBootTest
class TestApplicationTests {
@Test
void contextLoads() {
AnnotationConfigApplicationContext applicationContext = new
AnnotationConfigApplicationContext(Configruation01.class);
applicationContext.register(类名.class);
}
}
//这一种方式是在spring容器初始化之后再使用上下文对象注入bean的
二:bean的加载控制
编程式加载控制:
//实现这些接口可以根据条件动态的控制我们的bean加载
1:importselector
2:ImportDefinitionRegistry
3:ImportDefinitionRegistryPostPoressor
4:BeanDefinitionRegistryPostPoressor
5:使用上下文对象添加bean
注解式加载控制
注解方式动态的加载bean有点多,这里就写几个常用的感兴趣的可以springboot@Conditional源码
使用这些注解需要导入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
注意:如果多个加载条件注解搭配使用的话,那么条件需要都成立的情况下才会去加载对应的类
@ConditionalOnClass(name={"类路径",....})//如果当前环境有该类,那么将被注解的类注入ioc容器
@ConditionalOnMissingClass({"类路径",....})如果没有该类那么会将被注解的类注入ioc容器
@ConditionalOnBean(name={"类路径",....})如果ioc容器中有该bean那么将被注解的类注入ioc容器
@ConditionalOnNotWebAppliaction 如果当前环境不是web项目就会加载被注解的类注入ioc容器
@ConditionalOnWebAppliaction 如果当前环境是web项目就会加被注解的累注入ioc容器
EnableConfigruationProperties(类名.class)强制设置某个类加载成为bean
@Enablescheduling开启定时器,一般放在配置类上面会自动加载
@Schculed(cron="0/5 * * * * ?")一般放在方法上,配置每几秒执行一次方法
三:@SpringBootApplication启动类
@Target(ElementType.TYPE) //这注解是标记只在类上使用
@Retention(RetentionPolicy.RUNTIME)//运行时可见
@Documented //文档
@Inherited
@SpringBootConfiguration//底层就是@Configuration配置类注
@EnableAutoConfiguration//重点注解,自动装配
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })//包扫描,过滤哪些
@EnableAutoConfiguration注解
点击进入@EnableAutoConfiguration注解,底层依旧是组合注解,它将告诉Spring boot开启自动配置,以前需要配置的东西现在都不需要自动配置了。而@AutoConfigurationPackage是重点注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)//重点注解,导入bean
public @interface EnableAutoConfiguration {
AutoConfigurationImportSelector.Registrar静态类
AutoConfigurationPackages.Registrar.class这个类是AutoConfigurationPackages类下边的一个静态类。目的就是拿到主配置类所在包及子包下的所有注解所有bean信息,然后放入容器。
//注册bean的定义信息,通过这个方法拿到注解类的信息和注解的信息
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override //AnnotationMetadata metadata这个参数是获取注解的原数据
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName()/** 该注解所在包名 */);
}
//此处打断点调试
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}
}
然后再回来看@EnableAutoConfiguration注解怎么实现自动装配
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)//自动导入组件的选择器,
public @interface EnableAutoConfiguration {
重点是AutoConfigurationImportSelector.class类
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
AutoConfigurationImportSelector.class实现了DeferredImportSelector接口,DeferredImportSelector接口继承ImportSelector接口、public interface DeferredImportSelector extends ImportSelector,还有BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered等接口,通过他们几乎可以获取整个spring底层所有东西。
找到selectImports方法,方法处打断点逐步运行。
@Override
//方法的到String数组,就是全类名,此处打点debug,后续逐步运行,
public String[] selectImports(AnnotationMetadata annotationMetadata /** 这个参数是获取注解的原信息 */) {
if (!isEnabled(annotationMetadata)) {//没有自动装备的注解
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry/**获取配置类,点进去*/(autoConfigurationMetadata,
annotationMetadata);
//将所有需要导入容器的组件全部以全类名的方式导入容器
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations/**再点进去*/(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames/**主要是这个方法,再进去*/(getSpringFactoriesLoaderFactoryClass(),/**参数一,进去得到EnableAutoConfiguration.class*/
getBeanClassLoader()/**参数二,进去得到this.beanClassLoader*/);//使用类加载器机制
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader)/**再进去*/.getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
//通过类加载器得到一个资源,然后urls
try {
//配置文件位置public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION/**这里点进去得到配置文件位置*/):
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
//再通过资源得到一个properties配置文件,可见每一个配置类斗鱼配置文件相关联。
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
配置文件位置public static final String FACTORIES_RESOURCE_LOCATION = “META-INF/spring.factories”;就是从指定位置META-INF/spring.factories获取EnableAutoConfiguration类指定的值。导出的类是通过SpringFactoriesLoader.loadFactoryNames()读取了ClassPath下面的META-INF/spring.factories文件。
@EnableAutoConfiguration最后会给容器导入很多自动配置类,给容器中导入场景需要的所有自动配置类xxAutoConfiguration
总结: 简而言之就是@EnableAutoConfiguration注解可以帮springboot把所有符合条件的配置都加载到当前springboot创建并使用的IOC容器,其中springboot的原有的一个工具类springFactoriesLoader就发挥了很大的作用。有了自动配置类,就不用手动书写配置类和注入功能组件,底层被spring boot的配置类代替了。