spring boot运行过程中动态加载Controller

embedded/2024/10/18 21:54:42/

1.被加载的jar代码

java">package com.dl;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class App {public static void main(String[] args) {SpringApplication.run(App.class, args);}
}
java">package com.dl;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class JarController {@RequestMapping("/jar")String jar() {return "i  am  a  jar";}}
java"><?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.dl</groupId><artifactId>jar</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><name>Spring Boot Blank Project (from https://github.com/making/spring-boot-blank)</name><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.12</version></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><start-class>com.dl.App</start-class><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.8</source><target>1.8</target><excludes><!-- 去除指定的类--><exclude>**/App.java</exclude></excludes></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>2.6</version><configuration><archive><addMavenDescriptor>false</addMavenDescriptor></archive></configuration></plugin></plugins></build></project>

工程结构
在这里插入图片描述

2.实现代码

java">package com.dl;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class App {public static void main(String[] args) {SpringApplication.run(App.class, args);}
}
java">package com.dl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;/*** 测试*/
@RestController
public class HelloController {private final TestDynamicLoad testDynamicLoad;@Autowiredpublic HelloController(TestDynamicLoad testDynamicLoad) {this.testDynamicLoad = testDynamicLoad;}@RequestMapping("/")String hello() {return "Hello";}/**** @param path  jar文件的路径* @param fileName  jar文件的名称* @return  加载结果*/@RequestMapping("/load")String load(@RequestParam String path, @RequestParam String fileName) {try {testDynamicLoad.loadJar(path,fileName);} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {e.printStackTrace();return "失败:"+e.getMessage();}return "加载成功";}/**** @param name 卸载jar的名称* @return*/@RequestMapping("/unload")String unload(@RequestParam String name) {try {testDynamicLoad.unloadJar(name);} catch (IllegalAccessException | NoSuchFieldException e) {e.printStackTrace();return "失败:"+e.getMessage();}return "卸载成功";}}
java">package com.dl;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** 自定义类加载器*/
public class TestClassLoader  extends URLClassLoader{private Map<String, Class<?>> loadedClasses = new ConcurrentHashMap<>();public Map<String, Class<?>> getLoadedClasses() {return loadedClasses;}public TestClassLoader(URL[] urls, ClassLoader parent) {super(urls, parent);}//加载@Overrideprotected Class<?> findClass(String name) {// 从已加载的类集合中获取指定名称的类Class<?> clazz = loadedClasses.get(name);if (clazz != null) {return clazz;}try {// 调用父类的findClass方法加载指定名称的类clazz = super.findClass(name);// 将加载的类添加到已加载的类集合中loadedClasses.put(name, clazz);return clazz;} catch (ClassNotFoundException e) {e.printStackTrace();return null;}}//卸载public void unload() {try {for (Map.Entry<String, Class<?>> entry : loadedClasses.entrySet()) {// 从已加载的类集合中移除该类String className = entry.getKey();loadedClasses.remove(className);try{// 调用该类的destory方法,回收资源Class<?> clazz = entry.getValue();Method destory = clazz.getDeclaredMethod("destory");destory.invoke(clazz);} catch (Exception e ) {// 表明该类没有destory方法}}// 从其父类加载器的加载器层次结构中移除该类加载器close();} catch (Exception e) {e.printStackTrace();}}}
java">package com.dl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;@Component
public class TestDynamicLoad {@Autowiredprivate ApplicationContext applicationContext;private Map<String, TestClassLoader> myClassLoaderCenter = new ConcurrentHashMap<>();/*** 动态加载指定路径下指定jar包* @param path* @param fileName*/public void loadJar(String path, String fileName) throws ClassNotFoundException, InstantiationException, IllegalAccessException {//获取jar文件File file = new File(path +"/" + fileName);// 获取beanFactoryDefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();try {//创建URLConnectionURL url = new URL("jar:file:" + file.getAbsolutePath() + "!/");URLConnection urlConnection = url.openConnection();JarURLConnection jarURLConnection = (JarURLConnection)urlConnection;// 获取jar文件JarFile jarFile = jarURLConnection.getJarFile();Enumeration<JarEntry> entries = jarFile.entries();// 创建自定义类加载器,并加到map中方便管理TestClassLoader myClassloader = new TestClassLoader(new URL[] { url }, ClassLoader.getSystemClassLoader());myClassLoaderCenter.put(fileName, myClassloader);// 遍历文件while (entries.hasMoreElements()) {JarEntry jarEntry = entries.nextElement();if (jarEntry.getName().endsWith(".class")) {// 1. 加载类到jvm中// 获取类的全路径名String className = jarEntry.getName().replace('/', '.').substring(0, jarEntry.getName().length() - 6);// 1.1进行反射获取myClassloader.loadClass(className);}}Map<String, Class<?>> loadedClasses = myClassloader.getLoadedClasses();for(Map.Entry<String, Class<?>> entry : loadedClasses.entrySet()){String className = entry.getKey();Class<?> clazz = entry.getValue();// 此处beanName使用全路径名是为了防止beanName重复String packageName = className.substring(0, className.lastIndexOf(".") + 1);String beanName = className.substring(className.lastIndexOf(".") + 1);beanName = packageName + beanName.substring(0, 1).toLowerCase() + beanName.substring(1);// 2. 将有@spring注解的类交给spring管理// 2.1 判断类的类型String flag = hasSpringAnnotation(clazz);if(!flag.equals("pass")){// 2.2交给spring管理BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();// 2.3注册到spring的beanFactory中beanFactory.registerBeanDefinition(beanName, beanDefinition);// 2.4允许注入和反向注入beanFactory.autowireBean(clazz);beanFactory.initializeBean(clazz, beanName);// 2.5手动构建实例,并注入base service 防止卸载之后不再生成Object obj = clazz.newInstance();beanFactory.registerSingleton(beanName, obj);//3.特殊处理//3.1不同的spring核心类不同的处理,实例中只是写了contrllerhandle(flag,beanName);}}} catch (IOException e) {e.printStackTrace();throw new RuntimeException("读取jar文件异常: " + fileName);}}/*** 判断一个类是具体类型,如果是spring核心类需要交给spring管理* @param clazz 要检查的类* @return string 如果该类上添加了相应的 Spring 注解返回对应标识;否则返回 pass*/private  String hasSpringAnnotation(Class<?> clazz) {if (clazz == null) {return "pass";}//是否是接口if (clazz.isInterface()) {return "pass";}//是否是抽象类if (Modifier.isAbstract(clazz.getModifiers())) {return "pass";}//常规注解效验和处理try {if (clazz.getAnnotation(Component.class) != null ) {return "Component";}if (clazz.getAnnotation(Repository.class) != null) {return "Repository";}if (clazz.getAnnotation(Service.class) != null ) {return "Service";}if (clazz.getAnnotation(Configuration.class) != null ) {return "Configuration";}if (clazz.getAnnotation(Controller.class) != null || clazz.getAnnotation(RestController.class) != null) {return "Controller";}}catch (Exception e){e.printStackTrace();}return "pass";}/*** 处理类* @param type 类型标识* @param name bean名称*/private  void handle(String type ,String name){//这里只做了contrller类型标识的处理if(type.equals("Controller")){RequestMappingHandlerMapping handlerMapping = applicationContext.getBean(RequestMappingHandlerMapping.class);// 注册ControllerMethod method = null;try {method = handlerMapping.getClass().getSuperclass().getSuperclass().getDeclaredMethod("detectHandlerMethods", Object.class);} catch (NoSuchMethodException e) {e.printStackTrace();}// 将private改为可使用assert method != null;method.setAccessible(true);try {method.invoke(handlerMapping, name);} catch (IllegalAccessException | InvocationTargetException e) {e.printStackTrace();}}}/*** 动态卸载* @param name  卸载jar的名称*/public void unloadJar(String name) throws IllegalAccessException, NoSuchFieldException {// 获取加载当前jar的类加载器TestClassLoader myClassLoader = myClassLoaderCenter.get(name);// 获取beanFactory,准备从spring中卸载DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();Map<String, Class<?>> loadedClasses = myClassLoader.getLoadedClasses();Set<String> beanNames = new HashSet<>();for (Map.Entry<String, Class<?>> entry: loadedClasses.entrySet()) {// 截取beanNameString key = entry.getKey();String packageName = key.substring(0, key.lastIndexOf(".") + 1);String beanName = key.substring(key.lastIndexOf(".") + 1);beanName = packageName + beanName.substring(0, 1).toLowerCase() + beanName.substring(1);// 获取bean,如果获取失败,表名这个类没有加到spring容器中,则跳出本次循环Object bean = null;try{bean = applicationContext.getBean(beanName);}catch (Exception e){// 异常说明spring中没有这个beancontinue;}// 从spring中移除,这里的移除是仅仅移除的bean,并未移除bean定义beanNames.add(beanName);beanFactory.destroyBean(beanName, bean);}// 移除bean定义Field mergedBeanDefinitions = beanFactory.getClass().getSuperclass().getSuperclass().getDeclaredField("mergedBeanDefinitions");mergedBeanDefinitions.setAccessible(true);Map<String, RootBeanDefinition> rootBeanDefinitionMap = ((Map<String, RootBeanDefinition>) mergedBeanDefinitions.get(beanFactory));for (String beanName : beanNames) {beanFactory.removeBeanDefinition(beanName);// 父类bean定义去除rootBeanDefinitionMap.remove(beanName);}// 从类加载中移除try {// 从类加载器底层的classes中移除连接Field field = ClassLoader.class.getDeclaredField("classes");field.setAccessible(true);Vector<Class<?>> classes = (Vector<Class<?>>) field.get(myClassLoader);classes.removeAllElements();// 移除类加载器的引用myClassLoaderCenter.remove(name);// 卸载类加载器myClassLoader.unload();} catch (NoSuchFieldException | IllegalAccessException e) {e.printStackTrace();}}}
java"><?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.dl</groupId><artifactId>li</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><name>Spring Boot Blank Project (from https://github.com/making/spring-boot-blank)</name><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.12</version></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><start-class>com.dl.App</start-class><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.8</source><target>1.8</target></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.6.0</version></plugin></plugins></build></project>

3.测试

①启动项目
在这里插入图片描述

②测试url
在这里插入图片描述

③没有加载jar前
在这里插入图片描述

④加载jar
在这里插入图片描述

⑤加载后验证
在这里插入图片描述

⑥卸载jar
在这里插入图片描述

⑦验证
在这里插入图片描述


http://www.ppmy.cn/embedded/26351.html

相关文章

处理分支更新与pull操作

处理分支更新与pull操作 问题描述 There is no tracking information for the current branch. Please specify which branch you want to merge with. See git-pull(1) for details.git pull <remote> <branch> If you wish to set tracking information for th…

SpringCloudStream 3.x rabbit 使用

1. 前言 今天带来的是SpringCloudStream 3.x 的新玩法&#xff0c;通过四大函数式接口的方式进行数据的发送和监听。本文将通过 rabbitMQ 的方式进行演示 3.x版本后是 可以看到 StreamListener 和 EnableBinding 都打上了Deprecated 注解。后续的版本更新中会逐渐替换成函数式…

网络原理

端口号 IP地址&#xff08;确定主机&#xff09;端口号&#xff08;在机上的应用程序&#xff09; 网络层提供的概念 传输层提供的概念 一个字节&#xff1a; 有符号-128>127 无符号0>255 两个字节&#xff1a; 有符号&#xff1a;-3…

神经网络基础(Neural net foundations)

Today we’ll be learning about the mathematical foundations of deep learning: Stochastic gradient descent (SGD), and the flexibility of linear functions layered with non-linear activation functions. We’ll be focussing particularly on a popular combination…

基于H.264的RTP打包中的组合封包以及分片封包结构图简介及抓包分析;FU-A FU-B STAP-A STAP-B简介;

H.264视频流的RTP封装类型分析&#xff1a; 前言&#xff1a; 1.RTP打包原则&#xff1a; RTP的包长度必须要小于MTU(最大传输单元)&#xff0c;IP协议中MTU的最大长度为1500字节。除去IP报头&#xff08;20字节&#xff09;、UDP报头&#xff08;8字节&#xff09;、RTP头&a…

大语言模型在人类层面预测未来的研究与应用

概述 这项研究将探讨语言模型&#xff08;LM&#xff09;能否预测未来事件。在这项研究中&#xff0c;将开发一个系统来自动收集信息、生成和汇总预测结果。将从一个竞争性预测平台收集有关问题的数据&#xff0c;以评估 LM 的预测能力。结果表明&#xff0c;LM 可以与具有竞争…

C++性能优化实践 三

C性能优化实践 三 文章目录 一、C标准库线程间的通信二、内存屏障、获得与释放语义三、多线程优化总结 书接上回, 这篇文章继续来谈谈C 并发编程性能优化相关的内容。 参考文章: hhttps://boolan.com/ 先形象的说明一下并发与并行: 并发&#xff1a;类似与足球场踢足球, 大家为…

Python Json数据解析

本篇主要介绍Python 中json数据如何解析、转化、保存和加载。 目录 Json数据解析 概念 作用 Json文件组成 Json转化为Python数据类型 Python数据类型转化为json 写入本地json文件 读取本地的json文件 总结 Json数据解析 概念 一种保存数据的格式 作用 可以保存本地…