Spring 5.x与CDI 2.x集成方案咨询:适配JSF 2.3耦合场景
Spring 5.x + CDI 2.x + JSF 2.3 集成方案实践总结
在这类深度耦合CDI的JSF项目中,我实际踩过不少坑,整理了几个最靠谱的集成方案,你可以根据项目的现有架构和复杂度选择:
1. Apache DeltaSpike Spring 桥接器(最省心的官方社区方案)
这是目前最成熟的双容器桥接方案,DeltaSpike本身就是为了解决Java EE和Spring生态的整合问题而生,专门针对Spring 5.x和CDI 2.x做了适配。
- 核心原理:通过DeltaSpike的模块自动打通Spring ApplicationContext和CDI BeanManager,让两个容器的Bean可以互相注入。
- 实操步骤:
- 引入DeltaSpike Spring模块的Maven依赖(对应Spring 5.x的版本选delta-spike 1.9+)
- 在Spring配置类上添加
@EnableDeltaSpikeSpring注解,或者在web.xml中注册DeltaSpike的Spring监听器 - 之后你就可以在CDI的JSF Backing Bean中用
@Inject注入Spring的Service,也能在Spring的Bean中@InjectCDI的Bean
- 优势:几乎零自定义代码,社区维护成熟,解决了大部分生命周期和依赖冲突问题
- 注意点:如果项目用了Spring Boot,需要额外配置DeltaSpike的启动类,避免自动装配冲突
2. 自定义BeanFactory/BeanManager桥接(高度定制化场景)
如果DeltaSpike的默认桥接满足不了你的特殊需求(比如需要过滤特定Bean、自定义生命周期管理),可以自己写代码打通两个容器:
方案A:把CDI Bean注册到Spring容器
写一个Spring的ApplicationContextAware组件,获取CDI的BeanManager,将CDI Bean手动注册到Spring:
@Component public class CdiToSpringBridge implements ApplicationContextAware { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { BeanManager cdiBeanManager = CDI.current().getBeanManager(); // 遍历所有CDI管理的Bean,注册到Spring for (Bean<?> cdiBean : cdiBeanManager.getBeans(Object.class).values()) { Class<?> beanClass = cdiBean.getBeanClass(); String beanName = beanClass.getSimpleName(); if (!applicationContext.containsBean(beanName)) { Object beanInstance = cdiBeanManager.getReference(cdiBean, beanClass, null); applicationContext.getAutowireCapableBeanFactory() .registerSingleton(beanName, beanInstance); } } } }
方案B:把Spring Bean暴露为CDI Bean
通过CDI扩展机制,获取Spring的ApplicationContext,将Spring Bean注册为CDI的Bean:
public class SpringToCdiExtension implements Extension { public void afterBeanDiscovery(@Observes AfterBeanDiscovery event, BeanManager beanManager) { ApplicationContext springContext = WebApplicationContextUtils.getWebApplicationContext( FacesContext.getCurrentInstance().getExternalContext().getContext() ); // 遍历Spring Bean,注册为CDI Bean for (String beanName : springContext.getBeanDefinitionNames()) { Class<?> beanClass = springContext.getType(beanName); if (beanClass != null && !beanClass.isInterface()) { event.addBean() .types(beanClass) .createWith(ctx -> springContext.getBean(beanName)) .scope(Dependent.class); } } } }
然后在META-INF/services/javax.enterprise.inject.spi.Extension文件中注册这个扩展类。
3. 统一依赖注入模型(避免双容器复杂度)
如果项目还处于初期或者可以重构,优先选择单容器主导的方案,从根源上避免双容器的冲突:
- 以Spring为主导:
- 把所有JSF Backing Bean改用Spring注解:用
@Component代替@Named,@Autowired代替@Inject - 引入Spring Faces的依赖,配置
SpringBeanFacesELResolver到faces-config.xml,让JSF可以直接访问Spring Bean - 用Spring的
@Scope("view")(需要spring-webmvc-jsf依赖)代替CDI的@ViewScoped
- 把所有JSF Backing Bean改用Spring注解:用
- 以CDI为主导:
- 保留CDI作为JSF的依赖注入容器
- 通过Spring的CDI集成模块,把Spring的配置类和Bean暴露为CDI Bean,比如用
@Import把Spring配置类引入CDI的Bean中
4. JSF 2.3原生Spring集成(轻量场景)
JSF 2.3本身提供了有限的Spring集成支持,配合Spring Faces可以实现基础的互通:
- 在faces-config.xml中配置Spring的EL解析器:
<application> <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver> </application>
这样JSF页面和Backing Bean可以直接访问Spring的Bean,但如果需要CDI和Spring Bean互相注入,还是得配合桥接器。
内容的提问来源于stack exchange,提问作者Helder
相关产品推荐
相关产品推荐

