最近在开发系统的过程中遇到的一个需求。给系统一个接口,用户可以自定义接口的实现,将实现打包成jar包,上传到系统中。系统完成热部署,切换该接口的实现。定义一个简单的接口这里以一个简单的计算器函数为例。接口定义比较简单,直接添加代码。公共接口计算器{intcalculate(inta,intb);intadd(inta,intb);}这个接口的简单实现考虑到了用户实现接口的两种方式,使用spring上下文管理,或者不依赖spring管理的方法,分别称为注解方法和反射方法在这里。calculate方法对应注解方法,add方法对应反射方法。计算器接口实现类代码如下:@ServicepublicclassCalculatorImplimplementsCalculator{@AutowiredCalculatorCorecalculatorCore;/***注解方法*/@Overridepublicintcalculate(inta,intb){intc=calculatorCore.add(a,b);返回c;}/***反射方法*/@Overridepublicintadd(inta,intb){returnnewCalculatorCore().add(a,b);}}这里注入CalculatorCore的目的是为了验证在注解方式下,系统能够完整的构建bean依赖体系,并注册到当前的spring容器中。CalculatorCore的代码如下:@ServicepublicclassCalculatorCore{publicintadd(inta,intb){returna+b;}}这里有一个SpringBoot基础教程:https://github.com/javastacks...反射方式热部署用户上传jar包到系统指定目录。这里定义上传的jar文件路径为jarAddress,jarUrl路径为jarPath。privatestaticStringjarAddress="E:/zzq/IDEA_WS/CalculatorTest/lib/Calculator.jar";privatestaticStringjarPath="file:/"+jarAddress;可以要求用户在jar包中填写接口实现类的完整类名。接下来系统将上传的jar包加载到当前线程的类加载器中,然后通过全类名加载实现的Class对象。然后反射调用就可以了,完整代码:/***计算器接口热加载的实现反射方法*/publicstaticvoidhotDeployWithReflect()throwsException{URLClassLoaderurlClassLoader=newURLClassLoader(newURL[]{newURL(jarPath)},Thread.currentThread().getContextClassLoader());类clazz=urlClassLoader.loadClass("com.nci.cetc15.calculator.impl.CalculatorImpl");计算器calculator=(Calculator)clazz.newInstance();int结果=计算器。添加(1,2);System.out.println(result);}注解方式热部署如果用户上传的jar中包含spring上下文,那么需要扫描jar包中所有需要注入spring容器的bean并注册它们在当前系统的spring容器中。其实这是一个类的热加载+动态注册的过程。直接上传代码:/***添加jar包后,动态注册bean到spring容器,包括bean依赖*/publicstaticvoidhotDeployWithSpring()throwsException{SetclassNameSet=DeployUtils.readJarFile(jarAddress);URLClassLoaderurlClassLoader=newURLClassLoader(newURL[]{newURL(jarPath)},Thread.currentThread().getContextClassLoader());for(StringclassName:classNameSet){Classclazz=urlClassLoader.loadClass(className);如果(DeployUtils.isSpringBeanClass(clazz)){BeanDefinitionBuilderbeanDefinitionBuilder=BeanDefinitionBuilder.genericBeanDefinition(clazz);defaultListableBeanFactory.registerBeanDefinition(DeployUtils.transformName(className),beanDefinitionBuilder.getBeanDefinition());}}}这个过程中将jar加载到当前线程的类加载器中,过程和前面的反射方式一样。然后扫描jar包下的所有class文件,获取完整的类名,使用当前线程类加载器加载类名对应的类对象。判断类对象是否有spring注解,如果有则在系统的spring容器中注册该对象。DeployUtils包括读取jar包所有类文件的方法,判断类对象是否包含sping注解的方法,以及获取注册对象的对象名的方法。代码如下:/***读取jar包中的所有类文件*/publicstaticSetreadJarFile(StringjarAddress)throwsIOException{SetclassNameSet=newHashSet<>();JarFilejarFile=newJarFile(jarAddress);Enumerationentries=jarFile.entries();//遍历整个jar文件while(entries.hasMoreElements()){JarEntryjarEntry=entries.nextElement();字符串名称=jarEntry.getName();if(name.endsWith(".class")){StringclassName=name.replace(".class","").replaceAll("/",".");classNameSet.add(类名);}}返回类名集;}/***判断class对象是否有spring注解的方法说明*/publicstaticbooleanisSpringBeanClass(Class>cla){if(cla==null){returnfalse;}//是否是接口if(cla.isInterface()){returnfalse;}//是否是抽象类if(Modifier.isAbstract(cla.getModifiers())){returnfalse;}if(cla.getAnnotation(Component.class)!=null){returntrue;}if(cla.getAnnotation(Repository.class)!=null){returntrue;}if(cla.getAnnotation(Service.class)!=null){returntrue;}returnfalse;}/***类名首字母小写作为spring容器beanMap的key*/publicstaticStringtransformName(StringclassName){Stringtmpstr=className.substring(className.lastIndexOf(".")+1);returntmpstr.substring(0,1).toLowerCase()+tmpstr.substring(1);}删除jar时,需要同时删除spring容器中注册的bean。在切换或删除jar包时,需要删除bean的操作和删除之前注册到spring容器的spring容器的bean的注册操作是相反的过程。这里,要注意使用同一个springcontext。代码如下:/***删除jar包时,需要删除spring容器中的注入*/publicstaticvoiddelete()throwsException{SetclassNameSet=DeployUtils.readJarFile(jarAddress);URLClassLoaderurlClassLoader=newURLClassLoader(newURL[]{newURL(jarPath)},Thread.currentThread().getContextClassLoader());for(StringclassName:classNameSet){Classclazz=urlClassLoader.loadClass(className);如果(DeployUtils.isSpringBeanClass(clazz)){defaultListableBeanFactory.removeBeanDefinition(DeployUtils.transformName(className));}}}Test测试类手动模拟用户上传jar的功能。测试函数写入一个无限循环。如果一开始没有找到jar,就会抛出异常,异常会被捕获并休眠10秒。这时候可以手动把jar放到指定目录下。代码如下:ApplicationContextapplicationContext=newClassPathXmlApplicationContext("applicationContext.xml");DefaultListableBeanFactorydefaultListableBeanFactory=(DefaultListableBeanFactory)applicationContext.getAutowireCapableBeanFactory();while(true){try{hotDeployWithReflect();//hotDeployWithSpring();//delete();}catch(Exceptione){e.printStackTrace();线程.sleep(1000*10);}}看完,涨势没了?