Haven’t written blog for a long time, this period of time is mainly a variety of charging, because the front of some things, maybe we are not interested in or dislike what technical content, so this time specially under some effort. I actually spent two whole days over the weekend writing the first version of this blog, which is now open source, and is used as a starting point as before. Let’s get down to business!

When we want to implement a centralized configuration management in SpringBoot, automatic update will encounter the following awkward scenarios:

1. What? I only save a configuration and install a configuration center service. What if the configuration center service fails? Can you restart it for me?

2. What? Configuration center should also be highly available, but also deploy multiple to avoid single point of failure, server resources are free, CAN I have a small goal in minutes?

3. What? You told me that I need a separate place to store the configuration. What git, Apollo? Am I going to the moon?

4. What? I implement an online update configuration and rely on the Actuator module. What is this

5. What? I’m also relying on the message queue to say it’s not used

6. What? Also introduce SpringCloud bus, what the hell, don’t know what you mean

I think most people encounter those scenarios above, will be on the configuration center back, it is too much trouble. All I wanted to do was to automatically update my configuration by installing a separate service, and all sorts of things that a separate service should consider, load balancing, high availability, alas! This thing is not human use, already in use brothers and sisters, you are god! Very disgusted at the thought of micro services to deploy a lot of dependent services, what registry, service gateway, message queue I also endure, you put a configuration also want to a configuration center, but also to deploy multiple high availability, your ya don’t tell me to subordinate a single point on the line, you cow, you will never hang! So if there are not enough servers, don’t think about playing too much, each Java service needs to use a separate virtual machine to load the full set of JAR packages (here is talking about the most used JDK8, it is said that the later version can do a public part of the jar), which requires resources. Opportunism is the brainchild of people like me who are too lazy to learn new techniques and tricks. Let’s start by implementing a feature that can be easily embedded into our Own SpringBoot project without the need to introduce new services.

To realize an external public place to store the configuration, you can first think of saving the configuration on the local disk or network. Let’s take the local disk as an example for today’s sharing. Implementing a feature that changes the configuration at run time requires solving the following problems:

1. How to enable the service to start to read their own need to let him read the configuration file (local disk, network, database configuration)

2. How to modify the above configuration file at any time and refresh the configuration in the Spring container at the same time (hot update configuration)

3. How to integrate features into your Own SpringBoot project

To implement the first point is simple, if it is a local file system, Java NIO has a file listening function, can listen to a specified folder, folder changes will be notified by an event, as specified implementation can be. The second point is a little more difficult to implement. First, there is a common understanding that beans in Spring are all encapsulated at startup in a Map as BeanDefinition objects. These BeanDefinition objects are analogous to Java classes that have a Class object template. Subsequent objects are generated using the Class object as a template. Spring China also uses BeanDefinition as a template to generate objects, so basically all the information you need can be found in BeanDefinition. Since the vast majority of spring-managed objects in our project are singletons, no one is sick enough to make configuration classes into multiple instances. Since this is a singleton, we just need to find it in the Spring container, and then force the @Value modification property in it through reflection. If you thought it was that easy, there would be no blog post today. As follows:

