Using Spring's LocalValidatorFactoryBean with JSF

倾然丶 夕夏残阳落幕 提交于 2019-12-05 18:10:20

It is definately the way to go to add your own custom ValidatorFactory to the application map using the key BeanValidator.VALIDATOR_FACTORY_KEY. But instead of using a javax.faces.event.SystemEventListener, you could also approach it from the spring side. Registering your ValidatorFactory as an attribute in the ServletContext will be enough for it to be picked up and added to the application map (which is an abstraction for either the ServletContext or PortletContext, whatever you are using).

So the question is: how to add a spring bean as an attribute to the ServletContext. My solution was to use a helper bean that implements ServletContextAware:

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());
        }
    }

}

Note that you could use this class for any type of bean you want to add to the ServletContext.
In your spring context, you would then add:

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

where "validator" is the id of your LocalValidatorFactoryBean.

Hardy

I am not a spring expert, but I would expect that you either define a PersonManager in beans.xml as well or that you annotate it with @Component. See also Autowiring Unmanaged Beans Annotated With @Component

I also faced the same issue. In my case Spring+MyFaces+RichFaces are used. During the application startup Spring creates it's LocalValidatorFactoryBean, but MyFaces doesn't use that bean as a validation factory. Instead MyFaces and RichFaces both used their own validators even with spring-faces module.

To figure out how to make faces use LocalValidatorFactoryBean I looked inside javax.faces.validator.BeanValidator createValidatorFactory method. This method is used by MyFaces to create ValidatorFactory every time when validation is required. Inside of that method you can see the following:

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");
        }
    }
}

So as you can see it first tries to load ValidatorFactory from context before creating a new instance. So I implemented the following solution to make faces use Spring LocalValidatorFactoryBean: I created a SystemEventListener which runs on PostConstructApplicationEvent. This listener get's a Spring WebApplicationContext from servlet context, retrieves instance of LocalValidatorFactoryBean from it and stores it in 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();
    }
}

So when MyFaces try to get ValidatorFactory for the first time, LocalValidatorFactoryBean is already there and MyFaces don't create a new instance.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!