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

news/2024/9/25 0:29:53/

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/news/1446623.html

相关文章

Leetcode—1041. 困于环中的机器人【中等】

2024每日刷题&#xff08;121&#xff09; Leetcode—1041. 困于环中的机器人 实现代码 class Solution { public:bool isRobotBounded(string instructions) {int x 0;int y 0;int d 0;vector<vector<int>> direction{{0, 1}, {1, 0}, {0, -1}, {-1, 0}};for…

Java基础知识总结(82)

ConcurrentHashMap JDK 1.7中的ConcurrentHashMap 原理&#xff1a; JDK 1.7的ConcurrentHashMap的锁机制基于粒度更小的分段锁&#xff0c;分段锁其实是一种锁的设计&#xff0c;并不是具体的一种锁&#xff0c;对于ConcurrentHashMap而言&#xff0c;分段锁技术将Key分成一…

常用网络知识点(网管网工需掌握)

前言 最近在学习网络方面的内容时&#xff0c;看到很多知识点重复出现&#xff0c;不管在任何地方都能利用&#xff0c;我也就加班整理出一份针对于网络管理员/网络工程师等从业网络方面的知识点汇总 有需要的可以点赞搜藏慢慢看&#xff01;后续还会更新常见配置命令 BGP BGP…

Copilot Workspace是GitHub对人工智能驱动的软件工程的诠释

软件开发的未来是人工智能驱动的集成开发环境吗&#xff1f;至少GitHub 是这样想的。 在今年初秋于旧金山举行的 GitHub Universe 年度大会之前&#xff0c;GitHub 发布了 Copilot Workspace&#xff0c;这是一种开发环境&#xff0c;利用 GitHub 所称的 “Copilot 驱动的代理…

鸿蒙准备1

鸿蒙心路 感慨索性&#xff0c; 看看鸿蒙吧。打开官网相关介绍 新建工程目录结构 感慨 最近面试Android应用开发&#xff0c;动不动就问framework的知识&#xff0c;什么touch事件的触发源是啥&#xff08;eventHub&#xff09;&#xff0c;gc流程是啥&#xff0c;图形框架是什…

记录PR学习查漏补缺

记录PR学习查漏补缺 常用快捷键文件编辑素材序列标记字幕窗口帮助 效果基本3D高斯模糊查找边缘色彩颜色平衡超级键马赛克中间值变形稳定器 常用 快捷键 注意&#xff1a;比较常用的用红色字体显示 文件 快捷键作用Ctrl Alt N新建项目Ctrl O打开项目Ctrl I导入Ctrl S保存…

笔记-用Python脚本启停JAR程序

用Python脚本启停JAR程序&#xff0c;需要用到python中的以下内置模块 subprocess 是 Python 的一个标准库模块&#xff0c;用于在新进程中执行子命令&#xff0c;获取子进程的输入/输出/错误以及返回码等os 是 Python 的一个标准库模块&#xff0c;它提供了与操作系统交互的功…

计算机视觉的应用29-卷积神经网络(CNN)中的变种:分组卷积、转置卷积、空洞卷积的计算过程

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下计算机视觉的应用29-卷积神经网络(CNN)中的变种&#xff1a;分组卷积、转置卷积、空洞卷积的计算过程。分组卷积将输入通道分为几组&#xff0c;对每组独立进行卷积操作&#xff0c;以减少计算量和模型参数。转置卷…