一、业务场景

在有些业务场景下,需要SpringBoot来动态加载jar中的class文件,自动往spring容器中添加新的bean;如物联网设备上传的信息用物模型来解析,用java来解析物模型,但用户的设备千差万别,解析设备的物模型不可能包罗万象,设备往物联网平台上传的数据格式也是千差万别,这时就可以让用户自已实现解析物模型的java代码,然后打成jar包,上传到物联网平台,物联网平台就可以解析当前用的设备信息了。

二、准备工作

1、生成的jar需要布署在nginx当中,直接通过url地址可以下载,所以本次使用要用到nginx,nginx的下载地址,放在本博文最后,可以在后面查看下载

spring jar包热部署 springboot热加载jar包_spring boot

在conf文件夹下 nginx的配置如下

server{
        listen      5000;	
        server_name  127.0.0.1;
		location / {
		  root   ./jar;
		  index  index.html index.htm;
		}
    }

启动nginx,双击nginx.exe即可,复制 


http://127.0.0.1:5000/user-1.0.jar

到浏览器下,可以下载user-1.0.jar

三、maven代码结构

spring jar包热部署 springboot热加载jar包_spring boot_02

 说明:

1、user模块打成jar包,server微服务加载user打成的jar包user-1.0.jar 2、具体原理如下:inter是公共的模块,里面只有一个抽像CommonService,然后user模块里面的teacherService来实现这个抽像类,然把把user打成jar包供server服务来动态加载teacherService,加载后,就可以使用teacherService了 3、user-1.0.jar布署在nginx上,可以直接通过http://127.0.0.1:5000/user-1.0.jar下载,server模中,是直接访问http://127.0.0.1:5000/user-1.0.jar这个地址来读jar包的


四、相关核心代码

1、constroller中加载jar包,执行加载成功的teacherService

@RestController
@RequestMapping("/test")
@Api(tags = "LoadBeanController", description = "测试类")
public class LoadBeanController {

    @Autowired
    private BeanLoadProvider beanLoadProvider;
    @Autowired
    private ContextProvider contextProvider;
    @Autowired
    public Map<String, CommonService> commonServiceFactory;

    @GetMapping("/execute")
    @ApiOperation("执行加载的jar包中的Service")
    public String execute(String serviceName) throws NotFoundException {
        serviceName = StringUtils.uncapitalize(serviceName);
        String str= null;
        Object object = contextProvider.getBean(serviceName);
        if(object==null){
            throw new NotFoundException("未找到对应的service:"+serviceName);
        }else{
            CommonService commonService =(CommonService) object;
            str  = commonService.deal(serviceName);
        }
        return str;
    }

    @GetMapping("/load")
    @ApiOperation("测试loadJar,为jar地址,如布署在nginx上的下载地址http://127.0.0.1:5000/user-1.0.jar")
    public String load(String urlStr) {
        try {
            beanLoadProvider.loadJar(urlStr);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "成功";
    }

    @GetMapping("/unload")
    @ApiOperation("测试unLoadJar")
    public String unload(String urlStr) {
        try {
            beanLoadProvider.unLoadJar(urlStr);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "成功";
    }
}

2、加载jar包

/**
     * 加载jar包中的类,并注册到springBean容器中
     */
    public void loadJar(String urlStr){
        URL url=null;
        try {
            url = new URL("jar:" + urlStr + "!/");
            List<String> clazzList = getBeanNames(url);
            URLClassLoader loader = new URLClassLoader(new URL[]{url});
            for(String clazzName:clazzList) {
                try {
                    Class<?> clazz = loader.loadClass(clazzName);
                    registerBean(clazzName,clazz);
                    if(clazzName.endsWith("Controller")){
                        registerController(StringUtils.uncapitalize(clazzName.substring(clazzName.lastIndexOf(".") + 1)));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            reloadSwagger();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 从SpringBean容器中移出已经加载了jar包中的类
     */
    public void unLoadJar(String urlStr){
        URL url=null;
        try {
            url = new URL("jar:" + urlStr + "!/");
            List<String> clazzList = getBeanNames(url);
            URLClassLoader loader = new URLClassLoader(new URL[]{url});
            for(String clazzName:clazzList) {
                try {
                    Class<?> clazz = loader.loadClass(clazzName);
                    if(clazz!=null&&clazzName.endsWith("Controller")){
                        unregisterController(StringUtils.uncapitalize(clazzName.substring(clazzName.lastIndexOf(".") + 1)));
                    }
                    unRegisterBean(clazzName);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            reloadSwagger();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public List<String> getBeanNames(URL url){
        List<String> classList = new ArrayList<>();
        try {
            JarURLConnection conn = (JarURLConnection) url.openConnection();
            //JarFile jarFile = new JarFile(new File("d:\\dd\\my.jar"));
            JarFile jarFile = conn.getJarFile(); //获取jarFile
            //解析jar包每一项
            Enumeration<JarEntry> en = jarFile.entries();
            while (en.hasMoreElements()) {
                JarEntry je = en.nextElement();
                String name = je.getName();
                //这里添加了路径扫描限制
                if (name.endsWith(".class")) {
                    String className = name.replace(".class", "").replaceAll("/", ".");
                    System.out.println("className="+className);
                    classList.add(className);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return classList;
    }

3、TeacherService中的代码

@Service
public class TeacherService extends CommonService {

    @Override
    public String deal(String serviceName) {
        System.out.println("老师的实现TeacherService");
        String str = "参数为:"+serviceName;
        System.out.println(str);
        str+=",我是返回加的老师";
        return str;
    }

}

五、测试

1、没中加载jar包的情况下,执行teacherService的deal方法,http://localhost:8080/test/execute?serviceName=teacherService

结果报错,找不到对应的bean,如下截图

spring jar包热部署 springboot热加载jar包_jar_03

 2、加载jar包 http://localhost:8080/test/load?urlStr=http://127.0.0.1:5000/user-1.0.jar

spring jar包热部署 springboot热加载jar包_spring boot_04

 3、再次执行 http://localhost:8080/test/execute?serviceName=teacherService

spring jar包热部署 springboot热加载jar包_spring jar包热部署_05