本文从实践的角度出发,实现一个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; /** * @author jimo * @date 19-8-16 下午3:41 */ 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); // 打开和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(); // 定义 BufferedReader输入流来读取URL的响应 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块来关闭输入流 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 { /** * 连接超时时间,默认5s */ 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.factoriesspring-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