SpringBoot自动配置原理

SpringBoot自动配置原理

本次演示使用版本:JDK1.8、maven3.6.0、idea2018
1、创建一个maven工程,直接点击下一步即可,无需勾选任何选项
2、填写GAV(groupId、artifactId、version),填写完毕之后点击下一步

3、直接点击完成即可
4、因为我们需要创建一个SpringBoot的工程,所以我们要指定当前的parent为springBoot的子模块(开篇我们使用的是springBoot的2.2.2版本,一会儿为了比较与1版本的区别另外演示)
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.2.RELEASE</version>
</parent>
5、引入web的启动器(可以不引入,直接演示自动配置原理,但是为了将springBoot的starter知识引入进来,所以在此演示)
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
6、自己手动创建包结构,且【随意】定义一个类来作为启动类,放在任意包下均可,图中类SpringBootAutoConfigApplication.java为我随意起的
7、启动类头上加一个@SpringBootApplication注解,在该类中添加一个main方法。

代码如下:

package cn.cmysz.springboot;

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

@SpringBootApplication
public class SpringBootAutoConfigApplication {

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

8、添加一个Controller到一个包下(但是这个包要在刚刚启动类的同级包或者是其子包下,原因何故稍后解释),写上几行代码:

package cn.cmysz.springboot.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController//该注解为@Controller 与 @ResponseBody的组合注解
public class HelloController {


    @GetMapping("/hello")
    public String hello(){
        return "Hello";
    }

}

9、点击启动类的 运行按钮发现控制台的输出

发现2.2.2版本的SpringBoot内置Tomcat,且版本为9.0.29,contextPath为’ ',端口号为8080

其中contextPath与端口号均可以在配置文件中配置。

总结:以上就是一个简单的SpringBoot工程的快速入手,接下来说明他的自动配置原理

首先如果你使用的是idea开发工具,那么按住Ctrl+鼠标左键点击刚才在启动类上面添加的@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 {

前四个注解为java自带的元注解。

下方的@SpringBootConfiguration注解表示该类是一个配置类,当然感兴趣的可以点进去这个注解内部,就会发现这个注解的头上除了有java的元注解之外,看见了Spring的@Configuration注解的存在。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

再下方的@EnableAutoConfiguration这个注解是整个自动配置的核心。

点进这个注解之后发现除了一样的有java自带的元注解之外的两个注解

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

核心注解:

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
其一:

点击@AutoConfigurationPackage注解:

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

发现其为我们导入了AutoConfigurationPackages.Registrar.class这个静态内部类。

明显看见该内部类中的两个方法:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, 
                                    BeanDefinitionRegistry registry) {
        register(registry, new PackageImport(metadata).getPackageName());
    }

@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new PackageImport(metadata));
    }

}

其中最重要的方法:

registerBeanDefinitions(AnnotationMetadata metadata, 
                                    BeanDefinitionRegistry registry)

其内部调用了register方法,将断点打在此处。debug运行主方法。

步进该方法

我们可以通过两种方式看见我们所创建的包

​ 1、在控制台下方的变量中

​ 2、选中ClassUtils.getPackageName(metadata.getClassName()右键计算表达式

总结:SpringBoot在此处将我们所写的SpringBootAutoConfigApplication这个主启动类所在包及其子包下的所有类上被标注@Controller,@Service,@Repository,@Component这四个注解的类加载到IOC容器中。所以我们方才所写的Controller类要放在启动类同级包或者子包下。

注:此功能在SpringBoot1版本与2版本中相差甚微,故只做一次解释

其二:@Import(AutoConfigurationImportSelector.class)

该注解是为我们导入了一个自动配置导入选择器类AutoConfigurationImportSelector

点击AutoConfigurationImportSelector这个类之后发现此时会察觉到SpringBoot的1版本与2版本有所差距,现在我们先来介绍2.2.2版本情况:

点进来会看到有一个静态内部类AutoConfigurationGroup,在此类中有一个方法process

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

将断点打在此处。点击getAutoConfigurationEntry这个方法

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			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 = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

在此处打下断点。点击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方法。

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

在此处打下断点,并且点击loadSpringFactories方法

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

    try {
        Enumeration<URL> urls = (classLoader != null ?
                                 classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                                 ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryTypeName = ((String) entry.getKey()).trim();
                for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryTypeName, factoryImplementationName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                                           FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

点击FACTORIES_RESOURCE_LOCATION这个常量,即可看见:

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

此时就是使用的类加载器加载所有在META-INF目录下/spring.factories这个文件中的自动配置类,并且将他们加载进IOC容器中。

举例:

我们在继承parent模块时为我们引入的spring-boot-autoconfigure-2.2.2.RELEASE的META-INF下的spring.factories文件中所有的自动配置类都会将其加载进IOC文件,由此,便完成了SpringBoot的自动配置。

总结:以上是2版本的自动配置过程,接下来说明1版本。

首先将parent换成1版本,此处演示的是SpringBoot的1.5.10.RELEASE版本。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.10.RELEASE</version>
</parent>

在@EnableAutoConfiguration这个注解的上方2版本引入的是:AutoConfigurationImportSelector,1版本引入的是:EnableAutoConfigurationImportSelector

在此类中我们可以看出:

@Deprecated
public class EnableAutoConfigurationImportSelector
		extends AutoConfigurationImportSelector {

	@Override
	protected boolean isEnabled(AnnotationMetadata metadata) {
		if (getClass().equals(EnableAutoConfigurationImportSelector.class)) {
			return getEnvironment().getProperty(
					EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
					true);
		}
		return true;
	}

}

起作用的是它的父类AutoConfigurationImportSelector,是不是有点眼熟,就是2版本的AutoConfigurationImportSelector,但是在此类中两个静态内部类全无。

在此类中起作用的是selectImports方法

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    try {
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        List<String> configurations = getCandidateConfigurations(annotationMetadata,
                                                                 attributes);
        configurations = removeDuplicates(configurations);
        configurations = sort(configurations, autoConfigurationMetadata);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return configurations.toArray(new String[configurations.size()]);
    }
    catch (IOException ex) {
        throw new IllegalStateException(ex);
    }
}

将断点打在此处,并且点击进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这个方法。

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    try {
        Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                                 ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        List<String> result = new ArrayList<String>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
            String factoryClassNames = properties.getProperty(factoryClassName);
            result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
        }
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
                                           "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

此处又可看见FACTORIES_RESOURCE_LOCATION这个变量

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

自此以后流程全部一样都是将META-INF包下/spring.factories文件中的所有的自动配置类加载进IOC文件。

留下疑问:

1、我们知道SpringBoot是约定大于配置,那么它给我们默认配置了哪些?

2、我们可不可以修改?

3、那么多的自动配置类,我们要想修改为自己独特配置怎么做?假如我要修改mapper映射文件的地址怎么做?

4、我应该怎么写?就像开篇提到的contextPath和端口号,该怎么修改?

5、我还看见了:No active profile set, falling back to default profiles: default,这是什么?

6、自动配置类都放在了META-INFO/spring.factories文件中,我想自己定义一个属于自己的启动器该怎么做。

7、等等一系列问题。。请听下回。。。。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值