如何在不依赖实现细节的情况下确定Bean所属的Bean Archive?
确定CDI Bean所属的Bean Archive(不依赖实现细节)
由于CDI规范本身未暴露Bean Archive的API,要实现这个需求可以从类的来源定位或自定义元数据标记入手,以下是几种可行方案:
方案一:通过CDI扩展+类的资源定位识别
利用CDI扩展的ProcessBean事件,获取Bean对应的类,再通过类的ProtectionDomain或资源路径定位其所属的Jar(即Bean Archive)。
实现步骤:
- 创建CDI扩展类,监听
ProcessBean事件:
public class BeanArchiveTrackingExtension implements Extension { private final Map<Bean<?>, String> beanToArchiveMap = new ConcurrentHashMap<>(); <T> void observeProcessBean(@Observes ProcessBean<T> event) { Bean<T> bean = event.getBean(); Class<?> beanClass = bean.getBeanClass(); // 获取类所属的Jar路径 String archivePath = getArchivePath(beanClass); if (archivePath != null) { beanToArchiveMap.put(bean, archivePath); } } private String getArchivePath(Class<?> clazz) { ProtectionDomain domain = clazz.getProtectionDomain(); CodeSource codeSource = domain.getCodeSource(); if (codeSource != null) { URL location = codeSource.getLocation(); if (location != null) { // 提取Jar文件名或完整路径,根据需求调整 return location.getFile(); } } // 备选:通过类的资源路径推断 URL classResource = clazz.getResource(clazz.getSimpleName() + ".class"); if (classResource != null) { String resourcePath = classResource.toString(); // 截取Jar路径部分,格式通常为 jar:file:/xxx/yyy.jar!/com/example/MyClass.class if (resourcePath.startsWith("jar:")) { return resourcePath.substring(4, resourcePath.indexOf("!")); } } return null; } // 提供外部获取Bean所属Archive的方法 public String getArchiveForBean(Bean<?> bean) { return beanToArchiveMap.get(bean); } }
- 在
beans.xml中注册该扩展(或通过自动扫描,取决于CDI版本)。 - 后续需要查询时,注入该扩展并调用
getArchiveForBean方法。
注意事项:
- 部分容器环境下,
CodeSource可能返回null,需依赖资源路径的备选方案。 - 模块化环境(如JPMS)中,路径格式可能略有不同,需根据实际情况调整截取逻辑。
方案二:自定义Archive标记注解+扩展扫描
如果您能控制所有Bean Archive,可以给每个Archive添加一个专属的标记类,通过扩展扫描这些标记类来关联Bean与Archive。
实现步骤:
- 定义自定义标记注解:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface BeanArchive { String name(); // 标识Archive的名称 }
- 在每个Bean Archive中创建一个空类并添加该注解,例如:
// 在archive-1.jar中 @BeanArchive(name = "user-service-archive") public class UserServiceArchiveMarker {} // 在archive-2.jar中 @BeanArchive(name = "order-service-archive") public class OrderServiceArchiveMarker {}
- 创建CDI扩展,扫描所有标记类并记录类加载器与Archive名称的映射:
public class BeanArchiveTrackingExtension implements Extension { private final Map<ClassLoader, String> classLoaderToArchiveMap = new ConcurrentHashMap<>(); private final Map<Bean<?>, String> beanToArchiveMap = new ConcurrentHashMap<>(); void observeProcessAnnotatedType(@Observes ProcessAnnotatedType<?> event) { AnnotatedType<?> annotatedType = event.getAnnotatedType(); BeanArchive archiveAnnotation = annotatedType.getAnnotation(BeanArchive.class); if (archiveAnnotation != null) { ClassLoader classLoader = annotatedType.getJavaClass().getClassLoader(); classLoaderToArchiveMap.put(classLoader, archiveAnnotation.name()); } } <T> void observeProcessBean(@Observes ProcessBean<T> event) { Bean<T> bean = event.getBean(); ClassLoader beanClassLoader = bean.getBeanClass().getClassLoader(); String archiveName = classLoaderToArchiveMap.get(beanClassLoader); if (archiveName != null) { beanToArchiveMap.put(bean, archiveName); } } public String getArchiveForBean(Bean<?> bean) { return beanToArchiveMap.get(bean); } }
- 同样注册扩展后,即可通过扩展查询Bean所属的Archive名称。
优势:
- 相比路径定位,名称更直观,不受环境路径变化影响。
- 完全可控,适合您能管理所有Bean Archive的场景。
方案三:利用Bean的自定义元数据关联
如果允许给Bean添加额外元数据,可以在Bean类上添加自定义注解,直接标记其所属Archive:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface FromArchive { String value(); }
然后在Bean类上标注:
@FromArchive("user-service-archive") @ApplicationScoped public class UserService {}
之后通过CDI的Bean.getBeanClass()获取类,再读取该注解的值即可。
局限性:
- 需要手动给每个Bean添加注解,工作量较大,适合Bean数量较少的场景。
内容的提问来源于stack exchange,提问作者Byebye
相关产品推荐
相关产品推荐

