当与查询参数关联的转换/验证失败时执行重定向

Posted

技术标签:

【中文标题】当与查询参数关联的转换/验证失败时执行重定向【英文标题】:Performing a redirect, when conversion / validation associated with query parameters fails 【发布时间】:2014-05-27 19:30:51 【问题描述】:

以下是<f:viewAction>的简单用例。

<f:metadata>
    <f:viewParam name="id" value="#testManagedBean.id" maxlength="20"/>
    <f:viewAction action="#testManagedBean.viewAction"/>
</f:metadata>

涉及的托管 bean。

@ManagedBean
@ViewScoped
public final class TestManagedBean implements Serializable 

    private static final long serialVersionUID = 1L;
    private Long id; //Getter and setter.

    public void viewAction() 
        System.out.println("viewAction() called : " + id);
    

参数id 是通过URL 传递的。当通过相关 URL 传递像 xxx 这样的非数字值并且未调用与 &lt;f:viewAction&gt; 的侦听器关联的 viewAction() 方法时,会出现转换错误。

在这种情况下,id 的值是 null。我想重定向到另一个页面,当id 不可转换为所需的目标类型(如本例中)或id 未针对指定的验证标准进行验证以避免可能引发的潜在异常时每当尝试在相应的托管 bean 中访问这些参数时,PrimeFaces 的 LazyDataModel#load() 方法或相关托管 bean 中的其他位置。为此,应调用viewAction() 方法。

如何进行?我应该使用

<f:event type="preRenderView">

结合&lt;f:viewAction&gt;?

【问题讨论】:

目前,如果idnull,那么viewAction不被调用? 不,然后它被调用。它被调用,例如,如果 URL 看起来像这样,www.example.com/abc.jsf?id=id 在这里没有给出值)。当通过 URL 提供的 id 的值无法转换为 java.lang.Long 时,它不会被调用,例如 www.example.com/abc.jsf?id=xxx 【参考方案1】:

为什么不自己验证id

@ManagedBean
@ViewScoped
public final class TestManagedBean implements Serializable

    private String id;     //Getter and setter.
    private Long validId;  //Getter and setter.

    public void viewAction() 
        try 
            validId = Long.parseLong(id);
         catch (NumberFormatException ex) 
            FacesContext facesContext = FacesContext.getCurrentInstance();
            String outcome = "redirect.xhtml";
            facesContext.getApplication().getNavigationHandler().handleNavigation(facesContext, null, outcome);
        
    

【讨论】:

对不起,监听器方法,viewAction() 本身没有被调用,当作为查询字符串参数传递的id 的值不能像xxx 一样转换为java.lang.Long 时。那么我们如何在viewAction() 方法中验证id?这将导致 PrimeFaces LazyDataModel&lt;T&gt;load() 方法中的 NullPointerException 在我的测试用例 (Mojarra 2.2.6) 中,viewAction() 总是被调用。请注意,我已将id 的类型更改为String。 虽然我没有展示实际代码以避免代码噪音,id 实际上是使用自定义 JSF 转换器转换为 JPA 实体。我目前在&lt;f:metadata&gt; 中使用了一个额外的&lt;f:event&gt; 标记(它可能完全没有必要)在其侦听器方法中进行重定向,只要id 为空。如果没有更多答案,我会在几天后接受这个答案。【参考方案2】:

这是specified 行为。当PROCESS_VALIDATIONS 阶段以validation failure 结束时,UPDATE_MODEL_VALUESINVOKE_APPLICATION 阶段都将被跳过。与&lt;h:form&gt; 的“常规”形式完全一样。将&lt;f:viewParam&gt; 视为&lt;h:inputText&gt;,将&lt;f:viewAction&gt; 视为&lt;h:commandButton&gt;,它会变得更加清晰。

