@SpringBootApplication注解源码解读


1、从Springboot启动类开始

    创建一个springboot工程后,里面会有一个启动类,XxxApplication.java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

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

}

查看@SpringBootApplication源码,

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;

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

可以发现,@SpringBootApplication是一个组合注解:

@SpringBootConfiguration

@EnableAutoConfiguration

@ComponentScan

下面来详细解释这3个注解。




2、@SpringBootConfiguration

查看@SpringBootConfiguration源码如下

package org.springframework.boot;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AliasFor;

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

    @SpringBootConfiguration只有一个@Configuration而已。

    从Spring3.0,@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。

    在启动类里面标注了@Configuration,意味着它其实也是一个IoC容器的配置类。



3、@ComponentScan

    这个注解应该很熟悉了,@ComponentScan 对应于XML配置形式中的 context:component-scan,主要作用就是扫描指定路径下的标识了需要装配的类,自动装配到spring 的Ioc容器中。

    标识需要装配的类的形式主要是:@Component、@Repository、@Service、@Controller 这类的注解标识的类。ComponentScan 默认会扫描当前 package下的的所有加了相关注解标识的类到 IoC 容器中;所以一般启动类定义在最外层。



4、 @EnableAutoConfiguration

查看 @EnableAutoConfiguration源码如下,

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;

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

在这里先说一下它的作用:

    它会启用自动配置,他会去扫描SpringBoot 项目下,将所有符合条件的 @Configuration 注解所修饰的配置类,全部加载到当前 IOC 容器之中。

通过阅读注解源码,我们发现,在这个注解上面有个@Import。还有两个方法,他们的作用都是:自动装配时,排除指定的类:

    Class<?>[] exclude();指定类排除

    String[] excludeName():指定类的全路径排除



4.1.@Import

    import注解是什么意思呢?联想到xml形式下有一个<import  resource/>形式的注解,就明白它的作用了。

    import就是把多个分来的容器配置合并在一个配置中。在JavaConfig中所表达的意义是一样的。

    @Import注解用来导入一个或多个类(表示被spring容器托管),如果是一个配置类(配置类里的bean会被spring容器托管)



4.2.AutoConfigurationImportSelector

    AutoConfigurationImportSelector实现了DeferredImportSelector , DeferredImportSelector继承了ImportSelector 

public class AutoConfigurationImportSelector implements DeferredImportSelector

public interface DeferredImportSelector extends ImportSelector

    AutoConfigurationImportSelector的核心方法就是selectImports。

public interface ImportSelector {
	String[] selectImports(AnnotationMetadata importingClassMetadata);

	@Nullable
	default Predicate<String> getExclusionFilter() {
		return null;
	}
}

接下来看看selectImports在AutoConfigurationImportSelector中的实现。



4.3.AutoConfigurationImportSelector.selectImports

    选择并返回应装配的类,基于注解的原数据导入配置类。所以方法返回的是一个Class全路径的String数组,返回的Class会被Spring容器管理。

    selectImports方法所承担的责任是:根据我们的配置,动态加载所需的bean。


AutoConfigurationImportSelector.java

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


4.3.1-isEnabled

    查看代码可知,它默认是开启自动配置

protected boolean isEnabled(AnnotationMetadata metadata) {
	/**
	*	如果当前类不是AutoConfigurationImportSelector , 返回true。
	*	如果当前类是AutoConfigurationImportSelector ,就执行
	*	(Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true)
	*	第三个参数是默认值,
	*	从当前环境中获取变量"spring.boot.enableautoconfiguration",变量的值为Boolean类型。如果没有配置该变量,返回默认值true.
	*/
	return this.getClass() == AutoConfigurationImportSelector.class ? (Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true) : true;
}


4.3.2-AutoConfigurationMetadataLoader.loadMetadata

    它将META-INF/spring-autoconfigure-metadata.properties文件的元数据放到了PropertiesAutoConfigurationMetadata对象的properties属性中,后面就可以根据元数据进行类的过滤。

final class AutoConfigurationMetadataLoader {
    protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";

    private AutoConfigurationMetadataLoader() {
    }

    static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
        // 加载指定配置文件
        return loadMetadata(classLoader, "META-INF/spring-autoconfigure-metadata.properties");
    }

    static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
        try {
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources(path) : ClassLoader.getSystemResources(path);
            Properties properties = new Properties();

            while(urls.hasMoreElements()) {
                properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource((URL)urls.nextElement())));
            }

            return loadMetadata(properties);
        } catch (IOException var4) {
            throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", var4);
        }
    }
	……
}

SpringBootApplication注解源码解读



4.3.3-getAutoConfigurationEntry

    这个方法是关键点

