You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Spring管理的Service中注入所有实现某接口的JSF Bean?

Spring Service集成JSF DataHandler实现类的最优方案

背景

你定义了统一的数据处理接口:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 03:00:09