Как сделать перенаправление в JSF

у меня есть веб-приложение, где пользователи могут быть направлены непосредственно на некоторых определенных страниц (например, страница, где он может просматривать или редактировать элемент). Для этого мы предоставляем определенный url-адрес. Эти URL-адреса расположены за пределами текущее веб-приложение (т. е. они могут присутствовать в другом веб-приложении или в электронном письме).

url выглядит как http://myserver/my-app/forward.jsf?action=XXX&param=YYY, где:

  • действие представляет страницу, на которой пользователь будет переориентированный. Вы можете рассматривать это как from-outcome любого действия JSF в navigation-case на faces-config.xml.
  • actionParam является параметром для предыдущего действия (обычно id элемента)

так, например, у меня могут быть такие URL-адреса:

  • http://myserver/my-app/forward.jsf?action=viewItem&actionParam=1234
  • http://myserver/my-app/forward.jsf?action=editItem&actionParam=1234

конечно, у меня есть класс Java (bean), который проверит некоторые ограничения безопасности (т. е. пользователь может видеть / редактировать соответствующий пункт?), а затем перенаправить пользователя на нужную страницу (например,edit.xhtml, view.xhtml или access-denied.xhtml).


текущая реализация

в настоящее время, мы имеем основной путь выполнить вперед. Когда пользователь нажимает на ссылку, вызывается Следующая страница XHTML:

<html>
    <body id="forwardForm">
        <h:inputHidden id="myAction" binding="#{forwardBean.hiddenAction}"/>
        <h:inputHidden id="myParam" binding="#{forwardBean.hiddenActionParam}"/>
        <h:commandButton id="forwardBtn" actionListener="#{forwardBean.doForward}" style="display: none;"/>
    </body>
    <script type="text/javascript">
        document.getElementById('forwardForm:forwardBtn').click();
    </script>
</html>

Как видите, я связываю два <h:inputHidden> компоненты в моем Java bean. Они будут использоваться для хранения значения как action и actionParam запрос параметр (используя FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("actiontParam");). Я также предоставляю doForward метод, который будет вызываться сразу после отображения страницы, который перенаправит (снова) пользователя на реальную страницу. Метод:

public void doForward(ActionEvent evt) {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    String redirect = // define the navigation rule that must be used in order to redirect the user to the adequate page...
    NavigationHandler myNav = facesContext.getApplication().getNavigationHandler();
    myNav.handleNavigation(facesContext, null, redirect);
}

это решение работает, но у меня есть две проблемы с этим:

  • мне не нравится, как это реализовано. Я уверен, что у меня может быть что-то более простое (используя сервлет?).
  • это решение использует Javascript, и я должен не используйте Javascript (так как этот форвард может использоваться старыми пользователями Blackberry, где Javascript не поддерживается).

у меня вопрос как рефакторинг это перенаправление / перемоткой?

техническая информация

Java 1.6, JSF 1.2, Facelets, Richfaces

4 ответов


задайте параметры запроса GET как управляемые свойства в faces-config.xml так что вам не нужно собрать их вручную:

<managed-bean>
    <managed-bean-name>forward</managed-bean-name>
    <managed-bean-class>com.example.ForwardBean</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
    <managed-property>
        <property-name>action</property-name>
        <value>#{param.action}</value>
    </managed-property>
    <managed-property>
        <property-name>actionParam</property-name>
        <value>#{param.actionParam}</value>
    </managed-property>
</managed-bean>

таким образом, запрос forward.jsf?action=outcome1&actionParam=123 позволит JSF установить action и actionParam параметры action и actionParam свойства ForwardBean.

создать небольшой вид forward.xhtml (настолько маленький, что он помещается в буфер ответа по умолчанию (часто 2KB), чтобы его можно было сбросить с помощью navigationhandler, иначе вы должны увеличить ответ буфер в конфигурации servletcontainer), который вызывает метод bean на beforePhase на f:view:

<!DOCTYPE html>
<html xmlns:f="http://java.sun.com/jsf/core">
    <f:view beforePhase="#{forward.navigate}" />
</html>

на ForwardBean может выглядеть так:

public class ForwardBean {
    private String action;
    private String actionParam;

    public void navigate(PhaseEvent event) {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        String outcome = action; // Do your thing?
        facesContext.getApplication().getNavigationHandler().handleNavigation(facesContext, null, outcome);
    }

