如何高效缓存Java ServiceLoader.Provider
缓存ServiceLoader.Provider实现预计算式类型安全获取
核心方案:安全的Unchecked强制转换
通过在缓存初始化阶段确保类型一致性,直接对缓存的List进行unchecked转换,避免每次调用时的遍历复制,满足"预计算好值随时可用"的需求。
1. 缓存初始化逻辑
首次加载服务时,直接获取对应泛型类型的Provider列表,再存入Map(这里的类型转换是安全的,因为我们明确知道列表元素的类型):
private static final Map<Class<?>, List<ServiceLoader.Provider<?>>> servicesProvidersMap = new ConcurrentHashMap<>(); public static <T> void initProviders(Class<T> clazz) { servicesProvidersMap.computeIfAbsent(clazz, key -> { // 加载对应类型的Provider列表,泛型明确为T List<ServiceLoader.Provider<T>> typedProviders = ServiceLoader.load(clazz) .stream() .collect(Collectors.toCollection(CopyOnWriteArrayList::new)); // 两次转换绕过编译器检查,本质是安全的 return (List<ServiceLoader.Provider<?>>) (List<?>) typedProviders; }); }
2. 类型安全的获取方法
利用@SuppressWarnings("unchecked")抑制编译警告,直接返回转换后的列表:
@SuppressWarnings("unchecked") public static <T> List<ServiceLoader.Provider<T>> getProvidersOf(Class<T> clazz) { // 转换安全:存入时已保证该Class对应的List是ServiceLoader.Provider<T>类型 return (List<ServiceLoader.Provider<T>>) (List<?>) servicesProvidersMap.get(clazz); }
为什么这个方案可行且安全
- 缓存初始化时,通过
ServiceLoader.load(clazz)获取的Provider<T>必然符合泛型约束,存入Map的转换只是绕开编译器的泛型擦除检查,运行时类型完全匹配。 - 无需每次调用时复制或转换列表,完全满足"预计算、直接取用"的需求。
关于ServiceLoader.Provider长期缓存的说明
虽然ServiceLoader.Provider被设计为过渡对象,但它仅持有服务的加载元数据(如模块引用、构造器句柄),只要服务所在模块未被JVM卸载,缓存的Provider实例可以安全长期保存,调用get()依然能生成新的服务实例。
内容的提问来源于stack exchange,提问作者G. B. Wanscher
相关产品推荐
相关产品推荐

