## Spring啟動器
* SpringBoot工程可以在main方法中執行SpringApplication.run()這種方式來啟動。
```
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
```
* 如果我們需要在SpringBoot啟動過程中添加一些定制代碼(如定制啟動Banner,設置自定義監聽器等,設置啟動拓展,設置啟動環境變量),這種方式就無法滿足我們的要求了。
* 比如我們現在要定制啟動Banner,那么有如下兩種方式。
```
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(Application.class);
application.setBannerMode(Banner.Mode.OFF);
application.run(args);
}
}
```
```
@SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder()
.sources(Application.class)
.bannerMode(Banner.Mode.OFF)
.run(args);
}
}
```
* 只是定制一個banner,代碼就得如此,并不是很優雅,倘若我們有很多個微服務,那每個微服務都這么寫,代碼必定會顯得很冗余。
* 所以我們需要有一個優雅的解決方案,使得每個微服務都調用自定義的啟動器,這樣最終呈現效果就能如原生一樣簡潔優雅。
<br>
## 自定義啟動器
* 核心思路是采用`SpringApplicationBuilder`,將其封裝進一個核心類中進行拓展,以便于微服務啟動之用。
* 啟動器核心代碼如下
~~~
/**
* 項目啟動器,搞定環境變量問題
*
* @author Chill
*/
public class BladeApplication {
/**
* Create an application context
* java -jar app.jar --spring.profiles.active=prod --server.port=2333
*
* @param appName application name
* @param source The sources
* @return an application context created from the current state
*/
public static ConfigurableApplicationContext run(String appName, Class source, String... args) {
SpringApplicationBuilder builder = createSpringApplicationBuilder(appName, source, args);
return builder.run(args);
}
private static SpringApplicationBuilder createSpringApplicationBuilder(String appName, Class source, String... args) {
Assert.hasText(appName, "[appName]服務名不能為空");
// 讀取環境變量,使用spring boot的規則
ConfigurableEnvironment environment = new StandardEnvironment();
MutablePropertySources propertySources = environment.getPropertySources();
propertySources.addFirst(new SimpleCommandLinePropertySource(args));
propertySources.addLast(new MapPropertySource(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, environment.getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, environment.getSystemEnvironment()));
// 獲取配置的環境變量
String[] activeProfiles = environment.getActiveProfiles();
// 判斷環境:dev、test、prod
List<String> profiles = Arrays.asList(activeProfiles);
// 預設的環境
List<String> presetProfiles = new ArrayList<>(Arrays.asList(AppConstant.DEV_CDOE, AppConstant.TEST_CODE, AppConstant.PROD_CODE));
// 交集
presetProfiles.retainAll(profiles);
// 當前使用
List<String> activeProfileList = new ArrayList<>(profiles);
Function<Object[], String> joinFun = StringUtils::arrayToCommaDelimitedString;
SpringApplicationBuilder builder = new SpringApplicationBuilder(source);
String profile;
if (activeProfileList.isEmpty()) {
// 默認dev開發
profile = AppConstant.DEV_CDOE;
activeProfileList.add(profile);
builder.profiles(profile);
} else if (activeProfileList.size() == 1) {
profile = activeProfileList.get(0);
} else {
// 同時存在dev、test、prod環境時
throw new RuntimeException("同時存在環境變量:[" + StringUtils.arrayToCommaDelimitedString(activeProfiles) + "]");
}
String startJarPath = BladeApplication.class.getResource("/").getPath().split("!")[0];
String activePros = joinFun.apply(activeProfileList.toArray());
System.out.println(String.format("----啟動中,讀取到的環境變量:[%s],jar地址:[%s]----", activePros, startJarPath));
Properties props = System.getProperties();
props.setProperty("spring.application.name", appName);
props.setProperty("spring.profiles.active", profile);
props.setProperty("info.version", AppConstant.APPLICATION_VERSION);
props.setProperty("info.desc", appName);
props.setProperty("blade.env", profile);
props.setProperty("blade.name", appName);
props.setProperty("blade.is-local", String.valueOf(isLocalDev()));
props.setProperty("blade.dev-mode", profile.equals(AppConstant.PROD_CODE) ? "false" : "true");
props.setProperty("blade.service.version", AppConstant.APPLICATION_VERSION);
props.setProperty("spring.cloud.consul.host", ConsulConstant.CONSUL_HOST);
props.setProperty("spring.cloud.consul.port", ConsulConstant.CONSUL_PORT);
props.setProperty("spring.cloud.consul.config.format", ConsulConstant.CONSUL_CONFIG_FORMAT);
props.setProperty("spring.cloud.consul.watch.delay", ConsulConstant.CONSUL_WATCH_DELAY);
props.setProperty("spring.cloud.consul.watch.enabled", ConsulConstant.CONSUL_WATCH_ENABLED);
// 加載自定義組件
ServiceLoader<LauncherService> loader = ServiceLoader.load(LauncherService.class);
loader.forEach(launcherService -> launcherService.launcher(builder, appName, profile));
return builder;
}
/**
* 判斷是否為本地開發環境
*
* @return boolean
*/
private static boolean isLocalDev() {
String osName = System.getProperty("os.name");
return StringUtils.hasText(osName) && !(AppConstant.OS_NAME_LINUX.equals(osName.toUpperCase()));
}
}
~~~
<br>
## 如何使用
* 以我們之前做的 `blade-demo`模塊啟動為例,看下代碼
~~~
/**
* Demo啟動器
*
* @author Chill
*/
@SpringCloudApplication
@EnableFeignClients(AppConstant.BASE_PACKAGES)
public class DemoApplication {
public static void main(String[] args) {
BladeApplication.run(CommonConstant.APPLICATION_DEMO_NAME, DemoApplication.class, args);
}
}
~~~
* 可以看到非常簡約,與原生并沒有太多變化,只是多了一個appName的參數,此參數正是做為服務名注冊到注冊中心,用于和其他服務分類。
<br>
## 注意點
* 自定義啟動器已經將環境變量也設置好,無需再到`application.yml`中配置`spring.profiles.active`再打包。
* 打包后的app啟動時,若不設置`spring.profiles.active`,則默認為`dev`,如需設置只需在啟動的命令行加上即可。
`java -jar app.jar --spring.profiles.active=prod --server.port=2333`
* 無論是打包了fat-jar,還是打包了docker,都只需要打包一次,搭配上注冊中心就可以運行在任何設定好的環境中,這樣一來就實現了`一次打包,處處運行`的理念。
* 開發中,如果要修改為非`DEV`環境,可參考如下配置。

