能夠不會,可是得知道java
做爲一名默默無聞辛苦搬磚的程序員,搬磚和造輪子都不是最終目的。拜讀過許多優秀的文章,心想本身有什麼騷操做值得拿出來分享一下的,結合本身平時工做的遇到的問題,因而總結出這篇線上問題解決方案之偷樑換柱,免升級解決線上問題程序員
在一個風雨交加的晚上,本身躺在牀上心神不寧,彷佛在暗示着什麼。因而一成天的經歷在腦海裏像電影同樣放映着,因而畫面定格在了那一秒,下午的那一次升級,What's wrong with that,到底有什麼不對,因而趕忙打開本身的人腦debug模式,一行一行的去回憶的本身寫的每一行代碼。臥*,靈感到來忽然之間,不知道是該感嘆本身記憶的強大,仍是該爲這個NullPointerException而懼怕。事故、績效、年終獎、線上用戶多個詞同時映入在本身的腦海裏,越想越心神不寧,寫出去的代碼潑出去的水,還有什麼方式能夠補救,因而又一番思緒涌上心頭…web
假設線上有一個service(spring bean)的其中一個方法出現了空指針異常或者獲得的並非咱們想要的結果,如今用一個ErrorService(本身程序裏面的一個類模擬一下),假設這就是那個service,errorMethod方法是那個讓我躺在牀上久久不能入眠的那個方法,接下來經過代碼展現一下罪魁禍首spring
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Random;
/**
* 用來模擬線上異常service
*
* @author: ghb
* @date 2020/1/4
*/
@Service
public class ErrorService {
@Autowired
protected PrintService printService;
private int id;
public ErrorService() {
Random random = new Random();
id = random.nextInt(100);
System.out.println("調用ErrorService的構造方法");
}
/**
* 這個是須要進行修改的目標方法
*/
public String errorMethod(String msg) {
return printService.print("[ErrorService]-print-" + msg + id);
}
}
複製代碼
先來看一下接口的返回結果>>>json
罪魁禍首已經找到errorMethod獲得的結果並非我想要的,因而提出如下問題:api
想着想着腦海裏又浮現出復聯4中復聯大軍PK滅霸的場景,面對errorMethod,我何德何能,到底誰纔是帶給我但願的美國隊長和Iron Man 呢,形勢朝不保夕,接下來有請他們上場架構
下面經過一段代碼演示一下groovy 的stream遍歷跟java中不一樣的地方,其餘絕技會再其餘的文章進行介紹app
final personList = [
new Person("Regina", "Fitzpatrick", 25),
new Person("Abagail", "Ballard", 26),
new Person("Lucian", "Walter", 30),
]
assertTrue(personList.stream().filter { it.age > 20 }.findAny().isPresent())
assertFalse(personList.stream().filter { it.age > 30 }.findAny().isPresent())
assertTrue(personList.stream().filter { it.age > 20 }.findAll().size() == 3)
assertTrue(personList.stream().filter { it.age > 30 }.findAll().isEmpty())
複製代碼
官網請參考www.groovy-lang.org/dom
阿里巴巴在2018年7月份發佈Nacos, Nacos 支持幾乎全部主流類型的服務的發現、配置和管理Nacos 致力於幫助您發現、配置和管理微服務。Nacos 提供了一組簡單易用的特性集,幫助您快速實現動態服務發現、服務配置、服務元數據及流量管理。Nacos 幫助您更敏捷和容易地構建、交付和管理微服務平臺。 Nacos 是構建以「服務」爲中心的現代應用架構 (例如微服務範式、雲原生範式) 的服務基礎設施。jvm
Nacos具有服務優雅上下線和流量管理(API+後臺管理頁面),而Eureka的後臺頁面僅供展現,須要使用api操做上下線且不具有流量管理功能。Nacos具備分組隔離功能,一套Nacos集羣能夠支撐多項目、多環境。nacos具備Apollo大部分功能,最重要的是配置中心與註冊中心打通,能夠省去咱們在微服務治理方面 的一些投入
由於這篇位置奇異博士只是做爲配角出場,在此先不對其進行過多的介紹,這裏只是提供它傳送門的做用,先來了解一下spring cloud項目如何引入nacos
<!--配置中心-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>0.2.1.RELEASE</version>
</dependency>
<!--服務註冊與發現-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.2.1.RELEASE</version>
</dependency>
複製代碼
這個dataId爲偷樑換柱就是咱們的傳送門,目的就是經過它來將咱們用於幹掉ErrorService(滅霸)的那段代碼傳送過來,至於爲何選擇它做爲奇異博士還純屬我的喜愛。那麼咱們來看看傳送門內部長什麼樣吧
傳送門裏面是一個list列表,裏面的每一項對應着咱們須要進行替換的Service的名稱,已經知道了敵人是誰,那咱們就得把打boss的祕密武器釋放出來,那麼歡迎鋼鐵俠上場
import com.ghb.book.model.A
import com.ghb.book.service.ErrorService
import org.springframework.beans.factory.annotation.Autowired
class CorrectService extends ErrorService {
private int id;
/**
* 這裏注入一個spring bean而且調用它的get方法
*/
@Autowired
A a
public CorrectService() {
Random random = new Random();
id = random.nextInt(100);
System.out.println("調用CorrectService的構造方法");
}
@Override
String errorMethod(String msg) {
return printService.print("[CorrectService]-print" + msg + id);
}
}
複製代碼
先來介紹下這段代碼,這是用groovy聲明的一個類,其保存在nacos配置中內心面,爲何使用它保存咱們的代碼,由於做爲配置中心,它支持各類格式文件的保存,而且歷史版本能夠支持代碼回滾,以前一直使用apollo做爲配置中心,值得遇到了nacos,我才發現原來曾經深入認爲的並非我想要的…
該類繼承自ErrorService,從新了errorMethod方法,返回須要的正確的結果,如今它只是一個普通的類,裏面爲何能注入@Autowired一個spring bean呢,就是由於那是鋼鐵俠嗎,固然不是,若是劇情只有這麼簡單那豈不是很不過癮
剛纔奇異博士的傳送門咱們已經看到了,那麼這段代碼就是須要被傳送的祕密武器了,接下來請觀看奇異博士搞出傳送門這個東西,到底修煉了什麼功法
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.ghb.book.service.RegisterBeanService;
import com.ghb.book.util.GroovyScriptFactory;
import com.ghb.book.util.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.nacos.NacosConfigProperties;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.concurrent.Executor;
/**
* @author: ghb
* @date 2019/12/24
*/
@Component
@Slf4j
public class InitConfig {
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Autowired
private RegisterBeanService registerBeanService;
@PostConstruct
public void init() throws NacosException {
nacosConfigProperties.configServiceInstance()
.addListener("偷樑換柱", "DEFAULT_GROUP", new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String s) {
log.info("監聽到數據更新:{}", s);
JSONArray jsonArray = JSON.parseArray(s);
jsonArray.forEach(beanName -> {
try {
//驚奇隊長上線 註冊新的bean到spring容器中
register(String.valueOf(beanName));
} catch (NacosException e) {
log.info("偷樑換柱失敗");
}
});
}
});
}
/**
* 1 從nacos中獲取配置類
* 2 解析類
* 3 偷樑換柱
*
* @param beanName spring bean 命名
* @throws NacosException e
*/
public void register(String beanName) throws NacosException {
//讀取groovy配置
String groovy = nacosConfigProperties.configServiceInstance()
.getConfig(beanName, "groovy", 1000);
//加載groovy類 並獲取groovy class類類型
Class groovyClass = GroovyScriptFactory.getInstance().parseClass(groovy);
//註冊bean
Object bean = registerBeanService.registerBean(beanName, groovyClass);
log.info("bean---{}", bean.getClass().getName());
}
}
複製代碼
在spring容器啓動的時候,執行聲明週期回調方法,經過@PostConstruct去添加一個監聽器,去監聽傳送門裏面的數據,這裏這個傳送門是代碼裏面的偷樑換柱,若是監聽到數據有變化,去遍歷傳送門裏面的數據,而後將每一項交給美國隊長,註冊新的bean到spring容器中,關於如何解析一個groovy類本片文章先不作過多的贅述
美國漫畫中最「主旋律」的超級英雄非美國隊長莫屬。他用國名當作頭銜,制服是紅白藍加上明亮的星星;一面一樣顏色的盾牌就是他的武器。接下來期待下他的操做
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;
/**
* @author: ghb
* @date 2020/1/4
*/
@Component
@Slf4j
public class RegisterBeanService implements ApplicationContextAware {
ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public <T> T registerBean(String name, Class<T> clazz, Object... args) {
ConfigurableApplicationContext context = (ConfigurableApplicationContext) applicationContext;
// 經過BeanDefinitionBuilder建立bean定義
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
for (Object arg : args) {
beanDefinitionBuilder.addConstructorArgValue(arg);
}
//bean定義
BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
//從spring容器中獲取bean工廠
BeanDefinitionRegistry beanFactory = (BeanDefinitionRegistry) context.getBeanFactory();
if (context.containsBean(name)) {
//先從spring容器中獲取該bean
Object bean = context.getBean(name);
if (bean.getClass().isAssignableFrom(clazz)) {
log.info("bean:[{}]在系統中已存在,接下來對其進行替換操做", name);
if (clazz.getGenericSuperclass() == bean.getClass()) {
log.info("新bean是被替換bean的子類,符合邏輯,開始替換操做.....");
beanFactory.removeBeanDefinition(name);
beanFactory.registerBeanDefinition(name, beanDefinition);
} else {
throw new RuntimeException("偷樑換柱失敗,非法操做");
}
} else {
log.info("bean:[{}]系統中不存在存在,建立bean", name);
beanFactory.registerBeanDefinition(name, beanDefinition);
}
}
return applicationContext.getBean(name, clazz);
}
}
複製代碼
這段代碼就是註冊bean到spring容器中的核心操做了,這裏只是一個用於實現功能的簡化版的方法,咱們來看看它到底幹了什麼
上述操做完成後咱們已經實現了偷樑換柱而且滅霸最終被戰勝,彈響指的過程就是忘nacos配置中心中偷樑換柱中添加errorService的過程,固然只是使用nacos最爲一個媒介,經過接口調用也是能夠,咱們來看看從新迴歸和平後的模樣,終於守的雲開見月明,一切問題都迎刃而解
思考幾個問題
通過上述操做,結合groovy、nacos和spring實現了對問題的動態修復,滅霸頁最終被戰勝,今後不再用擔憂本身線上寫出來的bug了。在此我的只是提出了一種解決問題的方法和思路。代碼也並不完善,若是存在任何錯誤,歡迎你們指正。