public void backgroundProcess() {
// 如果reloadble=true而且有.class文件被修改过,就会重启上下文件。
if (reloadable && modified()) {
try {
Thread.currentThread().setContextClassLoader
(WebappLoader.class.getClassLoader());
if (context != null) {
context.reload();
} finally {
if (context != null && context.getLoader() != null) {
Thread.currentThread().setContextClassLoader
(context.getLoader().getClassLoader());
这个后台线程扫.class文件,如果有修改就使用WebappClassloader去动态加载被修改过的class到JVM中:context.reload()重启上下文。
我们现在有一个A类,一个自定义的WebappClassloader类(打破了双亲委派,尝试自己加载),一开始先用一个WebappClassloader实例加载A类,那么在jvm中就会存在一个A类的class对象,然后进行热加载,先停止上下文,再启动,在停止的时候会杀掉当前应用的所有线程(除开真正执行代码的线程),再启动时又会生成一个WebappClassloader实例来加载A类并缓存起来,如果热加载之前的那个A类的class对象还没有被回收的话,那么此时jvm中其实会存在两个A类的class对象,这是不冲突,因为class对象的唯一标志是类加载器实例对象+类的全限定名。
重启上下文之后,新的servlet对象会被缓存起来,每当有新的请求,会使用这些新的对象。
Jrebel热更新插件
启动远程服务器的热更新,配置好之后,本地代码更改的同时,远程服务器也会同步更改,并热替换更改后的class:https://www.cnblogs.com/sfnz/p/14157833.html
SPI机制
https://www.jianshu.com/p/3a3edbcd8f24
https://www.cnblogs.com/chanshuyi/p/deep_insight_java_spi.html
JDBC、Dubbo等的实现都是基于SPI的机制实现的
服务的提供者会在自己服务的文件夹META-INF/services下添加服务接口命名的文件,内容是实现类全限定名,并且在实现类的静态代码块中向服务管理器中注册(放入缓存),其中需要服务发现的项目,主动调用ServiceLoader的load(一般在静态代码块中使用),将各实现类通过遍历加载到内存时会触发静态代码块的注册,将实现类放入缓存,使用的地方遍历该缓存结构即可。
SPI的热替换:每次调用ServiceLoader的load都会重新读取文件里的内容,使用新的线程上下文类加载器加载实现类,这样就实现了热替换。
线程上下文类加载器的由来(https://www.jianshu.com/p/52dbe782ab8a)
Java 提供了很多SPI,允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。
ThreadContextClassLoader;
这些 SPI 的接口由 Java 核心库来提供,而这些 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)里。SPI接口中的代码经常需要加载具体的实现类。那么问题来了,SPI的接口是Java核心库的一部分,是由**启动类加载器(Bootstrap Classloader)来加载的;SPI的实现类是由系统类加载器(System ClassLoader 也叫 AppClassLoader)**来加载的。引导类加载器是无法找到 SPI 的实现类的,因为依照双亲委派模型,BootstrapClassloader无法委派AppClassLoader来加载类。这时就可以使用线程上下文加载器(在Launch中设置了AppClassLoader)来加载实现类,这样破坏了一部分双亲委派原则(如果一个类引用了另外一个类,那么这两个类的类加载器就必须是一样的。)
注:现在jdbc2.0开始用DataSource来替代DriverManager,如果使用了Druid连接池,那么在Spring中需要配置xml,如果是Springboot那么需要在配置文件中配置spring:datasource:url和username和password,然后Spring初始化容器时,会注入DruidDataSource类,初始化的时候会调用ServiceLoader启动spi机制。
实现力扣、牛客等在线判题系统
将字符串通过SimpleJavaFileObject动态编译成字节码(字节数组)
修改字节码中的静态常量依赖如Scanner/System路径(将标准输入输出缓存)
new一个自定义类加载器,加载该字节码,生成Class对象
通过反射调用Class对象的main方法
返回标准输出中的内容给客户端
通过cglib动态增加实体类的字段
public class ReflectUtil {
private static Logger logger = LoggerFactory.getLogger(ReflectUtil.class);
public static Object getTarget(Object dest, Map<String, Object> addProperties) {
PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean();
PropertyDescriptor[] descriptors = propertyUtilsBean.getPropertyDescriptors(dest);
Map<String, Class> propertyMap = new HashMap<>();
for (PropertyDescriptor d : descriptors) {
if (!"class".equalsIgnoreCase(d.getName())) {
propertyMap.put(d.getName(), d.getPropertyType());
addProperties.forEach((k, v) -> {
String sclass = v.getClass().toString();
if (sclass.equals("class java.util.Date")) {//对日期进行处理
propertyMap.put(k, Long.class);
} else {
propertyMap.put(k, v.getClass());
DynamicBean dynamicBean = new DynamicBean(dest.getClass(), propertyMap);
propertyMap.forEach((k, v) -> {
try {
if (!addProperties.containsKey(k)) {
dynamicBean.setValue(k, propertyUtilsBean.getNestedProperty(dest, k));
} catch (Exception e) {
logger.error("动态添加字段出错", e);
addProperties.forEach((k, v) -> {
try {
String sclass = v.getClass().toString();
if (sclass.equals("class java.util.Date")) {//动态添加的字段为date类型需要进行处理
Date date = (Date) v;
dynamicBean.setValue(k, date.getTime());
} else {
dynamicBean.setValue(k, v);
} catch (Exception e) {
logger.error("动态添加字段值出错", e);
Object obj = dynamicBean.getTarget();
return obj;
class DynamicBean {
private Object target;
private BeanMap beanMap;
public DynamicBean(Class superclass, Map<String, Class> propertyMap) {
this.target = generateBean(superclass, propertyMap);
this.beanMap = BeanMap.create(this.target);
public void setValue(String property, Object value) {
beanMap.put(property, value);
public Object getValue(String property) {
return beanMap.get(property);
public Object getTarget() {
return this.target;
* 根据属性生成对象
private Object generateBean(Class superclass, Map<String, Class> propertyMap) {
BeanGenerator generator = new BeanGenerator();
if (null != superclass) {
generator.setSuperclass(superclass);
BeanGenerator.addProperties(generator, propertyMap);
return generator.create();
在云原生时代,热替换的作用越来越弱了,但也并不意味着这项技术彻底失去了意义,如果你有使用到的场景,请在评论区留言吧