springboot启动类注解详解

Springboot启动类注解

有三个重要的注解

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan

一、@SpringBootConfiguration

这个注解是springboot的配置类,其作用和@configuration注解是基本上是一样的

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

可以看到其实就是直接对configuration注解进行了一层套娃式的封装
当我们在项目中使用了 @Indexed 之后,编译打包的时候会在项目中自动生成METAINT/spring.components文件。根据该文件进行扫描注入,可以提高效率,相当于为为扫描的类创建了索引文件,只需要一次io就能完成全部类的扫描。
当Spring应用上下文执行ComponentScan扫描时,META-INT/spring.components将会被CandidateComponentsIndexLoader 读取并加载,转换为CandidateComponentsIndex对象,这样的话@ComponentScan不在扫描指定的package,而是读取CandidateComponentsIndex对象,从而达到提升性能的目的。

简单点说,使用了@Indexed注解后,原来@ComponentScan可能需要扫描非常多的类,现在只需要在启动后对spring.components进行一次io,将类路径写入文件,后续将只需要读取文件即可获得所有类路径

proxyBeanMethods属性是是否为被注解的类修饰的属性每次都去ioc容器里获取它的代理类,默认为true开启,设置为false关闭后,每次获取这个类的实例会创建一个新的对象且不由IOC容器进行管理

二、@EnableAutoConfiguration注解

这个是springboot开启自动配置的核心所在

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})

@Import,当注入的类实现了ImportSelector接口,就不会将该类型注入到容器中,而是会注入selectImports方法返回的信息对应的对象(动态使用)
@Import有三种用法:

  1. .class数组方式;
    不额外实现其他接口,简单将class数组中的每个类注册到IOC容器中
  2. 实现ImportSelector(Spring Boot底层采用比较得多的方式);
    实现ImportSelector接口,重写selectImports()方法,返回的是要导入到容器中的类的全类名数组。然后会将返回的全类名数组对应的类全部加载到spring容器中
  3. ImportBeanDefinitionRegistrar方式
    实现ImportBeanDefinitionRegistrar接口
    手动将指定的bean注册到IOC容器中

@AutoConfigurationPackage注解的作用是扫描启动类所在的包及其子包下所有的组件,并将其添加到IOC容器中进行管理

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

    Class<?>[] basePackageClasses() default {};
}

这里最核心的就是这个Registrar类,正是通过它去将启动类所在的包及其子包下的所有组件注册到IOC容器

Registrar实现了ImportBeanDefinitionRegistrar接口
这个接口提供了两个重载的默认方法,Registrar类重写了一个默认方法

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
        }

这个方法提供了两个参数,第一个参数metadata包含了注解标注的类对象
在这里插入图片描述

第二个参数registry提供了spring的IOC容器对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HD6445N1-1690426675598)(./image/imgae-2023-07-26-21-38-51.png)]

通过idea去动态debug执行new AutoConfigurationPackages.PackageImports(metadata)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cvIXuhxK-1690426675599)(./image/imgae-2023-07-26-21-47-44.png)]

可以看到这里是获取到了启动类所在包的全限定名

继续执行AutoConfigurationPackages类中提供的register方法

    public static void register(BeanDefinitionRegistry registry, String... packageNames) {
        if (registry.containsBeanDefinition(BEAN)) {
            AutoConfigurationPackages.BasePackagesBeanDefinition beanDefinition = (AutoConfigurationPackages.BasePackagesBeanDefinition)registry.getBeanDefinition(BEAN);
            beanDefinition.addBasePackages(packageNames);
        } else {
            registry.registerBeanDefinition(BEAN, new AutoConfigurationPackages.BasePackagesBeanDefinition(packageNames));
        }

    }

这里的BEAN是AutoConfigurationPackages的全限定类名,判断IOC容器中是否注册了AutoConfigurationPackages这个bean
我们这里是debug进来第一次执行这段代码的,所以IOC容器中并没有这个bean,直接走else代码块new 一个BasePackagesBeanDefinition对象,然后将包名赋值给他的basePackages属性。

basePackages是个set集合,但是容器当中始终只有一个BasePackagesBeanDefinition对象,也就是只要代码当中添加@AutoConfigurationPackage注解,就会将注解所在的包名添加到basePackages集合当中。

然后调用BeanDefinitionRegistry对象的registerBeanDefinition方法,将这个BasePackagesBeanDefinition对象注册到IOC容器中交给spring管理

            if (this.hasBeanCreationStarted()) {
                synchronized(this.beanDefinitionMap) {
                    this.beanDefinitionMap.put(beanName, beanDefinition);
                    List<String> updatedDefinitions = new ArrayList(this.beanDefinitionNames.size() + 1);
                    updatedDefinitions.addAll(this.beanDefinitionNames);
                    updatedDefinitions.add(beanName);
                    this.beanDefinitionNames = updatedDefinitions;
                    this.removeManualSingletonName(beanName);
                }
            }

