美文网首页
spring boot原理-自动装配

spring boot原理-自动装配

作者: 三不猴子 | 来源:发表于2020-11-18 00:19 被阅读0次

    spring boot原理-自动装配

    什么是spring boot?

    服务于spring框架的框架,约定由于配置,提供各种开箱即用的组件,内置Tomcat、模板引擎、提供默认application.properties。可以快速构建一个web应用。

    我们如何在spring boot中动态装配bean?

    基于注解驱动实现

    先定义一个Bean:

    @Configuration
    public class HelloWorldConfiguration {
     
        @Bean
        public String helloWorld() { // 方法名即 Bean 名称
            return "Hello,World";
        }
    }
    

    然后再定义一个注解,使用@Import导入刚刚定义的bean:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Import(HelloWorldConfiguration.class)
    public @interface EnableHelloWorld {
    }
    

    完成上面两步之后,我们只要用@EnableHelloWorld标注在某个类上时,这个Bean就会加载到Spring容器中。

    @EnableHelloWorld
    public class EnableHelloWorldBootstrap {
        public static void main(String[] args) {
            ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableHelloWorldBootstrap.class)
                    .web(WebApplicationType.NONE)
                    .run(args);
            // helloWorld Bean 是否存在
            String helloWorld =
                    context.getBean("helloWorld", String.class);
            System.out.println("获取到的bean: " + hello);
            // 关闭上下文
            context.close();
        }
    }
    

    基于接口驱动实现

    1. 实现ImportSelector接口,实现它的selectImports方法,返回的是一个string类型的数组,数组里面存放的类名:
    public class HelloWorldImportSelector implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[] {HelloWorldConfiguration.class.getName()
            };   
      }
    

    然后将上面定义的注解改一下:

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    //@Import({HelloWorldConfiguration.class})  enable注解驱动的方式
    @Import({HelloWorldImportSelector.class})  //接口编程的方式
    public @interface EnableHelloWorld {
    }
    

    可以看到,基于接口的方式实现更加灵活,我们可以在selectImports中做一些判断,根据需要返回不同的类名数组,然后再根据类名进行装配。

    1. 实现 ImportBeanDefinitionRegistrar接口,重写 registerBeanDefinitions

    Spring Boot自动装配核心

    我们再看一下@EnableAutoConfiguration这个注解:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
        String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    }
    

    再点进@AutoConfigurationPackage看一下

    @Target(ElementType.TYPE) 
    @Retention(RetentionPolicy.RUNTIME) 
    @Documented 
    @Inherited 
    @Import(AutoConfigurationPackages.Registrar.class) 
    public @interface AutoConfigurationPackage {
      
    }
    

    使用@Import来给Spring容器中导入一个组件 ,这里导入的是Registrar.class

    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }
    
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
        }
    
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
        }
    }
    

    运行springboot项目,debug看一下,可以发现metadata是被@SpringBootApplication标注的类,再看看new PackageImport(metadata).getPackageName()的值:这个值就是扫描的包路径,也就是说,默认扫描的包路径是引导类所在的包以及子包。

    接着我们再看EnableAutoConfiguration也使用了@import,导入了 AutoConfigurationImportSelector.class,从这个类的名字我们就可以知道,它指定是实现了ImportSelector接口,重写了selectImports方法,所以我们直接看selectImports:

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }
    

    可以看到返回结果是通过autoConfigurationEntrygetConfigurations()获取的,所以我们直接看getAutoConfigurationEntry方法:

    protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }
    

    同理,这里我们进入getCandidateConfigurations方法看一下:

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
    }
    

    最终加载的配置类会从META-INF/spring.factories中获取,也就是说,Spring Boot在启动的时候会从类路径下的META-INF/spring.factories文件中将指定的值作为配置类导入到容器中,也就实现了自动配置。通过META-INF/spring.factoriesspring boot还实现了SPI(Service Provide Interface)的机制。

    条件装配

    条件装配注解有两种:

    Spring注解 场景说明 起始版本
    @Profile 配置化条件装配 3.1
    @Conditional 编程条件装配 4.0

    在4.0之后@profile也变成了@Conditional来实现

    应用场景:一般用于生产环境和开发环境之间的切换,就是在类或者方法上添加注解并设置环境标识比如java7java8,我们以一个简单的多整数求和来演示如何使用

    @Profile实现条件装配

    首先定义一个接口:

    public interface CalCulateService {
        /**
         * 整数求和
         * @param args
         * @return
         */
        Integer sum(Integer... args);
    }
    复制代码
    

    然后分别实现一个dev和test的求和方法。

    首先是dev的实现:

    /**
     * dev的方式实现求和
     */
    @Profile("dev")
    @Service
    public class DevCalCulateServiceImpl implements CalCulateService {
        @Override
        public Integer sum(Integer... args) {
            int sum = 0;
            for (int i = 0; i < args.length; i++) {
                sum+=args[i];
            }
            return sum;
        }
    }
    

    然后是test的实现:

    @Profile("test")
    @Service
    public class TestCalCulateServiceImpl implements CalCulateService {
        @Override
        public Integer sum(Integer... args) {
            int sum = Stream.of(args).reduce(0,Integer::sum);
            return sum;
        }
    }
    复制代码
    

    然后创建一个启动类,在启动容器的时候,使用.profiles("test")来指定使用哪个版本来计算:

    @SpringBootApplication(scanBasePackages = "com.lingxiao.springboot.service")
    public class CalCulateServiceBootstrap {
    
        public static void main(String[] args) {
            ConfigurableApplicationContext applicationContext =
                    new SpringApplicationBuilder(CalCulateServiceBootstrap.class)
                            .web(WebApplicationType.NONE)
                            .profiles("test")
                            .run(args);
            CalCulateService calCulateService = applicationContext
                    .getBean(CalCulateService.class);
            applicationContext.close();
        }
    }
    

    @Conditional实现条件装配

    梳理一下整个流程:

    1. 使用@EnableAutoConfiguration激活自动装配,Spring Boot会去spring.factories文件中解析需要自动装配的类 HelloWorldAutoConfiguration
    2. 装配HelloWorldAutoConfiguration的时候会去判断是否满足装配要求,这里是jdk1.8的环境,所以是满足要求的
    3. HelloWorldAutoConfiguration是被@EnableHelloWorld标注了,@EnableHelloWorld通过@Import({HelloWorldImportSelector.class})的方式引入了HelloWorldImportSelector
    4. HelloWorldImportSelectorselectImports方法中返回了HelloWorldConfiguration的类名
    5. HelloWorldConfiguration中加载helloWorld

    相关文章

      网友评论

          本文标题:spring boot原理-自动装配

          本文链接:https://www.haomeiwen.com/subject/julqiktx.html