简单来讲,就是不重启一个项目,使得部分代码更新,原理是经过java类加载器实现的。因为类的加载缺乏监控使得安全性不能获得保证,通常使用在开发环境,加快开发效率。俗称开发者模式。实现自定义加载器前,先讲一讲JVM的类加载器。java
BootStrap ClassLoader又叫启动类加载器,是最顶级的父类加载器,加载最核心的类,,如java.lang.*、java.uti.*等; 这些类位于$JAVA_HOME/jre/lib/rt.jar;安全
Extension ClassLoader 又叫扩展类加载器,负责加载%JAVA_HOME%/jre/lib/ext/目录下的扩展包ide
Application ClassLoader又叫应用类加载器,负责加载classpath也就是环境变量下的类测试
工做原理:类加载采用双亲委派机制,简单来讲,一个应用被加载时首先判断是否被加载过,若无则进行加载,并判断是否有父类,如有则要求父类进行加载,到顶级父类时再根据包的位置进行加载,不知足则要求子类加载。this
好处:能够避免重复加载,也能够保证类加载的安全性,若本身写了个String包,那是否会替换jdk包中的String类呢?显然不可能,由于使用了这种机制来避免这种问题。spa
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先判断该类是否已经被加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//尝试让父类加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
//自定义加载,findClass是个空方法就是让咱们重写的
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
复制代码
JVM是如何判断一个类是否被加载过的呢?经过findLoadedClass(name),即 java虚拟机已将此加载器记录为具备给定二进制名称的某个类的启动加载器,则返回该二进制名称的类。不然,返回null。3d
首先新建一个类并继承ClassLoader。并重写findClass()方法。注释已写方法用处。code
public class MyClassLoader extends ClassLoader {
@Override
//经过名称找到具体class文件,并进行加载
protected Class<?> findClass(String name) {
byte[] b = loadClassData(name);
name = name.split("\\.")[1];
//而后经过defineClass()将二进制文件转化为Class对象
return super.defineClass(name, b, 0, b.length);
}
//这个方法就是将具体位置的class文件转化为二进制流
private byte[] loadClassData(String name) {
try {
name = name.replace(".", "\\");
FileInputStream fileInputStream = new FileInputStream(new File("D:\\IntelliJ IDEA 2019.1\\JavaHotFix\\" + name+".class"));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int i = 0;
while ((i = fileInputStream.read()) != -1) {
byteArrayOutputStream.write(i);
}
fileInputStream.close();
return byteArrayOutputStream.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
复制代码
} 随便定义一个对象并定义一个方法cdn
public class People {
public void hello() {
System.out.println("我是People");
}
}
复制代码
进行测试对象
public class Main {
public static void main(String[] args) {
while (true) {
try {
MyClassLoader myClassLoader = new MyClassLoader();
Class<?> clazz = myClassLoader.loadClass("src.People");
Object people = clazz.newInstance();
clazz.getMethod("hello").invoke(people);
Thread.sleep(2000);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
复制代码
启动后: 因为就在本级目录,先进行
小bug:因为一开始直接name直接写People致使反复不能热加载,后打断点才发现,这个加载器根本没使用到,原来限定名为People致使直接在classpath就被加载了,也就是被父类AppClassLoader加载了。