beanDefinitionMap就是IOC容器所管理的bean集合,k是bean的名称,v是bean的实例对象

对IOC容器中的beanDefinitionMap加上synchronized锁,虽然这个beanDefinitionMap对象是ConCurrentHashMap的实例对象,这个map的实现类是能够保证并发线程安全的,但是它只保证了数据的最终一致性,get方法是可能拿到脏数据,而spring的IOC容器必须要能够保证强一致性,所以为了防止其他地方读取beanDefinitionMap拿到旧的副本,这里还是要上锁保证强一致性

执行完这串代码我们的BasePackagesBeanDefinition对象就被加载到IOC容器中了,同时将我们的启动类所在的包添加到BasePackagesBeanDefinition对象的的basepackges属性中,后续spring将会根据这个属性将这个包内所有的组件注册到IOC容器中

OK AutoConfigurationPackage注解的作用和原理我们就研究到这里了

@EnableAutoConfiguration注解中还包含了@Import({AutoConfigurationImportSelector.class})
引入了AutoConfigurationImportSelector这个类

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered

AutoConfigurationImportSelector实现的接口很丰富,但是我们这里只需要去注意DeferredImportSelector这个接口的实现就行了,其他Aware接口都是spring提供可以获取到BeanFactory和ResourceLoader等等的,有需要了解的可以自行去查阅其作用

这个类才是springboot实现自动装配的核心所在,下面我们开始分析这个类,首先分析一个类肯定是要从抽象到具体,所以我们需要搞清楚它实现的接口提供的作用

public interface DeferredImportSelector extends ImportSelector

可以看到这个接口是ImportSelector的子接口

实现DeferredImportSelector可以提高应用程序的启动速度和效率,因为只在需要时加载额外的配置。

@Nullable
    default Class<? extends DeferredImportSelector.Group> getImportGroup() {
        return null;
    }

    public interface Group {
        void process(AnnotationMetadata metadata, DeferredImportSelector selector);

        Iterable<DeferredImportSelector.Group.Entry> selectImports();

        public static class Entry {
            private final AnnotationMetadata metadata;
            private final String importClassName;

            public Entry(AnnotationMetadata metadata, String importClassName) {
                this.metadata = metadata;
                this.importClassName = importClassName;
            }

            public AnnotationMetadata getMetadata() {
                return this.metadata;
            }

            public String getImportClassName() {
                return this.importClassName;
            }

            public boolean equals(@Nullable Object other) {
                if (this == other) {
                    return true;
                } else if (other != null && this.getClass() == other.getClass()) {
                    DeferredImportSelector.Group.Entry entry = (DeferredImportSelector.Group.Entry)other;
                    return this.metadata.equals(entry.metadata) && this.importClassName.equals(entry.importClassName);
                } else {
                    return false;
                }
            }

            public int hashCode() {
                return this.metadata.hashCode() * 31 + this.importClassName.hashCode();
            }

            public String toString() {
                return this.importClassName;
            }
        }
    }

DeferredImportSelector内部自己定义了一个Group接口,还有一个默认方法用来获取Group接口的实现类的实例对象
同时它是ImportSelector的子接口,AutoConfigurationImportSelector重写了selectImports方法用来加载需要自动装配的组件,但是通过debug我们发现springboot在启动流程中并不会执行整个方法,而是执行了DeferredImportSelector接口提供的getImportGroup方法,所以我们直接跳过对selectImports方法的分析

@Import(class<?> extends DeferredImportSelector)将会走到这个流程中

class ConfigurationClassParser{
private class DeferredImportSelectorGroupingHandler
{

          public void register(ConfigurationClassParser.DeferredImportSelectorHolder deferredImport) {
            Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();
            ConfigurationClassParser.DeferredImportSelectorGrouping grouping = (ConfigurationClassParser.DeferredImportSelectorGrouping)this.groupings.computeIfAbsent(group != null ? group : deferredImport, (key) -> {
                return new ConfigurationClassParser.DeferredImportSelectorGrouping(this.createGroup(group));
            });
            grouping.add(deferredImport);
            this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getConfigurationClass());
        }

}
}

自动装配最终是在AutoConfigurationImportSelector的静态内部类AutoConfigurationGroup中完成的

        public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
            Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {
                return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());
            });
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
            this.autoConfigurationEntries.add(autoConfigurationEntry);
            Iterator var4 = autoConfigurationEntry.getConfigurations().iterator();

            while(var4.hasNext()) {
                String importClassName = (String)var4.next();
                this.entries.putIfAbsent(importClassName, annotationMetadata);
            }

        }

这一行代码
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
在这里面读取了自动装配的信息
step into进去看看
List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());

    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }

        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }

这里factoryTypeName是org.springframework.boot.autoconfigure.EnableAutoConfiguration,看到这里就很眼熟了吧,自动装配的配置类名

