直接干,我们先来自己制作一个SpringBoot Starter,拿又拍云的SDK来玩玩
创建一个Maven项目
这个不说了,创建完是这样一个结构
配置Pom.xml文件
<properties>
<spring-boot.version>2.0.0.RELEASE</spring-boot.version>
<upyun-sdk.version>3.16</upyun-sdk.version>
</properties>
<dependencies>
<!-- Spring Boot dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<optional>true</optional>
<version>${spring-boot.version}</version>
</dependency>
<!-- @ConfigurationProperties annotation processing (metadata for IDEs) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
<version>${spring-boot.version}</version>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<version>${spring-boot.version}</version>
</dependency>
<!--upyun sdk-->
<dependency>
<groupId>com.upyun</groupId>
<artifactId>java-sdk</artifactId>
<version>${upyun-sdk.version}</version>
</dependency>
</dependencies>
① spring-boot-configuration-processor 包的作用是编译时生成 spring-configuration-metadata.json ,此文件主要给IDE使用。如当配置此jar相关配置属性在 application.yml ,你可以用ctlr+鼠标左键点击属性名,IDE会跳转到你配置此属性的类中。
② spring-boot-autoconfigure 包包括了@ConditionalOn开头的各种注解,并且内置了一些组件的自动换配置,如freemarker,kafka等
新建Properties文件
/**
* upyun 配置文件
*/
@ConfigurationProperties(prefix = "upyun")
public class UpyunProperties {
/**
* 服务名称
*/
private String bucketName;
/**
* 操作员账号
*/
private String operId;
/**
* 操作员密码
*/
private String operPass;
//....省略get,set
}
新建AutoConfiguration文件
/**
* Upyun Auto
*/
@Configuration
@ConditionalOnClass(UpYun.class)
@EnableConfigurationProperties(UpyunProperties.class)
public class UpyunAutoConfiguration {
@Resource
private UpyunProperties upyunProperties;
@Bean
@ConditionalOnMissingBean
public UpYun upYunAutoConfig(){
return new UpYun(upyunProperties.getBucketName(),upyunProperties.getOperId(),upyunProperties.getOperPass());
}
}
@ConditionalOnClass 表示当前路径下存在指定的类,才会创建该Bean @EnableConfigurationProperties:这个注解可以提供一种方便的方式来将带有 @ConfigurationProperties 注解的类注入为 Spring 容器的 Bean。 @ConditionalOnMissingBean:当 Spring Context中不存在该Bean时,才创建Bean
创建spring.factories文件
创建resources/META-INF/spring.factories文件,Springboot将从该文件读取自动配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.upyun.autoconfigure.UpyunAutoConfiguration
mvn clean install 本地打包安装 试一下
通过@Autowired 就可以引入Upyun对象
原理分析
实践完,我们来看看SpringBoot是如何加载自己的starter的
从SpringBoot Application的 run()进入源码,省略前面几个run代码,下面是主要方法
public ConfigurableApplicationContext run(String... args) {
...
//进入getRunListeners()方法
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
...
listeners.running(context);
return context;
}
在这里看到了一个getSpringFactoriesInstances()方法,看名字不就是spring.factories吗,我们在深入
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
这里我们继续深入SpringFactoriesLoader.loadFactoryNames()方法
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
这么深入了,还没到,继续深入loadSpringFactories
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
终于到了加载spring.factories的地方了,这里会先判断内存中是否已经存在,不存在在从META-INF/spring.factories 加载,知道路径后,SpringBoot就知道已经加载哪些类了
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()) {
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
之前我们在spring.factories中已经有配置,SpringBoot就会根据配置加载我们的自定义starter了
到此,自己制作starter就实践完了,不知道你理解了没有,欢迎留言交流。