1.被加载的jar代码
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);
}
}
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";
}
}
<?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.实现代码
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);
}
}
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;
@Autowired
public 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 "卸载成功";
}
}
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);
}
//加载
@Override
protected 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();
}
}
}
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 {
@Autowired
private 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);
// 获取beanFactory
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
try {
//创建URLConnection
URL 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核心类不同的处理,实例中只是写了contrller
handle(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);
// 注册Controller
Method 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()) {
// 截取beanName
String 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中没有这个bean
continue;
}
// 从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();
}
}
}
<?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
⑦验证