关于动态注册dubbo的思路,做法
技术:
springboot,maven,dubbo,zookeeper
背景:
项目的功能类似一个中转路由,通过页面可以发送请求,请求到别的项目的接口,大家都知道dubbo接口的服务提供方需要把服务注册到zookeeper上,然后服务消费方获得服务提供方提供的facade包(也就是jar包),可以作为消费者去请求提供方的服务。
这里就有一个问题,dubbo接口的服务,无论是服务提供者还是服务消费者都有几种注册方式,可以查看官网。(为什么我说消费者也是注册,是因为在dubbo-admin中可以看到)
我的理解中比较常用的应该是:
1,xml
2,properties
3,api
需求:
动态注册dubbo服务,项目不需要重启
解释一下为什么有这个需求:
因为公司本身有自己的项目,这个独立的新的项目只是为了做测试各个接口的功能,有点类似于自动化测试,并且提供页面能够让开发人员测试自己写的接口。
那么也就是说,其他开发人员并不需要关注这个独立项目的代码,配置等等一切有关于这个项目本身的东西,他们只需要使用提供的功能,到服务器上看一看请求过来以后我们自己项目的日志。
这样子就必须要做到动态加载。因为人家不可能把你的代码拉到自己的电脑上,还要改你的代码配置,然后打包,重新部署。
所以,才会有这么一个需求。
问题:
作为服务的消费者,需要获得服务提供者提供的jar包与对应的接口信息才可以请求服务,那么问题来了,项目启动之后,服务提供者新提供了几个接口,那么现在作为消费者,我需要请求这几个新的接口,那么我能怎么做?
解决方案:
一般来说我都都会采用以下方式
更新maven的pom文件,下载新的jar包,更新xml,properties,api配置,重新打包。此类做法需要重启
但是现在需求是不重启,所以不能采用常规方式,必须动态加载
页面如下
在页面右上角提供一个按钮,点击效果如下
解决方案如下:
1,在springboot项目中提供一个外置文件dubbo_consumer.properties,里面的内容是key-value,key是方法名(也就是采用xml配置时的id),value是服务提供者提供的服务的类全路径(也就是采用xml配置时的interface)
xml配置如下
<dubbo:reference id="demoServiceRemote" interface="com.alibaba.dubbo.demo.DemoService" />
作为开发,自己写的接口,复制黏贴这个应该是没有问题的。
2,在开发好一个dubbo接口一个,作为服务提供者需要提供一个facade包也就是jar包给服务消费者,这个时候,我们就需要在springboot项目中提供一个外置的文件夹来存放jar包
3,springboot中的提供一个定时任务,每隔一分钟就去扫描这两个文件夹,查看是否有更新,如果有,那么dubbo_consumer.properties文件就直接替换,新的jar包就直接加载进项目
4,通过反射的形式,动态读取dubbo_consumer.properties中的配置,然后使用api的形式来注册dubbo消费者
具体的做法如下:
1,springboot项目启动的时候是1.0版本,这个时候在固定的路径下已经有dubbo_consumer.properties和一个facade包了
2,在项目中写一个单例作为缓存(之所以选择单例,而不使用redis之类是为了减少依赖)来存放初始加载的dubbo_consumer.properties配置和注册的dubbo消费者
3,定时任务每隔一分钟就去扫描这几个路径下的文件是否有更新(注意,项目启动之后,外置的facade包如果加载了是没有办法删除的,所以每次升级就是不断往里面添加facade包,关于数量问题,只需要定时重启项目,保留最新的facade包就行了,其他的删掉)
4,如果有更新就动态加载这些配置到单例缓存,并且注册新的dubbo消费者
下面提供部分代码:
dubbo基础配置:
public abstract class DubboBaseConfig { //应用名 public static final String APPLICATION = "test"; //连接zookeeper public static final String ADDRESS = "127.0.0.1:2181"; //选择的协议 public static final String PROTOCOL = "zookeeper"; //zookeeper对外的端口 public static final Integer PORT = 20880; //dubbo配置 public static final ApplicationConfig applicationConfig = new ApplicationConfig(); public static final RegistryConfig registryConfig = new RegistryConfig(); //加载参数 static { applicationConfig.setName(APPLICATION); registryConfig.setAddress(ADDRESS); registryConfig.setProtocol(PROTOCOL); registryConfig.setPort(PORT); } }
dubbo消费者配置:
public class DubboConsumerConfig extends DubboBaseConfig{ private DubboConsumerConfig() {}; private static final DubboConsumerConfig dubboConsumerConfig = new DubboConsumerConfig(); public static DubboConsumerConfig getInstance() { return dubboConsumerConfig; } /** * * @param applicationConfig 当前应用配置 * @param registryConfig 连接注册中心配置 * @param cls 引用的远程服务 * @param method */ public void registerConsumer(ApplicationConfig applicationConfig,RegistryConfig registryConfig,Class<?> cls,String method) { // 引用远程服务 ReferenceConfig<?> reference = new ReferenceConfig<>(); // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏 reference.setApplication(applicationConfig); reference.setRegistry(registryConfig); // 多个注册中心可以用setRegistries() reference.setInterface(cls); //放入缓存 AppCache.getInstance().getReferenceConfigMapCache().put(method, reference); } }
缓存配置:
public class AppCache { private AppCache(){} private static final AppCache serviceIdCache = new AppCache(); public static AppCache getInstance(){ return serviceIdCache; } //消费者缓存 private Map<String,ReferenceConfig<?>> referenceConfigMapCache = new HashMap<>(); public Map<String, ReferenceConfig<?>> getReferenceConfigMapCache() { return referenceConfigMapCache; } public void setReferenceConfigMapCache(Map<String, ReferenceConfig<?>> referenceConfigMapCache) { this.referenceConfigMapCache = referenceConfigMapCache; } }
初始化调用写法:
private void registryDubboService() { List<DubboServiceEntity> list = AppCache.getInstance().getInitServiceIdEntityList(); for(DubboServiceEntity entity:list) { try { DubboConsumerConfig.getInstance().registerConsumer(DubboBaseConfig.applicationConfig, DubboBaseConfig.registryConfig, Class.forName(entity.getDubboServiceValue()), entity.getDubboServiceKey()); } catch (Exception e) { System.out.println(e); } } }
调用写法中的实体类
public class DubboServiceEntity { private String dubboServiceKey;//消费者服务ID private String dubboServiceValue;//消费的服务类名 public String getDubboServiceKey() { return dubboServiceKey; } public void setDubboServiceKey(String dubboServiceKey) { this.dubboServiceKey = dubboServiceKey; } public String getDubboServiceValue() { return dubboServiceValue; } public void setDubboServiceValue(String dubboServiceValue) { this.dubboServiceValue = dubboServiceValue; } @Override public String toString() { return "DubboServiceEntity [dubboServiceKey=" + dubboServiceKey + ", dubboServiceValue=" + dubboServiceValue + "]"; } }
这样子就可以做到动态注册dubbo服务了
动态加载部分
在动态加载部分其实就是动态加载jar包
需要在绝对路径读取jar包,使用了URLClassLoader,此段代码参考如下:
package cn.fjs; import java.io.File; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; public final class JarLoaderUtil { /** URLClassLoader的addURL方法 */ private static Method addURL = initAddMethod(); /** 初始化方法 */ private static final Method initAddMethod() { try { Method add = URLClassLoader.class .getDeclaredMethod("addURL", new Class[] { URL.class }); add.setAccessible(true); return add; } catch (Exception e) { e.printStackTrace(); } return null; } private static URLClassLoader system = (URLClassLoader) ClassLoader.getSystemClassLoader(); /** * 循环遍历目录,找出所有的JAR包 */ private static final void loopFiles(File file, List<File> files) { if (file.isDirectory()) { File[] tmps = file.listFiles(); for (File tmp : tmps) { loopFiles(tmp, files); } } else { if (file.getAbsolutePath().endsWith(".jar") || file.getAbsolutePath().endsWith(".zip")) { files.add(file); } } } /** * <pre> * 加载JAR文件 * </pre> * * @param file */ public static final void loadJarFile(File file) { try { addURL.invoke(system, new Object[] { file.toURI().toURL() }); //System.out.println("加载JAR包:" + file.getAbsolutePath()); } catch (Exception e) { e.printStackTrace(); } } /** * <pre> * 从一个目录加载所有JAR文件 * </pre> * * @param path */ public static final void loadJarPath(String path) { List<File> files = new ArrayList<File>(); File lib = new File(path); loopFiles(lib, files); for (File file : files) { loadJarFile(file); } } } 参考:http://blog.csdn.net/fjssharpsword/article/de
根据以上代码,再自行修改就能做到动态读取jar文件并且动态注册dubbo服务了