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(); } }