当前位置:网站首页>Hard core! One configuration center for 8 classes!
Hard core! One configuration center for 8 classes!
2022-07-06 09:29:00 【Java confidant_】
Configuration center It is an important module when we use microservice architecture , There are many common configuration center components , From the early Spring Cloud Config, To Disconf、Apollo、Nacos etc. , The functions they support 、 Product performance and user experience are also different .
Although there are many functional differences , But the core problem they solve , There is no doubt that is The modified configuration file takes effect in real time , Sometimes while moving bricks Hydra Just wondering how real-time validation is achieved 、 If I were to design it, how would it be realized , So I took some free time these days , Fished out a simple version of the stand-alone configuration center , Let's see the effect first :
It's a simple version , First of all, because the core functions implemented only take effect in real time after configuration modification , And the implementation of the code is also very simple , A total of only 8 This core class implements this function , Look at the structure of the code , The core class is core
This in the bag 8 Classes :
Is it a little curious to see this , Although it is a low configuration version , Just a few classes can implement a configuration center ? Then let's take a look at the overall design process , Let's talk about the code in detail .
Code brief description
Following pair 8 Give a brief description of the three core classes and post the core code , Some classes have long code , May not be very friendly to the friends browsing on the mobile phone , It is recommended to open the computer browser after collecting ( Cheat wave collection , Planning communication !). in addition Hydra All the codes of the project have been uploaded to git
, If you need it, you can go to the end of the document to get the address .
1、ScanRunner
ScanRunner
Realized CommandLineRunner
Interface , It can be guaranteed that it will springboot Start the final execution , This will ensure that others Bean The instantiation has been completed and put into the container . As for why it was named ScanRunner
, Because the main function to be realized here is to scan the related functions of classes . First look at the code :
@Component
public class ScanRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
doScanComponent();
}
private void doScanComponent(){
String rootPath = this.getClass().getResource("/").getPath();
List<String> fileList = FileScanner.findFileByType(rootPath,null,FileScanner.TYPE_CLASS);
doFilter(rootPath,fileList);
EnvInitializer.init();
}
private void doFilter(String rootPath, List<String> fileList) {
rootPath = FileScanner.getRealRootPath(rootPath);
for (String fullPath : fileList) {
String shortName = fullPath.replace(rootPath, "")
.replace(FileScanner.TYPE_CLASS,"");
String packageFileName=shortName.replaceAll(Matcher.quoteReplacement(File.separator),"\\.");
try {
Class clazz = Class.forName(packageFileName);
if (clazz.isAnnotationPresent(Component.class)
|| clazz.isAnnotationPresent(Controller.class)
||clazz.isAnnotationPresent(Service.class)){
VariablePool.add(clazz);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
The real realization of file scanning function is called FileScanner
, Its implementation will be discussed later , In terms of function, it can scan all files in a directory according to the file suffix , Here we first scan out target
All under the directory are marked with .class
Final document :
Scan to all class
After the document , You can use the fully qualified name of the class to obtain the name of the class Class
object , The next step is to call doFilter
Method to filter the class . Here, we only consider passing @Value
Annotation is used to inject attribute values into the configuration file , So the next question is , What kind of @Value
The annotation will take effect ? The answer is through @Component
、@Controller
、@Service
Give these notes to spring Container managed classes .
Sum up , We use these annotations to filter out the qualified classes again , Find it and give it to VariablePool
Dealing with variables .
2、FileScanner
FileScanner
Is a tool class for scanning files , It can filter out a certain type of file according to the file suffix , In addition to the ScanRunner
Used it to scan class The documents , It will also be used in later logic to scan yml file . below , to glance at FileScanner
The specific code of file scanning implemented in :
public class FileScanner {
public static final String TYPE_CLASS=".class";
public static final String TYPE_YML=".yml";
public static List<String> findFileByType(String rootPath, List<String> fileList,String fileType){
if (fileList==null){
fileList=new ArrayList<>();
}
File rootFile=new File(rootPath);
if (!rootFile.isDirectory()){
addFile(rootFile.getPath(),fileList,fileType);
}else{
String[] subFileList = rootFile.list();
for (String file : subFileList) {
String subFilePath=rootPath + "\\" + file;
File subFile = new File(subFilePath);
if (!subFile.isDirectory()){
addFile(subFile.getPath(),fileList,fileType);
}else{
findFileByType(subFilePath,fileList,fileType);
}
}
}
return fileList;
}
private static void addFile(String fileName,List<String> fileList,String fileType){
if (fileName.endsWith(fileType)){
fileList.add(fileName);
}
}
public static String getRealRootPath(String rootPath){
if (System.getProperty("os.name").startsWith("Windows")
&& rootPath.startsWith("/")){
rootPath = rootPath.substring(1);
rootPath = rootPath.replaceAll("/", Matcher.quoteReplacement(File.separator));
}
return rootPath;
}
}
The logic of finding files is simple , Is in the given root directory rootPath
Next , Loop through each directory , Compare the suffixes of the found files , If the conditions are met, it will be added to the returned file name list .
As for the following getRealRootPath
Method , Because in windows In the environment , Get the running directory of the project as follows :
/F:/Workspace/hermit-purple-config/target/classes/
and class The file name is like this :
F:\Workspace\hermit-purple-config\target\classes\com\cn\hermimt\purple\test\service\UserService.class
Get the full name of the class you want , Then first remove the running Directory , Then put the backslash in the file name \
Replace with .
, This is to delete the running path in the file name and prepare in advance .
3、VariablePool
Go back to the main process above , Each in ScanRunner
With @Component
、@Controller
、@Service
Annotated Class
, All will be handed over to VariablePool
To deal with . seeing the name of a thing one thinks of its function ,VariablePool
It means variable pool , This container will be used to encapsulate all tapes @Value
Properties of annotations .
public class VariablePool {
public static Map<String, Map<Class,String>> pool=new HashMap<>();
private static final String regex="^(\\$\\{)(.)+(\\})$";
private static Pattern pattern;
static{
pattern=Pattern.compile(regex);
}
public static void add(Class clazz){
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Value.class)){
Value annotation = field.getAnnotation(Value.class);
String annoValue = annotation.value();
if (!pattern.matcher(annoValue).matches())
continue;
annoValue=annoValue.replace("${","");
annoValue=annoValue.substring(0,annoValue.length()-1);
Map<Class,String> clazzMap = Optional.ofNullable(pool.get(annoValue))
.orElse(new HashMap<>());
clazzMap.put(clazz,field.getName());
pool.put(annoValue,clazzMap);
}
}
}
public static Map<String, Map<Class,String>> getPool() {
return pool;
}
}
Let's briefly talk about the design idea of this code :
Get by reflection
Class
All properties in the object , And judge whether the attribute is added@Value
annotation@Value
If you want to inject the value in the configuration file , It must conform to${xxx}
The format of ( Don't think about it for the time being${xxx:defaultValue}
This format sets the default value ), Therefore, you need to use regular expressions to verify whether it conforms to , And remove the beginning after passing the verification${
And the ending}
, Get the fields in the real corresponding configuration fileVariablePool
A static... Is declared in HashMap, Used to store all Properties in the configuration file - class - Class The mapping relation of , Next, we need to store this relationship in thispool
in
Simply speaking , Variable pool is the following structure :
If it's hard to understand here, you can take a look at the example , We introduce two tests Service
:
@Service
public class UserService {
@Value("${person.name}")
String name;
@Value("${person.age}")
Integer age;
}
@Service
public class UserDeptService {
@Value("${person.name}")
String pname;
}
In all Class
After execution add
After the method , Variable pool pool
The data in is like this :
You can see in the pool
in ,person.name
Corresponding inner layer Map It contains two pieces of data , Namely UserService
Medium name
Field , as well as UserDeptService
Medium pname
Field .
4、EnvInitializer
stay VariablePool
After encapsulating all variable data ,ScanRunner
Would call EnvInitializer
Of init
Method , Began to yml File parsing , Complete the initialization of the configuration center environment . In fact, it's plain , This environment is static HashMap,key
It's the property name ,value
Is the value of the attribute .
public class EnvInitializer {
private static Map<String,Object> envMap=new HashMap<>();
public static void init(){
String rootPath = EnvInitializer.class.getResource("/").getPath();
List<String> fileList = FileScanner.findFileByType(rootPath,null,FileScanner.TYPE_YML);
for (String ymlFilePath : fileList) {
rootPath = FileScanner.getRealRootPath(rootPath);
ymlFilePath = ymlFilePath.replace(rootPath, "");
YamlMapFactoryBean yamlMapFb = new YamlMapFactoryBean();
yamlMapFb.setResources(new ClassPathResource(ymlFilePath));
Map<String, Object> map = yamlMapFb.getObject();
YamlConverter.doConvert(map,null,envMap);
}
}
public static void setEnvMap(Map<String, Object> envMap) {
EnvInitializer.envMap = envMap;
}
public static Map<String, Object> getEnvMap() {
return envMap;
}
}
First of all, use FileScanner
Scan the root directory for all .yml
Final document , And use spring Self contained YamlMapFactoryBean
Conduct yml File analysis . But there is a problem , all yml After the file is parsed, an independent Map, Need to carry out Map The merger of , Generate a configuration information table . As for this specific operation , All to the following YamlConverter
To deal with .
Let's start with a demonstration , Prepare two yml file , Configuration file one :application.yml
spring:
application:
name: hermit-purple
server:
port: 6879
person:
name: Hydra
age: 18
Configuration file two :config/test.yml
my:
name: John
friend:
name: Jay
sex: male
run: yeah
Let's take a look at the initialization of the environment , The generated data format is like this :
5、YamlConverter
YamlConverter
There are three main implementation methods :
doConvert()
: takeEnvInitializer
Multiple... Provided in Map Merge into a single layer MapmonoToMultiLayer()
: Single layer Map Convert to multi tier Map( In order to generate yml Format string )convert()
:yml The format string resolves to Map( In order to determine whether the attribute has changed )
Because the latter two functions are not involved yet , Let's look at the first code :
public class YamlConverter {
public static void doConvert(Map<String,Object> map,String parentKey,Map<String,Object> propertiesMap){
String prefix=(Objects.isNull(parentKey))?"":parentKey+".";
map.forEach((key,value)->{
if (value instanceof Map){
doConvert((Map)value,prefix+key,propertiesMap);
}else{
propertiesMap.put(prefix+key,value);
}
});
}
//...
}
Logic is very simple , Through loop traversal , Will be multiple Map Finally, they all merged to the purpose envMap
in , And if you encounter multiple layers Map Nesting situation , Then it will be multi-layered Map Of key Pass the point .
It's connected , Finally, we get the single layer of the style in the above picture Map.
The other two methods , Let's talk about the scenarios we use below .
6、ConfigController
ConfigController
As controller , Used to interact with the front end , There are only two interfaces save
and get
, Here are the introduction .
get
When the front-end page is opened, it will call ConfigController
Medium get
Interface , Fill in textArea
in . Have a look first get
Method implementation :
@GetMapping("get")
public String get(){
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
String yamlContent = null;
try {
Map<String, Object> envMap = EnvInitializer.getEnvMap();
Map<String, Object> map = YamlConverter.monoToMultiLayer(envMap, null);
yamlContent = objectMapper.writeValueAsString(map);
} catch (Exception e) {
e.printStackTrace();
}
return yamlContent;
}
Before, when the project started , The configuration file attributes have been encapsulated in EnvInitializer
Of envMap
in , And this envMap
It's a single layer Map, There is no nested relationship . But we're going to use jackson
Generate standard format yml file , This format does not meet the requirements , It needs to be reduced to a multi-layer with hierarchical relationship Map, You need to call YamlConverter
Of monoToMultiLayer()
Method .
monoToMultiLayer()
The code for the method is a bit long , Not here , Mainly based on key Medium .
Split and constantly create children Map, The multi-layer after the conversion is completed Map The data are as follows :
After obtaining this format Map after , You can call jackson
The method in will Map Convert to yml The format string is passed to the front end , Take a look at the string returned to the front end after processing :
save
In the front page, I modified yml Click save after content , Would call save
Method to save and update the configuration , The method is implemented as follows :
@PostMapping("save")
public String save(@RequestBody Map<String,Object> newValue) {
String ymlContent =(String) newValue.get("yml");
PropertyTrigger.change(ymlContent);
return "success";
}
It came from the front yml After the string , call PropertyTrigger
Of change
Method , Implement subsequent change logic .
7、PropertyTrigger
Calling change
After the method , There are two main things to do :
modify
EnvInitializer
Environment inenvMap
, Used to return new data when the front-end page is refreshed , And the next time the attribute changesmodify bean The value of the middle attribute , This is also the most important function of the entire configuration center
First look at the code :
public class PropertyTrigger {
public static void change(String ymlContent) {
Map<String, Object> newMap = YamlConverter.convert(ymlContent);
Map<String, Object> oldMap = EnvInitializer.getEnvMap();
oldMap.keySet().stream()
.filter(key->newMap.containsKey(key))
.filter(key->!newMap.get(key).equals(oldMap.get(key)))
.forEach(key->{
System.out.println(key);
Object newVal = newMap.get(key);
oldMap.put(key, newVal);
doChange(key,newVal);
});
EnvInitializer.setEnvMap(oldMap);
}
private static void doChange(String propertyName, Object newValue) {
System.out.println("newValue:"+newValue);
Map<String, Map<Class, String>> pool = VariablePool.getPool();
Map<Class, String> classProMap = pool.get(propertyName);
classProMap.forEach((clazzName,realPropertyName)->{
try {
Object bean = SpringContextUtil.getBean(clazzName);
Field field = clazzName.getDeclaredField(realPropertyName);
field.setAccessible(true);
field.set(bean, newValue);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
});
}
}
There's so much bedding in front , In fact, it is to realize the functions in this code , The specific logic is as follows :
call
YamlConverter
Ofconvert
Method , From the front end yml The format string is parsed and encapsulated into a single layer Map, Data format andEnvInitializer
MediumenvMap
identicalTraverse the old
envMap
, Check it out key In the new Map Whether the corresponding attribute value in has changed , If there is no change, do not do any subsequent operationIf there is a change , Replace... With a new value
envMap
The old value inBy attribute name , from
VariablePool
Get involved in changeClass
, And the fields in the classField
. And through the backSpringContextUtil
The method in gets this bean Instance object of , And then change the value of the field by reflectionWill modify the Map Write back to
EnvInitializer
MediumenvMap
Come here , All functions are realized .
8、SpringContextUtil
SpringContextUtil
By implementing ApplicationContextAware
The interface gets spring Containers , And through the container getBean()
The method can be easily obtained spring Medium bean, It is convenient for subsequent changes .
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public static <T> T getBean(Class<T> t) {
return applicationContext.getBean(t);
}
}
9、 The front-end code
As for the front-end code , It's a very simple form , Code, you can move git
see .
Last
So far, all the code is introduced , Finally, make a brief summary , Although a simple version of the configuration center function can be realized through these classes , But there are still many defects , for example :
Not dealt with
@ConfigurationProperties
annotationOnly deal with yml file , Not dealt with properties file
Currently dealing with bean It's all based on
singleton
Pattern , If the scope isprototype
, There will be problemsLow reflection performance , If a property involves many classes, it will affect performance
At present, it can only be used when the code is embedded in the project , Independent deployment and remote registration are not supported yet
……
in general , There is still a lot to be improved in the follow-up , It feels like a long way to go .
Finally, let's talk about the name of the project , Why is it called hermit-purple
Well , The source is jojo Middle two Joe's double The purple of the hermit , I feel that the ability of this double is quite matched with the perception function of the configuration center , So I used this, ha ha .
So that's all for this sharing , I am a Hydra, I wish you all a happy Spring Festival in the year of the tiger , Let's see you later .
project git Address :
https://github.com/trunks2008/hermit-purple-config
recommend
Main stream Java Advanced technology ( Learning material sharing )
Join in Spring Technology development community
PS: Because the official account platform changed the push rules. , If you don't want to miss the content , Remember to click after reading “ Looking at ”, Add one “ Star standard ”, In this way, each new article push will appear in your subscription list for the first time . spot “ Looking at ” Support us !
边栏推荐
猜你喜欢
Redis cluster
DCDC power ripple test
Nacos installation and service registration
Redis之Geospatial
Reids之删除策略
Solve the problem of inconsistency between database field name and entity class attribute name (resultmap result set mapping)
The five basic data structures of redis are in-depth and application scenarios
Activiti7工作流的使用
Design and implementation of online shopping system based on Web (attached: source code paper SQL file)
Pytest's collection use case rules and running specified use cases
随机推荐
Advance Computer Network Review(1)——FatTree
Global and Chinese market of appointment reminder software 2022-2028: Research Report on technology, participants, trends, market size and share
I-BERT
Redis' bitmap
Selenium+Pytest自动化测试框架实战(下)
Once you change the test steps, write all the code. Why not try yaml to realize data-driven?
【图的三大存储方式】只会用邻接矩阵就out了
Lua script of redis
Redis core configuration
QDialog
Pytest parameterization some tips you don't know / pytest you don't know
Research and implementation of hospital management inpatient system based on b/s (attached: source code paper SQL file)
Opencv+dlib realizes "matching" glasses for Mona Lisa
Chapter 1 :Application of Artificial intelligence in Drug Design:Opportunity and Challenges
Redis之哨兵模式
Servlet learning diary 7 -- servlet forwarding and redirection
Reids之删除策略
What is MySQL? What is the learning path of MySQL
Global and Chinese market of metallized flexible packaging 2022-2028: Research Report on technology, participants, trends, market size and share
Basic concepts of libuv