5

我正在尝试将 bean 注入到 customConstraintValidator中。我遇到了一些事情:

  • 在 validation-api-1.1.0 中支持 CDI(提供 Beta 版)
  • Hibernate Validator 5 似乎实现了validation-api-1.1.0(可用Alpha)
  • 使用 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我认为应该能够做到这一点)。

4

3 回答 3

2

我也面临同样的问题。在我的例子中,使用了 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,然后再创建一个新实例。所以我实现了以下解决方案来使用 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 不会创建新实例。

于 2013-07-05T15:17:00.527 回答
2

这绝对是使用键 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。
在您的 spring 上下文中,您将添加:

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

其中“validator”是您的 LocalValidatorFactoryBean 的 id。

于 2014-01-22T09:51:13.693 回答
0

我不是 Spring 专家,但我希望您也可以在 beans.xml 中定义PersonManager,或者使用 @Component 对其进行注释。另请参阅自动装配使用 @Component 注释的非托管 Bean

于 2013-01-17T09:23:30.210 回答