对于您的特殊要求,在转换/验证失败时执行重定向,至少有 3 种解决方案:

    如您所见,添加&lt;f:event listener&gt;。我宁愿挂在postValidate 事件上,而不是为了更好的自记录性。

    <f:metadata>
        <f:viewParam name="id" value="#bean.id" maxlength="20" />
        <f:event type="postValidate" listener="#bean.redirectIfNecessary" />
        <f:viewAction action="#bean.viewAction" />
    </f:metadata>
    
    public void redirectIfNecessary() throws IOException 
        FacesContext context = FacesContext.getCurrentInstance();
    
        if (!context.isPostback() && context.isValidationFailed()) 
            context.getExternalContext().redirect("some.xhtml");
        
    
    

    FacesContext#isPostback() 的检查可防止在同一视图(如果有)中对“常规”表单的验证失败执行重定向。


    扩展内置LongConverter,从而在getAsObject() 中执行重定向(验证器不适合,因为Long 的默认转换器在非数字输入上已经失败;如果转换器失败,验证器将永远不会被触发)。然而,这是糟糕的设计(紧耦合)。

    <f:metadata>
        <f:viewParam name="id" value="#bean.id" converter="idConverter" />
        <f:viewAction action="#bean.viewAction" />
    </f:metadata>
    
    @FacesConverter("idConverter")
    public class IdConverter extends LongConverter 
    
        @Override
        public Object getAsObject(FacesContext context, UIComponent component, String value) 
            if (value == null || !value.matches("[0-9]1,20")) 
                try 
                    context.getExternalContext().redirect("some.xhtml");
                    return null;
                
                catch (IOException e) 
                    throw new FacesException(e);
                
            
            else 
                return super.getAsObject(context, component, value);
            
        
    
    
    

    如有必要,您可以在&lt;f:viewParam&gt; 中使用&lt;f:attribute&gt; 将参数“传递”给转换器。

    <f:viewParam name="id" value="#bean.id" converter="idConverter">
        <f:attribute name="redirect" value="some.xhtml" />
    </f:viewParam>
    
    String redirect = (String) component.getAttributes().get("redirect");
    context.getExternalContext().redirect(redirect);
    

    创建一个与&lt;f:event listener&gt; 基本相同但不需要额外的支持bean 方法的自定义标记处理程序。

    <html ... xmlns:my="http://example.com/ui">
    
    <f:metadata>
        <f:viewParam name="id" value="#bean.id" maxlength="20" />
        <my:viewParamValidationFailed redirect="some.xhtml" />
        <f:viewAction action="#bean.viewAction" />
    </f:metadata>
    

    com.example.taghandler.ViewParamValidationFailed

    public class ViewParamValidationFailed extends TagHandler implements ComponentSystemEventListener 
    
        private String redirect;
    
        public ViewParamValidationFailed(TagConfig config) 
            super(config);
            redirect = getRequiredAttribute("redirect").getValue();
        
    
        @Override
        public void apply(FaceletContext context, UIComponent parent) throws IOException 
            if (parent instanceof UIViewRoot && !context.getFacesContext().isPostback()) 
                ((UIViewRoot) parent).subscribeToEvent(PostValidateEvent.class, this);
            
        
    
        @Override
        public void processEvent(ComponentSystemEvent event) throws AbortProcessingException 
            FacesContext context = FacesContext.getCurrentInstance();
    
            if (context.isValidationFailed()) 
                try 
                    context.getExternalContext().redirect(redirect);
                
                catch (IOException e) 
                    throw new AbortProcessingException(e);
                
            
        
    
    
    

    /WEB-INF/my.taglib.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <facelet-taglib
        xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
        version="2.0"
    >
        <namespace>http://example.com/ui</namespace>
    
        <tag>
            <tag-name>viewParamValidationFailed</tag-name>
            <handler-class>com.example.taghandler.ViewParamValidationFailed</handler-class>
        </tag>  
    </facelet-taglib>
    

    /WEB-INF/web.xml

    <context-param>
        <param-name>javax.faces.FACELETS_LIBRARIES</param-name>
        <param-value>/WEB-INF/my.taglib.xml</param-value>
    </context-param>
    

    的确,这是一段代码,但它最终以干净且可重复使用的&lt;my:viewParamValidationFailed&gt; 标记结束,并且实际上非常适合新的OmniFaces 功能。

【讨论】:

仅供参考:&lt;o:viewParamValidationFailed&gt; 已添加到 OmniFaces,并将在 2.0 中提供。另见omnifaces.org/docs/javadoc/current/org/omnifaces/taghandler/… 在最后一个示例或&lt;o:viewParamValidationFailed&gt; 中,是否可以在转换或验证失败时阻止执行相关视图/请求范围的托管bean 中的@PostConstruct 方法?有时,@PostConstruct 方法旨在调用一个或多个业务方法,即使转换或验证失败,这些方法也会非常不必要地执行昂贵的数据库调用。 @Tiny:不。要么使用&lt;f:viewAction&gt;,要么检查isValidationFailed()

以上是关于当与查询参数关联的转换/验证失败时执行重定向的主要内容,如果未能解决你的问题,请参考以下文章

数据当用户在验证失败(Python,Django)时被重定向回表单时如何保留表单?

如何在使用 keycloak 进行身份验证时处理 uri 重定向

从重定向角度 5 中获取查询参数

带有透明重定向的 PayPal PayFlow Pro - 用户身份验证失败问题/文档

Safari 中视图状态 MAC 的验证失败

Laravel如何在自动重定向验证后发送参数以查看