AutoConfigurationImportSelector.java

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
	if (!this.isEnabled(annotationMetadata)) {
		return EMPTY_ENTRY;
	} else {
        //获取注解里设置的属性,在@SpringBootApplication设置的exclude,excludeName属性值
		AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        //从spring-boot-autoconfigure-jar包里面META-INF/spring.factories加载配置类的名称
		List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
        //去重
		configurations = this.removeDuplicates(configurations);
        //获取自己配置不需要生成bean的类
		Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
        //校验被exclude的类是否都是springboot自动化配置里的类
		this.checkExcludedClasses(configurations, exclusions);
        //去除exclude的类
		configurations.removeAll(exclusions);
		configurations = this.filter(configurations, autoConfigurationMetadata);
        //过滤刷选
		this.fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
	}
}


AutoConfigurationImportSelector.java(this.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;
}


其中有个类SpringFactoriesLoader.java


    loadFactoryNames()-->loadSpringFactories()

public final class SpringFactoriesLoader {
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
    private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();

    private SpringFactoriesLoader() {
    }

    public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
        Assert.notNull(factoryType, "'factoryType' must not be null");
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }

        List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
        }

        List<T> result = new ArrayList(factoryImplementationNames.size());
        Iterator var5 = factoryImplementationNames.iterator();

        while(var5.hasNext()) {
            String factoryImplementationName = (String)var5.next();
            result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
        }

        AnnotationAwareOrderComparator.sort(result);
        return result;
    }

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

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

    private static <T> T instantiateFactory(String factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) {
        try {
            Class<?> factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader);
            if (!factoryType.isAssignableFrom(factoryImplementationClass)) {
                throw new IllegalArgumentException("Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]");
            } else {
                return ReflectionUtils.accessibleConstructor(factoryImplementationClass, new Class[0]).newInstance();
            }
        } catch (Throwable var4) {
            throw new IllegalArgumentException("Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]", var4);
        }
    }
}

     在loadSpringFactories中加载所有的META-INF/spring.factories, 返回类型是Map<String, List<String>>.


这里key是”org.springframework.boot.autoconfigure.EnableAutoConfiguration”。还有很多这样的key。值是字符串根据“,”分割的结果,所以是list。

一个spring.factories文件中可以配置多个key,每个key可以有多个值;不同的spring.factories是可以配置相同的key的。

最后从map中取出key是”org.springframework.boot.autoconfigure.EnableAutoConfiguration”的值,所以返回到AutoConfigurationImportSelector中的list是所有配置了@Configuration的类。(List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes); )

spring.factories



4.3.4-new AutoConfigurationEntry(configurations, exclusions);

    返回需要装配的类和排除的类

return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);

SpringBootApplication注解源码解读



    返回的类都有哪些特点呢?

返回的类上面都有@Configuration注解,这样spring就可将其中配置的bean加入到IOC容器中。

    这是官方约定的,在META-INF下创建spring.factories文件,配置key为org.springframework.boot.autoconfigure.EnableAutoConfiguration ,后面跟上配置类,根据这个原理,就可以自定义starter了。



5、扩展

    简单分析一下SpringFactoriesLoader这个工具类的使用。它其实和java中的SPI机制的原理是一样的,不过它比SPI更好的点在于不会一次性加载所有的类,而是根据key进行加载。

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


    在分析AutoConfigurationImportSelector的源码时,会先扫描spring-autoconfiguration-metadata.properties文件。

    最后在扫描spring.factories对应的类时,会结合前面的元数据进行过滤,为什么要过滤呢?

    原因是很多的@Configuration其实是依托于其他的框架来加载的,如果当前的classpath环境下没有相关联的依赖,则意味着这些类没必要进行加载,所以,通过这种条件过滤可以有效的减少@configuration类的数量从而降低SpringBoot的启动时间。



6、总结

    SpringBoot项目,在XxxApplication这个类上面添加@SpringBootApplication这个注解,表示这个类为启动类,在main方法中调用SpringApplication.run();即可启动项目。

@SpringBootApplication核心有三个注解组成,分别是

1.@SpringBootConfiguration

本质是@Configuration,配置Spring上下文中的对象

2.@EnableAutoConfiguration

作用在于让SpringBoot根据应用所声明的依赖来扫描需要加载的配置类,将配置类中的Bean加载到IOC容器中,从而实现自动装配

3.@ComponentScan

扫包加载

其中,最为核心的内容是@EnableAutoConfiguration,这个注解中导入了一个AutoConfigurationImportSelector。这个ImportSelector根据相关配置,动态加载所需的bean。




@SpringBootApplication @EnableAutoConfiguration

2020.12.07 22:23

https://www.meihaocloud.com.com/1037.html , 欢迎转载,请在文章页标出原文连接 !


Copyright © 2020 千夕网 联系站长

粤公网安备 44030302001408号 粤ICP备19099833号-1