前言
对于其他解释性语言来说,热更新根本不是什么事,但对于Java来说是多么的不容易,现在使用Java开发的热更新系统,基本使用JS编写脚本,然后用Java的JavaScript引擎来跑脚本。
spring-hot-plugin
现在有一款开源的Spring 热更新插件开发框架spring-hot-plugin,插件
- 支持编写各类Controller控制器,跟在Spring Boot 中写接口一样
- 支持热加载普通类、各类Spring Bean,在插件里面写各类@Service、@Component
- 支持热加载定时任务,在插件里面编写@Scheduled定时任务
- 支持插件中使用第三方依赖,在插件中使用JNI加载dll,或者使用第三方jar依赖
- 支持Mybatis、MybatisPlus,在插件中编写@Mapper,控制数据库
- 提供主程序监听插件启动卸载事件,用于在主程序中跟随插件生命周期控制相关任务
下面演示一下支持的各个特性
Controller控制器
@Slf4j
@RestController
public class IndexController {
@Resource
private TestService testService;
@GetMapping({"/hello"})
public String hello() {
return "Hello itsaysay!";
}
@GetMapping({"/hello/name"})
public Person helloName() {
Person person = new Person();
person.setName("Human");
String json = JSON.toJSONString(person);
log.info(json);
return person;
}
}
各类@Service、@Component
@Service
public class TestServiceImpl implements TestService {
@Resource
private TestMapper testMapper;
@Resource
private TestMapper2 testMapper2;
@Override
public User getUser(Integer id) {
return testMapper.selectById(id);
}
@Override
public User getUserXml(Integer id) {
return testMapper.selectByIdXml(id);
}
}
@Scheduled定时任务
@Slf4j
@Component
public class TaskDemo {
@Scheduled(cron = "0/1 * * * * ?")
public void task() {
log.info("task starting");
}
}
定时任务运行
使用JNI加载dll
@GetMapping("/hik/sdk/init")
public Boolean hikSDKInit() {
//海康的SDK dll依赖了其他的dll,需要指定目录,放插件包没用
//只有一个dll的sdk,不需要,可以放在插件包内
//这是java native的限制
HCNetSDK hCNetSDK = (HCNetSDK) Native.loadLibrary("HCNetSDK.dll", HCNetSDK.class);
if (hCNetSDK == null) {
return false;
}
return true;
}
使用第三方Jar包
pom文件中增加:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.39</version>
</dependency>
在Controller 中使用第三方依赖包中的类
@GetMapping({"/hello/name"})
public Person helloName() {
Person person = new Person();
person.setName("Human");
String json = JSON.toJSONString(person);
log.info(json);
return person;
}
fastJson 加载后使用
编写@Mapper
这里我引入MP来使用数据库
@Mapper
public interface TestMapper extends BaseMapper<User> {
User selectByIdXml(@Param("id") Integer id);
}
编写xml,xml要放在跟Mapper类一起
如果放到resources中,路径要跟Mapper类一样
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="csdn.itsaysay.demo.plugin.mapper.TestMapper">
<select id="selectByIdXml" resultType="csdn.itsaysay.demo.plugin.bean.User">
select * from usertb where id = #{id}
</select>
</mapper>
在Controller 中增加接口
@GetMapping("/mp/getUser/{id}")
public User getUser(@PathVariable("id") Integer id) {
return testService.getUser(id);
}
@GetMapping("/mp/getUserXml/{id}")
public User getUserXml(@PathVariable("id") Integer id) {
return testService.getUserXml(id);
}
编写Service
@Service
public class TestServiceImpl implements TestService {
@Resource
private TestMapper testMapper;
@Resource
private TestMapper2 testMapper2;
@Override
public User getUser(Integer id) {
return testMapper.selectById(id);
}
@Override
public User getUserXml(Integer id) {
return testMapper.selectByIdXml(id);
}
}
MP调用:
调用XML中的 id
插件事件监听
在主程序中编写如下代码:
/**
* 插件事件监听
* @author jujun.chen
*/
@Slf4j
@Component
public class PluginListener implements vip.aliali.spring.plugin.listener.PluginListener {
@Override
public void startSuccess(PluginInfo pluginInfo) {
log.info("{}--->启动成功", pluginInfo.getId());
}
@Override
public void startFailure(PluginInfo pluginInfo, Throwable throwable) {
log.info("{}--->启动失败", pluginInfo.getId());
}
@Override
public void stopSuccess(PluginInfo pluginInfo) {
log.info("{}--->停止成功", pluginInfo.getId());
}
@Override
public void stopFailure(PluginInfo pluginInfo, Throwable throwable) {
log.info("{}--->停止失败", pluginInfo.getId());
}
}
通过调用安装、卸载插件接口,控制台打印相应日志
项目免不掉一些Bug,需要不断迭代更新
项目开源地址:https://github.com/jujunchen/spring-hot-plugin