精华内容
下载资源
问答
  • springBoot自动装配原理
    2021-01-28 10:30:00

    springboot的自动装配就是通过自定义实现importSelector接口,

    从而导致项目启动时会自动将所有项目META-INF/spring.factories文件中所配置的配置类注入到spring容器中,从而实现了自动装配。

    我们去查看springBoot的源码,在项目启动类上有一个注解@SpringBootApplication,点进去

    ,有一个@EnableAutoConfiguration注解,继续点进去,有一个@Import(AutoConfigurationImportSelector.class)注解,

    @Import注解就是给spring容器导入了一些组件,这里传入了一个组件选择器 AutoConfigurationImportSelector,

    里面有一个selectImport方法,将所有需要导入的组件以全类名的方式返回。

    这些组件就会被加入到容器中。

    还有一些比较重要的方法,getAutoConfigurationEntity,这个方法里就是自动装配的逻辑,

    点进去,会看到它在加载配置,它在加载一些类,通过SpringFactoriesLoader.loadFactoryNames();

    这个方法扫描所有jar包类路径下META-INF/spring.factories文件,

    把扫描到的这些文件内容包装成properties对象,

    从properties中获取到EnableAutoConfiguration指定的值,

    将这些值作为自动配置类导入到容器中,自动配置类就生效了,帮我们进行自动配置工作。

    以前spring开发都需要我们自己配置,现在自动配置类都帮我们完成了。

    更多相关内容
  • SpringBoot自动装配原理

    2021-01-21 17:56:26
    SpringBoot自动装配原理一. @SpringBootApplication注解1.1 @Target注解1.2 @Retention注解1.3 @Documented注解二. @EnableAutoConfiguration2.1 @AutoConfigurationPackage注解案例1:Registrar的注册2.2 ...

    一般我们搭建一个SpringBoot框架的时候,我们肯定是要有一个启动类,并且为其添加上注解@SpringBootApplication,启动类主要负责SpringBoot项目的启动。以下案例是一个标准的启动类:

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

    那么接下来来说一说,在SpringBoot项目启动的时候,SpringBoot是如何完成自动装配的。

    一. @SpringBootApplication注解

    注解@SpringBootApplication由多个子注解来组成,其内容包含:

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

    再开始讲自动装配流程之前,这里先对上面涉及到的个别注解进行一个补充。

    1.1 @Target注解

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Target
    

    @Target主要用来设定注解的适用范围,并且通过ElementType枚举类来指定注解可以使用的范围。包含的值有:

    属性注解使用范围
    TYPE类,接口上或者枚举申明
    FIELD字段申明和枚举常量
    METHOD方法
    PARAMETER参数
    CONSTRUCTOR构造方法
    LOCAL_VARIABLE局部变量
    ANNOTATION_TYPE注解类型上
    PACKAGE安装包申明
    TYPE_PARAMETER类型参数申明
    TYPE_USE使用一种类型

    1.2 @Retention注解

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Retention 
    

    @Retention主要是用来设置注解保留的时间,主要通过RetentionPolicy来设置注解的生命周期,其中值有三种:

    属性注解使用范围
    SOURCE注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃
    CLASS注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期
    RUNTIME注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在

    1.3 @Documented注解

    @Documented注解表明这个注解是由 javadoc记录的。 如果一个类型声明被文档化,它的注解成为公共API的一部分。

    @SpringBootApplication注解中和自动配置有关的最重要的注解有两个:

    1. @EnableAutoConfiguration
    2. @Import({AutoConfigurationImportSelector.class})

    接下来从@EnableAutoConfiguration注解开始分析。

    二. @EnableAutoConfiguration

    @EnableAutoConfiguration注解的完整结构如下:

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import({AutoConfigurationImportSelector.class})
    public @interface EnableAutoConfiguration {
        String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
        Class<?>[] exclude() default {};
    
        String[] excludeName() default {};
    }
    

    可以发现,他的前缀名是Enable。一般情况下,如果是基于JavaConfig的形式对Bean实现装载,那么必须要使用以下两个注解:

    1. @Configuration
    2. @Bean

    而@Enable的本质就是对上面两个注解进行封装。 这里举个例子,拿Spring自带的定时器@EnableScheduling来说:

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

    最重要的是@Import注解,他负责将大括号里面的类进行导入,从而实现自动装配,那么做一个类比可以发现,SpringBoot自动装配的原理大致如下:

    1. 启动类上带有一个@SpringBootApplication注解。
    2. @SpringBootApplication中包含@EnableAutoConfiguration注解。(Enable开头)
    3. @EnableAutoConfiguration通过@Import注解将各种项目中应用到的类进行导入。(比如@EnableScheduling中导入SchedulingConfiguration.class)这里是AutoConfigurationImportSelector类,负责筛选。

    2.1 @AutoConfigurationPackage注解

    @EnableAutoConfiguration中还引入了@AutoConfigurationPackage注解。
    以下是该注解的完整结构:

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import({Registrar.class})
    public @interface AutoConfigurationPackage {
        String[] basePackages() default {};
    
        Class<?>[] basePackageClasses() default {};
    }
    

    这里可以发现,该注解通过@Import导入了Registrar类,在项目启动的时候,主要调用了Registrar类的registerBeanDefinitions()方法,我们来看一下这个类:

    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    	// 空构造器
        Registrar() {
        }
    	/**
    	 * metadata 启动类的元信息
    	 * registry Bean注册器
    	 */
    	// 该方法主要是用来注册Bean的,注意,Bean一般都是封装成BeanDefinition来使用的
    	// 封装成BeanDefinition有利于对Bean进行一些操作,比如Name的设置、生命周期的设置等等
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
        }
    
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
        }
    }
    

    这里我们根据一个案例来理解这个类:

    案例1:Registrar的注册

    // 启动类
    @SpringBootApplication
    public class DemoApplication {
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    }
    // Controller
    @Controller
    public class MyController {
    }
    

    目录结构如下:
    在这里插入图片描述
    打下断点后发现:
    元信息如下:
    在这里插入图片描述
    再来看下这个方法:

    AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
    

    这里就通过getPackageNames()方法来获取元信息中启动类所在路径的包名:
    在这里插入图片描述
    AutoConfigurationPackages.register这个方法的作用可以用一句话来概括:根据传入的注册器和包名来注册该包名及其子包下所有需要注册并且实例化的Bean。

    如下图,该方法将项目中所需用到的所有Bean都放到一个Map中:
    在这里插入图片描述
    这里对@AutoConfigurationPackage注解进行一个小总结就是:

    1. 该类的主要作用是用作包扫描自动配置。
    2. 通过导入Registrar类,将包路径(及子路径)下的Bean扫描并且注册到BeanFactory中

    2.2 AutoConfigurationImportSelector类

    接下来再看看@EnableAutoConfiguration注解中导入的类:AutoConfigurationImportSelector
    AutoConfigurationImportSelector实现了ImportSelector接口,这个接口中有一个抽象方法:

    public interface ImportSelector {
    	// 指定需要装配到IOC容器的类,当在@Import中导入一个ImportSelector的实现类后
    	// 就会把该实现类中返回的Class名称都装载到IOC容器里。
    	// 通过以String[] 数组的形式,意义为:支持Bean的批量装配。
    	// 并且可以通过上下文来决定哪些类能够被IOC容器初始化。
        String[] selectImports(AnnotationMetadata var1);
    }
    

    这里再通过案例的形式来了解这个选择器的使用:

    案例2:ImportSelector的使用

    项目结构如下:
    在这里插入图片描述
    定义两个Bean:

    public class FirstBean {
        private String name = "张三";
        private Integer age = 10;
    
        @Override
        public String toString() {
            return "FirstBean{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
    public class SecondBean {
        private String name = "李四";
        private Integer age = 20;
    
        @Override
        public String toString() {
            return "FirstBean{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    

    自定义的Selector:

    public class MyImportSelector implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata annotationMetadata) {
            return new String[]{FirstBean.class.getName(), SecondBean.class.getName()};
        }
    }
    

    自定义注解:

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

    启动类:

    @SpringBootApplication
    @MyEnableAutoImport
    public class DemoApplication {
    
        public static void main(String[] args) {
        	// 获取上下文对象
            ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
            // 获得容器中的Bean
            FirstBean bean1 = context.getBean(FirstBean.class);
            SecondBean bean2 = context.getBean(SecondBean.class);
            System.out.println(bean1.toString());
            System.out.println(bean2.toString());
        }
    }
    

    结果如下:
    在这里插入图片描述
    在了解这个ImportSelector的用法后,回归自动装配,我们可以推测:自动装配这个过程也就是通过扫描,将指定包下的所有符合条件的Bean,通过Selector进行批量的自动装配即可。

    2.2 小总结

    那么在这里再做一个小总结,对于SpringBoot的自动装配也就是:

    1. 通过@EnableAutoConfiguration注解引入两个功能。
    2. 引入@AutoConfigurationPackage注解实现包的扫描并实现注册。
    3. 导入AutoConfigurationImportSelector类(实现的顶层接口为ImportSelector,实现方法selectImports()),将扫描到的Bean实现批量装配。

    三. 自动装配原理分析

    上面通过两个案例来分析自动装配过程中的两个环节:Bean的注册和Bean的装配。接下来从SpringBoot本身的代码角度来对自动装配的原理进行分析。

    进入到AutoConfigurationImportSelector类中,快速定位到selectImports()方法。(tips:idea上面会显示当前所在的类,点击类的图标,直接键盘上搜索即可出来)
    在这里插入图片描述
    来看看这个方法:

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

    该方法主要的作用是:

    • 从META-INF/spring-autoconfigure-metadata.properties中加载自动装配所需的条件元数据。(只有满足条件的Bean才能够被自动装配

    接下来重点分析下else分支中的getAutoConfigurationEntry()方法

    protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
        	// 1.获得@EnableAutoConfiguration注解的属性,如exclude、excludeName等。
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            // 2.获得所有自动装配的配置类
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            // 3.将所有配置类进行一个去重的筛选
            configurations = this.removeDuplicates(configurations);
            // 4.根据前者获得的属性exclude,将不需要进行自动装配的类进行剔除
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.getConfigurationClassFilter().filter(configurations);
            // 5.进行事件广播
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            // 6.返回经过多重的判断、筛选、过滤而得到的最终自动装配的集合
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }
    

    这里的exclude、excludeName是在启动类上配置的,如下图:
    在这里插入图片描述
    此外,该方法中最终要的是第二步:获得所有的自动配置类,也就是getCandidateConfigurations()方法:

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    	// 扫描classpath下的META-INF/spring.factories文件。
        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;
    }
    

    这里说明几点:

    1. 上面的方法用到了SpringFactoriesLoader,它是Spring内部提供的一种约定俗成的加载方式。
    2. spring.factories文件中的数据以Key=Value的形式存储,因此SpringFactoriesLoader.loadFactoryNames()根据Key来获得对应的Value值。

    而对于自动装配而言,在项目启动的时候,这里的Key是EnableAutoConfiguration,Value为多个配置类
    在这里插入图片描述
    返回的Value集合:
    在这里插入图片描述

    案例3:RedisAutoConfiguration自动配置类

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({RedisOperations.class})
    @EnableConfigurationProperties({RedisProperties.class})
    @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
    public class RedisAutoConfiguration {
        public RedisAutoConfiguration() {
        }
    
        @Bean
        @ConditionalOnMissingBean(
            name = {"redisTemplate"}
        )
    }
    

    从这里可以观察到:

    1. RedisAutoConfiguration是一个基于JavaConfig形式的配置类。
    2. 除了基本的@Configuration注解,还有一个@ConditionalOnClass注解(条件控制机制),主要负责判断classpath下是否存在RedisOperations这个类,如果是,那么把当前配置类注册到IOC容器中。 说白了,就是看你项目中有没有用到这个配置类。
    3. @EnableConfigurationProperties是属性的配置,即我们可以在application.properties配置文件中配置Redis的相关参数,而这些配置会加载到RedisProperties类中。

    自动装配原理总结

    SpringBoot自动装配的流程如下:

    1. 启动类添加了@SpringBootApplication注解,该注解下有一个叫@EnableAutoConfiguration的注解。
    2. @EnableAutoConfiguration包含注解@AutoConfigurationPackage,负责项目中类的扫描和注册。
    3. @EnableAutoConfiguration通过@Import导入AutoConfigurationImportSelector类,将扫描到的类进行自动装配。
    4. AutoConfigurationImportSelector实现了ImportSelector接口,重写了方法selectImports(),用于实现配置类的批量导入。
    5. 方法selectImports()通过SpringFactoriesLoader类加载机制,扫描classpath下的META-INF/spring.factories文件,读取需要实现自动装配的配置类。
    6. 通过条件筛选,把不符合条件的 配置类剔除,最终返回实现自动装配。
    展开全文
  • 主要介绍了SpringBoot启动及自动装配原理过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • SpringBoot自动装配原理分析(初探)思维导图
  • SpringBoot自动装配原理源码详解

    千次阅读 2022-03-21 15:41:10
    文章目录自动装配原理及源码解析自动装配实现流程图简述自动装配步骤详解核心代码加载SpringBoot可以自动装配集合利用LinkedHashSet对得到的需要自动装配的集合去重过滤@SpringBootApplication注解中exclude参数集合...

    自动装配原理及源码解析

    自动装配实现流程图简述

    在这里插入图片描述

    自动装配步骤详解

    核心代码

    	@Override
    		public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
    			Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
    					() -> String.format("Only %s implementations are supported, got %s",
    							AutoConfigurationImportSelector.class.getSimpleName(),
    							deferredImportSelector.getClass().getName()));
    			//实现自动装配的核心代码
    			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
    					.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
    			this.autoConfigurationEntries.add(autoConfigurationEntry);
    			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
    				this.entries.putIfAbsent(importClassName, annotationMetadata);
    			}
    		}
    
    	protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
    															   AnnotationMetadata annotationMetadata) {
    		if (!isEnabled(annotationMetadata)) {
    			return EMPTY_ENTRY;
    		}
    		//通过加载项目中所有jar包中META-INF/spring.factories获取可自动装配的类全路径集合
    		AnnotationAttributes attributes = getAttributes(annotationMetadata);
    		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    
    		//利用LinkedHashSet对得到的需要自动装配的集合去重
    		configurations = removeDuplicates(configurations);
    	   /*
    	    获取@SpringBootApplication(exclude参数),去除不需要自动装配的类
    		例如@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    		*/
    		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    		checkExcludedClasses(configurations, exclusions);
    		configurations.removeAll(exclusions);
    
          /* 继续过滤不用自动装配的类(根据配置类上@ConditionalOnClass(KafkaTemplate.class)等条件过滤需要自动装配的类)
           * @ConditionalOnClass 当项目中存在改类,满足条件才是实例化该Bean,换句话说,就是需要kafka相关依赖
          * */
    		configurations = filter(configurations, autoConfigurationMetadata);
    		//触发自动装配相关的监听事件
    		fireAutoConfigurationImportEvents(configurations, exclusions);
    
    		return new AutoConfigurationEntry(configurations, exclusions);
    	}
    

    加载SpringBoot可以自动装配集合

    	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    		//通过加载项目中所有jar包中META-INF/spring.factories获取可自动装配的类全路径集合
    		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;
    	}
    

    SpringFactoriesLoader.loadFactoryNames方法

     public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
            String factoryTypeName = factoryType.getName();
            return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
        }
    

    加载所有包含META-INF/spring.factories的jar包中需要自动装配的类,并存放到map集合当中

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
            MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
            if (result != null) {
                return result;
            } else {
                try {
                    Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                    LinkedMultiValueMap result = new LinkedMultiValueMap();
    
                    while(urls.hasMoreElements()) {
                        URL url = (URL)urls.nextElement();
                        UrlResource resource = new UrlResource(url);
                        Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                        Iterator var6 = properties.entrySet().iterator();
    
                        while(var6.hasNext()) {
                            Entry<?, ?> entry = (Entry)var6.next();
                            String factoryTypeName = ((String)entry.getKey()).trim();
                            String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                            int var10 = var9.length;
    
                            for(int var11 = 0; var11 < var10; ++var11) {
                                String factoryImplementationName = var9[var11];
                                result.add(factoryTypeName, factoryImplementationName.trim());
                            }
                        }
                    }
    
                    cache.put(classLoader, result);
                    return result;
                } catch (IOException var13) {
                    throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
                }
            }
        }
    

    利用LinkedHashSet对得到的需要自动装配的集合去重

    protected final <T> List<T> removeDuplicates(List<T> list) {
    		return new ArrayList<>(new LinkedHashSet<>(list));
    	}
    

    过滤@SpringBootApplication注解中exclude参数集合

    核心方法:

    	protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    		Set<String> excluded = new LinkedHashSet<>();
    		excluded.addAll(asList(attributes, "exclude"));
    		excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
    		excluded.addAll(getExcludeAutoConfigurationsProperty());
    		return excluded;
    	}
    

    利用AutoConfigurationImportFilter过滤器对不满足条件的配置类,进行过滤

    先来看下会用的的过滤器,入下图所示
    在这里插入图片描述
    下面我们每个类简单说明一些

    OnClassCondition

    源码分析
    在这里插入图片描述
    重点分析
    See Also: ConditionalOnClass, ConditionalOnMissingClass
    @ConditionalOnClass :某个class位于类路径上,才会实例化一个Bean
    @ConditionalOnMissingClass: 某个class类路径上不存在的时候,才会实例化一个Bean
    简单描述:就是根据需要自动装配类上以上两个注解中条件满足与否过滤掉不需要加载的自动装配类

    OnWebApplicationCondition

    在这里插入图片描述
    同上重点分析
    See Also:ConditionalOnWebApplication, ConditionalOnNotWebApplication
    @ConditionalOnWebApplication: 当前项目是Web项目的条件下,才会实例化一个Bean
    @ConditionalOnNotWebApplication: 当项目不是web项目的条件下,才会实例化一个Bean

    OnBeanCondition

    在这里插入图片描述
    同上重点分析
    See Also:ConditionalOnBean, ConditionalOnMissingBean, ConditionalOnSingleCandidate
    @ConditionalOnBean:当容器里有指定Bean的条件下,才会实例化一个Bean
    @ConditionalOnMissingBean:当容器里没有指定Bean的情况下,才会实例化一个Bean
    @ConditionalOnSingleCandidate:当指定Bean在容器中只有一个,或者虽然有多个但是指定首选Bean,才会实例化一个Bean

    源码解析

    	private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
    		long startTime = System.nanoTime();
    		String[] candidates = StringUtils.toStringArray(configurations);
    		boolean[] skip = new boolean[candidates.length];
    		boolean skipped = false;
    		//final List<AutoConfigurationImportFilter> autoConfigurationImportFilters = getAutoConfigurationImportFilters();
    		//获取过滤器集合,过滤得到满足条件的集合
    		for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
    			invokeAwareMethods(filter);
    			boolean[] match = filter.match(candidates, autoConfigurationMetadata);
    			for (int i = 0; i < match.length; i++) {
    				if (!match[i]) {
    					skip[i] = true;
    					candidates[i] = null;
    					skipped = true;
    				}
    			}
    		}
    		if (!skipped) {
    			return configurations;
    		}
    		List<String> result = new ArrayList<>(candidates.length);
    		for (int i = 0; i < candidates.length; i++) {
    			if (!skip[i]) {
    				result.add(candidates[i]);
    			}
    		}
    		if (logger.isTraceEnabled()) {
    			int numberFiltered = configurations.size() - result.size();
    			logger.trace("Filtered " + numberFiltered + " auto configuration class in "
    					+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
    		}
    		return new ArrayList<>(result);
    	}
    

    获取过滤器集合的方法

    //主要获取到OnClassCondition,OnWebApplicationCondition,OnBeanCondition三个过滤器
    	protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
    		return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
    	}
    

    按照优先级,对配置类进行实例化(bean初始化)

    		@Override
    		public Iterable<Entry> selectImports() {
    			if (this.autoConfigurationEntries.isEmpty()) {
    				return Collections.emptyList();
    			}
    			Set<String> allExclusions = this.autoConfigurationEntries.stream()
    					.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
    			Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
    					.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
    					.collect(Collectors.toCollection(LinkedHashSet::new));
    			processedConfigurations.removeAll(allExclusions);
                //对配置类进行按照顺序进行Bean实例化
    			return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
    					.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
    					.collect(Collectors.toList());
    		}
    
    	//获取实例化集合
    		private List<String> sortAutoConfigurations(Set<String> configurations,
    													AutoConfigurationMetadata autoConfigurationMetadata) {
    			return new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata)
    					.getInPriorityOrder(configurations);
    		}
    
    		//实例化Bean
    		private MetadataReaderFactory getMetadataReaderFactory() {
    			try {
    				return this.beanFactory.getBean(SharedMetadataReaderFactoryContextInitializer.BEAN_NAME,
    						MetadataReaderFactory.class);
    			} catch (NoSuchBeanDefinitionException ex) {
    				return new CachingMetadataReaderFactory(this.resourceLoader);
    			}
    		}
    
    展开全文
  • SpringBoot自动装配原理及分析

    千次阅读 2022-02-15 09:18:32
    SpringBoot自动装配原理及分析

    一、什么是自动装配

    在使用SpringBoot的时候,会自动将Bean装配到IoC容器中。例如我们在使用Redis数据库的时候,会引入依赖spring-boot-starter-data-redis。在引入这个依赖后,服务初始化的时候,会将操作Redis需要的组件注入到IoC容器中进行后续使用

    自动装配大致过程如下:

    • 获取到组件(例如spring-boot-starter-data-redis)META-INF文件夹下的spring.factories文件
    • spring.factories文件中列出需要注入IoC容器的类
    • 将实体类注入到IoC容器中进行使用

    二、自动装配原理

    自动装配大致流程是通过@SpringBootApplication进行实现,这个注解声明在SpringBoot的启动类上

    1、SpringBoot启动类

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

    2、@SpringBootApplication注解

    SpringBoot启动类=>@SpringBootApplication

    @Target({ElementType.TYPE})//该注解作用于接口、类、枚举
    @Retention(RetentionPolicy.RUNTIME)//注解不仅被保存到class文件中,jvm加载class文件之后仍然存在
    @Documented//有了该注释后,如果有接口使用了该注解,生成的javadoc文件中,会把该注解展示出来
    @Inherited//如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解
    @SpringBootConfiguration//标识它是一个SpringBoot配置类
    @EnableAutoConfiguration//主要是通过这个注解实现自动装配
    @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })//配置类上添加@ComponentScan注解,该注解默认会扫描该类所在的包下所有的配置类
    public @interface SpringBootApplication { ... }

    3、@SpringBootConfiguration注解

    SpringBoot启动类=>@SpringBootApplication=>@SpringBootConfiguration

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration
    @Indexed
    public @interface SpringBootConfiguration {
    
    	@AliasFor(annotation = Configuration.class)
    	boolean proxyBeanMethods() default true;
    
    }

    通过@SpringBootConfiguration注解标识SpringBootApplication是一个SpringBoot配置类

    @AliasFor注解用于为注解属性声明别名(@SpringBootApplication注解也有@AliasFor注解)

    4、@EnableAutoConfiguration注解

    SpringBoot启动类=>@SpringBootApplication=>@EnableAutoConfiguration

    @Target({ElementType.TYPE})//该注解作用于接口、类、枚举
    @Retention(RetentionPolicy.RUNTIME)//注解不仅被保存到class文件中,jvm加载class文件之后仍然存在
    @Documented//有了该注释后,如果有接口使用了该注解,生成的javadoc文件中,会把该注解展示出来
    @Inherited//如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解
    @AutoConfigurationPackage//将添加该注解的类所在的package作为自动配置package进行管理
    @Import({AutoConfigurationImportSelector.class})
    public @interface EnableAutoConfiguration {
    
        String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
        Class<?>[] exclude() default {};
    
    	String[] excludeName() default {};
    
    }

    通过@EnableAutoConfiguration注解实现自动装配

    5、@AutoConfigurationPackage注解

    SpringBoot启动类=>@SpringBootApplication=>@EnableAutoConfiguration=>@AutoConfigurationPackage

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {
        String[] basePackages() default {};
    
        Class<?>[] basePackageClasses() default {};
    
    }

    通过@AutoConfigurationPackage注解将添加该注解的类所在的package作为自动配置package进行管理

    通过AutoConfigurationPackages工具类获取自动配置package列表,也就是说当SpringBoot应用启动时默认会将启动类所在的package作为自动配置的package

    6、@EnableAutoConfiguration注解最重要的是AutoConfigurationImportSelector.class,将需要装配的类装配到IoC容器中,下面重点分析一下这个类的实现

    三、核心类分析

    1、selectImport方法

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
    	if (!isEnabled(annotationMetadata)) {
    		return NO_IMPORTS;
    	}
    	AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    	return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

    AutoConfigurationImportSelector中的selectImport方法是自动装配的核心实现,它主要是读取META-INF/spring.factories文件,经过去重、过滤,返回需要装配的配置类集合

    2、getAutoConfigurationEntry方法

    selectImport方法=>getAutoConfigurationEntry方法

    protected AutoConfigurationEntry getAutoConfigurationEntry(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 = getConfigurationClassFilter().filter(configurations);
    	fireAutoConfigurationImportEvents(configurations, exclusions);
    	return new AutoConfigurationEntry(configurations, exclusions);
    }
    • getAttributes方法:获取@EnableAutoConfiguration中的exclude、excludeName等
    • getCandidateConfigurations方法:获取所有自动装配的配置类,也就是读取spring.factories文件,后面会再次说明
    • removeDuplicates方法:去除重复的配置项
    • getExclusions方法:根据@EnableAutoConfiguration中的exclude、excludeName移除不需要的配置类
    • fireAutoConfigurationImportEvents方法:广播事件
    • 最后根据多次过滤、判重返回配置类合集

    3、getCandidateConfigurations方法

    selectImport方法=>getAutoConfigurationEntry方法=>getCandidateConfigurations方法

    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;
    }

    通过loadFactoryNames方法,扫描classpath下的META-INF/spring.factories文件,里面是以key=value形式存储,读取其中key=EnableAutoConfiguration,value就是需要装配的配置类,也就是getCandidateConfigurations返回的值

    四、总结

    1)通过注解@SpringBootApplication=>@EnableAutoConfiguration=>@Import({AutoConfigurationImportSelector.class})实现自动装配

    2)AutoConfigurationImportSelector类中重写了ImportSelector中selectImports方法,批量返回需要装配的配置类

    3)通过Spring提供的SpringFactoriesLoader机制,扫描classpath下的META-INF/spring.factories文件,读取需要自动装配的配置类

    4)依据条件筛选的方式,把不符合的配置类移除掉,最终完成自动装配

    展开全文
  • 源码跟踪自动装配过程流程图: ...自动装配的核心原理流程图:上图中的AutoConfigurationImportSelector的内部类AutoConfigurationGroup中的process方法和selectImports方法完成以下自动装配核心逻辑处理。
  • 详解SpringBoot自动装配原理

    千次阅读 2021-12-07 10:06:18
    文章目录一、从RedisAutoConfiguration源码分析自动装配二、@EnableConfigurationProperties注解三、@Import注解3.1 类导入3.2 ImportSelector导入3.3 ImportBeanDefinitionRegistrar导入四、application.yml配置...
  • 1、问题的引出? 当我们使用spring boot时,已不需要像spring MVC那样配置很多文件,...@SpringBootApplication是一个很重要的注解,可以近似的把它看作是三个注解之和; (2)SpringBootApplication @...
  • #学习springboot自动装配 ##一,手动装配 ### 1,模式注解装配 @Component注解,或者@Component注解的扩展,@ Controller,@ Service,存储库,@ Configruation等, ### 2. @ Configuration启动容器+ @ Bean注册...
  • SpringBoot自动装配原理,这一篇就够了!

    万次阅读 多人点赞 2020-05-08 20:41:41
    学习SpringBoot,绝对避不开自动装配这个概念,这也是SpringBoot的关键之一 本人也是SpringBoot的初学者,下面的一些总结都是结合个人理解和实践得出的,如果有错误或者疏漏,请一定一定一定(不是欢迎,是一定)帮...
  • 1.启动类上因为加上了 @EnableEurekaServer这个注解 才可以实现自动装配 @SpringBootApplication @EnableEurekaServer public class EurekaApplication { public static void main(String[] args) { ...
  • 面试题:SpringBoot 自动装配原理

    千次阅读 2021-04-13 21:12:12
    1. @SpringBootApplication注解 首先,我们都知道SpringBoot程序的入口是通过@SpringBootApplication注解修饰的一个类,例如: @SpringBootApplication public class DemoApplication { public static void main...
  • springboot自动装配原理详解

    万次阅读 2020-02-15 20:48:38
    springboot自动装配原理详解 1)传统ssm整合redis的时候 需要在xml的配置文件中 进行大量的配置Bean 我们在这里使用springboot来代替ssm的整合,只是通过xml的形式来整合redis 第一步:加入配置 <dependency&...
  • 本篇文章会从springboot源码进行自动装配原理解析,并总结面试如何简洁的描述 2. 源码解析 我们以springboot 2.2.5.RELEASE版本进行解析 2.1 @SpringBootApplication源码解析 先看看springboo
  • 下面,这是我总结的在面试过程中介绍SpringBoot自动装配原理的话术,拿来即用就可。 Springboot自动装配过程:(每个步骤后的括号是对应的操作) 1. SpringBoot启动的时候会通过@EnableAutoConfiguration注解找到...
  • @SpringBootApplication 首先在springboot的启动类上有这么一个注解,我们点进去可以看到以下三个核心注解 @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = ...
  • 深入分析Spring Boot中的自动装配5.1简单分析@Configuration5.2简单分析@ComponentScan5.3 Import注解方式一:直接填class数组方式二:ImportSelector方式【重点】方式三:ImportBeanDefinitionRegistrar方式5.4 深入...
  • SpringBoot 这款框架几乎是现在企业级开发的标配,使用SpringBoot进行开发,能够大量减少xml配置文件的编写,并且能为我们提供一站式服务。SpringBoot我们只需要导入相关模块的starter,就可以使用相关功能,但是...
  • 浅析springboot自动装配原理(带源码) 写在前面: 内容参考:https://www.jianshu.com/p/5901da52ca09 引入: 还记得曾经为了引入一个框架,而在spring的xml文件里面写一大堆的配置或者以注解的形式,定义一大堆的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 12,661
精华内容 5,064
关键字:

springboot自动装配原理

spring 订阅
友情链接: LZO.rar