jrebel 热部署
jrebel在本地是可以class xml一起热部署,但是远程热部署xml不行,所以用arthas代替去热部署xml
1.jrebel 反向代理
因为jrebel是收费插件,所以要高一些小动作咱们才能‘正常’使用,当然你也可以拿别人代理好的操作,但别人跑路了你就又要找,索性自己搞吧
上面那个是linux,下面是win10启动自己选
win下面双击,linux的按下面命令启动
./ReverseProxy_linux_amd64 &
#默认代理8888端口,可以运行一下命令指定端口:
./ReverseProxy_linux_amd64 -l "ip:port" &
服务器安装JRebel并激活
根据的idea下载对应版本 https://www.jrebel.com/products/jrebel/download/prev-releases
我用的是idea2020.3.3,下面没有,只有2020.3.2,要是没你的idea版本,就选个近似的吧
centos7激活
emmmm,这个ip:port/guid,见下面方法,和idea一样
./activate.sh http://ip:port/guid 你的邮箱地址
2.idea插件
安装这两个插件,重启idea
点击 右边 change
ip:端口,ip就看你怎么反向代理的时候开的了,端口默认8888,后面的验证码可以在下面网站获取,下面那个邮箱随便写
https://www.guidgen.com/
有多个选择,就点一下这个,然后界面就剩它一个了,防止Jrebel本地热更新失效,开启Jrebel离线模式
激活成功
3.jrebel使用
现在界面就有这两个热启动按钮了
启动配置选这个
这个就不用重启部署了,这个按钮刷新项目快捷键Ctrl+shift+F9
4.jrebel远程热更新
远程服务器上安装JRebel
# 下载安装包
curl -O http://dl.zeroturnaround.com/jrebel-stable-nosetup.zip
# 解压下载文件
unzip jrebel-stable-nosetup.zip
# 将自己的激活URL和邮箱地址替换下面的指令参数 url就是ip 不用端口
bash jrebel/bin/activate.sh url email
# 为了安全起见,防止其他人通过远程连接到JRebel篡改程序,我们为JRebel设置一个密码。
java -jar jrebel/jrebel.jar -set-remote-password 12345678
生成启动指令
根据你自己实际情况,下面那个绿的java就是要去centos7执行的命令,紫色参数根据你自己情况修改
这里有两个地方需要修改,一个是-agentpath:就是刚刚在服务器上安装的JRebel目录,另外一个就是自己的jar包名称
java "-agentpath:/root/www/jrebel/lib/libjrebel64.so" -Drebel.remoting_plugin=true -jar demo-0.0.1-SNAPSHOT.jar
然后在远程服务器上执行命令启动服务,启动成功,控制台会输出JRebel的信息
每次要远程热更新就点击这个
右下角出现这个就是成功了,请求接口看看是不是成功没,对xml无效
5.jrebel 远程调试
添加远程调试
ok,在服务器上执行这个命令,按实际自己拼接
java "-agentpath:/root/www/jrebel/lib/libjrebel64.so" -Drebel.remoting_plugin=true -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar demo-0.0.1-SNAPSHOT.jar
成功了,打断点调试吧
arthas 热部署
centos7 安装java
yum安装的java-1.8.0-openjdk 没有 javac ,需要安装 devel 版本。只能先卸载yum安装的jdk,重新安装devel 版本的jdk了。
没javac 用不了arthas
#查看目前系统中的jdk
rpm -qa | grep jdk
#yum卸载 将上面全部删除,删完再次查询,删到没有为止
yum -y remove xxx
yum install -y java-1.8.0-openjdk-devel.x86_64
1.idea操作
项目中随便地方,两个文件复制进去
package com.gao.common;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
public ApplicationContext getApplicationContext() {
return context;
}
@Override
public void setApplicationContext(ApplicationContext ctx) {
context = ctx;
}
}
package com.gao.common;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* mapper xml 文件重新加载
*
* @author 汪小哥
* @date 01-05-2021
*/
@Slf4j
@Component
public class MybatisMapperXmlFileReloadService {
private List<SqlSessionFactory> sqlSessionFactoryList;
public MybatisMapperXmlFileReloadService(List<SqlSessionFactory> sqlSessionFactoryList) {
this.sqlSessionFactoryList = sqlSessionFactoryList;
}
/**
* 重新 加载mapper xml 【这里可以使用arthas 进行调用远程增强】
* eg ognl -x 3 '#springContext=@com.wangji92.arthas.plugin.demo.common.ApplicationContextProvider@context,#springContext.getBean("mybatisMapperXmlFileReloadService").reloadAllSqlSessionFactoryMapper("/root/xxx.xml")' -c xxx
*
* @param mapperFilePath
* @return
*/
public boolean reloadAllSqlSessionFactoryMapper(String mapperFilePath) {
if (CollectionUtils.isEmpty(sqlSessionFactoryList)) {
log.warn("not find SqlSessionFactory bean");
return false;
}
Path path = Paths.get(mapperFilePath);
if (!Files.exists(path)) {
log.warn("mybatis reload mapper xml not exist ={}", mapperFilePath);
return false;
}
AtomicBoolean result = new AtomicBoolean(true);
// 删除mapper 缓存 重新加载
sqlSessionFactoryList.parallelStream().forEach(sqlSessionFactory -> {
Configuration configuration = sqlSessionFactory.getConfiguration();
if (!this.removeMapperCacheAndReloadNewMapperFile(path, configuration)) {
log.warn("reload new mapper file fail {}", path.toString());
result.set(false);
}
});
return result.get();
}
private Object readField(Object target, String name) {
Field field = ReflectionUtils.findField(target.getClass(), name);
ReflectionUtils.makeAccessible(field);
return ReflectionUtils.getField(field, target);
}
/**
* 删除老的mapper 缓存 加载新的mapper 文件
*
* @param watchPath
* @param configuration
* @return
*/
private boolean removeMapperCacheAndReloadNewMapperFile(Path watchPath, Configuration configuration) {
try (InputStream fileInputStream = Files.newInputStream(watchPath)) {
XPathParser context = new XPathParser(fileInputStream, true, configuration.getVariables(), new XMLMapperEntityResolver());
XNode contextNode = context.evalNode("/mapper");
if (null == contextNode) {
return false;
}
String namespace = contextNode.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
this.removeOldMapperFileConfigCache(configuration, contextNode, namespace);
this.addNewMapperFile(configuration, watchPath, namespace);
return true;
} catch (Exception e) {
log.warn("load fail {}", watchPath.toString(), e);
}
return false;
}
/**
* 删除老的mapper 相关的配置文件
*
* @param configuration
* @param mapper
* @param namespace
* @see XMLMapperBuilder#configurationElement
*/
private void removeOldMapperFileConfigCache(Configuration configuration, XNode mapper, String namespace) {
String xmlResource = namespace.replace('.', '/') + ".xml";
((Set<?>) this.readField(configuration, "loadedResources")).remove(xmlResource);
for (XNode node : mapper.evalNodes("parameterMap")) {
String parameterMapId = this.resolveId(namespace, node.getStringAttribute("id"));
((Map<?, ?>) this.readField(configuration, "parameterMaps")).remove(parameterMapId);
}
for (XNode node : mapper.evalNodes("resultMap")) {
String resultMapId = this.resolveId(namespace, node.getStringAttribute("id"));
((Map<?, ?>) this.readField(configuration, "resultMaps")).remove(resultMapId);
}
for (XNode node : mapper.evalNodes("sql")) {
String sqlId = this.resolveId(namespace, node.getStringAttribute("id"));
((Map<?, ?>) this.readField(configuration, "sqlFragments")).remove(sqlId);
}
for (XNode node : mapper.evalNodes("select|insert|update|delete")) {
String statementId = this.resolveId(namespace, node.getStringAttribute("id"));
((Map<?, ?>) this.readField(configuration, "mappedStatements")).remove(statementId);
}
}
/**
* 加载新的mapper 文件
*
* @param configuration
* @param watchPath
* @param namespace
*/
private void addNewMapperFile(Configuration configuration, Path watchPath, String namespace) throws IOException {
try (InputStream fileInputStream = Files.newInputStream(watchPath)) {
String xmlResource = namespace.replace('.', '/') + ".xml";
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(fileInputStream, configuration,
xmlResource,
configuration.getSqlFragments());
xmlMapperBuilder.parse();
}
}
private String resolveId(String namespace, String id) {
return namespace + "." + id;
}
}
安装插件
修改你自己的包
我选的阿里云oss的,会自动上传拉取,热更新,自己去oss申请配置吧
要热部署的xml选择
出现这个,插件自动帮你复制好了,直接去服务器上执行这个命令就可以热部署了
2.服务器上操作
服务器上执行以下命令,不然会失败
yum install telnet
yum install --assumeyes unzip
#查看当前用户是什么
who am i
#我的是root,ah.sh见下图
sudo su root && ./as.sh
#等下从阿里oss拉取的xml,会自动放在/root/opt/arthas下面,所以给/root 读写权限
sudo chmod -R 777 /root
#然后执行上面idea 复制的命令
oss拉取的xml,下面全部都是
执行idea复制的ml,中途要 -l回车 再执行下面
成功替换(ps 注意 重启项目热更新的全部失效!!!!!!!!包括jerbel的)