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的配置类代替了。