- 第零章 序
- 序言
- 系統架構
- 視頻公開課
- 開源版介紹
- 商業版介紹
- 功能對比
- 答疑流程
- 第一章 快速開始
- 升級必看
- 環境要求
- 環境準備
- 基礎環境安裝
- Docker安裝基礎服務
- Nacos安裝
- Sentinel安裝
- 插件安裝
- 建數據庫
- 工程導入
- 導入Cloud版本
- 導入Nacos配置
- 導入Boot版本
- 工程運行
- 運行Cloud版本
- 運行Boot版本
- 工程測試
- 測試Cloud版本
- 測試Boot版本
- 第二章 技術基礎
- Java
- Lambda
- Lambda 受檢異常處理
- Stream 簡介
- Stream API 一覽
- Stream API (上)
- Stream API (下)
- Optional 干掉空指針
- 函數式接口
- 新的日期 API
- Lombok
- SpringMVC
- Swagger
- Mybatis
- Mybatis-Plus
- 開發規范
- 第三章 開發初探
- 新建微服務工程
- 第一個API
- API鑒權
- API響應結果
- Redis緩存
- 第一個CRUD
- 建表
- 建Entity
- 建Service和Mapper
- 新增 API
- 修改 API
- 刪除 API
- 查詢 API
- 單條數據
- 多條數據
- 分頁
- 微服務遠程調用
- 聲明式服務調用 Feign
- 熔斷機制 Hystrix
- 第四章 開發進階
- 聚合文檔
- 鑒權配置
- 跨域處理
- Xss防注入
- 自定義啟動器
- Secure安全框架
- Token認證簡介
- Token認證配置
- PreAuth注解配置
- Token認證實戰
- Token認證加密
- 日志系統
- 原理解析
- 功能調用
- Seata分布式事務
- 簡介
- 編譯包啟動
- 配置nacos對接
- docker啟動
- 對接微服務
- 代碼生成配置
- 前言
- 數據庫建表
- 代碼生成
- 前端配置
- 優化效果
- 第五章 功能特性
- SaaS多租戶
- 概念
- 數據隔離配置
- 線程環境自定義租戶ID
- 多終端令牌認證
- 概念
- 系統升級
- 使用
- 第三方系統登錄
- 概念說明
- 對接說明
- 對接準備
- 配置說明
- 操作流程
- 后記
- UReport2報表
- 報表簡介
- 對接配置
- 報表后記
- 接口報文加密
- 簡介
- 運行邏輯
- 對接準備
- 功能配置
- 接口測試
- 改造查詢
- 改造提交
- 改造刪除
- 動態數據權限
- 數據權限簡介
- 數據權限開發
- 純注解配置
- Web全自動配置
- 注解半自動配置
- 數據權限注意點
- 動態接口權限
- 樂觀鎖配置
- 統一服務登陸配置
- Skywalking追蹤監控
- Minio分布式對象存儲
- Boot版本對接至Cloud
- 第六章 生產部署
- windows部署
- linux部署
- jar部署
- docker部署
- java環境安裝
- mysql安裝
- docker安裝
- docker-compose安裝
- harbor安裝
- 部署步驟
- 寶塔部署
- 準備工作
- 安裝工作
- 部署準備
- 部署后端
- 部署前端
- 部署域名
- 結束工作
- k8s平臺部署
- 第七章 版本控制
- Git遠程分支合并
- Git地址更換
- 第八章 學習資料
- 第九章 FAQ
- 第十章 聯系我們