然后继续step into

    private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            HashMap result = new HashMap();

            try {
                Enumeration urls = classLoader.getResources("META-INF/spring.factories");

                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[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        String[] var10 = factoryImplementationNames;
                        int var11 = factoryImplementationNames.length;

                        for(int var12 = 0; var12 < var11; ++var12) {
                            String factoryImplementationName = var10[var12];
                            ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                                return new ArrayList();
                            })).add(factoryImplementationName.trim());
                        }
                    }
                }

                result.replaceAll((factoryType, implementations) -> {
                    return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
                });
                cache.put(classLoader, result);
                return result;
            } catch (IOException var14) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
            }
        }
    }

这里实际上是在读取springbootframework的jar包下的META-INF/spring.factories文件,并将它们放到cache里面缓存,下次就不需要重新读取了,直接从map中拿就行了

通过后续debug也可以发现springbootframework的jar包下的META-INF/spring.factories需要被多次读取,所以这里使用了一个map进行缓存

最终configurations被读取出来
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fXeeKhuo-1690426675599)(./image/imgae-2023-07-27-10-22-29.png)]

可以看到这里面有我们自己引入的mybatis和pagehelper druid等等

而且spring-boot-starter-autoconfigure包为我们整合了很多常用的组件,
在这里插入图片描述

只要我们引入了相应的jar包,springboot就会自动读取相应的配置信息,当然对于我们自定义的jar包,我们就需要自己写好spring.factories文件和自动装配的类,毕竟为了防止过于臃肿,springboot自动装配的这个包只是包括了一些常用的组件的自动装配实现

三、ComponentScan注解

在springbootApplication中,componentscan注解的作用主要是扫描@AutoConfigurationPackage中设置的basepackges属性指向的包及其子包

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

    ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

    String resourcePattern() default "**/*.class";

    boolean useDefaultFilters() default true;

    ComponentScan.Filter[] includeFilters() default {};

    ComponentScan.Filter[] excludeFilters() default {};

    boolean lazyInit() default false;

    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface Filter {
        FilterType type() default FilterType.ANNOTATION;

        @AliasFor("classes")
        Class<?>[] value() default {};

        @AliasFor("value")
        Class<?>[] classes() default {};

        String[] pattern() default {};
    }
}


这个注解是可以重复使用在一个类上的
@Repeatable(ComponentScans.class)
ComponentScans只有一个属性value,类型为ComponentScan数组,注解默认情况下都是不可在同一个作用对象上重复使用的
所以即使你是用了springbootapplication注解,你依然可以在启动类上使用ComponentScan注解去对springboot的扫描进行一些手动配置

配置缺省的情况下将按照AutoConfigurationPackage添加的basepackges属性进行扫描

总结

因为spring默认情况下只能扫描到我们项目启动类下的组件,但是对于项目引入的jar包文件我们就必须手动扫描,springboot通过读取jar包文件下的META-INF/spring.factories文件,将其中指定的需要装配的类读取到IOC容器中,从而使得非spring官方的组件也能够不修改原有代码的情况下,只需要增加一部分自动装配的文件配置和自动装配类,即可完成与springboot的整合,充分体现了Java中SPI的设计理念

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Boot启动流程可以分为以下几个步骤: 1. 应用主入口:Spring Boot的应用主入口是一个标有@SpringBootApplication注解。在启动过程中,它会被作为启动加载到内存中。 2. 配置形式:Spring Boot提供了多种配置Bean的形式。首先是通过定义Bean的方式,在应用主入口中使用@Bean注解来定义Bean。其次是通过@Configuration配置方式,在应用主入口外创建一个专门用于配置Bean的,并在该中使用@Bean注解来定义Bean。还有一种方式是通过Spring XML配置文件进行配置。最后,还可以通过自动配置来配置Bean,这些自动配置Spring Boot内部提供的,会根据配置文件和依赖自动完成一些配置工作。 3. 启动流程:在启动阶段,Spring Boot会依次执行以下步骤: - 加载Spring Boot的核心配置文件和依赖的配置文件。 - 创建并初始化Spring的ApplicationContext容器。 - 执行各个自动配置,完成自动配置工作。 - 执行应用主入口中的初始化方法,并启动Spring Boot应用。 4. Bean定义加载顺序:在Spring Boot启动过程中,Bean的加载顺序非常重要。如果在主线程加载Bean的同时,有异步线程进行Dubbo调用或加载Bean,可能会导致死锁。为了避免这种情况,应该保证只有一个线程在进行Spring加载Bean的操作。可以在Spring启动完成后再进行异步初始化操作,或者使用Spring的事件机制,在订阅ApplicationStartedEvent事件后再执行异步初始化操作。 综上所述,Spring Boot启动流程包括应用主入口、配置形式、启动流程和Bean定义加载顺序。在启动过程中,需要注意Bean的加载顺序,以避免死锁情况的发生。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值