代码实现逻辑总结
核心设计模式
这是一个多委托类加载器实现,扩展了Java标准的双亲委派模型:
关键实现特性
类加载策略 (loadClass)
先遵循双亲委派模型使用父类加载器
失败后依次尝试所有自定义加载器
实现了插件化类加载的容错机制
资源获取策略 (getResource / getResourceAsStream)
单个资源查找:返回第一个匹配的资源
使用函数式编程风格优雅处理null值
资源聚合策略 (getResources)
收集所有ClassLoader中的同名资源
支持SPI机制中的多服务提供者发现
应用场景
插件系统:动态加载不同插件JAR包中的类
多租户隔离:为不同业务加载独立的类实现
SPI扩展:支持从多个类加载器发现服务提供者
这种设计确保了Lattice框架能够灵活加载来自不同来源的扩展点实现,是实现业务隔离和插件化的基础设施。
/** * Lattice框架的自定义类加载器,支持多个自定义ClassLoader的委托加载 * 实现了双亲委派模型的扩展,在父类加载器加载失败后,会尝试使用自定义加载器列表进行加载 * * @author Rocky Yu * @since 2022/10/10 */ public class LatticeClassLoader extends ClassLoader { // 自定义类加载器列表,用于实现插件化的类加载机制 @Getter private final List<ClassLoader> customLoaders = Lists.newArrayList(); /** * 构造函数,指定父类加载器 * @param parent 父类加载器 */ public LatticeClassLoader(ClassLoader parent) { super(parent); } /** * 重写类加载方法,实现多ClassLoader的委托加载策略 * 加载顺序: * 1. 先使用父类加载器(遵循双亲委派模型) * 2. 如果父类加载器加载失败,遍历自定义加载器列表尝试加载 * 3. 所有加载器都失败则抛出ClassNotFoundException * * @param name 类的全限定名 * @return 加载的Class对象 * @throws ClassNotFoundException 如果所有加载器都无法加载该类 */ @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try { // 优先使用父类加载器加载(双亲委派) return super.loadClass(name); } catch (ClassNotFoundException ex) { // 父类加载失败,遍历自定义加载器列表 for (ClassLoader loader : customLoaders) { try { // 尝试使用当前自定义加载器加载 return loader.loadClass(name); } catch (ClassNotFoundException ignored) { // 当前加载器加载失败,继续尝试下一个 } } // 所有加载器都失败,抛出异常 throw new ClassNotFoundException(name); } } /** * 获取指定名称的资源URL * 查找顺序: * 1. 先从父类加载器查找 * 2. 如果未找到,遍历自定义加载器列表查找,返回第一个找到的资源 * * @param name 资源名称 * @return 资源的URL,未找到返回null */ @Nullable @Override public URL getResource(String name) { // 优先从父类加载器获取资源 URL url = super.getResource(name); if (null != url) { return url; } // 父类加载器未找到,使用Stream API遍历自定义加载器 return getCustomLoaders().stream() .map(p -> p.getResource(name)) // 从每个加载器获取资源 .filter(Objects::nonNull) // 过滤掉null结果 .findFirst().orElse(null); // 返回第一个找到的资源 } /** * 获取所有匹配的资源URL(支持多个同名资源) * 聚合策略: * 1. 收集父类加载器中的所有匹配资源 * 2. 收集所有自定义加载器中的匹配资源 * 3. 返回所有资源的聚合结果 * * @param name 资源名称 * @return 所有匹配资源的URL枚举 * @throws IOException 如果发生I/O错误 */ @Override public Enumeration<URL> getResources(String name) throws IOException { // 创建结果列表,用于聚合所有找到的资源 List<URL> urls = Lists.newArrayList(); // 先获取父类加载器中的所有资源 Enumeration<URL> enumeration = super.getResources(name); while (enumeration.hasMoreElements()) { urls.add(enumeration.nextElement()); } // 遍历所有自定义加载器,收集资源 for (ClassLoader classLoader : getCustomLoaders()) { enumeration = classLoader.getResources(name); while (enumeration.hasMoreElements()) { urls.add(enumeration.nextElement()); } } // 将List转换为Enumeration返回 return new IteratorEnumeration<>(urls.iterator()); } /** * 以输入流形式获取资源 * 查找顺序: * 1. 先从父类加载器获取 * 2. 如果未找到,遍历自定义加载器列表,返回第一个找到的资源流 * * @param name 资源名称 * @return 资源的输入流,未找到返回null */ @Nullable @Override public InputStream getResourceAsStream(String name) { // 优先从父类加载器获取资源流 InputStream inputStream = super.getResourceAsStream(name); if (null != inputStream) { return inputStream; } // 父类加载器未找到,使用Stream API遍历自定义加载器 return getCustomLoaders().stream() .map(p -> p.getResourceAsStream(name)) // 从每个加载器获取资源流 .filter(Objects::nonNull) // 过滤掉null结果 .findFirst().orElse(null); // 返回第一个找到的资源流 } }