Druid连接池在不重启服务的情况下修改用户名和密码

最近做项目过程中遇到了一个需求:要求Druid连接池在不重启服务的同时修改用户名和密码,并使之生效。要求用户名和密码不能为明文。

首先对用户名和密码的加密和解密:

     第一种方案:自定义一个类继承Druid连接池的datasource,并重写其setUsername和setPassword 在方法内部使用德鲁伊自带的同居类解密用户名和密码。需要注意的是加密使用私钥,解密使用公钥。安全期间建议自定义私钥和公钥。特别注意的是:解密完成后需要调用super.setUsername和super.setPassword,才能把解密后的用户名和密码设置进去。

    第二种方案:自定义一个类继承Druid连接池的DruidPasswordCallback,重写其setProperties方法,从传入的Properties中取需要的密码,进行解密,解密之后直接调用setPassword方法即可设置密码,但是DruidPasswordCallback却没有对用户名的相对设置,如果有其他可以实现的同学,留言相互学习一下。

    第三种方案:重写spring的PropertyPlaceholderConfigurer类,请自行搜索实现。

其次:需要实时监控对应的properties文件的变动:

     思路1:利用JDK自带的WatchService监控指定目录下文件的变动,大致代码如下

 

/**

* 文件变动行为枚举

*

*

*/

public enum FileAction {

    DELETE("ENTRY_DELETE"), CREATE("ENTRY_CREATE"), MODIFY("ENTRY_MODIFY");

    private String value;

 

    FileAction(String value) {

        this.value = value;

    }

 

    public String getValue() {

        return value;

    }

}

 

import java.io.File;

 

/**

* 文件操作的回调方法

*

*

*/

public abstract class FileActionCallback {

 

    public void delete(File file) {

    };

 

    public void modify(File file) {

    };

 

    public void create(File file) {

    };

 

}

 

import static java.nio.file.LinkOption.NOFOLLOW_LINKS;

 

import java.io.File;

import java.io.IOException;

import java.nio.file.FileSystems;

import java.nio.file.FileVisitResult;

import java.nio.file.Files;

import java.nio.file.Path;

import java.nio.file.Paths;

import java.nio.file.SimpleFileVisitor;

import java.nio.file.StandardWatchEventKinds;

import java.nio.file.WatchEvent;

import java.nio.file.WatchEvent.Kind;

import java.nio.file.WatchKey;

import java.nio.file.WatchService;

import java.nio.file.attribute.BasicFileAttributes;

import java.util.HashMap;

import java.util.Map;

 

/**

* 文件夹监控

*

* @author Goofy <a href="http://www.xdemo.org/">http://www.xdemo.org/</a>

* @Date 2015年7月3日 上午9:21:33

*/

public class WatchDir {

 

    private final WatchService watcher;

    private final Map<WatchKey, Path> keys;

    private final boolean subDir;

private static Long LAST_MODIFY=1000lL;

    /**

     * 构造方法

     *

     * @param file

     * 文件目录,不可以是文件

     * @param subDir

     * @throws Exception

     */

    public WatchDir(File file, boolean subDir, FileActionCallback callback) throws Exception {

        if (!file.isDirectory())

            throw new Exception(file.getAbsolutePath() + "is not a directory!");

 

        this.watcher = FileSystems.getDefault().newWatchService();

        this.keys = new HashMap<WatchKey, Path>();

        this.subDir = subDir;

 

        Path dir = Paths.get(file.getAbsolutePath());

 

        if (subDir) {

            registerAll(dir);

        } else {

            register(dir);

        }

        processEvents(callback);

    }

 

    @SuppressWarnings("unchecked")

    static <T> WatchEvent<T> cast(WatchEvent<?> event) {

        return (WatchEvent<T>) event;

    }

 

    /**

     * 观察指定的目录

     *

     * @param dir

     * @throws IOException

     */

    private void register(Path dir) throws IOException {

        WatchKey key = dir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);

