Spring JSF 集成:如何在 JSF 托管 bean 中注入 Spring 组件/服务?

Posted

技术标签:

【中文标题】Spring JSF 集成:如何在 JSF 托管 bean 中注入 Spring 组件/服务?【英文标题】:Spring JSF integration: how to inject a Spring component/service in JSF managed bean? 【发布时间】:2013-08-25 15:06:12 【问题描述】:

我了解托管 bean 的工作方式类似于控制器,因为您唯一的任务是将视图层与模型“链接”。

要将 bean 用作托管 bean,我必须声明 @ManagedBeanannotation,这样做我可以直接与 bean 通信 JSF。

如果我想在这个 managedBean 中注入一些组件(来自 Spring),我有两种可能的方法:

    在 ManagedBean 中选择属性(如“BasicDAO dao”)并在属性上方声明@ManagedProperty(#"basicDAO")。这样做,我从 ManagedBean 中的 Spring 注入 bean "basicDAO"

    在 ManagedBean 类中声明 @Controller,然后我将拥有 @ManagedBean@Controller 注释,一起使用。在 "BasicDAO dao" 属性中,我必须使用 Spring 中的 @Autowired

我的理解正确吗?

【问题讨论】:

【参考方案1】:

@ManagedBean@Controller

首先,你应该选择一个框架来管理你的bean。您应该选择 JSF 或 Spring(或 CDI)来管理您的 bean。虽然以下方法有效,但它从根本上是错误的:

@ManagedBean // JSF-managed.
@Controller // Spring-managed.
public class BadBean 

您最终会得到两个完全独立的同一个托管 bean 类的实例,一个由 JSF 管理,另一个由 Spring 管理。当您将其引用为#someBean 时,并不清楚实际上会在 EL 中使用哪个。如果您在faces-config.xml 中注册了SpringBeanFacesELResolver,那么它将是Spring 管理的,而不是JSF 管理的。如果您没有,那么它将是 JSF 管理的。

此外,当您从 javax.faces.* 包中声明 JSF 托管 bean 特定范围时,例如 @RequestScoped@ViewScoped@SessionScoped@ApplicationScoped,它只会被 @ManagedBean 识别和使用。 @Controller 不会理解它,因为它需要自己的 @Scope 注释。缺席时默认为单例(应用程序范围)。

@ManagedBean // JSF-managed.
@ViewScoped // JSF-managed scope.
@Controller // Spring-managed (without own scope, so actually becomes a singleton).
public class BadBean 

当您通过 #someBean 引用上述 bean 时,它将返回 Spring 管理的应用程序范围 bean,而不是 JSF 管理的视图范围 bean。


@ManagedProperty@Autowired

特定于 JSF 的 @ManagedProperty 仅适用于 JSF 管理的 bean,即当您使用 @ManagedBean 时。 Spring 特定的 @Autowired 仅适用于 Spring 管理的 bean,即当您使用 @Controller 时。以下方法或多或少等效,不能混合使用:

@ManagedBean // JSF-managed.
@RequestScoped // JSF-managed scope.
public class GoodBean 

    @ManagedProperty("#springBeanName")
    private SpringBeanClass springBeanName; // Setter required.

@Component // Spring-managed.
@Scope("request") // Spring-managed scope.
public class GoodBean 

    @Autowired
    private SpringBeanClass springBeanName; // No setter required.

请注意,当您按照javadoc 在faces-config.xml 中注册SpringBeanFacesELResolver 时,

<application>
    ...
    <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
</application>

因此您可以通过#springBeanName 在EL 中引用Spring 托管bean,然后您也可以在@ManagedProperty 中引用它们,因为它基本上设置了给定EL 表达式的评估结果。反过来,通过@Autowired 注入 JSF 托管 bean,则完全不受支持。但是,当您从 SpringBeanAutowiringSupport 扩展您的 bean 时,您可以在 JSF 托管 bean 中使用 @Autowired。这将在构造函数调用期间自动在 Spring 自动装配上下文中注册 JSF 托管 bean 实例,这意味着 @Autowired 的所有内容都将在 @PostConstruct 及更高版本中可用。

@ManagedBean // JSF-managed.
@ViewScoped // JSF-managed scope.
public class GoodBean extends SpringBeanAutowiringSupport implements Serializable 

    @Autowired
    private SpringBeanClass springBeanName; // No setter required.

    @PostConstruct
    private void init() 
        // springBeanName is now available.
    

或者当您的架构不允许从不同的基类扩展 bean 时,您始终可以在 Spring 自动装配上下文中手动注册 JSF 托管 bean 实例,如下所示。有关技巧,另请参阅 How to integrate JSF 2 and Spring 3 (or Spring 4) nicely。

@ManagedBean // JSF-managed.
@ViewScoped // JSF-managed scope.
public class GoodBean implements Serializable 

    @Autowired
    private SpringBeanClass springBeanName; // No setter required.

    @PostConstruct
    private void init() 
        FacesContextUtils
            .getRequiredWebApplicationContext(FacesContext.getCurrentInstance())
            .getAutowireCapableBeanFactory().autowireBean(this);

        // springBeanName is now available.
    


@XxxScoped@Scope

Spring 的 @Scope 对 JSF 作用域的支持有限。 JSF 的@ViewScoped 没有等价物。您基本上要么自行开发自己的范围,要么坚持在 Spring 自动装配上下文中手动注册 JSF 托管 bean 实例,如上所示。

而且,另一方面,Spring WebFlow 在 JSF 2.2 中通过新的 @FlowScoped 注释被接管。因此,如果您碰巧已经使用 JSF 2.2,那么如果您只想要流范围,则不一定需要使用 Spring WebFlow。


CDI - 试图统一它

从 Java EE 6 开始,CDI 作为 Spring DI 的标准替代品提供。它分别具有 @Named@Inject 注释以及它自己的一组范围。我不确定它如何与 Spring 交互,因为我不使用 Spring,但 @Inject@ManagedBean 内工作,而 @ManagedProperty@ManagedBean 内可以引用 @Named bean。另一方面,@ManagedProperty@Named bean 中不起作用。

CDI 的目的是将所有不同的 bean 管理框架统一到一个规范/接口中。 Spring 可能是一个完整的 CDI 实现,但他们选择仅部分实现它(仅支持 JSR-330 javax.inject.*,但不支持 JSR-299 javax.enterprise.context.*)。另见Will Spring support CDI? 和this tutorial。

JSF 将迁移到 CDI 进行 bean 管理,并在未来的版本中弃用 @ManagedBean 和朋友。

@Named // CDI-managed.
@ViewScoped // CDI-managed scope.
public class BetterBean implements Serializable 

    @Inject
    private SpringBeanClass springBeanName; // No setter required.

    @PostConstruct
    private void init() 
        // springBeanName is now available.
    

另见:

When is it necessary or convenient to use Spring or EJB3 or all of them together? JSF Service Layer Backing beans (@ManagedBean) or CDI Beans (@Named)? Using JSF as view technology of Spring MVC How to install and use CDI on Tomcat?

【讨论】:

您说:“JSF 特定的 @ManagedProperty 仅在 JSF 托管的 bean 中有效,即当您使用 @ManagedBean 时”。但是我使用@ManagedProperty 来引用Spring-Bean,这很好用。我怎么说:我使用的是@ManagedProperty(#'basicDAO'),而bean'basicDAO'是一个@Repository bean。 @ManagedProperty 是将spring与jsf(注入bean)集成的方法(我遇到过)。我相信什么(如果我错了,请纠正我) org.springframework.web.jsf.el.SpringBeanFacesELResolver 将 jsf bean 传播到 Spring Bean。通常我们创建构成jsf的Spring容器(org.springframework.web.context.ContextLoaderListener)。正如@BalusC 所说,到目前为止,Spring 不支持 Java EE 6 CDI。我非常想要 @ConversationalScope,它不受具有 JSF 的弹簧容器支持。 因此,ManagedProperty 类似于@Autowired,但在 JSF 中。 :D 一个 54 个赞成的答案被一个引用已弃用注释的新答案替换为已接受的答案...哇... @BalusC 关于 MVC 概念,您将 JSF 托管 bean 视为视图还是控制器?【参考方案2】:

执行此操作的简单方法是通过 XML。我在已经制作的 jsf 托管 bean 中使用了 @Component,但 @Autowired 不起作用,因为托管 bean 已经存在于 faces-config.xml 中。如果必须在 xml 文件中保留该托管 bean 定义及其托管属性,则建议将 spring bean 作为另一个托管属性添加到托管 bean 标记中。这里的 spring bean 是在 spring-config.xml 中定义的(可以在某个地方交替自动装配)。请参考 https://***.com/a/19904591/5620851

由我编辑。我建议要么通过注解@Managed 和@Component 完全实现它,要么通过xml 来实现它。

【讨论】:

【参考方案3】:

还有另一种在 JSF 管理的 bean 中使用 Spring 管理的 bean 的方法,只需从 SpringBeanAutowiringSupport 扩展您的 JSF bean,Spring 将处理依赖注入。

@ManagedBean // JSF-managed.
@ViewScoped // JSF-managed scope.
public class GoodBean extends SpringBeanAutowiringSupport 

    @Autowired
    private SpringBeanClass springBeanName; // No setter required.

    // springBeanName is now available.

【讨论】:

我知道 @ManagedBean 在 JSF 中已弃用。但是,当您看到提问者在问题中提到@ManagedBean 时,您又看到“54 个赞成的答案”也提到了@ManagedBean。如您所见,我以“还有另一种方式”开始回答。老实说,我没想到我的答案会被接受,而不是@BalusC 的出色答案。我只是想展示另一种更简单的方法。我不明白我怎么能配得上那些“我几乎倾向于投反对票”的话:)“问这个问题的同一个人?”大声笑,不 :D 你以前试过吗? :)【参考方案4】:

通过利用当前 WebApplication 上下文的 getBean,您可以在不使用 @Autowired 的情况下自动装配单个 bean。

更多详情请参考@BalusC 的回答。这只是对他的示例的轻微修改:

@ManagedBean // JSF-managed.
@ViewScoped // JSF-managed scope.
public class GoodBean implements Serializable 

    // @Autowired // No Autowired required
    private SpringBeanClass springBeanName; // No setter required.

    @PostConstruct
    private void init() 
        WebApplicationContext ctx = FacesContextUtils.getWebApplicationContext(FacesContext.getCurrentInstance());
        this.springBeanName = ctx.getBean(SpringBeanClass.class);
        // springBeanName is now available.
    

【讨论】:

以上是关于Spring JSF 集成:如何在 JSF 托管 bean 中注入 Spring 组件/服务?的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot JSF 集成

JSF 与 Spring Security 的集成

JSF - Spring 安全集成问题

JSF Spring Bean 设置属性

Spring Security 4 和 JSF 2 集成

我们可以集成 JSF 2.0 + Spring 4.2.X + Spring Security 4.2.X [重复]