如何在Spring管理的Service中注入所有实现某接口的JSF Bean?
背景
你定义了统一的数据处理接口:
public interface DataHandler { void getData(); String getControllerPrefix(); }
多个JSF控制器通过@ManagedBean实现该接口:
@ManagedBean @ViewScoped public class MyController implements DataHandler{ // 业务代码 }
尝试在Spring Service中通过CDI的@Any注入所有DataHandler实现类,但由于JSF Bean和Spring Bean分属不同上下文,注入失败。此前通过FacesContext手动获取单个Bean:
public static <T> T getBeanFromFacesCtx(String beanQualifier, Class<T> clazz) { FacesContext ctx = FacesContext.getCurrentInstance(); Application app = ctx.getApplication(); T bean = app.evaluateExpressionGet(ctx, beanQualifier, clazz); return Optional.ofNullable(bean) .orElseThrow(() -> new UnsupportedOperationException("It was not possible to return the Bean " + beanQualifier)); }
以下是几种更优的实现方案:
可行方案
方案1:将JSF Bean托管给Spring
这是最彻底的解决方案,把JSF控制器从@ManagedBean改为Spring注解,让Spring接管Bean生命周期,实现上下文统一。
修改JSF控制器:
@Component @Scope("view") // 对应JSF的@ViewScoped,Spring 4.3及以上版本支持 public class MyController implements DataHandler{ // 业务代码 }
之后在Spring Service中直接注入所有DataHandler实现类:
@Service public class Business { private final List<DataHandler> dataHandlers; // 构造注入所有DataHandler实现类 public Business(List<DataHandler> dataHandlers) { this.dataHandlers = dataHandlers; } public void getData(String controllerPrefix) { // 根据前缀匹配对应的处理器 DataHandler handler = dataHandlers.stream() .filter(h -> controllerPrefix.equals(h.getControllerPrefix())) .findFirst() .orElseThrow(() -> new IllegalArgumentException("未找到对应前缀的处理器: " + controllerPrefix)); handler.getData(); } }
注意:需要确保Spring扫描配置中包含这些控制器所在的包;JSF页面中引用Bean时,使用Spring默认的Bean名称(类名首字母小写,如myController)。
方案2:实现JSF-Spring上下文桥接
如果无法修改JSF Bean的托管方式,可以写一个Spring桥接Bean,专门从JSF上下文收集DataHandler实现类,供Service调用。
创建桥接类:
@Component public class DataHandlerBridge implements ApplicationListener<ContextRefreshedEvent> { private final Map<String, DataHandler> handlerMap = new ConcurrentHashMap<>(); @Override public void onApplicationEvent(ContextRefreshedEvent event) { // Spring上下文刷新时,扫描JSF中所有DataHandler实现类 FacesContext facesContext = FacesContext.getCurrentInstance(); if (facesContext == null) { // 启动时FacesContext未初始化的话,延迟到第一次使用时加载 return; } Application application = facesContext.getApplication(); // 获取所有实现DataHandler的JSF Bean名称 List<String> beanNames = application.getBeanManager().getBeans(DataHandler.class).stream() .map(Bean::getName) .collect(Collectors.toList()); for (String beanName : beanNames) { DataHandler handler = getBeanFromFacesCtx("#{" + beanName + "}", DataHandler.class); handlerMap.put(handler.getControllerPrefix(), handler); } } // 封装原有获取JSF Bean的方法 private <T> T getBeanFromFacesCtx(String beanQualifier, Class<T> clazz) { FacesContext ctx = FacesContext.getCurrentInstance(); Application app = ctx.getApplication(); T bean = app.evaluateExpressionGet(ctx, beanQualifier, clazz); return Optional.ofNullable(bean) .orElseThrow(() -> new UnsupportedOperationException("无法获取Bean: " + beanQualifier)); } public DataHandler getHandlerByPrefix(String controllerPrefix) { return handlerMap.get(controllerPrefix); } }
在Spring Service中注入桥接类并使用:
@Service public class Business { private final DataHandlerBridge handlerBridge; public Business(DataHandlerBridge handlerBridge) { this.handlerBridge = handlerBridge; } public void getData(String controllerPrefix) { DataHandler handler = handlerBridge.getHandlerByPrefix(controllerPrefix); if (handler == null) { throw new IllegalArgumentException("未找到对应前缀的处理器: " + controllerPrefix); } handler.getData(); } }
注意:如果Spring启动时FacesContext尚未初始化,可将扫描逻辑移到getHandlerByPrefix方法中,首次调用时再完成初始化。
方案3:CDI与Spring集成
如果项目同时使用CDI(JSF默认依赖CDI)和Spring,可以通过Spring的CdiBeanFactoryPostProcessor将CDI Bean导入到Spring上下文,实现直接注入。
添加Spring配置类:
@Configuration public class CdiIntegrationConfig { @Bean public static CdiBeanFactoryPostProcessor cdiBeanFactoryPostProcessor() { return new CdiBeanFactoryPostProcessor(); } }
之后在Spring Service中就能直接注入所有DataHandler实现类:
@Service public class Business { private final List<DataHandler> dataHandlers; public Business(List<DataHandler> dataHandlers) { this.dataHandlers = dataHandlers; } public void getData(String controllerPrefix) { DataHandler handler = dataHandlers.stream() .filter(h -> controllerPrefix.equals(h.getControllerPrefix())) .findFirst() .orElseThrow(() -> new IllegalArgumentException("未找到对应前缀的处理器: " + controllerPrefix)); handler.getData(); } }
注意:需要确保项目依赖Spring的CDI集成模块(spring-context已包含),且CDI容器已正常初始化。
内容的提问来源于stack exchange,提问作者Cristian Piste

