我们在开发普通的Java业务系统时,都是正常的编译、打包、加载启动。但是在一些平台类、工具类的系统,需要支持外部插件式,以及轻量规则引擎动态加载外部业务规则等功能。
运行时加载外部Jar包(class文件),是其中一种技术解决方案,该方式广泛使用在各种主流开源组件,让开源社区其他开发者,扩展更多的功能或数据库等中间件对接。
URLClassLoader可以从指定的URL路径中加载类资源,而不局限于传统的类路径,这些URL可以是文件系统路径、网络路径或者JAR文件路径等。这意味着可以从远程服务器或者动态生成的路径加载类文件,实现动态加载类,从而实现插件化或者动态扩展的功能。
import java.net.URL;import java.net.URLClassLoader;import java.net.MalformedURLException;public class Main { public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException { // 定义URL数组,指定要加载的类路径 URL[] urls = new URL[]{new URL("file:/path/to/classes/")}; // 创建URLClassLoader实例 URLClassLoader classLoader = new URLClassLoader(urls); /** 使用jar包方式加载 URLClassLoader classLoader = new URLClassLoader(new URL[] { new File("path/to/your.jar").toURI().toURL() }); */ // 使用URLClassLoader加载指定类 Class> clazz = classLoader.loadClass("com.example.MyClass"); // 实例化类对象 Object instance = clazz.newInstance(); // TODO: 使用加载的类对象进行操作 }}
可以看到,URLClassLoader每次初始化,都需要对jar包或class文件进行加载,这会非常消耗机器CPU性能,调用loadClass,同样会消耗CPU性能,因此在高并发的业务系统中,需要对ClassLoader以及加载后的class进行本地缓存。需要做好刷新、控制这个缓存,或做好版本的控制。
以下代码截取自DataX源码(阿里巴巴开源的数据同步工具)
import com.alibaba.datax.common.exception.DataXException;import com.alibaba.datax.core.util.FrameworkErrorCode;import org.apache.commons.lang.StringUtils;import org.apache.commons.lang.Validate;import java.io.File;import java.io.FileFilter;import java.net.URL;import java.net.URLClassLoader;import java.util.ArrayList;import java.util.List;/** * 提供Jar隔离的加载机制,会把传入的路径、及其子路径、以及路径中的jar文件加入到class path。 */public class JarLoader extends URLClassLoader { public JarLoader(String[] paths) { this(paths, JarLoader.class.getClassLoader()); } public JarLoader(String[] paths, ClassLoader parent) { super(getURLs(paths), parent); } private static URL[] getURLs(String[] paths) { Validate.isTrue(null != paths && 0 != paths.length, "jar包路径不能为空."); List dirs = new ArrayList(); for (String path : paths) { dirs.add(path); JarLoader.collectDirs(path, dirs); } List urls = new ArrayList(); for (String path : dirs) { urls.addAll(doGetURLs(path)); } return urls.toArray(new URL[0]); } private static void collectDirs(String path, List collector) { if (null == path || StringUtils.isBlank(path)) { return; } File current = new File(path); if (!current.exists() || !current.isDirectory()) { return; } for (File child : current.listFiles()) { if (!child.isDirectory()) { continue; } collector.add(child.getAbsolutePath()); collectDirs(child.getAbsolutePath(), collector); } } private static List doGetURLs(final String path) { Validate.isTrue(!StringUtils.isBlank(path), "jar包路径不能为空."); File jarPath = new File(path); Validate.isTrue(jarPath.exists() && jarPath.isDirectory(), "jar包路径必须存在且为目录."); /* set filter */ FileFilter jarFilter = new FileFilter() { @Override public boolean accept(File pathname) { return pathname.getName().endsWith(".jar"); } }; /* iterate all jar */ File[] allJars = new File(path).listFiles(jarFilter); List jarURLs = new ArrayList(allJars.length); for (int i = 0; i < allJars.length; i++) { try { jarURLs.add(allJars[i].toURI().toURL()); } catch (Exception e) { throw DataXException.asDataXException( FrameworkErrorCode.PLUGIN_INIT_ERROR, "系统加载jar包出错", e); } } return jarURLs; }}
缓存JarLoader
/** * jarLoader的缓存 */ private static Map jarLoaderCenter = new HashMap(); @SuppressWarnings("unchecked") private static synchronized Class extends AbstractPlugin> loadPluginClass throw Exception( String pluginKey, String pluginClassName) { JarLoader jarLoader = LoadUtil.getJarLoader(pluginKey); try { return (Class extends AbstractPlugin>) jarLoader .loadClass(pluginClassName); } catch (Exception e) { throw e; } } public static synchronized JarLoader getJarLoader(String pluginKey, String pluginPath) { JarLoader jarLoader = jarLoaderCenter.get(pluginKey)); if (null == jarLoader) { if (StringUtils.isBlank(pluginPath)) { throw new RunningTimeException(String.format( "插件[%s]路径非法!", pluginKey)); } jarLoader = new JarLoader(new String[]{pluginPath}); jarLoaderCenter.put(pluginKey, jarLoader); } return jarLoader; }
分享让更多人看到