直接干,我们先来自己制作一个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就实践完了,不知道你理解了没有,欢迎留言交流。