场景:

  • 本地调试(频繁的启动/停止服务器)

  • 线上发布(每次都需要启动/停止服务器)


优点:

  • 无论本地还是线上,都适用

  • 无需重启服务器,提高开发、调试效率;提升发布、运维效率,降低运维成本

BaiduShurufa_2018-6-17_14-0-33.jpg

一、java实现热部署有哪几种方式?

  • 通过配置tomcat,直接把项目放在webapps里。

  • 在tomcat\conf\server.xml中的<host></host>中添加<context debug="0" docBase="" path="" privileged="true" reloadable="true"/>标签

  • 在tomcat\conf\Catalina\localhost中添加xml,关键属性<context docBase="" reloadable="true" />


BaiduShurufa_2018-6-17_14-0-33.jpg

实现一个Java类热加载的实例

1.定义类加载器

//自定义类加载器,需实现findClass方法(核心类)
public class MyClassLoader extends ClassLoader {
	
	//定义加载的路径
	private String classPath;
	
	public MyClassLoader(String classPath) {
		//调用父类的加载器
		super(ClassLoader.getSystemClassLoader());
		this.classPath = classPath;
	}

	//1.重新findClass方法
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		byte[] b = this.loadData(name);
		return this.defineClass(name, b, 0, b.length);
	}

	//加载class文件中的内容
	private byte[] loadData(String name) {
		//.替换为//表示
		name = name.replaceAll(".", "//");
		try {
			FileInputStream is = new FileInputStream(new File(classPath + name + ".class"));
			ByteArrayOutputStream aos = new ByteArrayOutputStream();
			int b = 0;
			while((b = is.read())!=-1){
				aos.write(b);
			}
			is.close();
			aos.close();
			return aos.toByteArray();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return null;
	}
}


2.实现热加载的功能(即:哪些方法被热加载了,可以动态更新)

//该子类下的需要动态更新
public interface BaseManager {

	public void logic();
}
//实现具体的热加载功能
public class MyManager implements BaseManager{

	@Override
	public void logic() {
		System.out.println("热加载:logic...");
		
	}
}


3.定义封装类,定义需要加载的信息

public class LoadInfo {

	private MyClassLoader loader;
	
	//(起一个线程)记录要加载的类的时间戳-->加载的时间
	private long loadTime;
	
	private BaseManager manager;

	public LoadInfo(MyClassLoader loader, long loadTime) {
		super();
		this.loader = loader;
		this.loadTime = loadTime;
	}
	//get、set方法
	
}


4.建立工厂类,加载manager

//加载manager的工厂
public class ManagerFactory {

	//记录热加载类的加载信息
	private static final Map<String, LoadInfo> loadTimeMap = 
			new HashMap<>();
	
	//要加载类的classpath
	private static final String CLASS_PATH = "E:/workspace/class_loader/bin";
	
	//实现热加载的类的全名称(包名+类名)
	public static final String MY_MANAGER = "class_loader.MyManager";
	
	public static BaseManager getManager(String className){
		File loadFile = new File(CLASS_PATH+className.replaceAll("\\.", "/")+".class");
		//取的最后一次的修改时间
		long lastModified = loadFile.lastModified();
		//loadTimeMap不包含className为key的LoadInfo信息,证明这个类没有被加载,那么需要加载这个类到jvm
		if (loadTimeMap.get(className) == null) {
			load(className,lastModified);
		//加载类的时间戳变化了,同样需要重新加载这个类到jvm
		}else if (loadTimeMap.get(className).getLoadTime() != lastModified) {
			load(className,lastModified);
		}
		return loadTimeMap.get(className).getManager();
	}

	private static void load(String className, long lastModified) {
		MyClassLoader myClassLoader = new MyClassLoader(CLASS_PATH);
		Class<?> loadClass = null;
		try {
			loadClass = myClassLoader.loadClass(className);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		BaseManager manager = newInstance(loadClass);
		LoadInfo loadInfo = new LoadInfo(myClassLoader, lastModified);
		loadInfo.setManager(manager);
		loadTimeMap.put(className, loadInfo);
	}

	//以反射的方式创建BaseManager子类对象
	private static BaseManager newInstance(Class<?> loadClass) {
		try {
			return (BaseManager) loadClass.getConstructor(new Class[]{}).newInstance(new Object[]{});
		} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
				| NoSuchMethodException | SecurityException e) {
			e.printStackTrace();
		}
		return null;
	}
}


5.检测修改过的class

//后台启动一个线程不断刷新重新加载实现热加载的类
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();
			}
		}
	}
}


6.测试

public class ClassLoaderTest {

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

运行Test,结果如下:

BaiduShurufa_2018-6-17_14-0-33.jpg


二、Spring Boot怎么实现热部署?

实现方式有两种

  • 使用Spring Loaded

  • 使用Spring-boot-devtools


第一种:

BaiduShurufa_2018-6-17_14-0-33.jpg


第二种:

BaiduShurufa_2018-7-1_17-46-5.jpg