JSF - 如何从支持 bean 操作方法内部确定当前 JSP 页面

发布于 2024-09-26 05:17:45 字数 2660 浏览 1 评论 0原文

我有一个包含两个 JSP 页面的 JSF 应用程序,这两个页面都显示来自会话范围容器对象的一些相同数据。每个页面显示的数据都不同,每个数据表中的数据在页面之间也有所不同。到目前为止一切正常。

我的问题是,我在如何确定从我的支持 bean 操作方法内部请求的页面方面有点作弊。在每个页面上,我都使用了数据表的绑定。

draftReport.jsp

<t:dataTable 
    border="1"
    id="reportDraftDataTable"
    binding="#{controller.reportDraftDataTable}"
    value="#{sessionData.reportDraftAdapterList}" 
    var="currentRow" 
    rowClasses="dataTableOddRow, dataTableEvenRow">

report.jsp

<t:dataTable 
    border="1"
    id="reportDataTable"
    binding="#{controller.reportDataTable}"
    value="#{sessionData.reportAdapterList}" 
    var="currentRow" 
    rowClasses="dataTableOddRow, dataTableEvenRow">

我有一个请求范围的支持 bean(名为 Controller),其中包含这些页面的一些操作方法。我不想在支持 bean 上重复代码(每个类似的 JSP 页面都有一个类似的方法),而是想弄清楚正在呈现哪个页面,并将其用作通用处理程序方法(可以处理两个页面的操作)的参数。支持豆。所以我作弊并这样做了:

public class Controller {
    ...

    private HtmlDataTable preArrivalReportDataTable;
    private HtmlDataTable preArrivalReportDraftDataTable;
    private static enum ReportType {
        NON_DRAFT,
        DRAFT
    }
    ...

    private ReportType determineReportTypeFromControlBindings() {
        Validate.isTrue(this.preArrivalReportDataTable != null ^
                this.preArrivalReportDraftDataTable != null,
            "Either preArrivalReportDataTable XOR " +
            "preArrivalReportDraftDataTable must be null in " +
            "determineReportTypeFromControlBindings()");
        if (this.preArrivalReportDataTable != null) {
            return ReportType.NON_DRAFT;
        } else {
            return ReportType.DRAFT;
        }
    }
    ...

    public String actionOnReport() {
        ReportType reportType = null;
        reportType = determineReportTypeFromControlBindings();
        handleReportAction(reportType);
        return "REFRESH";
    }
    ...
}

这在我的 Controller 类的操作方法中工作正常,但是随后我需要添加另一个方法,最终破坏了我的黑客代码:

    public String getStyleClass() {
        ReportType reportType = determineReportTypeFromControlBindings();
        switch (reportType) {
            case NON_DRAFT:
                return "styleA";
            case DRAFT:
                return "styleB";
            default:
                return null;
        }
    }

在我的 JSP 中,JSF-EL 表达式位于我在支持 bean 中使用的数据表来确定我所在的页面。此时,defineReportTypeFromControlBindings() 在验证检查时抛出异常,可能是因为控件绑定尚未发生。

我对这种情况的发生并不感到惊讶。总感觉方式不对。但我的问题是:

从请求范围的支持 bean 操作方法确定当前请求的 JSP 页面的正确方法是什么?

如果相关,我正在使用 MyFaces 1.2 Tomahawk 标签库。

I have a JSF application containing two JSP pages that both display some of the same data from a session-scoped container object. Each page displays the data differently, each in a data table that varies between pages. This all works properly so far.

My problem is that I have been cheating a bit with how I figure out what page was requested from inside my backing bean action methods. On each page, I have used a binding for my data table.

draftReport.jsp:

<t:dataTable 
    border="1"
    id="reportDraftDataTable"
    binding="#{controller.reportDraftDataTable}"
    value="#{sessionData.reportDraftAdapterList}" 
    var="currentRow" 
    rowClasses="dataTableOddRow, dataTableEvenRow">

report.jsp:

<t:dataTable 
    border="1"
    id="reportDataTable"
    binding="#{controller.reportDataTable}"
    value="#{sessionData.reportAdapterList}" 
    var="currentRow" 
    rowClasses="dataTableOddRow, dataTableEvenRow">

I have a request-scoped backing bean (named Controller) with some of the action methods for these pages. Rather than duplicate code on the backing bean (one similar method for each similar JSP page), I wanted to figure out what page was being rendered and use that as a parameter to a generic handler method (that can handle actions from both pages) on the backing bean. So I cheated and did this:

