使用Spring的LocalValidatorFactoryBean和JSF

时间:2013-01-16 14:48:38

标签: spring jsf dependency-injection bean-validation autowired

我正在尝试将bean注入自定义ConstraintValidator。我遇到过一些事情:

  • validation-api-1.1.0(可用的测试版)
  • 支持CDI
  • Hibernate Validator 5似乎实现了validation-api-1.1.0(Alpha available)
  • 使用Seam验证模块
  • 使用Spring的LocalValidatorFactoryBean

最后一个似乎最适合我的情况,因为我们已经使用Spring(3.1.3.Release)。

我已将验证器工厂添加到XML应用程序上下文并启用了注释:

<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-3.1.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.1.xsd">

    <context:component-scan base-package="com.example" />
    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
</beans>

验证员:

public class UsernameUniqueValidator implements
    ConstraintValidator<Username, String>
{
    @Autowired
    private PersonManager personManager;

    @Override
    public void initialize(Username constraintAnnotation)
    {
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context)
    {
        if (value == null) return true;
        return personManager.findByUsername(value.trim()) != null;
    }
}

验证适用于Person

public class Person
{
    @Username
    private String username;
}

支持bean:

@Named
@Scope("request")
public class PersonBean
{
    private Person person = new Person();
    @Inject
    private PersonManager personManager;

    public create()
    {
        personManager.create(person);
    }
}

在JSF页面中我有:

<p:inputText value="#{personBean.person.username}" />

调用验证器但该字段未自动装配/注入并保持为空。这当然会产生NullPointerException。

我正在使用Hibernate验证器4.2进行测试(因为LocalValidatorFactoryBean我应该能够这样做)。

3 个答案:

答案 0 :(得分:0)

我不是春天专家,但是我希望您在beans.xml中定义 PersonManager 或者使用@Component注释它。另请参阅Autowiring Unmanaged Beans Annotated With @Component

答案 1 :(得分:0)

我也面临同样的问题。在我的例子中使用Spring + MyFaces + RichFaces。在应用程序启动期间,Spring会创建它的LocalValidatorFactoryBean,但MyFaces不会将该bean用作验证工厂。相反,MyFaces和RichFaces都使用自己的验证器,即使是spring-faces模块。

要想弄清楚如何使用LocalValidatorFactoryBean,我在javax.faces.validator.BeanValidator中查看了createValidatorFactory方法。每次需要验证时,MyFaces都会使用此方法创建ValidatorFactory。在该方法内部,您可以看到以下内容:

Map<String, Object> applicationMap = context.getExternalContext().getApplicationMap();
Object attr = applicationMap.get(VALIDATOR_FACTORY_KEY);
if (attr instanceof ValidatorFactory)
{
    return (ValidatorFactory) attr;
}
else
{
    synchronized (this)
    {
        if (_ExternalSpecifications.isBeanValidationAvailable())
        {
            ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
            applicationMap.put(VALIDATOR_FACTORY_KEY, factory);
            return factory;
        }
        else
        {
            throw new FacesException("Bean Validation is not present");
        }
    }
}

因此,您可以看到它首先尝试在创建新实例之前从上下文加载ValidatorFactory。所以我实现了以下解决方案,使face使用Spring LocalValidatorFactoryBean:我创建了一个在PostConstructApplicationEvent上运行的SystemEventListener。该侦听器从servlet上下文获取Spring WebApplicationContext,从中检索LocalValidatorFactoryBean的实例并将其存储在ExternalContext ApplicationMap中。

public class SpringBeanValidatorListener implements javax.faces.event.SystemEventListener {
    private static final long serialVersionUID = -1L;

    private final Logger logger = LoggerFactory.getLogger(SpringBeanValidatorListener.class);

    @Override
    public boolean isListenerForSource(Object source) {
        return true;
    }

    @Override
    public void processEvent(SystemEvent event) {
        if (event instanceof PostConstructApplicationEvent) {
            FacesContext facesContext = FacesContext.getCurrentInstance();
            onStart(facesContext);
        }
    }

    private void onStart(FacesContext facesContext) {
        logger.info("--- onStart ---");

        if (facesContext == null) {
            logger.warn("FacesContext is null. Skip further steps.");
            return;
        }

        ServletContext context = getServletContext(facesContext);

        if (context == null) {
            logger.warn("ServletContext is not available. Skip further steps.");
            return;
        }

        WebApplicationContext webApplicationContext = (WebApplicationContext) context.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

        if (webApplicationContext == null) {
            logger.warn("Spring WebApplicationContext was not set in ServletContext. Skip further steps.");
            return;
        }

        LocalValidatorFactoryBean validatorFactory = null;

        try {
            validatorFactory = webApplicationContext.getBean(LocalValidatorFactoryBean.class);
        } catch (BeansException ex){
            logger.warn("Cannot get LocalValidatorFactoryBean from spring context.", ex);
        }

        logger.info("LocalValidatorFactoryBean loaded from Spring context.");

        Map<String, Object> applicationMap = facesContext.getExternalContext().getApplicationMap();
        applicationMap.put(BeanValidator.VALIDATOR_FACTORY_KEY, validatorFactory);

        logger.info("LocalValidatorFactoryBean set to faces context.");
    }

    private ServletContext getServletContext(FacesContext facesContext) {
        return (ServletContext) facesContext.getExternalContext().getContext();
    }
}

因此,当MyFaces第一次尝试获取ValidatorFactory时,LocalValidatorFactoryBean已经存在且MyFaces不会创建新实例。

答案 2 :(得分:0)

绝对是使用密钥BeanValidator.VALIDATOR_FACTORY_KEY将自己的自定义ValidatorFactory添加到应用程序映射的方法。 但是,除了使用javax.faces.event.SystemEventListener之外,您还可以从spring方面接近它。将ValidatorFactory注册为ServletContext中的属性就足以将其拾取并添加到应用程序映射中(无论您使用什么,它都是ServletContext或PortletContext的抽象)。

所以问题是:如何将spring bean作为属性添加到ServletContext中。我的解决方案是使用一个实现ServletContextAware的辅助bean:

public class ServletContextAttributePopulator implements ServletContextAware {

    Map<String,Object> attributes;

    public Map<String, Object> getAttributes() {
        return attributes;
    }

    public void setAttributes(Map<String, Object> attributes) {
        this.attributes = attributes;
    }

    @Override
    public void setServletContext(ServletContext servletContext) {
        for (Map.Entry<String,Object> entry : attributes.entrySet()) {
            servletContext.setAttribute(entry.getKey(), entry.getValue());
        }
    }

}

请注意,您可以将此类用于要添加到ServletContext的任何类型的bean。
在你的春天环境中,你会添加:

<bean  id="servletContextPopulator" class="my.package.ServletContextAttributePopulator">
    <property name="attributes">
    <map>
        <entry key="javax.faces.validator.beanValidator.ValidatorFactory" value-ref="validator"/>
    </map>
    </property>
</bean>

其中&#34;验证者&#34;是LocalValidatorFactoryBean的id。