【问题标题】:How to inject in @FacesValidator with @EJB, @PersistenceContext, @Inject, @Autowired如何使用@EJB、@PersistenceContext、@Inject、@Autowired 注入@FacesValidator
【发布时间】:2011-11-26 04:55:16
【问题描述】:

如何在@FacesValidator 中注入@EJB@PersistenceContext@Inject@AutoWired 等依赖项?在我的具体情况下,我需要通过 @AutoWired 注入 Spring 托管 bean:

@FacesValidator("emailExistValidator")
public class EmailExistValidator implements Validator {

    @Autowired
    private UserDao userDao;

    // ...
}

但是,它没有被注入,它仍然是null,导致java.lang.NullPointerException。 看来@EJB@PersistenceContext@Inject也不起作用。

如何在我的验证器中注入服务依赖项以便我可以访问数据库?

【问题讨论】:

    标签: spring validation jsf-2 dependency-injection service-layer


    【解决方案1】:

    JSF 2.3+

    如果您已经使用 JSF 2.3 或更新版本,并且想要通过例如注入 CDI 支持的工件。 @EJB@PersistenceContext@Inject,然后只需将 managed=true 添加到 @FacesValidator 注释即可使其由 CDI 管理。

    @FacesValidator(value="emailExistValidator", managed=true)
    

    JSF 2.2-

    如果您还没有使用 JSF 2.3 或更新版本,那么您基本上需要将其设为托管 bean。使用 Spring 的 @Component、CDI 的 @Named 或 JSF 的 @ManagedBean 而不是 @FacesValidator 以使其成为托管 bean,从而有资格进行依赖注入。

    例如,假设您要使用 CDI 的@Named

    @Named
    @ApplicationScoped
    public class EmailExistValidator implements Validator {
        // ...
    }
    

    您还需要在 EL 中通过 #{name} 将其作为托管 bean 引用,而不是作为硬编码字符串中的验证器 ID。因此,所以

    <h:inputText ... validator="#{emailExistValidator.validate}" />
    

    而不是

    <h:inputText ... validator="emailExistValidator" />
    

    <f:validator binding="#{emailExistValidator}" />
    

    而不是

    <f:validator validatorId="emailExistValidator" />
    

    对于 EJB,有一种解决方法是从 JNDI 中手动获取它,另请参阅 Getting an @EJB in @FacesConverter and @FacesValidator

    如果您碰巧使用 JSF 实用程序库 OmniFaces,从 1.6 版开始,它添加了对在 @FacesValidator 类中使用 @Inject@EJB 的透明支持,无需任何额外配置或注释。另见the CDI @FacesValidator showcase example

    另见:


    【讨论】:

    • 1) 对不起,我不做 Spring。我做 EJB 并且对 CDI 有所了解。但我真的不能进入春天的细节。 2)因为你不想在不同的请求之间共享 DAO 状态。 3) binding 期望在 EL 范围内的任何地方都有一个具体实例(由 @ManagedBean@Named 准备)。 validatorId 需要一个 @FacesValidator id(但由于我们删除了它,它不再存在)。
    • 如果您的 DAO 类拥有一个状态,并且您将其设置为会话或应用程序范围,那么它将在多个请求或用户之间共享。这可能会导致不希望的结果/行为(即不是线程安全的)。如果您的 DAO 类不包含任何状态(对于您使用 JPA 的情况,也不是来自持久性上下文!),那么您可以安全地将其设置为会话或应用程序范围。
    • 持态是什么意思,请举个小例子?
    • 一个对变化敏感的实例变量,被一个或多个方法使用。例如。 public class FooDAO { private Bar bar; } 这里,barFooDAO 类的状态。如果它在会话或应用程序范围内并且一个请求更改了它,那么它将反映在所有其他请求/会话中。这可能是不可取的。在这种情况下,您应该将其保留在请求范围内,以便它不会被所有其他请求/会话共享。但是,如果它不保持状态并且所有工作都是基于同一个方法块内的变量完成的,那么您可以安全地放入会话/应用程序范围内。
    • 如果 DAO 是无状态的(即没有任何字段/属性),那么您可以安全地将其保存在广泛的范围内。但如果它是有状态的(即具有任何对基于请求的更改敏感的字段/属性),那么您宁愿将其保留在请求范围内。
    【解决方案2】:

    如果您使用的是 Java EE 8 和/或 JSF 2.3,现在可以注入 JSF 验证器。

    在 Payara Server 5.192 #badassfish 上使用 Mojarra 2.3.9.payara-p2 进行测试。

    <?xml version='1.0' encoding='UTF-8' ?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:h="http://xmlns.jcp.org/jsf/html">
        <h:body>
            Hello from Facelets
            <h:form>
                <h:messages/>
                <h:inputText value="#{someBean.txtField}" validator="someValidator"/>
            </h:form>
        </h:body>
    </html>
    
    import javax.inject.Named;
    import javax.enterprise.context.Dependent;
    
    @Named(value = "someBean")
    @Dependent
    public class SomeBean {
    
      private String txtField;
    
      public String getTxtField() {
        return txtField;
      }
    
      public void setTxtField(String txtField) {
        this.txtField = txtField;
      }
    }
    
    import javax.faces.application.FacesMessage;
    import javax.faces.component.UIComponent;
    import javax.faces.context.FacesContext;
    import javax.faces.validator.FacesValidator;
    import javax.faces.validator.Validator;
    import javax.faces.validator.ValidatorException;
    import javax.inject.Inject;
    
    @FacesValidator(value = "someValidator", managed = true)
    public class CustomValidator implements Validator<String> {
    
      @Inject
      NewClass newClass;
    
      @Override
      public void validate(FacesContext context, UIComponent component, String value)
          throws ValidatorException {
    
        System.out.println("validator running");
        System.out.println("injected bean: " + newClass);
    
        if (value != null && value.equals("badvalue")) {
          throw new ValidatorException(new FacesMessage(newClass.getMessage()));
        }
      }
    }
    
    public class NewClass {
    
      public String getMessage() {
        return "secret message";
      }
    }
    
    import javax.faces.annotation.FacesConfig;
    
    // WITHOUT THIS INJECTION WILL NOT WORK!
    @FacesConfig(version = FacesConfig.Version.JSF_2_3)
    public class ConfigurationBean {
    }
    

    应该呈现如下内容:

    在意识到需要ConfigurationBean 之前,我在墙上撞了大约一个小时。来自文档:

    FacesConfig.Version.JSF_2_3 此值指示 CDI 应用于 EL 解析以及启用 JSF CDI 注入,如第 5.6.3 节“用于 EL 解析的 CDI”和第 5.9 节“CDI 集成”中所述

    从这个 GitHub 问题,https://github.com/eclipse-ee4j/glassfish/issues/22094:

    默认情况下,JSF 2.3 以与 JSF 早期版本兼容的模式运行,除非应用程序中包含 CDI 托管 bean,并带有注释 @javax.faces.annotation.FacesConfig。要切换到 JSF 2.3 模式,您需要如下配置 bean:(显示 ConfigurationBean)

    ...

    JSF 需要切换到“当前版本”这一事实引起了极大的争议。几乎整个 EG 都投了反对票,但最终我们无法绕过 JCP 为 Java EE 设置的向后兼容性要求以及规范负责人强制执行的要求。

    【讨论】:

    • 有趣的是,注释@FacesConfig(version = FacesConfig.Version.JSF_2_3) 必须存在,即使version="2.3" 已经在 faces-config.xml 中设置。这需要一些时间才能弄清楚。
    猜你喜欢
    • 2011-12-01
    • 2016-01-30
    • 2015-12-12
    • 1970-01-01
    • 2015-02-24
    • 1970-01-01
    • 2015-02-14
    • 1970-01-01
    相关资源
    最近更新 更多