天天看点

Java之——类热加载

一、问题阐述

有时候我们会遇到热加载的需求,即在修改了类的代码后,不重启的情况下动态的加载这个类

看到这个问题,我想到有两种方式来实现:(能力有限,不知道还有没有其他的方案)

1:把原来的类信息卸载掉,然后重新加载此类。

2:新建一个类加载器(new),重新加载此类,不管原来的类信息,等待垃圾回收它。

第一种方案是行不通的,因为java并没有给我们提供手动卸载类信息的功能,也就是运行时方法区内的类信息是不能卸载的,除非这个类已经不再使用,这时GC会自动把它回收掉。所以我们只能通过第二种方案来实现。

几个问题

在使用这种方案实现之前我们考虑几个问题,注意:下面的问题是在这种方案下提出的,也在这种方案下回答,不适用与其他方案。

1:是不是所有的类都能进行热加载呢?

我们程序的入口类都是系统类加载器加载的,也就是AppClassLoader加载的。当你重新使用系统类加载器加载这个类的时候是不会被重新加载的。因为虚拟机会检测这个类是否被加载过,如果已经被加载过,那么就不会重新加载。所以由系统类加载器加载的类,是不能进行热加载的。只有使用我们自定义的类加载器加载的类才能热加载。

2:自定义类加载器的父加载器应该是哪个加载器?

我们自定义类加载器的父加载器有两种选择,一个是系统类加载器(AppClassLoader),另一种是扩展类加载器(ExtClassLoader)。首先我们要知道,扩展类加载器是系统类加载器的父加载器。我们先考虑第一种,如果父加载器是系统类加载器(当然如果你不指定父加载器,默认就是系统类加载器),那么会出现一个现象,这个动态加载的类不能出现在系统类加载器的classpath下。因为如果在系统类加载器的classpath下的话,当你用自定义类加载器去加载的时候,会先使用父类加载器去加载这个类,如果父类加载器可以加载这个类就直接加载了,达不到热加载的目的。所以我们必须把要热加载的类从classpath下删除。

在考虑第二种,如果父加载器是扩展类加载器,这时候热加载的类就可以出现在classpath下,但又会出现另一种现象,这个类中不能引用由系统类加载器加载的类。因为这时候,自定义类加载器和系统类加载器是兄弟关系,他们两个加载器加载的类是互不可见的。这个问题貌似是致命的。除非热加载的类中只引用了扩展类加载器加载的类(大部分javax开头的类)。所以我们自定义的类加载器的父加载器最好是系统类加载器。

3:热加载的类要不要实现接口?

要不要实现接口,要根据由系统类加载器加载的类A中是否有热加载类B的引用。如果有B的引用,那么加载A的时候,系统类加载器就会去加载B,这时候B不在classpath下,就会加载报错。这时候我们就需要为B定义一个接口,A类中只有接口的引用。这样我们使用系统类加载器加载接口,使用自定义类加载器加载B类,这样我们就可以热加载B类。如果A中没有B的引用的话,就灵活多了,可以实现接口,也可以不实现接口,都可以进行热加载。

解决了这三个问题后,实现代码就已经在我们心中了。下面直接看代码:。

二、源码

具体代码如下:

1、自定义类加载器MyClassLoader

package com.lyz.hot.classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;

/**
 * 自定义类加载器
 * @author liuyazhuang
 *
 */
public class MyClassLoader extends ClassLoader {
    private String classpath;

    public MyClassLoader(String classpath){
        super(ClassLoader.getSystemClassLoader());
        this.classpath = classpath;
    }

    @Override
    public Class<?> findClass(String name) {
        System.out.println("加载类==="+name);
        byte[] data = loadClassData(name);
        return this.defineClass(name, data, 0, data.length);
    }