public class Controller {
    ...

    private HtmlDataTable preArrivalReportDataTable;
    private HtmlDataTable preArrivalReportDraftDataTable;
    private static enum ReportType {
        NON_DRAFT,
        DRAFT
    }
    ...

    private ReportType determineReportTypeFromControlBindings() {
        Validate.isTrue(this.preArrivalReportDataTable != null ^
                this.preArrivalReportDraftDataTable != null,
            "Either preArrivalReportDataTable XOR " +
            "preArrivalReportDraftDataTable must be null in " +
            "determineReportTypeFromControlBindings()");
        if (this.preArrivalReportDataTable != null) {
            return ReportType.NON_DRAFT;
        } else {
            return ReportType.DRAFT;
        }
    }
    ...

    public String actionOnReport() {
        ReportType reportType = null;
        reportType = determineReportTypeFromControlBindings();
        handleReportAction(reportType);
        return "REFRESH";
    }
    ...
}

This worked OK inside action methods in my Controller class, but then I needed to add another method that finally broke my hacky code:

    public String getStyleClass() {
        ReportType reportType = determineReportTypeFromControlBindings();
        switch (reportType) {
            case NON_DRAFT:
                return "styleA";
            case DRAFT:
                return "styleB";
            default:
                return null;
        }
    }

In my JSP, the JSF-EL expression is located above the control binding for the data table that I'm using in the backing bean to determine what page I'm on. At that point the determineReportTypeFromControlBindings() throws an exception at the Validate check, presumably because the control binding has not occurred yet.

I'm not surprised that this is happening. It always felt like the wrong way. But my question is:

What is the correct way to determine the currently requested JSP page from a request-scoped backing bean action method?

In case it is relevant, I'm using the MyFaces 1.2 Tomahawk tag library.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

冷心人i 2024-10-03 05:17:45

我可以想到几种方法,一种是主动的,其中页面告诉 bean 视图是什么,一种是被动的,其中 bean 推断视图。最后一个是拥有一个抽象 bean,其中包含草稿和非草稿的具体实现。

我更喜欢最后一种方法,它感觉最像Java,而且最不hacky。但这里有一些关于如何实现前两项的基本想法。

主动:在 renderResponse 阶段调用方法来设置报告类型。我只在会话范围的 bean 中完成了此操作,不确定它在请求范围内的工作效果如何,您可能需要检查其他阶段,或者可能只是应用它,而不管实际阶段如何。

Controller.java

public void draftInitializer(PhaseEvent event) {
  if (event.getPhaseId().equals(PhaseId.RENDER_RESPONSE)) {
    reportType = DRAFT;
  }
} 

draftReport.jsp

<f:view beforePhase="#{controller.draftInitializer}">

Reactive: 从请求中获取 URL。

控制器.java

  private String getRequestURL(){
    HttpServletRequest request = (HttpServletRequest)FacesContext.getExternalContext().getRequest();
    return request.getRequestURL();
  }

  private boolean isDraft() {
    return getRequestURL().contains(DRAFT_URL_IDENTIFIER);
  }

I can think of a few approaches, one is proactive, where the page tells the bean what the view is, a reactive one, where the bean infers the view. And the last one would be to have an abstract bean with concrete implementations for draft and non-draft.

I prefer the last approach, it feels the most Java-like, and the least hacky. But here are some basic ideas on how to do the first two.

Proactive: Call a method during the renderResponse phase to set the report type. I've only done this in session scoped beans, not sure how well it will work in request scoped, you may need to check other phases, or possibly just apply it regardless of the actual phase.

Controller.java

public void draftInitializer(PhaseEvent event) {
  if (event.getPhaseId().equals(PhaseId.RENDER_RESPONSE)) {
    reportType = DRAFT;
  }
} 

draftReport.jsp

<f:view beforePhase="#{controller.draftInitializer}">

Reactive: Get the URL from the request.

Controller.java

  private String getRequestURL(){
    HttpServletRequest request = (HttpServletRequest)FacesContext.getExternalContext().getRequest();
    return request.getRequestURL();
  }

  private boolean isDraft() {
    return getRequestURL().contains(DRAFT_URL_IDENTIFIER);
  }
寂寞美少年 2024-10-03 05:17:45

因此,我最终通过检查在请求期间从 FacesContext 获取的 UIViewRoot 对象来解决这个问题。我用 RequestedPage 枚举替换了 ReportType 枚举,因为它看起来更具可读性。

