使用URLClassLoader动态加载jar

2021-01-26 04:14

阅读:458

标签:param   conf   alt   schedule   stack   path   led   mic   img   

背景介绍

在某些项目中会使用插件化技术实现一些动态“插拔”或热更新的功能。一般的做法是,定义一个标准接口,然后将实现分离进行独立部署或更新。

 

现在有个场景,系统希望引入一些特殊的业务“函数”,并支持热更新。来看看我们是怎么实现的。

 

业务函数接口:IFunction.java

技术图片技术图片
/** 业务函数接口 **/
public interface IFunction {

    /** 函数名称 **/
    public String getName();

    /** 函数描述 **/
    public String getDesc();

    /** 函数运行异常时返回默认值 **/
    public Object getDefVal();

    /** 调用函数 **/
    public Object process(Object... args) throws Exception;

    /** 检查入参是否为空 **/
    default boolean checkArgsIsEmpty(Object... args) {
        System.out.println(">> args=" + Arrays.toString(args));
        return args == null || args.length == 0;
    }
}
View Code

 

函数调用工具类:FunctionUtil.java

技术图片技术图片
public class FunctionUtil {
    private static Map FUNCTIONS = null;

    protected FunctionUtil() {
    }

    /** load functions from jar file **/
    public static Map loadFunctions(URL jar) {
        Map functions = new ConcurrentHashMap();
        try {
            JarURLClassLoader classLoader = new JarURLClassLoader(jar);
            Set classes = classLoader.loadClass(IFunction.class, "com.example.function");
            if (classes != null && classes.size() > 0) {
                for (Class clazz : classes) {
                    IFunction function = (IFunction) clazz.newInstance();
                    String name = function.getName();
                    functions.put(name, function);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return functions;
    }

    /** way to reset the functions **/
    protected static synchronized void resetFunctions(Map functions) {
        FunctionUtil.FUNCTIONS = functions;
    }

    /** load functions from default jar file **/
    private static Map loadFunctions() {
        String jarFile = "jar:http://192.168.1.1:8000/biz-functions-v1.0.jar!/";
        try {
            URL jar = new URL(jarFile);
            return loadFunctions(jar);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private static Map getFunctions() {
        if (FUNCTIONS == null) {
            synchronized (FunctionUtil.class) {
                if (FUNCTIONS == null) {
                    FUNCTIONS = loadFunctions();
                }
            }
        }
        return FUNCTIONS;
    }

    private static IFunction getFunction(String name) {
        Map funtions = getFunctions();
        if (funtions == null || funtions.size() == 0) {
            return null;
        }
        return funtions.get(name);
    }

    /** call the function **/
    @SuppressWarnings("unchecked")
    public static  T call(String name, Object... args) {
        IFunction function = getFunction(name);
        if (function == null) {
            System.err.println("function \"" + name + "\" not exist!");
            return null;
        }
        try {
            return (T) function.process(args);
        } catch (Exception e) {
            e.printStackTrace();
            return (T) function.getDefVal();
        }
    }

}
View Code

 

支持从jar读取的类加载器:JarURLClassLoader.java

技术图片技术图片
public class JarURLClassLoader {
    private URL jar;
    private URLClassLoader classLoader;

    public JarURLClassLoader(URL jar) {
        this.jar = jar;
        classLoader = new URLClassLoader(new URL[] { jar });
    }

    /**
     * 在指定包路径下加载子类
     * 
     * @param superClass
     * @param pkgName
     * @return
     */
    public Set loadClass(Class> superClass, String basePackage) {
        JarFile jarFile;
        try {
            jarFile = ((JarURLConnection) jar.openConnection()).getJarFile();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return loadClassFromJar(superClass, basePackage, jarFile);
    }

    private Set loadClassFromJar(Class> superClass, String basePackage, JarFile jar) {
        Set classes = new HashSet();
        String pkgPath = basePackage.replace(".", "/");
        Enumeration entries = jar.entries();
        Class> clazz;
        while (entries.hasMoreElements()) {
            JarEntry jarEntry = entries.nextElement();
            String entryName = jarEntry.getName();
            if (entryName.charAt(0) == ‘/‘) {
                entryName = entryName.substring(1);
            }
            if (jarEntry.isDirectory() || !entryName.startsWith(pkgPath) || !entryName.endsWith(".class")) {
                continue;
            }
            String className = entryName.substring(0, entryName.length() - 6);
            clazz = loadClass(className.replace("/", "."));
            if (clazz != null && !clazz.isInterface() && superClass.isAssignableFrom(clazz)) {
                classes.add(clazz);
            }
        }
        return classes;
    }

    private Class> loadClass(String name) {
        try {
            return classLoader.loadClass(name);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

}
View Code

 

将IFunction的实现分离,放在独立的工程内,如下图:

技术图片

 

 

Base64Encode.java

技术图片技术图片
public class Base64Encode implements IFunction {

    @Override
    public String getName() {
        return "base64Encode";
    }

    @Override
    public String getDesc() {
        return "Base64加密";
    }

    @Override
    public Object process(Object... args) {
        if (checkArgsIsEmpty(args)) {
            return "";
        }
        String s = (String) args[0];
        return Base64.getEncoder().encodeToString(s.getBytes());
    }

    @Override
    public Object getDefVal() {
        return "";
    }
}
View Code

 

 将BizFunction打包成jar,部署在可供访问的服务器上,如:http://192.168.1.1:8000/biz-functions-v1.0.jar

热更新的方式一般有2种:

1.定时刷新,如发现jar文件发生变化则重新加载;

2-动态触发,下发指定的更新动作进行重新加载;

 

方式1的简单实现 :

@Configuration
@EnableScheduling
public class CronJob {
    @Value("${function.jar.url}")
    private String jarUrl;

    /**
     * 更新函数的定时任务
     */
    @Scheduled(cron = "${function.update.cron}")
    public void updateFunction() {
        try {
            UpdateFunction.updateIfModified(jarUrl);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /** 更新函数的内部工具类 **/
    private static class UpdateFunction extends FunctionUtil {
        private static long lastModified = 0L;
        
        private static void updateIfModified(String jarUrl) throws Exception {
            URL jar = new URL(jarUrl);
            long modified = jar.openConnection().getLastModified();
            // 判断jar是否发生变化
            if (lastModified == modified) {
                return;
            } else {
                // 保存最新的修改时间
                lastModified = modified;
            }
            Map functions = loadFunctions(jar);
            resetFunctions(functions);
        }
    }
}

 

 

>> OK, THIS IS IT! 

使用URLClassLoader动态加载jar

标签:param   conf   alt   schedule   stack   path   led   mic   img   

原文地址:https://www.cnblogs.com/lichmama/p/12858517.html


评论


亲,登录后才可以留言!