本文从实践的角度出发,实现一个spring-boot的starter依赖包,为理解spring-boot的starter原理做好准备.关于原理篇参看:spring-boot starter原理
应用场景: 假如有一个开源库,为了顺应时代潮流,想方便使用spring-boot的开发者使用,就需要实现一个方便的依赖,让spring-boot自动加载,让用户以spring-boot的方式使用 这个库。这个依赖就是starter依赖。
例如:我们经常使用的中间件:redis、kafka、mybatis等,都有了官方的starter包。
现在,我们使用一个简单的网络工具为例,说明在自定义starter的过程中会遇到的情况和方法。
使用EnableAutoConfiguration实现自动配置注入:spring.factories
增加IDE配置提示
项目结构:
这是一个多模块工程,位于spring-boot-starter-demo下的两个子模块:
app:用来测试
spring-boot-starter-tool: 实现starter的工具类模块
下面就是具体步骤。
0.maven依赖 spring-boot-starter-tool的依赖如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 <modelVersion > 4.0.0</modelVersion > <groupId > com.jimo</groupId > <artifactId > spring-boot-starter-tool</artifactId > <version > 0.0.1</version > <name > spring-boot-starter-tool</name > <description > Demo project for Spring Boot</description > <properties > <java.version > 1.8</java.version > <spring-boot.version > 2.1.7.RELEASE</spring-boot.version > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-configuration-processor</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > </dependencies > <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-dependencies</artifactId > <version > ${spring-boot.version}</version > <type > pom</type > <scope > import</scope > </dependency > </dependencies > </dependencyManagement > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build >
注意: spring-boot-configuration-processor
依赖会自动生成spring-configuration-metadata.json
文件,这是用于IDE提供参数提醒。
参考:blog
1.写好工具类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.io.BufferedReader;import java.io.InputStreamReader;import java.net.URL;import java.net.URLConnection;import java.nio.charset.StandardCharsets;public class NetTool { private Logger logger = LoggerFactory.getLogger(NetTool.class); private int connectTimeout; private String userAgent; public NetTool (NetToolProperties properties) { connectTimeout = (int ) properties.getConnectTimeout().getSeconds(); userAgent = properties.getUserAgent(); } public String get (String url) { StringBuilder result = new StringBuilder (); BufferedReader in = null ; try { if (!url.startsWith("http://" )) { url = "http://" + url; } URL realUrl = new URL (url); URLConnection connection = realUrl.openConnection(); connection.setConnectTimeout(connectTimeout); connection.setRequestProperty("accept" , "*/*" ); connection.setRequestProperty("connection" , "Keep-Alive" ); connection.setRequestProperty("user-agent" , userAgent); connection.connect(); connection.getHeaderFields(); in = new BufferedReader (new InputStreamReader (connection.getInputStream(), StandardCharsets.UTF_8)); String line; while ((line = in.readLine()) != null ) { result.append(line); } } catch (Exception e) { logger.error("send get request fail!" + e); return e.toString(); } finally { try { if (in != null ) { in.close(); } } catch (Exception e2) { logger.error("close BufferedReader fail!" + e2); } } return result.toString(); } }
NetProperties.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import org.springframework.boot.context.properties.ConfigurationProperties;import java.time.Duration;@ConfigurationProperties(prefix = "jimo.util.net") public class NetToolProperties { private Duration connectTimeout = Duration.ofSeconds(5 ); private String userAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)" ; public Duration getConnectTimeout () { return connectTimeout; } public void setConnectTimeout (Duration connectTimeout) { this .connectTimeout = connectTimeout; } public String getUserAgent () { return userAgent; } public void setUserAgent (String userAgent) { this .userAgent = userAgent; } }
2.配置 这一步可以按条件构造Bean或class,参考文档:boot-features-condition-annotations
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import com.jimo.tool.net.NetTool;import com.jimo.tool.net.NetToolProperties;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration @EnableConfigurationProperties({NetToolProperties.class}) public class ToolConfiguration { private final NetToolProperties netToolProperties; public ToolConfiguration (NetToolProperties netToolProperties) { this .netToolProperties = netToolProperties; } @Bean public NetTool netTool () { return new NetTool (netToolProperties); } }
3.spring.factories spring-boot会通过这个文件找到哪些配置需要自动注入,其内容如下:
位于:resources/META-INF/spring.factories
1 2 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.jimo.tool.ToolConfiguration
3.使用 在app模块的依赖里,只需要引入即可:
1 2 3 4 5 <dependency > <groupId > com.jimo</groupId > <artifactId > spring-boot-starter-tool</artifactId > <version > 0.0.1</version > </dependency >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @SpringBootApplication @RestController public class AppApplication { private final NetTool tool; public AppApplication (NetTool tool) { this .tool = tool; } public static void main (String[] args) { SpringApplication.run(AppApplication.class, args); } @GetMapping("/get/{url}") public String get (@PathVariable String url) { return tool.get(url); } }
修改配置参数:application.yml, 会发现IDEA会有提示
1 2 3 4 jimo: util: net: connect-timeout: 3s
关于配置如何会提示,我们可以这么做:
1.打包spring-boot-starter-tool
: $ mvn package
2.打开jar包,结构如图:
原因就在这个spring-configuration-metadata.json
文件,其内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 { "groups" : [ { "name" : "jimo.util.net" , "type" : "com.jimo.tool.net.NetToolProperties" , "sourceType" : "com.jimo.tool.net.NetToolProperties" } ] , "properties" : [ { "name" : "jimo.util.net.connect-timeout" , "type" : "java.time.Duration" , "description" : "连接超时时间,默认5s" , "sourceType" : "com.jimo.tool.net.NetToolProperties" , "defaultValue" : "5s" } , { "name" : "jimo.util.net.user-agent" , "type" : "java.lang.String" , "sourceType" : "com.jimo.tool.net.NetToolProperties" , "defaultValue" : "Mozilla\/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)" } ] , "hints" : [ ] }
完整的项目代码参考https://github.com/jimolonely/spring-boot-starter-demo
到目前为止,我们依然停留在使用层面上,其原理还一窍不通,不过没关系,慢慢来。
参考
官方文档
https://www.jianshu.com/p/85460c1d835a
https://github.com/linux-china/spring-boot-starter-httpclient
https://blog.csdn.net/hry2015/article/details/78127567