作为一名合格的程序猿,相信大家都对Spring比较熟悉。在Spring的演进历史中,我们一般都经历了SSH(Spring+Struts+Hibernate),Spring MVC,Spring Boot,Spring Cloud等一系列Spring的优秀作品。
在目前软件开发公司特别是互联网企业,项目开发周期一般都很短,项目必须采用快速迭代以满足不断变化的市场需求。所以作为企业的项目开发团队必须选择一个能够快速构建项目工程的框架,能够让整个项目团队的学习成本比较低,上手容易,而且框架扩展性比较强和稳定性比较好。SpringBoot一般是大部分开发团队的不二之选。
为什么SpringBoot如此深受开发者的青睐呢?纵观Spring的官网文档和Spring的开发者社区,SpringBoot提供了大多数场景的脚手架,方便开发人员上手,学习成本比较低,而且开发更容易,能够让开发人员从框架层面解脱出来,全身心的投入到业务逻辑层面的实现。
那么SpringBoot到底都有哪些比较有实用价值的脚手架呢?我们随便找到一个简单的SpringBoot工程,来看看它都引入了哪些stater
通过上面的截图我们可以看到,一个简单的SpringBoot一般都引入了数据库操作脚手架,日志脚手架,安全脚手架,模板引擎脚手架等。然而这只是冰山一角。纵观Spring开发者社区和GitHub我们发现有许多脚手架starter是社区提供的,我们可以直接拿来使用。
言归正传,下面我们通过一个简单的例子来演示下如何构建一个我们自定义的SpringBoot Starter,该示例通过一个Starter向钉钉机器人发送告警信息:
构建dingtalk-robot-starter项目工程:
项目工程结构如下:
pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>4.0.0org.springframework.bootspring-boot-starter-parent2.3.0.RELEASEorg.springframework.bootdingtalk-robot-starter0.0.1dingtalk-robot-starterdingtalk robot send message starter1.8org.springframework.bootspring-boot-starterorg.springframework.bootspring-boot-devtoolsruntimetrueorg.springframework.bootspring-boot-configuration-processortrueorg.projectlomboklomboktrueorg.springframework.bootspring-boot-starter-testtestorg.junit.vintagejunit-vintage-enginecom.dingtalk.opentaobao-sdk-java-auto1479188381469-20200609com.alibabafastjson1.2.47
这里我们首先需要安装钉钉官方网站提供的SDK工具
cd dingtalk-robot-startermvn install:install-file -DgroupId=com.dingtalk.open -DartifactId=taobao-sdk-java-auto -Dversion=1479188381469-20200609 -Dpackaging=jar -Dfile=lib/taobao-sdk-java-auto_1479188381469-20200609.jar
DingTalkAutoConfiguration.java内容如下:
package org.springframework.dingtalk.robot;import lombok.extern.log4j.Log4j2;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import javax.annotation.Resource;import javax.crypto.Mac;import javax.crypto.spec.SecretKeySpec;import java.io.UnsupportedEncodingException;import java.net.URLEncoder;import java.nio.charset.StandardCharsets;import java.security.InvalidKeyException;import java.security.NoSuchAlgorithmException;import java.util.Base64;@Log4j2@Configuration@EnableConfigurationProperties(DingTalkProperties.class)public class DingTalkAutoConfiguration { // 使用配置 @Resource private DingTalkProperties dingTalkProperties; // 在Spring上下文中创建一个对象 @Bean @ConditionalOnMissingBean public DingTalkRobotClient dingTalkRobotClient() throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException { String secret = dingTalkProperties.getSecret(); StringBuilder stringBuilder = new StringBuilder(dingTalkProperties.getWebhook()); stringBuilder.append("×tamp="); Long timestamp = System.currentTimeMillis(); stringBuilder.append(timestamp); String stringToSign = timestamp + Const.NEW_LINE + secret; Mac mac = Mac.getInstance(Const.ALGORITHM); mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Const.ALGORITHM)); byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8)); String sign = URLEncoder.encode(new String(Base64.getEncoder().encode(signData)),"UTF-8"); stringBuilder.append("&sign="); stringBuilder.append(sign); String url = stringBuilder.toString(); log.debug("url:{}", url); return new DingTalkRobotClient(url, dingTalkProperties.isEnable()); }}
DingTalkProperties.java
package org.springframework.dingtalk.robot;import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;@Data@ConfigurationProperties(prefix = "dingtalk.robot")public class DingTalkProperties { private String webhook = "https://oapi.dingtalk.com/robot/send"; private String secret; private boolean enable = true;}
DingTalkRobotClient.java
package org.springframework.dingtalk.robot;import com.alibaba.fastjson.JSON;import com.dingtalk.api.DefaultDingTalkClient;import com.dingtalk.api.request.OapiRobotSendRequest;import com.dingtalk.api.response.OapiRobotSendResponse;import com.taobao.api.ApiException;import lombok.extern.log4j.Log4j2;import org.springframework.scheduling.annotation.Async;import org.springframework.scheduling.annotation.EnableAsync;import java.net.InetAddress;import java.net.UnknownHostException;import java.util.List;@Log4j2@EnableAsyncpublic class DingTalkRobotClient extends DefaultDingTalkClient { private boolean enable; public DingTalkRobotClient(String serverUrl, boolean enable) { super(serverUrl); this.enable = enable; } private String getHostName() { String hostName = "localhost"; try { hostName = InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { log.error("getHostName", e); } return "[" + hostName + "]:"; } @Async public void sendTextMessage(String message) { if (enable) { message = getHostName() + message; OapiRobotSendRequest request = new OapiRobotSendRequest(); request.setMsgtype("text"); OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text(); text.setContent(message); request.setText(text); try { OapiRobotSendResponse response = execute(request); log.debug("sendTextMessage:{}", JSON.toJSONString(response)); } catch (ApiException e) { log.error("ApiException", e); } } } @Async public void sendTextMessage(String message, boolean isAtAll, List mobiles) { if (enable) { message = getHostName() + message; OapiRobotSendRequest request = new OapiRobotSendRequest(); request.setMsgtype("text"); OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text(); text.setContent(message); request.setText(text); if (isAtAll) { OapiRobotSendRequest.At at = new OapiRobotSendRequest.At(); at.setAtMobiles(mobiles); // isAtAll类型如果不为Boolean,请升级至最新SDK at.setIsAtAll(isAtAll); request.setAt(at); } try { OapiRobotSendResponse response = execute(request); log.debug("sendTextMessage:{}", JSON.toJSONString(response)); } catch (ApiException e) { log.error("ApiException", e); } } } @Async public void sendLinkMessage(String message, String title, String messageUrl, String picUrl) { if (enable) { message = getHostName() + message; OapiRobotSendRequest request = new OapiRobotSendRequest(); request.setMsgtype("link"); OapiRobotSendRequest.Link link = new OapiRobotSendRequest.Link(); link.setMessageUrl(messageUrl); link.setPicUrl(picUrl); link.setTitle(title); link.setText(message); request.setLink(link); try { OapiRobotSendResponse response = execute(request); log.debug("sendLinkMessage:{}", JSON.toJSONString(response)); } catch (ApiException e) { log.error("ApiException", e); } } } @Async public void sendLinkMessage(String message, String title, String messageUrl, String picUrl, boolean isAtAll, List mobiles) { if (enable) { message = getHostName() + message; OapiRobotSendRequest request = new OapiRobotSendRequest(); request.setMsgtype("link"); OapiRobotSendRequest.Link link = new OapiRobotSendRequest.Link(); link.setMessageUrl(messageUrl); link.setPicUrl(picUrl); link.setTitle(title); link.setText(message); request.setLink(link); if (isAtAll) { OapiRobotSendRequest.At at = new OapiRobotSendRequest.At(); at.setAtMobiles(mobiles); // isAtAll类型如果不为Boolean,请升级至最新SDK at.setIsAtAll(isAtAll); request.setAt(at); } try { OapiRobotSendResponse response = execute(request); log.debug("sendLinkMessage:{}", JSON.toJSONString(response)); } catch (ApiException e) { log.error("ApiException", e); } } }}
到处,源代码编写完毕,我们需要增加Starter的配置文件,能够在SpringBoot启动的时候加载到我们的DingTalkAutoConfiguration并且将DingTalkRobotClient自动注入到SpringBoot容器中。
在resources资源文件夹下新建META-INF文件夹,创建spring.factories内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.dingtalk.robot.DingTalkAutoConfiguration
通过该配置文件,SpringBoot在启动的时候会去查找这个文件并且根据该文件的配置项,自动装配Java Bean。该配置文件可以定义多个类似的配置项,在这里我们只定义了一个配置项。
下面我们定义另一个配置文件,同样在工程的resources/META-INF文件夹下,文件名是additional-spring-configuration-metadata.json,内容如下:
{ "properties": [ { "sourceType": "org.springframework.dingtalk.robot.DingTalkProperties", "name": "dingtalk.robot.webhook", "type": "java.lang.String", "defaultValue": "https://oapi.dingtalk.com/robot/send", "description": "Dingtalk WebHook" }, { "sourceType": "org.springframework.dingtalk.robot.DingTalkProperties", "name": "dingtalk.robot.secret", "type": "java.lang.String", "description": "Dingtalk secret" }, { "sourceType": "org.springframework.dingtalk.robot.DingTalkProperties", "name": "dingtalk.robot.enable", "type": "java.lang.Boolean", "defaultValue": "true", "description": "enable or disable Dingtalk" } ]}
这个配置文件主要起到配置项提示的功能。到这里Starter已经构建完成,下面介绍如何使用我们自定义的Starter。
使用Starter
先将Starter安装到本地的Maven仓库中
cd dingtalk-robot-startermvn clean install -Dmaven.test.skip=true
在其他需要使用该自定义Starter的地方,将该Starter的maven dependency引入
org.springframework.boot dingtalk-robot-starter 0.0.1
在application.properties或者application.yml文件中添加该Starter的配置项:
dingtalk: robot: webhook: https://oapi.dingtalk.com/robot/send?access_token=this is access token secret: this is secret enable: true
源代码使用方法如下:
@Autowiredprivate DingTalkRobotClient dingTalkRobotClient;dingTalkRobotClient.sendTextMessage(message);
通过这种简单的配置,我们就可以将一些警告信息或者错误信息发送到钉钉机器人了。
钉钉机器人的创建过程在这里就不再赘述,可以自行参考官网文档整个创建过程都有详细的介绍。
大概总结下整个过程:
- 构建一个自定义的Starter项目工程;
- 编写AutoConfiguration自动装配配置类;
- 实现Starter需要实现的功能;
- 编写spring.factories配置文件,告诉SpringBoot在启动的时候需要扫描的配置类;
- 在需要使用该Starter的项目中引入maven dependency;
- 在需要使用该Starter的项目中添加该Starter的配置项;
- 在需要使用该Starter的项目中使用@Autowired注入Java Bean,并且调用该Java Bean里面的方法;