欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Boot核心原理分析

程序员文章站 2022-05-04 17:11:12
...

什么是SpringBoot

   简而言之是一个服务于框架的框架,Spring全家桶的配置本身过于繁琐,所以SpringBoot提供了解决
思路,就是约定优于配置。SpringBoot本身并没有新技术,只是对于已有的框架进行封装,达到开箱即用。

约定优于配置

在SpringBoot中的体现在 
    1.Maven的目录结构(默认会以jar方式打包,默认会有resources文件夹)
    2.Spring-boot-starter-web。(内置了Tomcat、resources\{static/templates}去放置静态资源与模板资源)
    3.默认application.properties进行配置

SpringBoot.starter

SpringBootApplication注解

该注解是SpringBoot项目中的核心注解,该注解也是一个复合注解。

@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) })
@ConfigurationPropertiesScan
public @interface SpringBootApplication {  
   @AliasFor(annotation = EnableAutoConfiguration.class)
   Class<?>[] exclude() default {}@AliasFor(annotation = EnableAutoConfiguration.class)
   String[] excludeName() default {};
   @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
   String[] scanBasePackages() default {};
   @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
   Class<?>[] scanBasePackageClasses() default {};
   @AliasFor(annotation = Configuration.class)
   boolean proxyBeanMethods() default true;

}
在这些注解中有几个有过Spring基础的都不会陌生: 
   1.ComponentScan
   2.EnableAutoConfiguration
   3.Configuration(SpringBootConfiguration)
这三个注解的功能也就决定了SpringBootApplication注解的功能,接下来分别看下这三个注解

Configuration

    在以前使用IOC的时候需要基于xml来配置bean依赖关系,在Spirng3的时候提供了JavaConfig,
即任何一个标注了@Configuration的java类定义为以个JavaConfig配置类,在该类中的任何标注了
@Bean的方法,该返回值都会作为Bean定义到SpringIOC容器之中,Bean的id默认是方法名。
示例代码
public class DemoOne {
    public void say(){
        System.out.println("Say:hello boot");
    }
}
@Configuration
public class ConfigurationDemo {
    @Bean
    public DemoOne demoOne(){
        return new DemoOne();
    }
}
public class ConfigurationMain {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(ConfigurationDemo.class);
        DemoOne demoOne=applicationContext.getBean(DemoOne.class);
        demoOne.say();
    }
}

ComponentScan

   ComponentScan这个注解在使用过Spring的过程中接触最多了吧,相当于把xml文件中的
<context:componet-scan>,其主要作用就是扫描指定路径下的标识了需要装配的类,自动配置到
SpringIOC容器中。
标识需要装配的类的形式主要是:@Componet、@Reposity、@Service、@Controller这类注解标识类
ComponentScan默认会扫描当前package下的所有加了相关注解标识的类到IOC容器中。
@ComponentScan
public class ConfigurationMain {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(ConfigurationMain.class);
        String[] defName=applicationContext.getBeanDefinitionNames();
        for (String s : defName) {
            System.out.println(s);
        }
    }
}
    需要注意的是AnnotationConfigApplicationContext构造方法中传入的是ComponentScan注解
的类, 并且清除了ConfigurationDemo 中的Bean,如果要扫描其他包在ComponentScan注解中添加
basePackages属性即可,打印结果如下

Spring Boot核心原理分析

EnableAutoConfiguration

在EnableAutoConfiguration定义两个注解
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
在这个注解中@import是用于导入外部资源配置。在该注解中导入了
AutoConfigurationImportSelector.class
而在AutoConfigurationPackage注解中也导入了
AutoConfigurationPackages.Registrar.class
经过前面两个注解的分析,可以想到,SpringBoot的自动配置核心应该是在这两个类之中。
在看这两个类之前,我们前面所介绍的注解都是一个目的,把Bean交由SpringIOC去管理,可是当你想
配置不同的Bean或者根据条件去装载Bean却力有不逮,所以可以想到EnableAutoConfiguration注解与
AutoConfigurationImportSelector、AutoConfigurationPackages.Registrar两个Class目的都是
为了实现动态注入Bean
打开AutoConfigurationImportSelector的类关系图可以发现它顶层是继承了ImportSelector接口。
在此试着自己写一个类来继承该接口,看下该接口的作用。

Spring Boot核心原理分析

public class CacheImprotSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //获取注解原信息
        Map<String, Object>  attributes =
                importingClassMetadata.getAnnotationAttributes(EnableDefineService.class.getName());
        return new String[]{CacheService.class.getName()}; //返回的是固定的CacheService
    }
}

同时自定义了一个注解(仿照EnableAutoConfiguration)

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(CacheImprotSelector.class)
public @interface EnableDefineService {
    //配置一些方法
    Class<?>[] exclude() default {};
}

重新写一个启动方法

@SpringBootApplication
@EnableDefineService(exclude={LoggerService.class})
public class EnableDemoMain {
    public static void main(String[] args) {

       ConfigurableApplicationContext ca=  SpringApplication.run(EnableDemoMain.class,args);
        System.out.println(ca.getBean(CacheService.class));
    }
}
    在CacheImprotSelector类的selectImports打上断点调试,可以发现可以通过
AnnotationMetadata获取注解的原信息,也可以通过
返回new String[]{CacheService.class.getName()}来决定启动哪个bean,
那么在此就已经很明朗了,SpringBoot通过EnableAutoConfiguration中的导入类去控制Bean加载。

Spring Boot核心原理分析

   上面有说导入两个Class,那么另一个——AutoConfigurationPackages.Registrar.class的作用是
 什么呢?Registrar继承了ImportBeanDefinitionRegistrar接口,这个接口是另一种动态装载Bean
 的方式。编写一个类继承ImportBeanDefinitionRegistrar介绍其使用。

Spring Boot核心原理分析
继承接口类实现了registerBeanDefinitions方法

public class LoggerDefinitionRegistart 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);//传入beanName 和Bean
    }
}

在前面的EnableDefineService 注解中重新修改下Import注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({CacheImprotSelector.class,LoggerDefinitionRegistart.class})
public @interface EnableDefineService {
    //配置一些方法
    Class<?>[] exclude() default {};
}

启动方法

@SpringBootApplication
@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));
    }
}

Spring Boot核心原理分析
可以看到两个类都被注入成功,在此也就明了SpringBoot启动时的Bean的自动注入。接下来我们去看一下AutoConfigurationImportSelector源码,这样也就不至于两眼一抹黑了。

@Override
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);//排除已经在注解加过exclude的类
   checkExcludedClasses(configurations, exclusions)
   configurations.removeAll(exclusions);
   configurations = filter(configurations, autoConfigurationMetadata);//过滤
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}
/*
*这是个加载器,它会加载 META-INF/spring.factories下所对应的文件
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
         getBeanClassLoader());
   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;
}

上面两段代码就是为了一个选择性装配,通过loadMetadata读取注解元数据,就是决定要在EnableAutoConfiguration加载的Bean,再通过getAutoConfigurationEntry去条件删选哪些要加载,哪些不用加载。