该项目里面包含:pom.xml、application.properties、Application 和 TestRunner 文件。
先看看pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<version>1.3.1</version>
<groupId>com.sue</groupId>
<artifactId>spring-boot-id-generate-test</artifactId>
<name>spring-boot-id-generate-test</name>
<dependencies>
<dependency>
<groupId>com.sue</groupId>
<artifactId>id-generate-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
</dependencies>
</project>
由于只测试刚刚定义的id生成功能,所以只引入的id-generate-spring-boot-starter jar包。
application.properties配置资源文件
sue.workId=123
只有一行配置,因为我们的IdProperties中目前只需要这一个参数。
Application是测试程序启动类
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
很简单,就是一个普通的springboot启动类
TestRunner是我们的测试类
@Component
public class TestRunner implements ApplicationRunner {
@Autowired
private IdGenerateService idGenerateService;
public void run(ApplicationArguments args) throws Exception {
Long sysNo = idGenerateService.generate();
System.out.println(sysNo);
}
}
它实现了ApplicationRunner接口,所以在springboot启动的时候会调用该类的run方法。
好了,所有自定义starter的代码和测试代码都已经就绪。接下,运行一下Application类的main方法。
运行结果:
176
完美,验证成功了。
接下来,我们分析一下starter的底层实现原理。
4 starter的底层原理是什么?通过上面编写自己的starter的例子,相信大家对starter的认识更进一步了,现在跟大家一起看看starter的底层是如何实现的。
id-generate-starter.jar其实是一个空项目,依赖于id-generate-autoconfiguration.jar。
id-generate-starter.jar是一个入口,我们给他取一个更优雅的名字:门面模式,其他业务系统想引入相应的功能,必须要通过这个门面。
我们重点分析一下 id-generate-autoconfiguration.jar
该jar包核心内容是:IdGenerateConfiguration,这个配置类中创建了IdGenerateService对象,IdGenerateService是我们所需要自动配置的具体功能。
接下来一个最重要的问题:IdGenerateConfiguration为什么会自动加载的呢?
还记得我们定义的spring.factories文件不?
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.sue.IdGenerateAutoConfiguration
它里面只包含一行配置,其中key是EnableAutoConfiguration,value是IdGenerateAutoConfiguration。
要搞明白这个过程,要从Application类的@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 {};
}
从上面可以看出该注解里面包含了@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注解会引入AutoConfigurationImportSelector类。
该类的selectImports方法一个关键方法:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//配置有没有配置spring.boot.enableautoconfiguration开关,默认为true
//如果为false,则不执行自动配置的功能,直接返回
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
//找spring-autoconfigure-metadata.properties中的元素
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
//获取EnableAutoConfiguration注解中的属性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//获取工程下所有配置key为EnableAutoConfiguration的值,即IdGenerateConfiguration等类。
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 StringUtils.toStringArray(configurations);
}
这里就是starter能够自动配置的秘密。
此外,有些朋友看其他人定义的springboot starter可能会有疑惑。
先看看druid-spring-boot-starter
alibaba定义的druid-spring-boot-starter只有xxx-spring-boot-starter.jar文件,而没有xxx-spring-boot-autoconfigure.jar文件。
再看看spring-boot-starter-jdbc:
更神奇的是这个文件中连pom.xml都没有,一脸懵逼。。。。。。。
是不是我讲错了?
答:其实没有。
SpringBoot的原则是约定优于配置。
从spring-boot-starter-jdbc内部空实现来看,它的约定是要把xxx-spring-boot-starter.jar和xxx-spring-boot-autoconfigure.jar区分开的。个人认为,alibaba定义得并不好,没有遵照springboot的约定,虽然功能不受影响。(这个地方欢迎一起探讨一下)
而springboot自己定义的spring-boot-starter-jdbc为什么连pom.xml文件也没有呢?
它不需要依赖xxx-spring-boot-autoconfigure.jar文件吗?
因为springboot把所有的自动配置的类都统一放到spring-boot-autoconfigure.jar下面了:
spring.factories文件内容如下: