嘛小伙伴們都問(wèn)我我是怎么抽那么多時(shí)間來(lái)看書的岗钩,其實(shí)說(shuō)難也不難說(shuō)簡(jiǎn)單其實(shí)也不簡(jiǎn)單女坑,就是提高效率和擠時(shí)間嘛薇宠。你要相信在一天中偷办,每個(gè)時(shí)間都有它自己應(yīng)該待的位置,做好工作計(jì)劃澄港,提升工作效率椒涯,你會(huì)發(fā)現(xiàn)一天下來(lái)你會(huì)有稍微多個(gè)一兩個(gè)小時(shí)的時(shí)間,不然就只是忙忙忙然之后到最后不知道自己在忙什么回梧。
至于怎么看書废岂,我看書的時(shí)間點(diǎn)大概就兩個(gè),一個(gè)是午飯后狱意,第二個(gè)是睡覺(jué)前湖苞,都會(huì)看個(gè)一章或者兩章,久而久之详囤,你會(huì)發(fā)現(xiàn)你看的書比旁邊吃雞的同學(xué)看多了很多的書财骨。當(dāng)然呢,也別問(wèn)我看什么書有用藏姐,我什么書都看隆箩。你看過(guò)的那些書,可能你會(huì)忘記羔杨,但會(huì)沉淀在你的骨頭里捌臊,在你潛意識(shí)里《挡模總有一天你會(huì)偶然看到一個(gè)東西理澎,恍然大悟,咦這個(gè)小玩意我好像認(rèn)識(shí)曙寡,雖然不知道在哪里見(jiàn)過(guò)但就是很眼熟矾端。嗯。
接下來(lái)都是技術(shù)干貨卵皂,非技術(shù)戰(zhàn)斗人員請(qǐng)立刻左上角退出戰(zhàn)場(chǎng)秩铆。
今天這個(gè)關(guān)于配置中心的小項(xiàng)目是早上起床抽空花了差不多兩個(gè)小時(shí)寫的~希望能幫大家理解理解配置中心實(shí)現(xiàn)的原理。
記得先啟動(dòng)ConfigurationCenter,再啟動(dòng)ConfigurationMiniServer殴玛,JDK用1.8捅膘,至于詳細(xì)的內(nèi)容嘛,容我細(xì)細(xì)道來(lái)滚粟。
原理就是這樣寻仗,配置中心起一個(gè) RPC 進(jìn)程 ConfigurationCenterService ,用來(lái)提供注冊(cè)的服務(wù)凡壤。服務(wù)器所有的配置項(xiàng)都從類的靜態(tài)域里取署尤,服務(wù)器本地起一個(gè) RPC 進(jìn)程 ConfigurationMiniService,用來(lái)接收來(lái)自配置中心的配置更新的 push 亚侠,取到之后替換掉靜態(tài)域的值曹体。那么下次配置項(xiàng)的使用方在使用的時(shí)候就能獲取到新的值啦匾旭。
原理說(shuō)完了蹦渣,那我們看幾個(gè)核心的東西活喊。
首先定義了一個(gè)注解 Config 栋齿,這個(gè)注解的作用域是 FIELD 也就是每個(gè)類的屬性覆山。這個(gè)注解只有一個(gè)作用髓迎,就是把當(dāng)前的屬性標(biāo)記為配置項(xiàng)識(shí)別出來(lái)而已粥庄,為什么要實(shí)現(xiàn)成注解呢薯酝?原因只有一個(gè)狮杨,就是對(duì)程序無(wú)入侵母截,如果想作為配置項(xiàng),那就加上注解橄教。如果某個(gè)值不想作為配置項(xiàng)微酬,直接把注解去掉即可,裝卸十分方便颤陶。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Config {
public String desc() default "lazy";
}
這里我們定義了一個(gè)真正的配置類颗管,也就是我們平時(shí)開發(fā)的時(shí)候使用到的類。所有定義為配置項(xiàng)的地方滓走,用我們剛剛定義的 @Config 注解進(jìn)行注冊(cè)垦江。我們需要使用配置項(xiàng)的時(shí)候,直接從這個(gè)類的靜態(tài)域獲取即可搅方。最終呢比吭,在配置變更的時(shí)候,服務(wù)器接收到配置變更的請(qǐng)求的時(shí)候會(huì)直接替換類里靜態(tài)域的值姨涡。
public class Configuration {
@Config(desc = "數(shù)字配置")
public static Integer NUMBER_CONFIG = 5;
@Config(desc = "開關(guān)型配置")
public static Boolean SWITCH_CONFIG = true;
}
ConfigurationCenterService 定義了三個(gè)行為衩藤。
第一個(gè)是 register 注冊(cè),給服務(wù)器注冊(cè)自己的配置項(xiàng)用的涛漂。
第二個(gè)是 pushConfig 配置推送赏表,給 client 或者 web 頁(yè)面進(jìn)行配置推送用的检诗。第三個(gè)是 getAllConfig() ,給 client 或者 web 頁(yè)面獲取當(dāng)前所有配置項(xiàng)用的瓢剿。
public interface ConfigurationCenterService extends Remote {
int register(ConfigDTO configDTO) throws RemoteException;
int pushConfig(ConfigDTO configDTO) throws RemoteException;
void getAllConfig() throws RemoteException;
}
ConfigurationMiniService 定義了三個(gè)行為逢慌。
第一個(gè)是 registerClass 注冊(cè),給本地的配置類注冊(cè)到配置中心用的间狂。
第二個(gè)是 changeConfig 配置變更攻泼,暴露給配置中心,配置中心有配置變更的請(qǐng)求就直接調(diào)用本地的 mini 服務(wù)器的ConfigurationMiniService 進(jìn)行配置變更的才做鉴象。
第三個(gè)是 init() 忙菠,是一個(gè)普通的初始化方法。
public interface ConfigurationMiniService extends Remote {
int changeConfig(ConfigDTO configDTO) throws RemoteException;
void registerClass(Class c) throws RemoteException;
void init() throws RemoteException, AlreadyBoundException, MalformedURLException, NotBoundException;
}
ConfigurationMiniServer 是真正的本地 mini 服務(wù)器纺弊,首先定義了哪些類是配置類牛欢,這個(gè)我只是簡(jiǎn)單實(shí)現(xiàn)了,真正做的使用可以給類加一個(gè)注解俭尖,用對(duì)包進(jìn)行掃描的形式發(fā)現(xiàn)配置類氢惋。然后實(shí)例化了一個(gè)本地 RPC 進(jìn)程ConfigurationMiniService洞翩。接著把所有的類一個(gè)一個(gè)使用本地的 RPC 進(jìn)程進(jìn)行注冊(cè)稽犁。
Set<Class> classesToRegister = new HashSet<>();
classesToRegister.add(Configuration.class);
ConfigurationMiniService service = new ConfigurationMiniServiceImpl("127.0.0.1","8000");
service.init();
for(Class currentClass : classesToRegister){
service.registerClass(currentClass);
}
那么是怎么注冊(cè)的呢?其實(shí)也不難骚亿。先在服務(wù)本地記錄一下配置類已亥,準(zhǔn)備開始注冊(cè)。獲得目標(biāo)類的所有的 Field来屠,然后判斷這個(gè) Field 是不是有 @Config 注解虑椎,如果有,那么獲得當(dāng)前的類名俱笛,屬性捆姜,值,描述迎膜,服務(wù)器信息等泥技,調(diào)用配置中心的 ConfigurationCenterService 進(jìn)行注冊(cè)。
public void registerClass(Class currentClass) throws RemoteException {
this.configClasses.put(currentClass.getName(),currentClass);
Field[] fields = currentClass.getDeclaredFields();
for(Field field : fields){
field.setAccessible(true);
if(field.isAnnotationPresent(Config.class)){
ConfigDTO configDTO = new ConfigDTO();
configDTO.setServer(serverUri);
configDTO.setClassName(currentClass.getName());
configDTO.setFiled(field.getName());
configDTO.setDesc(field.getAnnotation(Config.class).desc());
try {
configDTO.setValueType(field.getType().getName());
Object value = field.get(null);
configDTO.setValue(String.valueOf(value));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
configurationCenterService.register(configDTO); }
}
}
數(shù)據(jù)結(jié)構(gòu)長(zhǎng)這樣磕仅,因?yàn)?RPC 要經(jīng)過(guò)網(wǎng)絡(luò)傳輸珊豹,所以一定要實(shí)現(xiàn)序列化。
public class ConfigDTO implements Serializable{
private String server;
private String className;
private String filed;
private String desc;
private String valueType;
private String value;
}
ConfigurationCenterService 配置中心接收到消息之后呢榕订,就在本地記錄一下店茶,順便把目標(biāo) mini 服務(wù)器的 RPC 調(diào)用進(jìn)行初始化。
@Override public int register(ConfigDTO configDTO) throws RemoteException {
configs.add(configDTO);
getOrCreateBundle(configDTO.getServer());
Logger.log(configDTO);
return 200;
}
到這里劫恒,一個(gè)配置項(xiàng)的注冊(cè)就算完成了贩幻,那么如何進(jìn)行配置變更呢?下面這些代碼很長(zhǎng),但是目的只有一個(gè)段直,就是封裝出目標(biāo)服務(wù)器吃溅,目標(biāo)類,目標(biāo) Field 鸯檬,要變更的值决侈,以及值的類型,然后 push 給 mini服務(wù)器就好了喧务。
Scanner scanner = new Scanner(System.in);
System.out.println("input\n" +
"get //to get all configs \n" +
"push 127.0.0.1:8000/cfg_miniserver config.Configuration NUMBER_CONFIG 6 java.lang.Integer \r\n"); while (scanner.hasNext()){
String command = scanner.nextLine();
String[] commanArray = command.split(" "); String cmd = commanArray[0];
if(cmd.equals("get")){
service.getAllConfig();
}
else if(cmd.equals("push")){
String server = commanArray[1];
String className = commanArray[2];
String field = commanArray[3];
String value = commanArray[4];
String valueType = commanArray[5];
ConfigDTO configDTO = new ConfigDTO();
configDTO.setFiled(field);
configDTO.setClassName(className);
configDTO.setServer(server);
configDTO.setValue(value);
configDTO.setValueType(valueType); int result = service.pushConfig(configDTO);
if(result == 200){
Logger.log("[success]推送配置 "+field+" ,值為 "+value+" 到服務(wù)器"+server+" 成功");
}else{
Logger.log("[error]推送配置 "+field+" ,值為 "+value+" 到服務(wù)器"+server+" 失敗");
}
}
}
喏赖歌,簡(jiǎn)單的直接 push 給 mini 服務(wù)器。
@Override public int pushConfig(ConfigDTO configDTO) throws RemoteException {
ConfigurationMiniService currentService = getOrCreateBundle(configDTO.getServer()); return currentService.changeConfig(configDTO);
}
當(dāng) mini 服務(wù)器接收到來(lái)自配置中心的請(qǐng)求的是時(shí)候功茴,會(huì)進(jìn)行本地值的替換庐冯,我們?cè)趥鬏數(shù)臅r(shí)候都是序列化的字符串,所以要轉(zhuǎn)一下坎穿。原理也很簡(jiǎn)單展父,就是利用反射識(shí)別出目標(biāo)類的目標(biāo) Field,將值變更為新的值玲昧。
@Override public int changeConfig(ConfigDTO configDTO) throws RemoteException {
Class targetClass = this.configClasses.get(configDTO.getClassName());
if(targetClass == null){
return 500;
}
try {
Field field = targetClass.getField(configDTO.getFiled());
field.setAccessible(true);
switch (configDTO.getValueType()){
case "java.lang.Integer":
field.set(null,Integer.valueOf(configDTO.getValue()));
break;
case "java.lang.Boolean":
field.set(null,Boolean.valueOf(configDTO.getValue()));
break;
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return 200; }
有人說(shuō)我怎么知道它變更了呢栖茉?喇喇喇。早就給你想好了孵延,每3秒輸出一次當(dāng)前的值吕漂,這樣子值一變更就可以肉眼看到了。當(dāng)然實(shí)際在使用的時(shí)候基本可以實(shí)現(xiàn)配置中心推完尘应,就實(shí)時(shí)更新惶凝,這個(gè)要看網(wǎng)絡(luò)延遲了。
Runnable check = new Runnable() {
@Override
public void run() {
Logger.log(Configuration.NUMBER_CONFIG);
Logger.log(Configuration.SWITCH_CONFIG);
}
}; ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); scheduledExecutorService.scheduleAtFixedRate(check,0,3, TimeUnit.SECONDS);
看犬钢,注冊(cè)成功會(huì)顯示這個(gè)苍鲜,給配置中心發(fā)送請(qǐng)求 get 也可以獲取到。
我們?cè)囋嚳?push 一下值玷犹,如果成功會(huì)看到推送成功混滔。
也能看到值會(huì)從 5 變更我們推過(guò)去的 6 了。
好啦箱舞,今天的配置中心就講到這里,大家有什么想法都可以留言晴股,也歡迎大家跟我一起邊玩代碼邊學(xué)習(xí)愿伴。代碼我已經(jīng)放到 github上了隔节,github 的地址發(fā)送"配置中心"獲取喔鹅经,喜歡的小伙伴可以下載下來(lái)自己玩玩怎诫。
碼了這么多字這么多代碼,你不轉(zhuǎn)發(fā)贊賞一下嗎幻妓?一毛錢也是認(rèn)可是不蹦误?