    // Add/generate the usual boilerplate.
}

на navigation-rule говорит сам за себя (обратите внимание на <redirect /> записи, которые сделали бы ExternalContext#redirect() вместо ExternalContext#dispatch() под одеялом):

<navigation-rule>
    <navigation-case>
        <from-outcome>outcome1</from-outcome>
        <to-view-id>/outcome1.xhtml</to-view-id>
        <redirect />
    </navigation-case>
    <navigation-case>
        <from-outcome>outcome2</from-outcome>
        <to-view-id>/outcome2.xhtml</to-view-id>
        <redirect />
    </navigation-case>
</navigation-rule>

альтернативой является использование forward.xhtml as

<!DOCTYPE html>
<html>#{forward}</html>

обновить navigate() метод для вызова на @PostConstruct (который будет вызываться после построения Бина и установки всех управляемых свойств):

@PostConstruct
public void navigate() {
    // ...
}    

он имеет тот же эффект, но сбоку вид не очень самодокументируемыми. Все, что он в основном делает, это печать ForwardBean#toString() (и тем самым неявно конструируя боб, если его еще нет).


Примечание для пользователей JSF2, есть более чистый способ передачи параметров с <f:viewParam> и более надежный способ обработки перенаправления/судоходства <f:event type="preRenderView">. См. также другие:


FacesContext context = FacesContext.getCurrentInstance();
HttpServletResponse response = (HttpServletResponse)context.getExternalContext().getResponse();
response.sendRedirect("somePage.jsp");

вы должны использовать действие вместо actionListener:

<h:commandLink id="close" action="#{bean.close}" value="Close" immediate="true" 
                                   />

и в близком методе вы правы что-то вроде:

public String close() {
   return "index?faces-redirect=true";
}

где index-одна из ваших страниц(index.в XHTML)

конечно, весь этот персонал должен быть написан на нашей оригинальной странице, а не в промежуточном. И внутри close() метод вы можете использовать параметры для динамического выбора места перенаправления.


Изменить 2

я, наконец, нашел решение, реализовав свое прямое действие следующим образом:

private void applyForward() {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    // Find where to redirect the user.
    String redirect = getTheFromOutCome();

    // Change the Navigation context.
    NavigationHandler myNav = facesContext.getApplication().getNavigationHandler();
    myNav.handleNavigation(facesContext, null, redirect);

    // Update the view root
    UIViewRoot vr = facesContext.getViewRoot();
    if (vr != null) {
        // Get the URL where to redirect the user
        String url = facesContext.getExternalContext().getRequestContextPath();
        url = url + "/" + vr.getViewId().replace(".xhtml", ".jsf");
        Object obj = facesContext.getExternalContext().getResponse();
        if (obj instanceof HttpServletResponse) {
            HttpServletResponse response = (HttpServletResponse) obj;
            try {
                // Redirect the user now.
                response.sendRedirect(response.encodeURL(url));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

он работает (по крайней мере, в отношении моих первых тестов), но мне все еще не нравится, как он реализован... Есть идеи получше?


редактировать данное решение не работа. Действительно, когда doForward() вызывается функция, жизненный цикл JSF уже запущен, а затем воссоздать новый запрос не вероятный.


одна идея решить эту проблему, но мне это не очень нравится, это заставить doForward() действия во время одного из setBindedInputHidden() способ:

private boolean actionDefined = false;
private boolean actionParamDefined = false;

public void setHiddenActionParam(HtmlInputHidden hiddenActionParam) {
    this.hiddenActionParam = hiddenActionParam;
    String actionParam = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("actionParam");
    this.hiddenActionParam.setValue(actionParam);
    actionParamDefined = true;
    forwardAction();
}

public void setHiddenAction(HtmlInputHidden hiddenAction) {
    this.hiddenAction = hiddenAction;
    String action = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("action");
    this.hiddenAction.setValue(action);
    actionDefined = true;
    forwardAction();
}

private void forwardAction() {
    if (!actionDefined || !actionParamDefined) {
        // As one of the inputHidden was not binded yet, we do nothing...
        return;
    }
    // Now, both action and actionParam inputHidden are binded, we can execute the forward...
    doForward(null);
}

это решение не включает в себя вызов Javascript, и работает тут не работа.