    public byte[] loadClassData(String name) {
        try {
            name = name.replace(".", "//");
            FileInputStream is = new FileInputStream(new File(classpath + name + ".class"));
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;
            while ((b = is.read()) != -1) {
                baos.write(b);
            }
            is.close();
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}      

2、接口BaseManager

package com.lyz.hot.classloader;
/**
 * @author liuyazhuang
 * 此接口的子类需要动态更新
 */
public interface BaseManager {
    public void logic();
}      

3、LoadInfo

封装类的加载信息

package com.lyz.hot.classloader;

/**
 * 封装加载的类信息
 * @author liuyazhuang
 *
 */
public class LoadInfo {

  private MyClassLoader myLoader;
  private long loadTime;
  private BaseManager manager;

  public LoadInfo(MyClassLoader myLoader, long loadTime) {
    this.myLoader = myLoader;
    this.loadTime = loadTime;
  }

  public MyClassLoader getMyLoader() {
    return myLoader;
  }

  public void setMyLoader(MyClassLoader myLoader) {
    this.myLoader = myLoader;
  }

  public long getLoadTime() {
    return loadTime;
  }

  public void setLoadTime(long loadTime) {
    this.loadTime = loadTime;
  }

  public BaseManager getManager() {
    return manager;
  }

  public void setManager(BaseManager manager) {
    this.manager = manager;
  }

}      

4、Logic

package com.lyz.hot.classloader;

/**
 * 打印信息
 * @author liuyazhuang
 *
 */
public class Logic {
  public void logic() {
    System.out.println("logic");
  }

}      

5、动态加载Magager的工厂类ManagerFactory

package com.lyz.hot.classloader;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

/**
 * 动态加载Magager的工厂类
 * @author liuyazhuang
 *
 */
public class ManagerFactory {

  /**
   * 记录热加载类的加载信息
   */
  private static final Map<String, LoadInfo> loadTimeMap = new HashMap<String, LoadInfo>();

  public static final String CLASS_PATH = "D:/";

  public static final String MY_MANAGER = "com.lyz.hot.classloader.MyManager";

  public static BaseManager getManager(String className) {
    File loadFile = new File(CLASS_PATH + className.replaceAll("\\.", "/")
        + ".class");
    long lastModified = loadFile.lastModified();

    // 查看是否被加载过 ,如果没有被加载过则加载
    if (loadTimeMap.get(className) == null) {
      load(className, lastModified);
    } else if (loadTimeMap.get(className).getLoadTime() != lastModified) {// 如果被加载过,查看加载时间,如果该类已经被修改,则重新加载
      load(className, lastModified);
    }

    return loadTimeMap.get(className).getManager();
  }

  private static void load(String className, long lastModified) {

    MyClassLoader myLoader = new MyClassLoader(CLASS_PATH);
    Class<?> loadClass = null;
    try {
      loadClass = myLoader.loadClass(className);
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
    BaseManager manager = newInstance(loadClass);
    LoadInfo loadInfo2 = new LoadInfo(myLoader, lastModified);
    loadInfo2.setManager(manager);
    loadTimeMap.put(className, loadInfo2);
  }

  private static BaseManager newInstance(Class<?> cls) {
    try {
      return (BaseManager) cls.getConstructor(new Class[] {})
          .newInstance(new Object[] {});
    } catch (InstantiationException | IllegalAccessException
        | IllegalArgumentException | InvocationTargetException
        | NoSuchMethodException | SecurityException e) {
      e.printStackTrace();
    }
    return null;
  }
}      

6、开启线程不断刷新重新加载类MsgHandler

package com.lyz.hot.classloader;
/**
 * 
 * 开启线程不断刷新重新加载类
 * @author liuyazhuang
 *
 */
public class MsgHandler implements Runnable{

    @Override
    public void run() {
        while(true){
            BaseManager manager = ManagerFactory.getManager(ManagerFactory.MY_MANAGER);
            manager.logic();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        new Thread(new MsgHandler()).start();
    }

}      

7、热加载的类MyManager

package com.lyz.hot.classloader;

/**
 * 动态加载的类MyManager
 * @author liuyazhuang
 *
 */
public class MyManager implements BaseManager {

  @Override
  public void logic() {
    System.out.println("bbb");
    System.out.println("logic");
  }
}      

运行后,修改MyManager类后输出如下:

加载类===com.load.manager.MyManager
bbb
logic
bbb
logic
bbb
logic
bbb
logic
bbb
logic
加载类===com.load.manager.MyManager
aaaaaaaaaaaaaaaaaaaa
logic
aaaaaaaaaaaaaaaaaaaa
logic
aaaaaaaaaaaaaaaaaaaa
logic
aaaaaaaaaaaaaaaaaaaa
logic      

可见,修改后重新加载了MyManager。到此热加载成功。