private void updateValue(Map<String,Object> props) { Map<String,Object> classMap = applicationContext.getBeansWithAnnotation(RefreshScope.class); if(classMap == null || classMap.isEmpty()) { return; } classmap. forEach((beanName,bean) -> {classMap. * Properties in the configuration class modified by this annotation will disappear in the proxy class, leaving only the corresponding getXX and setXX methods, so that the value of the property cannot be changed directly by reflection. */ / saveProxyClass(bean); // saveProxyClass(bean); Class<? > clazz = bean.getClass(); /** * get all available attributes */ Field[] fields = clazz.getDeclaredFields(); /** * setValue(bean,fields,props); }); } private void setValue(Object bean,Field[] fields,Map<String,Object> props) { for(Field field : fields) { Value valueAnn = field.getAnnotation(Value.class); if (valueAnn == null) { continue; } String key = valueAnn.value(); if (key == null) { continue; } key = key.replaceAll(VALUE_REGEX,"$1"); key = key.split(COLON)[0]; if (props.containsKey(key)) { field.setAccessible(true); try { field.set(bean, props.get(key)); } catch (Exception e) { e.printStackTrace(); }}}} /** * Export proxy Object for test only then decompile * @param bean */ private void saveProxyClass(Object bean) {byte[] bytes = ProxyGenerator.generateProxyClass("T", new Class[]{bean.getClass()}); try { Files.write(Paths.get("F:\\fail2\\","T.class"),bytes, StandardOpenOption.CREATE); } catch (IOException e) { e.printStackTrace(); }}Copy the code

As shown in the above code, using reflection to force the property value directly can solve some of the problem, but there is also a part of the @Configuration class can not do this, because spring uses Cglib to proxy the class that modified the annotation. A completely different class has been created in which the @value modified attributes have been removed, leaving behind a bunch of setXX methods that use who knows what keys to inject configuration. It would be nice if someone said they would use the code above and leave the Configuration they need to change out of the @Configuration class. If there is so low, I do not need to write this blog, to achieve the realization of a function of all five organs, can not let users indulge your lack of. The @refreshScope annotation above is not the configuration service annotation in SpringBoot, but is a self-defined annotation of the same name, because it is not worth importing a dependency for the configuration service. Here are the core classes that implement configuration updates:

package com.rdpaas.easyconfig.context;

import com.rdpaas.easyconfig.ann.RefreshScope;
import com.rdpaas.easyconfig.observer.ObserverType;
import com.rdpaas.easyconfig.observer.Observers;
import com.rdpaas.easyconfig.utils.PropUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.stereotype.Component;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * 自定义的springboot上下文类
 * @author rongdi
 * @date 2019-09-21 10:30:01
 */public class SpringBootContext implements ApplicationContextAware {

    private Logger logger = LoggerFactory.getLogger(SpringBootContext.class);

    private final static String REFRESH_SCOPE_ANNOTATION_NAME = "com.rdpaas.easyconfig.ann.RefreshScope";

    private final static Map<Class<?>, SpringAnnotatedRefreshScopeBeanInvoker> refreshScopeBeanInvokorMap = new HashMap<>();

    private final static String VALUE_REGEX = "\\$\\{(.*)}";

    private static ApplicationContext applicationContext;

    private static String filePath;

    @Override
    public void setApplicationContext(ApplicationContext ac) throws BeansException {
        applicationContext = ac;
        try {
            /**
             * 初始化准备好哪些类需要更新配置,放入map
             */
            init();

            /**
             * 如果有配置文件中配置了文件路径,并且是本地文件,则开启对应位置的文件监听
             */
            if(filePath != null && !PropUtil.isWebProp(filePath)) {
                File file = new File(filePath);
                String dir = filePath;
                /**
                 * 谁让java就是nb,只能监听目录
                 */
                if(!file.isDirectory()) {
                    dir = file.getParent();
                }
                /**
                 * 开启监听
                 */

                Observers.startWatch(ObserverType.LOCAL_FILE, this, dir);
            }
        }  catch (Exception e) {
            logger.error("init refresh bean error",e);
        }

    }

    /**
     * 刷新spring中被@RefreshScope修饰的类或者方法中涉及到配置的改变,注意该类可能被@Component修饰,也有可能被@Configuration修饰
     * 1.类中被@Value修饰的成员变量需要重新修改更新后的值(
     * 2.类中使用@Bean修饰的方法,如果该方法需要的参数中有其他被@RefreshScope修饰的类的对象,这个方法生成的类也会一同改变
     * 3.类中使用@Bean修饰的方法循环依赖相互对象会报错,因为这种情况是属于构造方法层面的循环依赖,spring里也会报错,
     * 所以我们也不需要考虑循环依赖
     */
    private void init() throws ClassNotFoundException {
        /**
         * 将applicationContext转换为ConfigurableApplicationContext
         */
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        /**
         * 获取bean工厂并转换为DefaultListableBeanFactory
         */
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
        /**
         * 获取工厂里的所有beanDefinition,BeanDefinition作为spring管理的对象的创建模板,可以类比java中的Class对象,
         */
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for(String beanName : beanDefinitionNames) {

            BeanDefinition bd = defaultListableBeanFactory.getBeanDefinition(beanName);
            /**
             * 使用注解加载到spring中的对象都属于AnnotatedBeanDefinition,毕竟要实现刷新配置也要使用@RefreshScope
             * 没有人丧心病狂的使用xml申明一个bean并且在类中加一个@RefreshScope吧,这里就不考虑非注解方式加载的情况了
             */
            if(bd instanceof AnnotatedBeanDefinition) {
                /**
                 * 得到工厂方法的元信息,使用@Bean修饰的方法放入beanDefinitionMap的beanDefinition对象这个值都不会为空
                 */
                MethodMetadata factoryMethodMeta = ((AnnotatedBeanDefinition) bd).getFactoryMethodMetadata();
                /**
                 * 如果不为空,则该对象是使用@Bean在方法上修饰产生的
                 */
                if(factoryMethodMeta != null) {
                    /**
                     * 如果该方法没有被@RefreshScope注解修饰,则跳过
                     */
                    if(!factoryMethodMeta.isAnnotated(REFRESH_SCOPE_ANNOTATION_NAME)) {
                        continue;
                    }

                    /**
                     * 拿到未被代理的Class对象,如果@Bean修饰的方法在@Configuration修饰的类中,会由于存在cglib代理的关系
                     * 拿不到原始的Method对象
                     */
                    Class<?> clazz = Class.forName(factoryMethodMeta.getDeclaringClassName());
                    Method[] methods = clazz.getDeclaredMethods();
                    /**
                     * 循环从class对象中拿到的所有方法对象,找到当前方法并且被@RefreshScope修饰的方法构造invoker对象
                     * 放入执行器map中,为后续处理@ConfigurationProperties做准备
                     */
                    for(Method m : methods) {
                        if(factoryMethodMeta.getMethodName().equals(m.getName()) && m.isAnnotationPresent(RefreshScope.class)) {
                            refreshScopeBeanInvokorMap.put(Class.forName(factoryMethodMeta.getReturnTypeName()),
                            new SpringAnnotatedRefreshScopeBeanInvoker(true, defaultListableBeanFactory, beanName, (AnnotatedBeanDefinition)bd, clazz,m));
                        }
                    }
                } else {
                    /**
                     * 这里显然是正常的非@Bean注解产生的bd对象了,拿到元信息判断是否被@RefreshScope修饰,这里可不能用
                     * bd.getClassName这个拿到的是代理对象,里面自己定义的属性已经被去掉了,更加不可能拿到被@Value修饰
                     * 的属性了
                     */
                    AnnotationMetadata at = ((AnnotatedBeanDefinition) bd).getMetadata();
                    if(at.isAnnotated(REFRESH_SCOPE_ANNOTATION_NAME)) {
                        Class<?> clazz = Class.forName(at.getClassName());
                        /**
                         * 先放入执行器map,后续循环处理,其实为啥要做
                         */
                        refreshScopeBeanInvokorMap.put(clazz,
                            new SpringAnnotatedRefreshScopeBeanInvoker(false, defaultListableBeanFactory, beanName, (AnnotatedBeanDefinition)bd, clazz,null));

                    }
                }

            }
        }
    }

    /**
     * 根据传入属性刷新spring容器中的配置
     * @param props
     */
    public void refreshConfig(Map<String,Object> props) throws InvocationTargetException, IllegalAccessException {
        if(props.isEmpty() || refreshScopeBeanInvokorMap.isEmpty()) {
            return;
        }

        /**
         * 循环遍历要刷新的执行器map,这里为啥没用foreach就是因为没法向外抛异常,很让人烦躁
         */
        for(Iterator<Map.Entry<Class<?>, SpringAnnotatedRefreshScopeBeanInvoker>> iter = refreshScopeBeanInvokorMap.entrySet().iterator(); iter.hasNext();) {
            Map.Entry<Class<?>, SpringAnnotatedRefreshScopeBeanInvoker> entry = iter.next();
            SpringAnnotatedRefreshScopeBeanInvoker invoker = entry.getValue();
            boolean isMethod = invoker.isMethod();
            /**
             * 判断执行器是不是代表的一个@Bean修饰的方法
             */
            if(isMethod) {
                /**
                 * 使用执行器将属性刷新到@Bean修饰的方法产生的对象中,使其支持@ConfigurationProperties注解
                 */
                invoker.refreshPropsIntoBean(props);
            } else {
                /**
                 * 使用执行器将属性刷新到对象中
                 */
                invoker.refreshPropsIntoField(props);
            }
        }

    }

    public static void setFilePath(String filePath) {
        SpringBootContext.filePath = filePath;
    }

}
Copy the code

 

The main idea is to implement the ApplicationContextAware interface to give me an applicationContext when SpringBoot initializes, so that I can iterate over all beanDefinitions. The class or method block decorated by @RefreshScope is first found and placed into the global map when the applicationContext is obtained. Then in monitoring configuration changes after receiving the event trigger refresh configuration, refresh configuration process is to use reflection to force changes the value of the instance due to spring management object is A singleton, basic assumptions in the spring container have two objects A and B, which cited A, B to modify A property, then the reference object of B will also follow changes, Because the reference to A in B has changed, but the reference address has not changed, calling A again is actually calling A’s changed method. The process of writing a program is actually a divide-and-conquer method of breaking up a large task into smaller tasks, delegating them to multiple classes, and finally returning them together. Each class is a transparent wrapper to the caller, and the effects of each modification are ultimately reflected to the caller. Getting back to business, the encapsulated actuator classes used in the core class are as follows

package com.rdpaas.easyconfig.context; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.boot.context.properties.ConfigurationProperties; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; /** * encapsulates the actuator, Mainly responsible for real modify attribute values * @ author rongdi * @ the date 2019-09-21 10:10:01 * / public class SpringAnnotatedRefreshScopeBeanInvoker { private final static String SET_PREFIX = "set"; private final static String VALUE_REGEX = "\\$\\{(.*)}"; private final static String COLON = ":"; private DefaultListableBeanFactory defaultListableBeanFactory; private boolean isMethod = false; private String beanName; private AnnotatedBeanDefinition abd; private Class<? > clazz; private Method method; public SpringAnnotatedRefreshScopeBeanInvoker(boolean isMethod, DefaultListableBeanFactory defaultListableBeanFactory, String beanName, AnnotatedBeanDefinition abd, Class<? > clazz, Method method) { this.abd = abd; this.isMethod = isMethod; this.defaultListableBeanFactory = defaultListableBeanFactory; this.beanName = beanName; this.clazz = clazz; this.method = method; } public boolean isMethod() { return isMethod; @param props */ public void refreshPropsIntoField(Map<String,Object> props) throws IllegalAccessException {/** * beanName */ Object bean = defaultListableBeanFactory.getBean(beanName); if(bean == null) { bean = defaultListableBeanFactory.getBean(clazz); } / * * * to get on the class might be modified annotations * / ConfigurationProperties cp = clazz getAnnotation (ConfigurationProperties. Class); String prefix = ""; if(cp ! = null && cp.prefix() ! = null && !" ".equals(cp.prefix().trim())) { prefix = cp.prefix() + "."; } /** * get all available attributes */ Field[] fields = clazz.getDeclaredFields(); For (Field Field: fields) {/** * If the attribute is decorated with @Value */ Value valueAnn = field.getannotation (value.class); if (valueAnn == null && "".equals(prefix)) { continue; } String key = ""; /** * if(valueAnn == null) {key = prefix + field.getName(); } else { key = valueAnn.value(); Extract / * * * @ Value (" ${xx. Yy: dd} ") in the key: xx, yy * / key = key. The replaceAll (VALUE_REGEX, "$1"); /** * if prefix is not empty, add prefix */ key = prefix + key.split(COLON)[0]; } /** * If the attribute map contains a key from the @value annotation, forcing reflection to change the Value in it, * strictly, using the corresponding setXX method to change the attribute Value, */ if (props. ContainsKey (key)) {field.setaccessible (true); field.set(bean, props.get(key)); @param props */ public void refreshPropsIntoBean(Map<String,Object> props) throws InvocationTargetException, IllegalAccessException { if(! method.isAnnotationPresent(ConfigurationProperties.class)) { return; } / * * * access methods can be modified on the annotation * / ConfigurationProperties cp = method. The getAnnotation (ConfigurationProperties. Class); /** * get the prefix information in the annotation and spell */ String prefix = cp.prefix() + "."; /** * get the return type of the @bean method */ Class<? > returnClazz = method.getReturnType(); / * * * according to beanName first, then according to the returned beanType access Object * / in the spring container Object bean. = defaultListableBeanFactory getBean (beanName); if(bean == null) { bean = defaultListableBeanFactory.getBean(returnClazz); } / * * * all the setXX Method in the circulation return type, call the corresponding Method to modify the returned object attribute values in the * / Method. [] the methods = returnClazz getDeclaredMethods (); For (Method m: methods) {/** * String name = getNameBySetMethod(m); if(name == null) { continue; } String key = prefix + name; if (props.containsKey(key)) { m.invoke(bean,props.get(key)); } /** * Private String getNameBySetMethod(Method setMethod) {String setName = setMethod.getName(); / * * * if method called empty * if not begin with the set length is less than 4 * * if the method name if not capitalized the first letter after the set * * / if these all not setXX method (setMethodName = = null | |! setMethodName.startsWith(SET_PREFIX) || setMethodName.length() < 4 || ! Character.isUpperCase(setMethodName.charAt(3))) { return null; String tempName = setMethodName.subString (3); Return tempName. Substring (0, 1). The toLowerCase () + tempName. Substring (1); }}Copy the code

 

In the code above, it’s just a few simple reflection calls, and the comments are written in the code.

package com.rdpaas.easyconfig.ann; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Custom modifiers can be refreshed annotations, Imitate springcloud annotations of the same name * @ author rongdi * @ the date 2019-09-21 10:00:01 * / @ Target ({ElementType. TYPE, ElementType METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface RefreshScope { }Copy the code

Here is a utility class

package com.rdpaas.easyconfig.utils; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * @author rongdi * @date 2019-09-21 16:30:07 */ public class PropUtil {public static Boolean isWebProp(String filePath) { return filePath.startsWith("http:") || filePath.startsWith("https:"); } public static Map<String,Object> prop2Map(Properties prop) { Map<String,Object> props = new HashMap<>(); prop.forEach((key,value) -> { props.put(String.valueOf(key),value); }); return props; }}Copy the code

Add a meta-INF file to the resources directory, and add a meta-INF file to the resources directory, and add a meta-INF file to the resources directory. Then create a new Spring. factories file in the folder with the following content:

org.springframework.boot.env.EnvironmentPostProcessor=com.rdpaas.easyconfig.boot.InitSettingsEnvironmentPostProcessor
Copy the code

The implementation class in the configuration class is as follows

package com.rdpaas.easyconfig.boot; import com.rdpaas.easyconfig.context.SpringBootContext; import com.rdpaas.easyconfig.utils.PropUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertiesPropertySource; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import org.springframework.core.io.support.PropertiesLoaderUtils; import java.io.File; import java.io.IOException; import java.util.Properties; /** * Use the environment's back processor to put your configuration first, Here is actually modeled after springboot * SpringApplication constructor - > setInitializers () - > getSpringFactoriesInstances () - > loadFactoryNames () - > * LoadSpringFactories (@nullable ClassLoader ClassLoader) loaded jar packages * FACTORIES_RESOURCE_LOCATION = "Meta-inf /spring.factories" file, so here we configure this class in the same * location in this module, Content is org. Springframework. Boot. The env. EnvironmentPostProcessor = com. Rdpaas. Easyconfig. Boot. InitSettingsEnvironmentPostProcessor * Here's how Java's SPI actually works, * @ this pattern is used in great quantities springboot author rongdi * @ the date 2019-09-21 11:00:01 * / public class InitSettingsEnvironmentPostProcessor implements EnvironmentPostProcessor { private Logger logger = LoggerFactory.getLogger(InitSettingsEnvironmentPostProcessor.class); private final static String FILE_KEY = "easyconfig.config.file"; private final static String FILE_PATH_KEY = "easyconfig.config.path"; @Override public void postProcessEnvironment(ConfigurableEnvironment configurableEnvironment, SpringApplication Application) {/** * get all the configuration of the current environment */ MutablePropertySources propertySources = configurableEnvironment.getPropertySources(); Try {/** * get the bootstrap.properties file, And read */ File resourceFile = new File(InitSettingsEnvironmentPostProcessor.class.getResource("/bootstrap.properties").getFile()); FileSystemResource resource = new FileSystemResource(resourceFile); Properties prop = PropertiesLoaderUtils.loadProperties(resource); */ String filePath = prop.getProperty(FILE_KEY); /* String filePath = prop.getProperty; */ Boolean isWeb = proputil. isWebProp(filePath); /** * Boolean isWeb = proputil. isWebProp(filePath); /** * The configuration information is initialized according to the resource type, network or local file system. In SpringCloud, the configuration service can be obtained directly from a URL, which can also be placed here. Spring is a good thing. Properties config = new Properties(); / Properties config = new Properties(); Resource configRes = null; if(isWeb) { configRes = new UrlResource(filePath); } else { configRes = new FileSystemResource(filePath); } try {/ * * * fill of resources into config. * / PropertiesLoaderUtils fillProperties (config, configRes); AddFirst (new PropertiesPropertySource("Config", Config)); / / PropertiesPropertysources. addFirst(new PropertiesPropertySource("Config", Config)) } catch (IOException e) { logger.error("load config error",e); } / * * * to read out the filePath set into the environment class, only make a file, it is very easy to make multiple files * / SpringBootContext setFilePath (filePath); } catch (Exception e) { logger.info("load easyconfig bootstrap.properties error",e); }}}Copy the code

The bootstrap.properties file is the only configuration file used by the springBoot service. The bootstrap.properties file is the only configuration file used by the springBoot service.

package com.rdpaas.easyconfig.observer; import com.rdpaas.easyconfig.context.SpringBootContext; import java.io.IOException; import java.util.concurrent.ExecutorService; @author rongdi * @date 2019-09-21 14:30:01 */ public abstract class Observer {protected volatile Boolean isRun = false; public abstract void startWatch(ExecutorService executorService, SpringBootContext context, String target) throws IOException; public void stopWatch() throws IOException { isRun = false; } public abstract void onChanged(SpringBootContext context, Object... data); }Copy the code
package com.rdpaas.easyconfig.observer; import com.rdpaas.easyconfig.context.SpringBootContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.support.PropertiesLoaderUtils; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardWatchEventKinds; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.ExecutorService; */ public class LocalFileObserver extends Observer {private Logger Logger = LoggerFactory.getLogger(LocalFileObserver.class); @Override public void startWatch(ExecutorService executorService, SpringBootContext context, String filePath) throws IOException { isRun = true; */ WatchService WatchService = filesystems.getdefault ().newwatchService (); Path p = Paths.get(filePath); /** * register listener event, modify, create, Delete * / p.r egister (watchService, StandardWatchEventKinds ENTRY_MODIFY, StandardWatchEventKinds. An ENTRY_DELETE, StandardWatchEventKinds.ENTRY_CREATE); Executorservice.execute (() -> {try {while(isRun){/** * Take out a poll of all events, if any events trigger watchKey.Pollevents (); WatchKey WatchKey = watchService.take(); WatchKey = watchService.take(); List<WatchEvent<? >> watchEvents = watchKey.pollEvents(); for(WatchEvent<? > event : */ String fileName = filePath + file.separator +event.context(); logger.info("start update config event,fileName:{}",fileName); onChanged(context,fileName); } watchKey.reset(); } } catch (InterruptedException e) { e.printStackTrace(); }}); } @Override public void onChanged(SpringBootContext context, Object... / / File resourceFile = new File(string.valueof (data[0])); / / File resourceFile = new File(string.valueof (data[0])); FileSystemResource resource = new FileSystemResource(resourceFile); Try {/** * Use the Spring utility class to load resources. Spring is a really nice thing. You can think of basic have * / Properties prop = PropertiesLoaderUtils. LoadProperties (resource); Map<String,Object> props = new HashMap<>(); prop.forEach((key,value) -> { props.put(String.valueOf(key),value); }); /** * Call SpringBootContext to refresh the configuration */ context.refreshConfig(props); } catch(InvocationTargetException | IllegalAccessException e1){ logger.error("refresh config error",e1); }catch (Exception e) { logger.error("load config error",e); }}}Copy the code
package com.rdpaas.easyconfig.observer; import com.rdpaas.easyconfig.context.SpringBootContext; import java.io.IOException; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * Observers * @author rongdi * @date 2019-09-21 15:30:09 */ public class Observers {private final static The ExecutorService ExecutorService = new ThreadPoolExecutor (1, 0, TimeUnit. MILLISECONDS, new ArrayBlockingQueue < > (10)); private static Observer currentObserver; /** * Start observer * @param type * @param context * @param target * @throws IOException */ public static void startWatch(ObserverType type, SpringBootContext context,String target) throws IOException { if(type == ObserverType.LOCAL_FILE) { currentObserver = new LocalFileObserver(); currentObserver.startWatch(executorService,context,target); } /** * Close observer * @param type * @throws IOException */ public static void stopWatch(ObserverType type) throws IOException { if(type == ObserverType.LOCAL_FILE) { if(currentObserver ! = null) { currentObserver.stopWatch(); }}}}Copy the code
package com.rdpaas.easyconfig.observer; /** * Observer type, such as observing local files, network files, * @author rongdi * @date 2019-09-21 16:30:01 */ public enum ObserverType {LOCAL_FILE, DATEBASE; }Copy the code

The file listener in NIO is used to listen for file changes in the folder. If changes are made, the onChanged method is called to modify the configuration in the Spring container. Initialization actually reads the network configuration from the local file system and other registries. Those of you who have used the configuration center should know that the configuration provided by the configuration center is directly accessible from a browser over an HTTP connection. Just configure the following configuration in bootstrap.properties

# configuration using the local or network configuration files here, you can use the configuration services provided by the HTTP address easyconfig.. Config file: E: / test/config / 11. TXTCopy the code

But the first version of the above monitoring is just to realize the local file system monitoring, if you want to realize the network file or database monitoring, is the need to open a timer polling is good, but also very convenient implementation, later free these will fill, interested in their own implementation, should not be difficult. Springboot-like @enablexx is used to implement this function in springboot projects.

package com.rdpaas.easyconfig.ann; import com.rdpaas.easyconfig.boot.EasyConfigSelector; import org.springframework.context.annotation.Import; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * open easyConfig annotation, in fact, the various springboot interface, just use spring * importSelector to play a trick, So here's the key @Import(EasyConfigSelector. Class) * Specific can see spring5 org. Springframework. Context. The annotation. ConfigurationClassParser# processImports (org. Springframework. The context. annotation.ConfigurationClass, org.springframework.context.annotation.ConfigurationClassParser.SourceClass, java.util.Collection, Boolean) * There are many ways to implement @enablexx such as attaching @Component to SpringBootContext and then annotate it with the following comment section * @ComponentScan("com.rdpaas") or @import ({springBootContext.class}) force-scan the classes you need to scan and load them * The beauty of Spring is that the extension is flexible, if you can't think of it, @@author rongdi * @date 2019-09-22 8:01:36 */ @Retention(retentionPolicy.runtime) @target (elementtype.type) @Import(EasyConfigSelector.class) //@ComponentScan("com.rdpaas") //@Import({SpringBootContext.class}) public @interface EnableEasyConfig { }Copy the code

There are many extension methods, please read the code in the comments, blog is not good at typesetting, or write things in the comments convenient point, ha ha!

import com.rdpaas.easyconfig.context.SpringBootContext; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata; /** * this is the purpose of using @enableeasyConfig. Use @import (EasyConfigSelector. Class) * on the @enableeasyConfig annotation to have Spring load the classes represented in the array provided in the following selectImports method when it detects the annotation. To avoid the need to use the @Component annotation in the * SpringBootContext display, After all, in case someone doesn't use it or someone else doesn't configure scan code at all in your * com.rdpaas package, there will be cases where the SpringBootContext class won't be scanned properly and won't work properly. In short, your dependencies should * be managed directly by Spring using the @Component annotation (which requires you to scan your package name). * @author rongdi * @date 2019-09-22 8:05:33 */ public class EasyConfigSelector implements ImportSelector{ @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[]{SpringBootContext.class.getName()}; }}Copy the code

There are detailed comments in the code, so far the first version of the function has been all realized, if interested in you can directly copy to your own project to use on the line, with their own Spring Boot micro services together also do not need to deploy what configuration center what. As for the friends who use Spring directly without PringBoot, ACTUALLY I thought the BeanDefinition structure of SpringBoot and Spring should be the same. I also used Spring to do it directly at the beginning, but I found it was in vain. The structure of BD is very different. Specifically, the bd generated by the @Configuration class has a different way of obtaining the non-proxy class name. If you are interested, please leave a message. I can post the initial processing code of Spring. Examples of how to use this blog are available on Github, so as not to waste space, you can view the detailed code directly on Github :github.com/rongdi/easy… There is a problem with this implementation

* Self-styled configuration non-central, although it does follow its own business microservice startup, but the configuration in the local file also means that a microserver has to change once, although the configuration can be read from the network but there is no network configuration file listener. This is actually quite easy to implement as mentioned above *

Say personal opinion again finally, disagreeable of bothersome can ignore directly, ha ha!

 

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — this is boring Secant — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

I think the goal of open source is to make it accessible to most people, not to play around and say look at this code you need a foundation, and then cause most developers to be stuck in business code for more than 10 years. It’s not that I don’t want to learn it, it’s just that you’re having a hard time understanding it. It doesn’t matter that foreign open source projects are difficult to understand, but maybe foreign brain circuits are different and others can understand them. A domestic open source project to domestic programmers are very difficult to understand, you are afraid not for others to understand it, just to brag about your awesome b function. In fact, a programmer with a better foundation can’t write a framework if you give him time (you know some basic things like reflection, AOP, dynamic proxy, SPI, etc., and can’t imitate some popular frameworks on the market to communicate with me). The difference between individual and team open source things is in the details and performance, of course, these two things used in the same scenario may not need to consider, the big point of the project need details to implement the function is ok, enough is ok, need performance, only 100 people use you to talk to me about these useless. The fundamental reason for me to repeat the wheel after I have already had many mature plans is that I cannot control the wheel provided by others, and it may even be difficult to understand it. I have no big demand. Do you want me to spend ten days and half a month to study it? Program to play more flowers, the method call write deep enough, after the real compilation is not likely to be directly optimized by the virtual machine into a method, you have what can cattle it. While claiming that the program is for people to see, I want to ask you open source code really anyone can understand. It is not that the ability of others is not enough mainly because everyone’s thinking is not the same, can not use their own simple to apply to others. Some people or organizations write code that has a very deep call level on purpose, in the name of extensibility. What do you want to extend, when do you want to extend, and is it necessary to make it so complicated in the beginning? Or is it just to confuse people? B. So sincere open source, or notes to write clear good, I am small, is an unknown still struggling in the poverty line of small code farmers, if offended each god please do not see it wrong, pure when I become nervous good!