All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.voovan.tools.hotswap.Hotswaper Maven / Gradle / Ivy

package org.voovan.tools.hotswap;

import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import org.voovan.Global;
import org.voovan.tools.TEnv;
import org.voovan.tools.TObject;
import org.voovan.tools.hashwheeltimer.HashWheelTask;
import org.voovan.tools.log.Logger;

import java.io.File;
import java.io.IOException;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.UnmodifiableClassException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 热部署核心类
 *      采用 JavaAgent 方式进行类的热部署
 *      使用这种热部署方式,不能新增方法和属性,也就说类的结构不能发生变化
 *
 * @author: helyho
 * Voovan Framework.
 * WebSite: https://github.com/helyho/Voovan
 * Licence: Apache v2 License
 */
public class Hotswaper {
    private List classFileInfos;
    private List excludePackages;
    private int reloadIntervals;
    private HashWheelTask reloadTask;

    /**
     * 构造函数
     * @throws IOException IO 异常
     * @throws AttachNotSupportedException 附加指定进程失败
     * @throws AgentLoadException Agent 加载异常
     * @throws AgentInitializationException Agent 初始化异常
     */
    public Hotswaper() throws IOException, AgentInitializationException, AgentLoadException, AttachNotSupportedException {
        init(null);
    }

    /**
     * 构造函数
     * @param agentJarPath AgentJar 文件
     * @throws IOException IO 异常
     * @throws AttachNotSupportedException 附加指定进程失败
     * @throws AgentLoadException Agent 加载异常
     * @throws AgentInitializationException Agent 初始化异常
     */
    public Hotswaper(String agentJarPath) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
        init(new File(agentJarPath));
    }

    private void init(File agentJar) throws AgentInitializationException, AgentLoadException, AttachNotSupportedException, IOException {
        reloadIntervals = 5;
        classFileInfos = new ArrayList();

        excludePackages = TObject.asList("java.","sun.","javax.","com.sun","com.oracle");

        TEnv.agentAttach(null);

        if(TEnv.instrumentation==null){
            throw new AgentInitializationException("instrumentation is not inited");
        }

        loadCustomClass();

    }

    /**
     * 读取所有的用户类
     * @return 用户类信息
     */
    private void loadCustomClass(){
        for(Class clazz : TEnv.instrumentation.getAllLoadedClasses()){

            if(isExcludeClass(clazz)){
                continue;
            }

            ClassFileInfo classFileInfo = new ClassFileInfo(clazz, true);
            classFileInfos.add(classFileInfo);
        }
    }

    /**
     * 检查是否是热部署排除在外的class
     * @param clazz class 对象
     * @return true:排除的 class, false:未排除的 class
     */
    private boolean isExcludeClass(Class clazz){
        try {

            String packageName = clazz.getPackage().getName();

            //基本类型部排除
            if (clazz.isPrimitive()) {
                return true;
            }

            //如果是内部类则排除
            if (clazz.isMemberClass()) {
                return true;
            }

            //CodeSource 对象为空的不加载
            if (clazz.getProtectionDomain().getCodeSource() == null) {
                return true;
            }

            //没有完全现定名的 class 不加载
            if (packageName == null) {
                return true;
            }

            //排除的包中的 class 不加载
            for (String excludePackage : excludePackages) {
                if (packageName.startsWith(excludePackage)) {
                    return true;
                }
            }

            //加载过的 class 不加载
            for (ClassFileInfo classFileInfo : classFileInfos) {
                if (classFileInfo.getClazz() == clazz) {
                    return true;
                }
            }

            return false;
        } catch (Throwable e){
            return true;
        }
    }

    /**
     * 文件变更监视器
     * @return 发生变更的 ClassFileInfo 对象
     */
    private List fileWatcher(){
        loadCustomClass();
        List changedFiles = new ArrayList();
        for(ClassFileInfo classFileInfo : classFileInfos){
            if(classFileInfo.isChanged()){
                changedFiles.add(classFileInfo);
            }
        }
        return changedFiles;
    }

    /**
     * 重新热加载Class
     * @param clazzDefines 有过变更的文件信息
     * @throws UnmodifiableClassException  不可修改的 Class 异常
     * @throws ClassNotFoundException Class未找到异常
     */
    public static void reloadClass(Map clazzDefines) throws UnmodifiableClassException, ClassNotFoundException {

        for(Map.Entry clazzDefine : clazzDefines.entrySet()){
            Class clazz = clazzDefine.getKey();
            byte[] classBytes = clazzDefine.getValue();

            ClassDefinition classDefinition = new ClassDefinition(clazz, classBytes);
            try {
                Logger.info("[HOTSWAP] class:" + clazz + " will reload.");
                TEnv.instrumentation.redefineClasses(classDefinition);
            } catch (Exception e) {
                Logger.error("[HOTSWAP] class:" + clazz + " reload failed", e);
            }
        }
    }

    /**
     * 重新热加载Class
     * @param changedFiles 有过变更的文件信息
     * @throws UnmodifiableClassException  不可修改的 Class 异常
     * @throws ClassNotFoundException Class未找到异常
     */
    public void reloadClass(List changedFiles) throws UnmodifiableClassException, ClassNotFoundException {
        HashMap classDefines = new HashMap();
        for(ClassFileInfo classFileInfo : changedFiles) {
            classDefines.put(classFileInfo.getClazz(), classFileInfo.getBytes());
        }

        reloadClass(classDefines);
    }

    /**
     * 获取当前自动热部署间隔事件
     * @return 自动热部署间隔事件
     */
    public int getReloadIntervals() {
        return reloadIntervals;
    }

    /**
     * 自动热加载 Class
     * @param  intervals 自动重读的时间间隔, 单位: 秒
     * @throws UnmodifiableClassException  不可修改的 Class 异常
     * @throws ClassNotFoundException Class未找到异常
     */
    public void autoReload(int intervals) throws UnmodifiableClassException, ClassNotFoundException {
        this.reloadIntervals = intervals;

        cancelAutoReload();

        Logger.simple("[HOTSWAP] Start auto reload and hotswap every " + intervals + " seconds");

        reloadTask = new HashWheelTask() {
            @Override
            public void run() {
                try {
                    List changedFiles = fileWatcher();
                    reloadClass(changedFiles);
                } catch (UnmodifiableClassException |ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        };

        Global.getHashWheelTimer().addTask(reloadTask, intervals, true);
    }

    /**
     * 取消自动热加载操作
     */
    public void cancelAutoReload(){
        if(reloadTask != null) {
            reloadTask.cancel();
        }
    }

    public List getClassFileInfos() {
        return classFileInfos;
    }

    public void setClassFileInfos(List classFileInfos) {
        this.classFileInfos = classFileInfos;
    }

    /**
     * Class 文件信息
     */
    public class ClassFileInfo {
        private Class clazz;
        private long lastModified;

        public ClassFileInfo(Class clazz, boolean isAutoChek) {
            this.clazz = clazz;
            if(isAutoChek) {
                this.lastModified = TEnv.getClassModifyTime(clazz);
            }
        }

        public boolean isChanged(){

            long currentModified = TEnv.getClassModifyTime(clazz);

            if(currentModified < 0 ){
                return false;
            }

            if(currentModified != lastModified){
                lastModified = currentModified;
                return true;
            }else{
                return false;
            }
        }



        public byte[] getBytes() {
            return TEnv.loadClassBytes(clazz);
        }

        public Class getClazz() {
            return clazz;
        }

        public void setClazz(Class clazz) {
            this.clazz = clazz;
        }

        public long getLastModified() {
            return lastModified;
        }

        public void setLastModified(long lastModified) {
            this.lastModified = lastModified;
        }

        public String toString(){
            return clazz.getCanonicalName();
        }

        public boolean equals(ClassFileInfo classFileInfo) {
            return classFileInfo.getClazz() == this.clazz;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy