一、业务场景
在有些业务场景下,需要SpringBoot来动态加载jar中的class文件,自动往spring容器中添加新的bean;如物联网设备上传的信息用物模型来解析,用java来解析物模型,但用户的设备千差万别,解析设备的物模型不可能包罗万象,设备往物联网平台上传的数据格式也是千差万别,这时就可以让用户自已实现解析物模型的java代码,然后打成jar包,上传到物联网平台,物联网平台就可以解析当前用的设备信息了。
二、准备工作
1、生成的jar需要布署在nginx当中,直接通过url地址可以下载,所以本次使用要用到nginx,nginx的下载地址,放在本博文最后,可以在后面查看下载
在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代码结构
说明:
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,如下截图
2、加载jar包 http://localhost:8080/test/load?urlStr=http://127.0.0.1:5000/user-1.0.jar
3、再次执行 http://localhost:8080/test/execute?serviceName=teacherService