• 已删除用户
Administrator
发布于 2021-08-19 / 831 阅读
0

Springboot自动配置

Springboot自动配置

Springboot框架是为了能够帮助使用spring框架的开发者快速高效的构建一个基于spring框架以及spring生态体系的应用解决方案。它是对“约定优于配置”这个理念下的一个最佳实践。因此它是一个服务于框架的架构,服务的范围是简化配置文件。

一、初识Springboot

@SpringBootApplication
public class SpringBootStudyApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootStudyApplication.class, args);
    }
}

二、Springboot四大核心

EnableAutoConfiguration 自动装配
Starter组件, 开箱即用
Actuator 监控
Spring Boot Cli 为Spring Cloud 提供了Spring Boot 命令行功能

三、Enable*注解的作用

Enable是启用的意思,相当于开启某一个功能;

EnableScheduling、EnableHystrix、EnableAsync、EnableAutoConfiguration、EnableWebMvc;

每一个涉及到Enable开头的注解,都会带有一个@Import的注解;

四、深入分析Springboot中的自动装配

在Spring Framework中,最核心的功能是IOC和AOP,ioc容器主要功能是可以管理对象的生命周期,也就是bean的管理。我们把bean对象托管到Spring IOC容器的过程称为装配,那什么是自动装配?

Springboot自动装配是基于EnableAutoConfiguration来实现的。

4.1简单分析@Configuration

@Configuration是JavaConfig形式的基于Spring IOC容器的配置类使用的一种注解。因为SpringBoot本质上就是一个spring应用,所以通过这个注解来加载IOC容器的配置是很正常的。所以在启动类里面标注了@Configuration,意味着它其实也是一个IoC容器的配置类。

Configuration注解的本质是一个Component注解,这个注解会被AnnotationConfigApplicationContext加载,AnnotationConfigApplicationContext是ApplicationContext的一个具体实现,表示根据配置注解启动应用上下文。

因此我们在Main方法中通过AnnotationConfigApplicationContext去加载JavaConfig后,可以得到Ioc容器中的bean的实例。

4.2简单分析@ComponentScan

ComponentScan这个注解是大家接触得最多的了,相当于xml配置文件中的context:component-scan。 它的主要作用就是扫描指定路径下的标识了需要装配的类,自动装配到spring的Ioc容器中。

标识需要装配的类的形式主要是:@Component、@Repository、@Service、@Controller这类的注解标识的类。

4.3Import注解

Import就是把多个分来的容器配置合并在一个配置中,在JavaConfig中所表达的意义是一样的。import注解使用的方式如下:

方式一:直接填class数组

@Configuration
@Import(UserClass.class)
public class DemoConfiguration {

    @Bean
    public DemoClass getDemoClass(){
        return new DemoClass();
    }
}

方式二:ImportSelector方式

如果想要更加的灵活,动态的去加载的话,可以通过Import接口的第二种使用方式,也就是ImportSelector这种方式。

public class GpDefineImportSelector implements ImportSelector {
    /**
     * AnnotationMetadata 注解元数据
     * @param annotationMetadata
     * @return
     *      要被IOC容器加载的bean信息
     */
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        // 我们可以基于注解元数据信息来动态的返回要加载的bean信息
        annotationMetadata
                .getAllAnnotationAttributes(EnableDefineService.class.getName(),true)
        .forEach((k,v)->{
            System.out.println(annotationMetadata.getClassName());
            System.out.println("---> " + k+":" + String.valueOf(v));
        });

        return new String[]{CacheService.class.getName()};
    }
}


@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(GpDefineImportSelector.class)
public @interface EnableDefineService {

    String[] packages() default "";

}


@EnableDefineService()
@SpringBootApplication
public class EnableDemoTest {

    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(EnableDemoTest.class);//SpringApplication.run(EnableDemoTest.class,args);
        System.out.println(ac.getBean(CacheService.class));
        System.out.println(ac.getBean(LogService.class));
    }
}

方式三:ImportBeanDefinitionRegistrar方式

这种方式和第二种方式很相似,同样要实现ImportBeanDefinitionRegistrar接口;

public class GpImportDefinitionRegister implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        // 指定bean的定义信息
        RootBeanDefinition beanDefinition = new RootBeanDefinition(CacheService.class);
        RootBeanDefinition beanDefinition1 = new RootBeanDefinition(LogService.class);
        // 注册一个bean
        beanDefinitionRegistry.registerBeanDefinition("cacheService1111",beanDefinition);
        beanDefinitionRegistry.registerBeanDefinition("cacheService2222",beanDefinition1);

    }
}

4.4深入分析EnableAutoConfiguration原理

了解了ImportSelector和ImportBeanDefinitionRegistrar后,对于EnableAutoConfiguration的理解就容易一些了它会通过import导入第三方提供的bean的配置类:AutoConfigurationImportSelector;

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        try {
// 加载META-INF/spring-autoconfigure-metadata.properties 下的元数据信息
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 获取候选加载的配置信息 META-INF/spring.factories
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 去掉重复的配置信息
            configurations = this.removeDuplicates(configurations);
// 排序
            configurations = this.sort(configurations, autoConfigurationMetadata);
            // 获取 注解中配置的 exclusion 信息
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
// 检查
            this.checkExcludedClasses(configurations, exclusions);
// 移除需要排除的信息
            configurations.removeAll(exclusions);
// 过滤,检查候选配置类上的注解@ConditionalOnClass,如果要求的类不存在,则这个候选类会被过滤不被加载
            configurations = this.filter(configurations, autoConfigurationMetadata);
// 广播事件
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            // 返回要被加载的类数组
return (String[])configurations.toArray(new String[configurations.size()]);
        } catch (IOException var6) {
            throw new IllegalStateException(var6);
        }
    }
}

本质上来说,其实EnableAutoConfiguration会帮助Springboot应用把所有符合@Configuration配置都加载到当前Springboot创建的IOC容器中,而这里面借助了Spring框架提供的一个工具类SpringFactoriesLoader的支持。以及用到了Spring提供的条件注解@Conditional,选择性的针对需要加载的bean进行条件过滤。

4.5SpringFactoriesLoader

SpringFactoriesLoader这个工具类的作用是从classpath/META-INF/spring.factories文件中,根据key来加载对应的类到Spring IOC容器中。

4.6深入理解条件过滤

在分析AutoConfigurationImportSelector的源码时,会先扫描spring-autoconfiguration-metadata.properties文件,最后在扫描spring.factories对应的类时,会结合前面的元数据进行过滤,为什么要过滤呢? 原因是很多的@Configuration其实是依托于其他的框架来加载的,如果当前的classpath环境下没有相关联的依赖,则意味着这些类没必要进行加载,所以,通过这种条件过滤可以有效的减少@configuration类的数量从而降低SpringBoot的启动时间。