背景
由于在深入jvm虚拟机中看到了有部分说道class可以通过网络的方式加载,于是就想到了是不是可以通过在网络上发布jar包,然后程序动态加载网络上的jar包(可拓展为热更新)
代码地址
调用模块 https://coding.net/u/mich/p/easytry/git/tree/master/src/com/netclassloader
实现模块 https://coding.net/u/mich/p/easytry/git/tree/master/netlogicImpl
接口模块 https://coding.net/u/mich/p/easytry/git/tree/master/netlogicInterface
说明
该内容主要由三部分组成
- 接口模块 主要包含接口jar包
- 实现模块 导入接口jar包,然后实现接口的具体逻辑,打包成jar包,最后通过工具包,生成对应的接口文件,将jar包和接口文件都上传至网络,我这里就直接使用了git上的地址,这里还有生成了接口协议文件,类似于双方协议,实现类和调用者的对应关系
- 调用模块 自定义一个管理类,下载接口文件与接口jar包,解析jar包中的所有有效class文件,然后将协议文件以及类与class的map存放在成员变量中,当网络加载器加载类指定类时,通过刚才的map获取class文件的byte[],定义该类,管理类同时提供一个获取服务的方法,通过协议文件,将接口与实现类绑定在一起
内容
接口模块
接口模块相对来说比较简单,两个接口
package com.netresource.logic; /** * Created by Mich on 2017/7/22. */ public interface ISayHello { Object sayHello(); }
package com.netresource.logic; /** * Created by Mich on 2017/7/22. */ public interface ISayWorld { Object sayWorld(); }
实现模块
实现模块需要先导入刚才接口模块,然后写两个接口的实现类
package com.netlogic; import com.netresource.logic.ISayHello; /** * Created by Mich on 2017/7/22. */ public class SayHelloImpl implements ISayHello { @Override public Object sayHello() { return "Hello"; } }
package com.netlogic; import com.netresource.logic.ISayWorld; /** * Created by Mich on 2017/7/22. */ public class SayWorldImpl implements ISayWorld { @Override public Object sayWorld() { return "World"; } }
你会发现还有两个util,fileutil主要是我一直使用的对文件的一些处理比较方便,就直接引用了,PropertiesUtil主要是协议文件的生成工具,通过传入实现类的包名,然后扫描该包下的所有类把接口和实现做对应关系存放在output.properties文件中,需要注意的是,如果一个接口并不支持有多个实现类,但是一个实现类实现多个接口是可以的
最后将实现类打jar包,以及将协议文件上传至网络,我这里图方便直接放到了coding上https://coding.net/u/mich/p/easytry/git/raw/master/src/com/netclassloader/output/netlogicImpl.jar
https://coding.net/u/mich/p/easytry/git/raw/master/src/com/netclassloader/output/output.properties
调用模块
同样调用模块需要引用接口模块的jar包,然后介绍一下调用模块的具体目录结构(这里无视output文件夹,这主要是我刚才上传的两个文件,jar包和协议文件,为了在一个模块里才放这里,实际不需要这个文件夹)
NetClassManager,由于代码有点多就不在这里显示了,具体可以再coding上去看,这里主要介绍一下结构
- 构造函数,需要传入两个网络地址,一个是协议文件网络地址,一个是jar包的网络地址
- getService通过传入ClassLoader和Class,会根据协议文件的对应关系获得网络jar包上的对应实现类
- initProperties初始化协议文件,主要是从网络下载协议文件,然后存放在成员变量中
- initImplJar初始化jar包,将jar包暂存在本地,然后解析获得各个内部的class的byte[],通过协议文件,获取所需要的类,存放在classMap成员变量中
- getClassMap外部获取对应map,主要是给自定义的classLoader可以获得指定的class的byte[]
NetClassLoader类加载器
package com.netclassloader; /** * Created by Mich on 17/7/17. */ public class NetClassLoader extends ClassLoader { private NetClassManager netClassManager; public NetClassLoader(NetClassManager netClassManager) { this.netClassManager = netClassManager; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] bytes = netClassManager.getClassMap().get(name); return defineClass(name, bytes, 0, bytes.length); } }
这个就比较简单了,继承了ClassLoader,构造函数传入刚刚说明的管理类NetClassManager,然后重写了findClass的方法,通过管理类的map来获取字节数组
Main作为测试的入口函数
public static void main(String[] args) { String jarUrl = "https://coding.net/u/mich/p/easytry/git/raw/master/src/com/netclassloader/output/netlogicImpl.jar"; String propertiesUrl = "https://coding.net/u/mich/p/easytry/git/raw/master/src/com/netclassloader/output/output.properties"; NetClassManager netClassManager = new NetClassManager(propertiesUrl, jarUrl); NetClassLoader classLoader = new NetClassLoader(netClassManager); ISayHello hello = netClassManager.getService(classLoader, ISayHello.class); ISayWorld world = netClassManager.getService(classLoader, ISayWorld.class); System.out.println(hello.sayHello()); System.out.println(world.sayWorld()); }
最后就是比较简单的调用了,运行结果
最后
其实这里也只是抛砖,如果具体使用,应该还需要版本控制,可以有一个专门的类,或者手动调用,去获取最新的jar包,和最新的协议接口文件,当然更好一点可以再添加一个实现类的版本控制,这样就需要修改的粒度更小了。对了如果要添加动态更新还需要修改NetClassLoader类,现在目前只是第一次加载处理,如果有更新,就需要重写loadClass方法了。最后想想其实协议文件,和版本控制可以直接放在jar包中。。。等下次再继续改进吧。。。