基于LinuxWatchService实现定制化热加载

 

概述:

一下代码整合springboot properties 属性实现热加载功能,在不重启项目的情况下修改application.properties 里边的属性达到修改后的属性整合进spring propertie enviroment 的效果

@Configuration
@ConditionalOnProperty(matchIfMissing = true,//Default if no , pushing startup
        prefix = "heat.exacutio.master",
        name = "deploy",
        havingValue = "true")
public class AutoNotifyHeatReloadWatcherConfiguration {

    @Autowired
    Environment environment;

    private static final Logger LOGGER = LoggerFactory.getLogger(AutoNotifyHeatReloadWatcherConfiguration.class);

    @Bean(initMethod = "init")
    @ConditionalOnMissingBean
    public RecompileNotifyWatcher Watcher() {
        if (!System.getProperty(OS_NAME).equalsIgnoreCase(LINUX)){return null;}
        LOGGER.info("Init RecompileNotifyWatcher of event notify based on unix poll ");
        return new RecompileNotifyWatcher(environment);
    }

}




/**
 * Example to watch a directory (or tree) for changes to files.
 *
 * @date 19-7-15
 * @auther jack
 * @description TODO
 */
@Slf4j
public final class RecompileNotifyWatcher {

    private WatchService watcher;
    private Map keys;
    private boolean recursive;
    private boolean trace;
    private Environment environment;
    private List pPatternContainer = Arrays.asList(".properties");

    public RecompileNotifyWatcher(Environment environment) {
        try {
            this.watcher = FileSystems.getDefault().newWatchService();
            this.keys = new HashMap<>(8);
            this.recursive = true;
            this.environment = environment;
        } catch (Exception e) {
            log.error("Watcher initialized error !", e);
            System.exit(-1);
        }

    }

    private  RecompileNotifyWatcher (){

    }

    /**
     * Creates a WatchService and registers the given directory
     */
    public RecompileNotifyWatcher(Path dir, boolean recursive, Environment environment) {
        this(environment);
        this.initRegisterAboutRecusive(dir, recursive);
        this.processEvents();
    }



    /**
     * Register the given directory with the WatchService
     */
    private void register(Path dir) throws IOException {
        WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
        if (trace) {
            Path prev = keys.get(key);
            if (prev == null) {
                log.debug("Path of directory register is {}\n", dir);
            } else {
                if (!dir.equals(prev)) {
                    log.debug("update directory register: %s -> {}\n", prev, dir);
                }
            }
        }
        keys.put(key, dir);
    }


    /**
     * Register the given directory, and all its sub-directories, with the
     * WatchService.
     */
    private void registerAll(final Path start) throws IOException {
        // register directory and sub-directories
        Files.walkFileTree(start, new SimpleFileVisitor() {
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                    throws IOException {
                register(dir);
                return FileVisitResult.CONTINUE;
            }
        });
    }


    private void initRegisterAboutRecusive(Path dir, boolean recursive) {
        try {
            if (recursive) {
                log.info("Scanning register directory for {} ...\n", dir);
                this.registerAll(dir);
                log.info("Register Done.");
            } else {
                register(dir);
            }
        } catch (IOException e) {
            log.error("Running construct method matches error~");
            System.exit(-1);
        }

        /* enable trace after initial registration */
        this.trace = true;
    }




     WatchEvent cast(WatchEvent event) {
        return (WatchEvent) event;
    }


