SpringBoot学习(一)--从HelloWorld入手,解析SpringBootApplication

1.SpringBoot 简介

简化Spring应用开发的框架,是整个Spring技术栈的一个大整合,J2EE开发的一站式解决方案

2.从简单的Hello Word入手

新建一个SpringBoot有两种方法:

  • Spring官网教程,新建一个HelloWorld的SpringBoot项目

  • 新建一个Maven项目,pom.xml如下

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

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	</dependencies>
	
   <!-- 添加插件,可以打包成一个可执行的jar包 -->
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

代码片段如下

/**
 * @SpringBootApplication 标注为SpringBoot应用
 */
@SpringBootApplication
public class PracticeApplication {

    public static void main(String[] args) {
        //SpringBoot应用启动起来
        SpringApplication.run(PracticeApplication.class, args);
    }
}

@Controller
public class HelloController {
    @ResponseBody
    @RequestMapping("/hello")
    public String hello(){
        return "Hello World!";
    }
}

直接从main方法启动,访问localhost:8080/hello,页面显示Hello World

3.Hello World 探究

此处以spring-boot 2.1.8.RELEASE为例

1.pom.xml

1.1 父项目:

从pom.xml看到项目的父依赖为 spring-boot-starter-parent

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

进入spring-boot-starter-parent,可以看到还有一层父依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.1.8.RELEASE</version>
    <relativePath>../../spring-boot-dependencies</relativePath>
  </parent>

进入此POM可以看到,该文件中的属性配置了整个项目的所依赖的jar包和版本控制,可以将其视为整个项目的版本仲裁中心。
在这里插入图片描述

1.2启动器

在pom.xml父项目下面还可以看到关于web的启动器相关,

     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
     </dependency>

点进去可以看到有关Web开发所必备的jar包依赖,如

 <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <version>2.1.8.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.hibernate.validator</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>6.0.17.Final</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.1.9.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.1.9.RELEASE</version>
      <scope>compile</scope>
    </dependency>

在Spring官网上我们还可以看到很多的启动器,点进去都会看到模块正常运行所需要的jar包依赖。
这样在我们需要某个模块的时候就可以直接导入该模块的stater依赖即可。

2.@SpringBootApplication注解

@SpringBootApplication标注在一个类上,表明该类为Spring Boot的主配置类,此注解为一个组合注解:

@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 

2.1@SpringBootConfiguration

表明该类为一个Spring Boot的配置类,底层还是Spring注解@Configuration–表明该类为一个配置类

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component//表示配置类也是容器中的一个组件
public @interface Configuration

2.2@EnableAutoConfiguration

启用自动配置,@EnableAutoConfiguration为一个组合注解,源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration 
2.2.1 @AutoConfigurationPackage 自动配置包
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)//导入组件(用法见Spring注解开发(一)组件注册)
public @interface AutoConfigurationPackage 
  1. AutoConfigurationPackages.Registrar实现了ImportBeanDefinitionRegistrar接口,
    实现ImportBeanDefinitionRegistrar接口,我们可以为容器注册组件(Spring注解开发(一)组件注册简单提过这个)
  2. 实现了DeferredImportSelector:与ImportSelector类似,区别在于处理操作被延迟到所有其他配置项都处理完毕再进行。
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports 

在AutoConfigurationPackages.Registrar类的registerBeanDefinitions方法上加上断点:
进入debug模式,调用链如下图:
在这里插入图片描述
可以看到在refresh容器的时候会调用该方法的registerBeanDefinitions方法。

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		register(registry, new PackageImport(metadata).getPackageName());
}
————————————>
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
		if (registry.containsBeanDefinition(BEAN)) {
			BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
			ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
			constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
		}
		else {
			GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
			beanDefinition.setBeanClass(BasePackages.class);
			beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
			beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			registry.registerBeanDefinition(BEAN, beanDefinition);
		}
	}

此处从metadata中获取的PackageName即为SpringBoot启动类所在的包名,该方法将此包下的所有组件都注册到容器中。
并注册一个name为org.springframework.boot.autoconfigure.AutoConfigurationPackages的bean定义信息,并将包名称设置到bean定义中去

  • @Import注解,给容器中导入一个组件。这个注解帮助我们将多个配置文件(可能是按功能分,或是按业务分)导入到单个主配置中,以避免将所有配置写在一个配置中。
2.2.2 导入组件AutoConfigurationImportSelector
  • 先看一下SpringApplication创建时的源码
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		//判断是否是Web应用
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		//由此处进入下面的方法,并读取"META-INF/spring.factories"中的配置
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

在SpringFactoriesLoader的loadSpringFactories方法上打上断点

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

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 factoryClassName = ((String) entry.getKey()).trim();
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryClassName, factoryName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}

上面第十行代码处,读取spring-boot-autoconfigure jar包下的/META-INF/spring-factories路径,并读取其中的K-V,保存到properties中。
在这里插入图片描述
具体如下图(此处主要关注EnableAutoConfiguration):
在这里插入图片描述
也可以看下spring-boot-autoconfigure jar包下的/META-INF/spring-factories文件:
在这里插入图片描述
最终将其保存在了SpringFactoriesLoader中的cache属性中。

  • 再看下Import导入的工作
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

AutoConfigurationImportSelector 实现了DeferredImportSelector,DeferredImportSelector是延期导入,当所有的@Configuration都处理过后才会执行。
该类会在refresh容器的时候执行ConfigurationClassParser的parse方法(见Spring注解开发之@Configuration解析),最终会到
AutoConfigurationImportSelecto的process方法
断点图如下:
在这里插入图片描述
在下一步的getAutoConfigurationEntry方法中看到:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		//调用到了上一点的在SpringFactoriesLoader中的cache属性,获取了EnableAutoConfiguration对应的117个值
		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);
	}

经过过滤后返回包装的自动配置类,放入了AutoConfigurationImportSelector $ AutoConfigurationGroup的autoConfigurationEntries属性中。
最后在ConfigurationClassParser $ DeferredImportSelectorGroupingHandler类的processGroupImports方法中将集合中的组件导入到容器中。

posted @ 2020-03-08 22:29  寻找艾伦  阅读(25)  评论(0编辑  收藏  举报