SpringBoot的starter机制解析

一、前言

在没有springBoot之前,我们新建一个项目,要做很多的事情,像配置一堆依赖,配置web.xml、数据库连接、视图解析等等。往往创建一个新项目,配置这些信息,都要花费大半天,也很容易出错,如加入的依赖有冲突或者漏掉某些依赖,导致项目启动失败。

使用springBoot,大大简化了配置的难度,像pom文件中的依赖,直接继承自springBoot,还有各种starter可以引用。另外,springBoot使用内嵌服务器,打包后是一个jar包,可以用java命令直接启动,不再需要把项目打包成war,部署到额外的服务器中。

二、springBoot项目例子

maven依赖

<dependencyManagement>
        <dependencies>
            <dependency>
                <!-- Import dependency management from Spring Boot -->
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.1.5.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
		<dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.19</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

那些xxx-starter的包,会自动帮我们引入了相关的jar包,简化我们的配置内容。
例如 spring-boot-starter-web,帮我们引入了web模块开发需要的jar包,也不需要我们像以前那样,配置web.xml,配置视图解析…

项目启动类代码

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

springBoot项目的启动类,基本都是这样,有用的信息就是 @SpringBootApplication注解和 SpringApplication.run(Application.class, args)——启动应用。
那接下来,就从这两点,去研究一下SpringBoot启动过程中都做了哪些事情。

源码分析

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

注解的属性可以不用关注,只是可以排除指定“自动配置类”和指定扫描的范围。
关键是组合的几个注解:@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan
从这里可以看出来,@SpringBootApplication注解基本等价于SpringBootConfiguration+EnableAutoConfiguration+ComponentScan,用这三个注解,也能启动springBoot应用

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

接下来,分析一下这三个注解的代码和作用:

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

@SpringBootConfiguration 注解没有定义任何新的内容,单纯只是应用了 @Configuration注解,可以把它与 @Configuration作用等同。
@Configuration注解大家应该都不陌生,有该注解的类,会成为一个配置类,用来代替以前spring的xml配置文件。

  • @ComponentScan 注解应该是大家最为熟悉的一个spring注解之一,它的作用就是自动扫描并加载符合条件的组件,比如 @Component@Repository 等,最终将这些bean定义加载到spring容器中。代码如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
	@AliasFor("basePackages")
	String[] value() default {};
	@AliasFor("value")
	String[] basePackages() default {};	
	......

该注解最重要的属性就是basePackages,通过配置该属性,来控制自动扫描的范围,如果没有指定的话,就会从声明了 @ComponentScan 注解的的类所在的包进行扫描。这就意味着,springBoot项目,默认会从启动类所在的包进行扫描。

  • @EnableAutoConfiguration 注解是最重要的,maven依赖中,xxx-start的包能够起到那么神奇的作用,都是靠这个注解。
    在spring框架中,有很多名字是以 @Enable 开头的注解,例如 @EnableScheduling@EnableCaching@EnableMBeanExport等,这些都是借助@Import的帮助,将所有符合要求的bean加载到ioc容器中。
    @EnableAutoConfiguration就会通过扫描类路径中jar包里的自动配置类。
    先看看 @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 {};

}

@EnableAutoConfiguration注解引入了 @AutoConfigurationPackage@Import这两个注解。@AutoConfigurationPackage的作用就是自动配置的包,@Import导入需要自动配置的组件。
AutoConfigurationImportSelector类的代码很长,只看几个重要的方法:

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		// 获取自动配置的信息
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
				autoConfigurationMetadata, annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

	protected AutoConfigurationEntry getAutoConfigurationEntry(
			AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		// 调用getCandidateConfigurations方法加载自动配置类名列表
		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);
	}

	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
			AnnotationAttributes attributes) {
		// 用SpringFactoriesLoader来加载所有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方法从所有的jar包中读取META-INF/spring.factories文件信息。
SpringFactoriesLoader属于Spring框架私有的一种扩展方案,其主要功能就是从指定的配置文件META-INF/spring.factories加载配置。

public abstract class SpringFactoriesLoader {
    //...
    public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
        ...
    }
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        ....
    }
}

配合 @EnableAutoConfiguration使用的话,它更多是提供一种配置查找的功能支持,即根据 @EnableAutoConfiguration的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration作为查找的Key,获取对应的一组 @Configuration类,并通过反射实例化对应的标注了
所以,@EnableAutoConfiguration自动配置的魔法骑士就变成了:从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中 @Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。
下面是spring-boot-autoconfigure这个jar中spring.factories文件部分内容,其中有一个key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值定义了需要自动配置的bean,通过读取这个配置获取一组 @Configuration类。

org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\

每个xxxAutoConfiguration都是一个基于java的bean配置类。实际上,这些xxxAutoConfiguratio不是所有都会被加载,会根据xxxAutoConfiguration上的 @ConditionalOnClass等条件判断是否加载;通过反射机制将spring.factories中 @Configuration类实例化为对应的java实列。

如果我们想自己写一个xxx-starter的包,那有两点需要注意:
1、在META-INF目录下创建一个spring.factories文件,在里面配置上需要加载的自动配置类。
2、自动配置类要注意是否需要加 org.springframework.boot.autoconfigure.condition包下的条件类,如 @ConditionalOnClass在满足条件的情况下,才使配置生效。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值