ClassLoader热加载的简单实现

Posted caoxb

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ClassLoader热加载的简单实现相关的知识,希望对你有一定的参考价值。

当我们在eclipse中修改了一个.java文件时,并通过【ctrl + s 】保存了此java文件,相应的bin目录中,会发现.class文件也发生了修改。通常情况下,java文件是在我们的web项目已经启动了的情况下进行修改的,而.class文件早已加载至虚拟机中。因 此,在没有使用热部署插件的情况下,必须重启tomcat服务。而热部署插件其原理就是将修改后的.class文件重新加载至jvm中的。

 

 

 

public class Test {


    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        ClassLoader classLoader = Test.class.getClassLoader();
        Class<?> clazz = classLoader.loadClass("com.classloader.Test");
        Test test = (com.classloader.Test) clazz.newInstance();
        test.logic();
    }
    
    public void logic() {
        System.out.println("hello classloader");   
    }
}

 

 

1.自定义一个MyClassLoader 类

public class MyClassLoader extends ClassLoader {
    
    private static final String CLASS_PATH = System.getProperty("java.class.path");    // 编译生成的.class文件的bin目录
    
    public MyClassLoader() {
        super(ClassLoader.getSystemClassLoader());
    }
    
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        byte[] b = loadClassFile(className);
        return super.defineClass(className, b, 0, b.length);
    }

    private byte[] loadClassFile(String className) {
        File file = new File(CLASS_PATH + "/" + className + ".class");
        try {
            FileInputStream fis = new FileInputStream(file);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;
            while ( (b=fis.read())!=-1 ) {
                baos.write(b);
            }
            fis.close();
            return baos.toByteArray();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        return null;
    }
    
}

 

 

2.创建一个工厂类

public class ObjectFactory {

	private static final String CLASS_PATH = System.getProperty("java.class.path");	// 编译生成的.class文件的bin目录
	private static Map<String, Object> map = new HashMap<String, Object>();
	private static long lastModified;	// 最后修改时间
	
	private ObjectFactory() {
		super();
	}
	
	public static Object getInstance(String className) {
		File loadFile = new File(CLASS_PATH + "/" + className.replace(".", "/") + ".class");	// 打开项目中bin目录下的*.class文件
		long newModified = loadFile.lastModified();
		// 文件第一次加载,通过反射的方式创建一个对象
		if (map.get(className)==null) {
			loadClass(className);
		}
		// .class 文件被修改过,通过ClassLoader方式 创建一个对象
		if (lastModified!=newModified) {
			loadClass(className);
		}
		lastModified = newModified;
		return map.get(className);
	}
	
	private static void loadClass(String className) {
		MyClassLoader myClassLoader = new MyClassLoader();
		try {
			Class<?> clazz = myClassLoader.findClass(className);
			Object object = clazz.newInstance();
			map.put(className, object);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

  

 

3.定义一个接口,供方法调用【注:这里必须要定义一个接口类,否则会抛出类型转换错误】

public interface PrintService {

    public void print();
}

 

4.接口的实现类

public class PrintServiceImpl implements PrintService {

    @Override
    public void print() {
        System.out.println("测试一下 bbb 1111111111");
    }
}

 

 

5.编写一个用于观察的线程类

public class PrintThread implements Runnable {

    @Override
    public void run() {
        String className = PrintServiceImpl.class.getName();
        // 一直不断地向控制台输出信息,方便测试“当修改print 中的方法时” 输出信息是否发生变化
        while (true) {
            PrintService printService = (PrintService) ObjectFactory.getInstance(className);
            printService.print();
            
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

 

 

public class Test {

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Thread thread = new Thread(new PrintThread());
        thread.start();
    }
}

 

以上是关于ClassLoader热加载的简单实现的主要内容,如果未能解决你的问题,请参考以下文章

Android 逆向类加载器 ClassLoader ( 类加载器源码简介 | BaseDexClassLoader | DexClassLoader | PathClassLoader )(代码片段

SpringBoot : 利用devtools实现热部署,改动代码自动生效

Java类加载器(ClassLoader)

springboot项目利用devtools实现热部署,改动代码自动生效

IntelliJ IDEA Spring boot实现热部署

从Java的类加载机制谈起:聊聊Java中如何实现热部署(热加载)