public static enum RequestedPage {
    REPORT,
    REPORT_DRAFT,
    UNKNOWN
}

然后我在 Controller 类中创建了几个字符串常量和一个新方法。

private final static String REPORT_DRAFT_JSP_NAME = "draftReport.jsp";
private final static String REPORT_JSP_NAME = "report.jsp";

/**
 * This method should only be invoked from inside an action method.
 * An exception will be thrown if the method is called when either
 * the FacesContext or UIViewRoot are not available. This is normally
 * the case outside of an active request or before the RESTORE_VIEW
 * phase has been completed.
 * 
 * @return A non-null RequestedPage reference
 */
private RequestedPage determineRequestedPageFromViewId() {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    Validate.notNull(facesContext);
    UIViewRoot uiViewRoot = facesContext.getViewRoot();
    Validate.notNull(uiViewRoot);
    String viewId = uiViewRoot.getViewId();
    logger.info("view id: " + viewId);
    RequestedPage requestedPage = null;
    if (viewId.contains(REPORT_DRAFT_JSP_NAME)) {
        requestedPage = RequestedPage.REPORT_DRAFT;
    } else if (viewId.contains(REPORT_JSP_NAME)) {
        requestedPage = RequestedPage.REPORT;
    } else {
        requestedPage = RequestedPage.UNKNOWN;
    }
    return requestedPage;
}

UNKNOWN 枚举旨在覆盖我在操作方法中不关心其身份的所有页面。只要我遵守方法 Javadoc 注释中提到的约束,这似乎就可以正常工作。

这种方法唯一令人失望的事情是我想在我的 Controller 类的初始化方法中进行 RequestedPage 解析。不幸的是,这不起作用,因为初始化程序是在 RESTORE_VIEW 阶段开始之前调用的,因此 UIViewRoot 仍然为 null。

这是无法工作的代码:

@PostConstruct
public void init() {
    logger.info("init() has been invoked");
    RequestedPage requestedPage = 
        determineRequestedPageFromViewId();
    // An exception is always thrown before I get here...
    this.theRequestedPage = requestedPage;
    logger.info("init() finished");
}

我可以接受这个,除非其他人有一个简单的替代方法来替代我的方法。

So I ended up solving this by inspecting the UIViewRoot object that is obtained from the FacesContext during the request. I replaced my ReportType enum with a RequestedPage enum because it seemed more readable.

public static enum RequestedPage {
    REPORT,
    REPORT_DRAFT,
    UNKNOWN
}

Then I created a couple String constants and a new method inside my Controller class.

private final static String REPORT_DRAFT_JSP_NAME = "draftReport.jsp";
private final static String REPORT_JSP_NAME = "report.jsp";

/**
 * This method should only be invoked from inside an action method.
 * An exception will be thrown if the method is called when either
 * the FacesContext or UIViewRoot are not available. This is normally
 * the case outside of an active request or before the RESTORE_VIEW
 * phase has been completed.
 * 
 * @return A non-null RequestedPage reference
 */
private RequestedPage determineRequestedPageFromViewId() {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    Validate.notNull(facesContext);
    UIViewRoot uiViewRoot = facesContext.getViewRoot();
    Validate.notNull(uiViewRoot);
    String viewId = uiViewRoot.getViewId();
    logger.info("view id: " + viewId);
    RequestedPage requestedPage = null;
    if (viewId.contains(REPORT_DRAFT_JSP_NAME)) {
        requestedPage = RequestedPage.REPORT_DRAFT;
    } else if (viewId.contains(REPORT_JSP_NAME)) {
        requestedPage = RequestedPage.REPORT;
    } else {
        requestedPage = RequestedPage.UNKNOWN;
    }
    return requestedPage;
}

The UNKNOWN enum is meant to cover all the pages whose identity I don't care about in my action methods. As long as I obey the constraints that I mention in my method Javadoc comments, this seems to work fine.

The only disappointing thing about this approach is that I wanted to do the RequestedPage resolution inside the initializer method for my Controller class. Unfortunately, this won't work because the initializer is called before the RESTORE_VIEW phase begins and therefore the UIViewRoot is still null.

Here is the code that will NOT work:

@PostConstruct
public void init() {
    logger.info("init() has been invoked");
    RequestedPage requestedPage = 
        determineRequestedPageFromViewId();
    // An exception is always thrown before I get here...
    this.theRequestedPage = requestedPage;
    logger.info("init() finished");
}

I can live with this, unless someone else has a simple alternative to my approach.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文