配置使用CDI的Java库与CDI框架:如何避免内部Bean被外部应用的CDI容器扫描到
Great question—this is a super common headache when building shared CDI frameworks without module boundaries. Let's walk through solutions for both your scenarios step by step:
The approach depends on which CDI implementation you're using (Weld, OpenWebBeans, etc.), but here are the most common methods:
For Weld (WildFly, JBoss, etc.)
- Declarative exclusion via
beans.xml:
Add<exclude>tags inside the<scan>section to target specific packages or classes:<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd" version="2.0"> <scan> <!-- Exclude an entire package --> <exclude name="com.example.external.*"/> <!-- Exclude a single class --> <exclude name="com.example.UnwantedExternalBean"/> </scan> </beans> - Programmatic exclusion via CDI Extension:
Write a custom extension to veto unwanted beans during the discovery phase:
Don't forget to register the extension inpublic class ExternalBeanExclusionExtension implements Extension { void processBean(@Observes ProcessBean<?> processBean) { Bean<?> bean = processBean.getBean(); // Veto beans from external packages if (bean.getBeanClass().getPackageName().startsWith("com.example.external")) { processBean.veto(); } } }META-INF/services/javax.enterprise.inject.spi.Extensionby adding the full class name of your extension.
For OpenWebBeans (Apache TomEE, etc.)
- Declarative exclusion via
beans.xml:
Use<excludedPackages>or<excludedClasses>to filter out unwanted beans:<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="2.0"> <scan> <excludedPackages>com.example.external</excludedPackages> <excludedClasses>com.example.UnwantedExternalBean</excludedClasses> </scan> </beans>
Your goal is to prevent App A's CDI container from scanning L1/L2's internal beans, while letting L1/L2 access those beans via the same container. Since you can't use module-info.java, here's the most robust approach:
Recommended Approach: @Vetoed + Custom CDI Extensions
This method ensures internal beans are only registered by their respective libraries, and App A's container won't discover them automatically:
Mark internal beans with
@Vetoed:
Add the@Vetoedannotation to L1/L2's internal beans. This tells CDI to ignore them during default discovery:@Vetoed public class L1InternalBean { // Internal logic here }Write library-specific CDI extensions:
Create an extension for L1 (and another for L2) that manually registers the internal beans during theBeforeBeanDiscoveryphase:public class L1InternalBeanExtension implements Extension { void beforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd, BeanManager bm) { // Register L1's internal bean with the container AnnotatedType<L1InternalBean> annotatedType = bm.createAnnotatedType(L1InternalBean.class); bbd.addAnnotatedType(annotatedType, "L1InternalBean"); // If you need to add qualifiers or scope programmatically, you can modify the AnnotatedType here } }Register the extensions:
For each library, create a file namedjavax.enterprise.inject.spi.ExtensioninMETA-INF/services/, and add the full class name of the extension (e.g.,com.example.l1.L1InternalBeanExtension).
Why this works:
- App A's CDI container won't scan L1/L2's internal beans because they're marked
@Vetoed. - L1/L2's extensions explicitly register their internal beans, so the libraries can still
@Injectthem normally. - The beans remain
publicbut are effectively hidden from App A's code (unless someone intentionally uses reflection, which is outside standard CDI usage).
Alternative: Package Scoping + beans.xml Filters
If you can organize L1/L2's internal beans into dedicated internal packages (e.g., com.example.l1.internal), you can use package filters:
- In App A's
beans.xml, exclude the internal packages:<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="2.0"> <scan> <exclude name="com.example.l1.internal.*"/> <exclude name="com.example.l2.internal.*"/> </scan> </beans> - In L1/L2's
beans.xml, include their own internal packages:<!-- L1's beans.xml --> <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="2.0"> <scan> <include name="com.example.l1.internal.*"/> </scan> </beans>
Note: This relies on your CDI implementation merging scan configurations correctly, so test thoroughly with your setup.
Alternative: Programmatic Bean Lookup
If L1/L2 don't need to @Inject internal beans (and can fetch them manually), you can skip container scanning entirely:
public class L1Service { @Inject BeanManager beanManager; public L1InternalBean getInternalBean() { Set<Bean<?>> beans = beanManager.getBeans(L1InternalBean.class); if (beans.isEmpty()) { // Fallback to manual instantiation if needed return new L1InternalBean(); } Bean<L1InternalBean> bean = (Bean<L1InternalBean>) beans.iterator().next(); CreationalContext<L1InternalBean> ctx = beanManager.createCreationalContext(bean); return bean.create(ctx); } }
This avoids scanning configuration but requires manual bean management.
内容的提问来源于stack exchange,提问作者Byebye