        keys.put(key, dir);

    }

 

    /**

     * 观察指定的目录,并且包括子目录

     */

    private void registerAll(final Path start) throws IOException {

        Files.walkFileTree(start, new SimpleFileVisitor<Path>() {

            @Override

            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {

                register(dir);

                return FileVisitResult.CONTINUE;

            }

        });

    }

 

    /**

     * 发生文件变化的回调函数

     */

    @SuppressWarnings("rawtypes")

    void processEvents(FileActionCallback callback) {

        for (;;) {

            WatchKey key;

            try {

                key = watcher.take();

            } catch (InterruptedException x) {

                return;

            }

            Path dir = keys.get(key);

            if (dir == null) {

                System.err.println("操作未识别");

                continue;

            }

 

            for (WatchEvent<?> event : key.pollEvents()) {

                Kind kind = event.kind();

 

                // 事件可能丢失或遗弃

                if (kind == StandardWatchEventKinds.OVERFLOW) {

                    continue;

                }

 

                // 目录内的变化可能是文件或者目录

                WatchEvent<Path> ev = cast(event);

                Path name = ev.context();

                Path child = dir.resolve(name);

                File file = child.toFile();

                Long lastModify=file.lastModified();

                if (kind.name().equals(FileAction.DELETE.getValue())) {

                    callback.delete(file);

                } else if (kind.name().equals(FileAction.CREATE.getValue())) {

                    callback.create(file);

                } else if (kind.name().equals(FileAction.MODIFY.getValue())

                 &&!lastModify.equals(LAST_MODIFY)&&file.length()>0) {

                     LAST_MODIFY=lastModify;

                    callback.modify(file);

                } else {

                    continue;

                }

 

                // if directory is created, and watching recursively, then

                // register it and its sub-directories

                if (subDir && (kind == StandardWatchEventKinds.ENTRY_CREATE)) {

                    try {

                        if (Files.isDirectory(child, NOFOLLOW_LINKS)) {

                            registerAll(child);

                        }

                    } catch (IOException x) {

                        // ignore to keep sample readbale

                    }

                }

            }

 

            boolean valid = key.reset();

            if (!valid) {

                // 移除不可访问的目录

                // 因为有可能目录被移除,就会无法访问

                keys.remove(key);

                // 如果待监控的目录都不存在了,就中断执行

                if (keys.isEmpty()) {

                    break;

                }

            }

        }

    }

 

}

注意标红部分,如果不添加这些逻辑,那么当文件只修改一次,会触发两次Modify时间,这段代码的意思是,每当文件发生变动就比较一下最后修改时间和变动内容的大小,防止重复触发。

使用代码如下

 

public class Usage {

注入Applicationcontext;

    public static void main(String[] args) throws Exception {

        final File file = new File("D:\\upload");

        new Thread(new Runnable() {

            @Override

            public void run() {

                try {

                    new WatchDir(file, true, new FileActionCallback() {

                        @Override

                        public void create(File file) {

                            System.out.println("文件已创建\t" + file.getAbsolutePath());

                        }

 

                        @Override

                        public void delete(File file) {

                            System.out.println("文件已删除\t" + file.getAbsolutePath());

                        }

 

                        @Override

                        public void modify(File file) {
                           从容器中拿到指定的连接池bean
                           DruidDataSource dds=(DruidDataSource) applicationcontext.getBean("datasource");
                          Properties proerties=new Properties();
                           InputStream is=new FileInputStream(file);
把最新的文件读进properties中
                           properties.load(is);
判断此时的连接池是否已经初始化 如果初始化了就重启 如果不重启 那么setUsername setPassword
将无法执行 会报错
                           if(dds.isInited()){
                            对连接池进行重启 这一步非常重要 要确保 连接池处于未初始化状态 重启的同时会释放并关闭所有的连接,有朋友反映直接重启会因为存在存活的连接导致重启失败,又看了一下源码,发现调用close()方法会关闭所有存活的连接因此

                            dds.close();
                            dds.restart();
重新设置密码和用户名
                            dds.setUsername(properties.getProperty("username"));
dds.setPassword(properties.getProperty("password"));
                             }else{
如果连接池处于未初始化状态直接设置密码和用户名
                                   dds.setUsername(properties.getProperty("username"));
dds.setPassword(properties.getProperty("password"));                

}
最后对连接池进行初始化 使设置的用户名和密码生效
                            dds.init();

                            System.out.println("文件已修改\t" + file.getAbsolutePath());

                        }

                    });

                } catch (Exception e) {

                    e.printStackTrace();

                }

            }

        }).start();

 

        System.out.println("正在监视文件夹:" + file.getAbsolutePath() + "的变化");

    }

 

}

当初开发的时候只是调用连接池的close方法,虽然释放并关闭了连接,但是整个连接池依旧是已经初始化的状态,这个时候去设置密码和用户名会报错,经过源码跟踪发现,只有处于未初始化状态的连接池才可以重新设置用户名和密码。最后重新初始化连接池,那么德鲁伊连接池会重新初始化,并且使用的是最新的用户名和密码。

但是注意:对连接池重启会断开所有连接,为了不对业务造成影响,请在没有业务处理的时候执行密码与用户名的更新操作。

如果大家有更好的方案,或者发现不足的地方,请留言交流。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值