Spring Boot核心原理分析--自动配置

什么是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属性即可,打印结果如下

在这里插入图片描述

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接口。
在此试着自己写一个类来继承该接口,看下该接口的作用。

在这里插入图片描述

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加载。

在这里插入图片描述

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

在这里插入图片描述
继承接口类实现了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));
    }
}

在这里插入图片描述
可以看到两个类都被注入成功,在此也就明了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去条件删选哪些要加载,哪些不用加载。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值