这是一篇关于项目经验积累的文章,在实际的项目中我们常常会遇到很多的问题,可能有很多种不同的解决办法,但是将自己的解决办法通过文字的方式记录下来,不失为一种经验值积累的好方法,毕竟好记性不如烂笔头嘛,下面就开始我们的正题,go!!!

这几天,非非做公司的项目,需要做如下的一个需求,对于我一个基本没有项目经验的实习生来说,这可把我给难住了,终于在我翻了几十篇博客后,给出了我的解决方法:
需求如下:

  • 有这么一个需求,我们提供一个接口标准,写成了一个jar,这个标准给不同的厂商去实现,实现完了打成一个jar,然后再把这个jar放到我们平台上运行, 具体厂商是怎么实现的我们不关心,我们需要做到的是只依赖于这个接口,而不依赖于具体的实现。而且我们希望厂商实现完了这些接口打成的jar放到他们的服务器上,或者修改了这些jar的实现,从新发布到服务器上,我们不需要重新发布项目。 即实现一个动态加载jar的功能。

上述需求,我们需要三个项目:

  • 第一个就是这个接口项目,我们定义接口标准,打成jar给其他厂商。
  • 第二个就是厂商实现了这个jar的接口,将实现了这些接口的项目打成一个jar交给我们,或者发布到他们的服务器上。
  • 第三个就是我们的平台项目。

解决办法:
基于上述需求我们的做法:首先我们需要把我们定义的接口jar引入到我们的项目依赖中。其次我们需要动态的去加载第三方厂商的jar,动态的去获取实现类。这个实现类的jar,可能在某个磁盘的某个位置,或者就放在项目的某个目录下,或者在远程服务器上面,总之没有将他加入到项目的依赖中去(一般在远程服务器上面,随便别人怎么玩儿,与我无关,我只依赖于接口)。然后通过我们定义的接口去调用具体的实现。

具体解决步骤如下:

  • 第一步,通过文件操作,将这个jar文件加载到内存中。
  • 第二步,通过自定义classLoader将jar文件中的接口的实现类加载到JVM。
  • 第三步:通过classLoader获取到jar文件具体的实现类。
  • 第四步:通过类反射生成对应的实例化对象
  • 第五步:然后通过这个具体的实现类的实例对象就可以调用这个jar中的方法

到此基本完成了上述需求。

参考Demo代码如下:

// 定义第三方jar文件的路径,此处是相对路径,当然也可以写绝对路径,也可以是远程服务器路径。
String path = "/adapterClassLoader/lib/adapter-0.0.1-SNAPSHOT.jar";


//获取 JarFile
JarFile jarFile = new JarFile(new File(path));


//获取这个jar的URL    固定写法
URL url = new URL("file:" + path);


//定义ClassLoader   并将URL给ClassLoader,这个URL是个数组,当然此处我只有一个URL,直接这么写的。
ClassLoader loader = new URLClassLoader(new URL[] { url }); 


//定会 JarEntry      这个就是说,我们这个jar中有很多的类
Enumeration<JarEntry> es = jarFile.entries();


//然后我们需要遍历这次entry节点,获取到每一个类,然后进行操作


while (es.hasMoreElements()) {
				JarEntry jarEntry = (JarEntry) es.nextElement();
				String name = jarEntry.getName();
				if (name != null && name.endsWith(".class")) { // 只解析了.class文件
                    //将类加载到JVM
					Class<?> c = loader.loadClass(name.replace("/", ".").substring(0, name.length() - 6)); // 处理 .class后缀名
                    //反射获取某个类对象的实例  直接c.newInstance()方法已经被废弃了。
					Object object = c.getDeclaredConstructor().newInstance();
                    //此处为什么可以进行类型转换,因为我们将接口定义的jar引入到了项目的依赖中。否则是无法进行转换的,我们只有通过其他的方式进行方法调用。
					AdapterService adapterService = (AdapterService) object;
                    //调用接口定义的方法
					adapterService.eatMeat();
                   //其实到这里我们的功能需求就已经实现了,当然通过反射,我们还可以获取到这个类的更多信息,此处就不一一罗列了。

				}

			}

当然有些需求需要我们动态的去加载jar中的某个类,而不是整个jar文件,那么我们可以通过以下的方式可以拿到想要的类

//动态的去加载jar文件中的某一个类
            URL url = new URL("file:C:\\Users\\Administrator\\IdeaProjects\\test\\data.jar");
			URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { url });
			Class<?> c1 = urlClassLoader.loadClass("AdapterServiceImpl");
			Object newInstance = c1.getConstructor().newInstance();
			AdapterService AdapterService= (AdapterService) newInstance;
			AdapterService.eatMeat();

通过以上方法,非非我完美的解决了这个需求,老大夸我可真是个小机灵鬼,嘿嘿嘿。

作者感言:
在实际的项目需求中,我们会遇到各种各样的问题,在网上我们也能搜到各种解决方案,可能有的适合自己,有的不适合,这是需要我们去辨别的。我自己遇到的问题,可能也是别人会遇到的问题,自己只是提供了一种解决方案,希望可以帮助到其他人。对于自己的话,也是一个成长的记录,等几年回过头来看,都是自己的程序人生的点点滴滴,说不定会有许多感慨了,可能以后的文章内容不再局限于知识点的讲解了,慢慢的会有一些解决问题的文章出来,记录自己在实际项目中遇到的一些问题以及解决方案。顺便提一句,2020年的秋招已经来到,希望各位小伙伴们可以找到自己心仪的工作,当然非非自己也要努力了,争取早日拿到offer,还可以过个开心的年,哈哈。