前言
最近分享的一些源碼、框架設計的東西。我發現大家熱情不是特別高,想想大多數應該還是正兒八經寫代碼的居多;這次就分享一點接地氣的: SpringBoot 使用中的一些小技巧。
算不上多高大上的東西,但都還挺有用。
屏蔽外部依賴
第一個是屏蔽外部依賴,什麼意思呢?
比如大家日常開發時候有沒有這樣的煩惱:
項目是基於 SpringCloud 或者是 dubbo 這樣的分佈式服務,你需要依賴許多基礎服務。
比如說某個訂單號的生成、獲取用戶信息等。
由於服務拆分,這些功能都是在其他應用中以接口的形式提供,單測還好我還可以利用 Mock 把它屏蔽掉。
但如果自己想把應用啟動起來同時把自己相關的代碼跑一遍呢?
通常有幾種做法:
- 本地把所有的服務都啟動起來。
- 把註冊中心換為開發環境,依賴開發環境的服務。
- 直接把代碼推送到開發環境自測。
看起來三種都可以,以前我也是這麼幹的。但還是有幾個小問題:
- 本地啟動有可能服務很多,全部起來電腦能不能撐住還兩說,萬一服務有問題就進行不下去了。
- 依賴開發環境的前提是網絡打通,還有一個問題就是開發環境代碼很不穩定很大可能會影響你的測試。
- 推送到開發環境應該是比較靠譜的方案,但如果想調試只有日誌大法,沒有本地 debug 的效率高效。
那如何解決問題呢?既可以在本地調試也不用啟動其他服務。
其實也可以利用單測的做法,把其他外部依賴 Mock 掉就行了。
大致的流程分為以下幾步:
- SpringBoot 啟動之後在 Spring 中找出你需要屏蔽的那個 API 的 bean(通常情況下這個接口都是交給 Spring 管理的)。
- 手動從 bean 容器中刪除該 bean。
- 重新創建一個該 API 的對象,只不過是通過 Mock 出來的。
- 再手動註冊進 bean 容器中。
以下面這段代碼為例:
@Override
public BaseResponsegetUserByHystrix(@RequestBody UserReqVO userReqVO) {
OrderNoReqVO vo = new OrderNoReqVO();
vo.setAppId(123L);
vo.setReqNo(userReqVO.getReqNo());
BaseResponseorderNo = orderServiceClient.getOrderNo(vo);
return orderNo;
}
這是一個 SpringCloud 應用。
它依賴於 orderServiceClient 獲取一個訂單號。
其中的 orderServiceClient 就是一個外部 API,也是被 Spring 所管理。
替換原有的 Bean
下一步就是替換原有的 Bean。
@Component
public class OrderMockServiceConfig implements CommandLineRunner {
private final static Logger logger = LoggerFactory.getLogger(OrderMockServiceConfig.class);
@Autowired
private ApplicationContext applicationContext;
@Value("${excute.env}")
private String env;
@Override
public void run(String... strings) throws Exception {
// 非本地環境不做處理
if ("dev".equals(env) || "test".equals(env) || "pro".equals(env)) {
return;
}
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
OrderServiceClient orderServiceClient = defaultListableBeanFactory.getBean(OrderServiceClient.class);
logger.info("======orderServiceClient {}=====", orderServiceClient.getClass());
defaultListableBeanFactory.removeBeanDefinition(OrderServiceClient.class.getCanonicalName());
OrderServiceClient mockOrderApi = PowerMockito.mock(OrderServiceClient.class,
invocationOnMock -> BaseResponse.createSuccess(DateUtil.getLongTime() + "", "mock orderNo success"));
defaultListableBeanFactory.registerSingleton(OrderServiceClient.class.getCanonicalName(), mockOrderApi);
logger.info("======mockOrderApi {}=====", mockOrderApi.getClass());
}
}
其中實現了 CommandLineRunner 接口,可以在 Spring 容器初始化完成之後調用 run() 方法。
代碼非常簡單,簡單來說首先判斷下是什麼環境,畢竟除開本地環境其餘的都是需要真正調用遠程服務的。
之後就是獲取 bean 然後手動刪除掉。
關鍵的一步:
OrderServiceClient mockOrderApi = PowerMockito.mock(OrderServiceClient.class,
invocationOnMock -> BaseResponse.createSuccess(DateUtil.getLongTime() + "", "mock orderNo success"));
defaultListableBeanFactory.registerSingleton(OrderServiceClient.class.getCanonicalName(), mockOrderApi);
創建了一個新的 OrderServiceClient 對象並手動註冊進了 Spring 容器中。
第一段代碼使用的是 PowerMockito.mock 的 API,他可以創建一個代理對象,讓所有調用 OrderServiceClient 的方法都會做默認的返回。
BaseResponse.createSuccess(DateUtil.getLongTime() + "", "mock orderNo success"))
測試一下,當我們沒有替換時調用剛才那個接口並且本地也沒有啟動 OrderService:
因為沒有配置 fallback 所以會報錯,表示找不到這個服務。
替換掉 bean 時:
再次請求沒有報錯,並且獲得了我們默認的返回。
通過日誌也會發現 OrderServiceClient 最後已經被 Mock 代理了,並不會去調用真正的方法。
配置加密
下一個則是配置加密,這應該算是一個基本功能。
比如我們配置文件中的一些賬號和密碼,都應該是密文保存的。
因此這次使用了一個開源組件來實現加密與解密,並且對 SpringBoot 非常友好只需要幾段代碼即可完成。
- 首先根據加密密碼將需要加密的配置加密為密文。
- 替換原本明文保存的配置。
- 再使用時進行解密。
使用該包也只需要引入一個依賴即可:
com.github.ulisesbocchio
jasypt-spring-boot-starter
1.14
同時寫一個單測根據密碼生成密文,密碼也可保存在配置文件中:
jasypt.encryptor.password=123456
接著在單測中生成密文。
@Autowired
private StringEncryptor encryptor;
@Test
public void getPass() {
String name = encryptor.encrypt("userName");
String password = encryptor.encrypt("password");
System.out.println(name + "----------------");
System.out.println(password + "----------------");
}
之後只需要使用密文就行。
由於我這裡是對數據庫用戶名和密碼加密,所以還得有一個解密的過程。
利用 Spring Bean 的一個增強接口即可實現:
@Component
public class DataSourceProcess implements BeanPostProcessor {
@Autowired
private StringEncryptor encryptor;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof DataSourceProperties){
DataSourceProperties dataSourceProperties = (DataSourceProperties) bean;
dataSourceProperties.setUsername(encryptor.decrypt(dataSourceProperties.getUsername())) ;
dataSourceProperties.setPassword(encryptor.decrypt(dataSourceProperties.getPassword()));
return dataSourceProperties ;
}
return bean;
}
}
這樣就可以在真正使用時還原為明文。
同時也可以在啟動命令中配置剛才的密碼:
java -Djasypt.encryptor.password=password -jar target/jasypt-spring-boot-demo-0.0.1-SNAPSHOT.jar
總結
這樣兩個小技巧就講完了,大家有 SpringBoot 的更多使用技巧歡迎留言討論。
閱讀更多 程序員小新人學習 的文章