基于Tomcat10的JSF2.3+CDI+Spring Bean整合问题咨询
环境配置
- Tomcat 10.x,搭载JBoss Weld(weld-servlet-shaded 5.0.0)以支持CDI
- GlassFish 4.0.0 提供的 Jakarta Faces(jakarta.faces)
- Spring Core、Context、JDBC 6.0.2,不使用spring-web(以JSF作为Web框架),未使用Spring Boot
代码示例
JSF Bean (UserProfile)
@Named @RequestScoped public class UserProfile { @Inject private UserService userService; public String getMessage() { return userService.getMessage(); } }
Spring Service (UserService)
@Service public class UserService { @Inject private UserDao userDao; public String getMessage() { return userDao.getMessage(); } }
Spring DAO (UserDao)
@Repository public class UserDao extends JdbcDaoSupport{ @Autowired private DataSource dataSource; @PostConstruct public void init() { setDataSource(dataSource); } public String getMessage(){ Integer empCount = getJdbcTemplate().queryForObject("SELECT COUNT(*) FROM EMPLOYEE", Integer.class) ; return empCount.toString(); } }
问题描述
数据源、Hikari配置及JdbcTemplate定义在applicationContext.xml中,但数据源未初始化,传统JSF与Spring整合方式无效,现提出以下疑问:
- 是否需要spring-web?已用JSF,不想增加复杂度。
- 是否需要JBoss Weld?Tomcat10支持CDI2.0,能否省略?
- 如何让Spring Bean在Tomcat启动时初始化,供JSF调用?
解答
1. 是否需要spring-web?
不需要。spring-web核心作用是支持Spring MVC的Web层整合,你以JSF作为Web框架,完全可以仅依赖Spring Core、Context、JDBC模块。但要注意:没有spring-web的情况下,Spring不会自动在Web容器启动时加载上下文,需要手动处理初始化逻辑。
2. 是否需要JBoss Weld?
不能省略。Tomcat 10.x本身不内置CDI容器,它仅支持Servlet 5.0(Jakarta EE 9)规范,CDI功能需要单独引入实现。而JSF 2.3依赖CDI管理@Named注解的Bean,以及处理@Inject依赖注入,没有CDI容器的话,JSF的核心依赖注入逻辑会失效。
3. 如何让Spring Bean在Tomcat启动时初始化并供JSF调用?
需要实现Spring上下文与CDI的桥接,具体步骤如下:
步骤1:通过监听器初始化Spring上下文
编写ServletContextListener,在Tomcat启动时加载Spring的ApplicationContext:
import jakarta.servlet.ServletContextEvent; import jakarta.servlet.ServletContextListener; import jakarta.servlet.annotation.WebListener; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; @WebListener public class SpringContextInitializer implements ServletContextListener { private static ApplicationContext springContext; @Override public void contextInitialized(ServletContextEvent sce) { // 加载applicationContext.xml配置文件 springContext = new ClassPathXmlApplicationContext("applicationContext.xml"); // 将上下文存入ServletContext,方便后续获取 sce.getServletContext().setAttribute("springContext", springContext); } @Override public void contextDestroyed(ServletContextEvent sce) { if (springContext instanceof ClassPathXmlApplicationContext) { ((ClassPathXmlApplicationContext) springContext).close(); } } public static ApplicationContext getSpringContext() { return springContext; } }
步骤2:实现CDI生产者暴露Spring Bean
CDI无法直接识别Spring的@Service/@Repository Bean,需要通过@Producer方法将Spring Bean转换为CDI可注入的实例:
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Produces; import org.springframework.context.ApplicationContext; @ApplicationScoped public class SpringBeanProducer { @Produces public UserService produceUserService() { ApplicationContext context = SpringContextInitializer.getSpringContext(); return context.getBean(UserService.class); } @Produces public UserDao produceUserDao() { ApplicationContext context = SpringContextInitializer.getSpringContext(); return context.getBean(UserDao.class); } // 其他需要暴露给CDI的Spring Bean,添加对应Producer方法即可 }
步骤3:修复数据源初始化问题
检查applicationContext.xml的配置,确保HikariCP依赖已正确引入,示例配置如下:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 启用组件扫描,识别@Service/@Repository注解 --> <context:component-scan base-package="你的服务包路径, 你的DAO包路径"/> <!-- Hikari数据源配置 --> <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/你的数据库名"/> <property name="username" value="数据库用户名"/> <property name="password" value="数据库密码"/> <property name="maximumPoolSize" value="10"/> </bean> <!-- 可选:显式定义JdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg ref="dataSource"/> </bean> </beans>
确保项目中已添加HikariCP依赖(如com.zaxxer:HikariCP:5.0.1),Spring会自动初始化数据源并注入到UserDao的dataSource字段。
步骤4:验证整合效果
完成上述配置后,JSF的UserProfile Bean通过@Inject注入的UserService,会由CDI生产者提供对应的Spring Bean实例,Spring上下文会在Tomcat启动时完成初始化,数据源及DAO层逻辑即可正常工作。
内容的提问来源于stack exchange,提问作者Vandan Hatkar