    /**
     * Process all events for keys queued to the watcher
     */
    public void processEvents() {

         for ( ; ; ) {
            /* wait for key to be signalled */
            WatchKey key;
            try {
                key = watcher.take();
                log.debug("Hold on that for starting signal key {}", key.toString());
            } catch (InterruptedException x) {
                return;
            }
            //key.
            Path dir = keys.get(key);
            if (dir == null) {
                log.info("WatchKey haven't been recognized!!");
                continue;
            }

            /* StartUp to  heat deploy logically ~ */
            try {

                /* Delete key event to be handled */
                //if (!this.catchDeleteEvent(key)) {

                    for (WatchEvent event : key.pollEvents()) {
                        WatchEvent.Kind kind = event.kind();
                        log.debug("Current event be catched ,which's  {} type", kind.type());

                        /* TBD - provide example of how OVERFLOW event is handled */
                        if (kind == OVERFLOW) {
                            continue;
                        }

                        if (kind == ENTRY_DELETE){
                            log.info("Delete entry signal was be catched for {}", ENTRY_DELETE.name());
                            this.doCompile();
                            break ;
                        }

                        //TODO  Need to refine a major event in many incidents
                        Iterator iterator = pPatternContainer.iterator();
                        boolean flag =  false;

                        /* Context for directory entry event is the file name of entry */
                        Path child = this.handleInvocableEventPath(dir, event);

                        log.info("The real path of watchable is {},this path is Path before analysis",child.toFile().getAbsolutePath());

                        //filter [^*\\.property | ^*\\.class]
                        while (iterator.hasNext()){
                            if (child.toString().endsWith(iterator.next())){
                                flag = true;
                                break;
                            }
                        }

                        if (!flag){
                            continue ;
                        }

                        /* Path to the event about handling for validation */
                        if (this.formalPathValidate(child)) {
                            /* property hot reload */
                            this.PropertyReloadThroughPath(child);

                            /* if directory is created, and watching recursively, then register it and its sub-directories */
                            if (recursive && (kind == ENTRY_CREATE)) {
                                try {
                                    if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
                                        registerAll(child);
                                    }
                                } catch (IOException x) {
                                    /* ignore to keep sample readable */
                                }
                            }
                        }
                    }
            } catch (Exception e) {
                log.error("Internal errors occur", e);
                e.fillInStackTrace();
                continue;
                /* Ignore error , but only to print error stack message */
            }

            /* reset key and remove from set if directory no longer accessible */
            boolean valid = key.reset();
            if (!valid) {
                keys.remove(key);

                /* all directories are inaccessible */
                if (keys.isEmpty()) {
                    break;
                }
            }
        }
    }


    private boolean formalPathValidate(Path dir) {
        return dir.toString().endsWith(SUBFFIX_PROPERTIES) ||
                dir.toString().endsWith(SUBFFIX_JAVA);
    }


    /* Path analysis */
    private Path handleInvocableEventPath(Path dir, WatchEvent event) {
        WatchEvent ev = this.cast(event);
        Path name = ev.context();
        return dir.resolve(name);
    }


    /**
     * Load the spring cloud config configuration and integrate the properties of the hot deployment
     *
     * @param path
     * @throws Exception
     */
    protected void PropertyReloadThroughPath(Path path) throws Exception {

        this.doCompile();
        assert environment instanceof StandardServletEnvironment;
        StandardServletEnvironment standardServletEnvironment = (StandardServletEnvironment) environment;

        synchronized (standardServletEnvironment) {
            MutablePropertySources mutablePropertySources = standardServletEnvironment.getPropertySources();
            PropertySource propertySource = mutablePropertySources.get("applicationConfig: [classpath:/application");
            Map maps = ((OriginTrackedMapPropertySource) propertySource).getSource();

            if (path == null ||
                    path.toFile().length() == 0 ||
                    !path.toString().endsWith(SUBFFIX_PROPERTIES)) {
                return;
            }

            try (InputStream uls = path.
                    toUri().
                    toURL().
                    openStream()) {

                Properties p = new Properties();
                p.load(uls);

                log.info("Extra value map has been added into environment,newly loaded value is {}", p.toString());

                assert p instanceof Map;

                maps.putAll((Map) p);
            } catch (Exception e) {
                /* If the path is just moving, it is possible to detect an abnormal flow. just ignore*/
                return;
            }
        }

    }

    /**
     * pre-compile-handle
     *
     * @throws IOException
     * @throws InterruptedException
     */
    private void doCompile() throws Exception {
        //SystemUtils.IS_OS_LINUX
        if (!this.isLinux()){return;}

        Process process = Runtime.getRuntime().exec(DEPLOY_STATIC_ACTION);
        int status = process.waitFor();

        if (status != 0) {
            log.error("Failed to call shell's command");
        }

        try (BufferedReader var = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            while (var.readLine() != null) {
                log.info(var.readLine());
            }
        }
    }

    private boolean isLinux() {
        return System.getProperty(OS_NAME).equalsIgnoreCase(LINUX);
    }


    private void init() {
        Path dir = Paths.get(System.getProperty(USER_DIR) + File.separator + "src");
        Thread deason = new Thread(()-> {
            try {
                new RecompileNotifyWatcher(dir, true,environment);
            } catch (Exception e) {
                //ignore
                log.warn("Initialized of heat-reload exocutia has been occurs error , please attend to check");
            }
        });
        deason.setDaemon(true);
        deason.setName("exacutio");
    }
}

 

 

你可能感兴趣的:(技术笔记)