论坛首页 Java版 Webwork

Portal展现机制研究

浏览 15992 次
该帖已经被评为精华帖
作者 正文
时间:2004-08-29
0、前言
作为企业信息和应用的统一入口,Portal所涵盖的内容是很广的,Portal到底应该包含那些功能也没有一个统一的定义。buaawhl这个帖子的“2.1 Dynamic Include”部分提到实际上是Portal展现机制,正好是我之前比较感兴趣的一部分,可以借这个机会整理一下。

1、需求
首先让我们看看涉及到Portal展现部分,存在哪些需求:
1)最终用户。
a. 能够自由定制自己的版面,选择想要看的部分
b. 能够更改版面的显示风格,包括布局、颜色、字体等
2)管理人员
a. 管理人员的需求和最终用户类似,但他可以制定全局的版面,权限更大
3)开发人员
a. 能够根据客户需求开发特定展现方案
b. 容易开发
上面的需求总结的比较粗略。

2、组件树
一个Portal应用可能包含十几个到上百个Portlet,为了分门别类地管理、显示这些Portlet,需要引入新的组件对Portlet进行组织,例如buaawhl提到“Portal的结构分为三层(Page,Pane,Portlet)”。对Portal/Portlet的组织方式没有标准,每种产品叫法各自不同,但作用都是类似的,在本文中,我用Book/Page/Portlet来描述这个结构。实际上这个层次不是固定的,例如Book也可以包含Book,那么我们就能得到一个无限扩展的结构。商业产品在这方面的定义很更加复杂,这里是BEA WebLogic Portal 8.1对Portal/Portlet的组织方式一张图(http://dev2dev.bea.com.cn/download/school/workshop/WorkshopCNHelp/doc/zh/portal/images/ovPortalHierarchy.gif)。

这个结构可以称之为Portal组件树,如下图:


对于用户而言,个性化自己的版面,就是在定制自己的组件树;管理人员也可以事先制订好一些组件树模版提供给最终用户。而所谓Portal展现机制,就是如何将这样的一棵组件树转换为最终的界面。


3、展现(render)机制
那么采用什么方法将Portal组件树转换为最终的界面呢?buaawhl列举了几种方法,这些方法解决了存在多个Portlet的情况。但是整个组件树的结构实际上是存放在JSP文件中,只是Portlet部分可变。实际应该中Portal组件树的结构要求的灵活性更高,例如下面的一个应用:


我们很难实现写出这个不算复杂的JSP页面,这样的一个组件树结构应该是存放在数据库(或其他存储机制)中,当用户访问应用时,Portal系统根据相关信息查询数据库,构建出当前用户所需要的组件树。

因此相关的显示页面应该是Portal系统在遍历组件树的过程中动态生成的:


现在的问题是,对于具体的组件Portal系统如何来展现它呢?例如对于Page组件,是采用HTML TABLE还是其他手段,能否提供多种机制?

这个问题的解决方法就是让组件来决定自己应该如何展现。这样遍历过程中,展现引擎只需调用每个组件的展现方法,将所有组件的展现输出组合起来,就成了最终的、完整的HTML页面,如下图:


4、展现方案
最后的问题是如何来定义每个组件的展现方法,一种方法是通过分离的JSP文件来实现,每个组件对应一个指定名称的JSP文件。

在遍历Portal组件树的过程中,除了叶子节点外其他节点都会经过两次。在上图中我们可以看到Page组件的输出内容是需要包含Portlet组件的输出内容,也就是Page组件的输出是分为“开头”和“结尾”两部分的。因此组件的JSP文件也需要分为两部分,遍历过程中每次经过时输出其中一部分,这部分可以通过JSP Tag实现。

JSP文件负责将Portal应用转换为HTML页面,以及界面的布局等等。但是对于图标、颜色、字体等情况,则可以独立出来以CSS的形式定义,只是要求实现定义好HTML页面中每种元素的CSS Class。

这样整个展现机制就是:


在这个机制中,一套JSP文件加上CSS文件加上一些图片,就是一套展现方案。开发人员可以根据客户的需求开发各种展现方案,例如在Book.jsp中加上树形导航菜单;而用户可以通过变更展现方案获得不同的显示界面。如果做得更加灵活的话,则允许每个组件选用不同的展现方案,例如存在多个Page,每个Page可以选用不同的展现方案。

另外还可以针对特定的设备(例如Palm, PPC)编写相应的展现方案,而且JSP页面并不限于输出HTML代码,还可以输出WML代码支持WAP手机。当然这种情况下还要求我们的Portlet也可以适应不同的设备。

5、小结
本文讨论Portal应用的展现机制,主要的观点是:
1)Portal组件树。构造灵活结构的Portal应用,用户自定制Portal结构的基础;
2)让组件来决定自己应该如何展现。
3)JSP文件 + CSS文件 + 图片 = 展现方案

本文的结果主要是基于对BEA WebLogic Portal 8.1的学习,欢迎各位讨论补充。
   
时间:2004-08-30
恩,其实flyisland所说的就是关于Portal部分对于View层解析的机制,很同意引入组件树这个概念,以前我倒没这么考虑过,主要是对View层进行OO,抽出共性,然后组成各不同的对象以及层次关系,由各层负责进行各自的数据的准备并最终统一交由render服务解析生成页面
   
0 请登录后投票
时间:2004-08-30
多谢flyisland提供这样图文并茂的好文章。文中还用不同颜色标志出“组件树”的不同层次,一目了然。

我可不可以这样理解。

Book.jsp 大致是这样。
[code:1]
<div>
<h1>Book's Title</h1>
<bea-tag: page ....> // page tag's attributes
</div>
[/code:1]

Page tag的代码大致是这样。
[code:1]
class PageTag extends TagSupport{
.....
public int doStartTag() throws JspException {
out.println("<table>");
out.print("<tr><td><h2>"); out.print(this.title);out.print("</h2></td></tr>");
out.println("<tr>");

// now we start to output portlets
Portlet[] portlets = getPortlets(...);

for(int i = 0; i < portlets.length; i++){
out.println("<td>");

Portlete portlet = portlets[i];
portlet.render(request, response);

out.println("</td>");
}
//

out.println("</tr>");
out.println("</table>");
}
.....
}
[/code:1]
   
0 请登录后投票
时间:2004-08-30
buaawhl那样理解也没什么问题,只不过那样一定程度上造成了树结构的固定
象我现在做的View层解析框架,是通过Page-->Layout-->Portlet来构成
Layout又支持嵌套Layout,在Layout类中只负责获取当前用户页面中的一级子节点,并将此节点的相应信息传递至对应层次的标签类,由该标签再次调用View层解析框架完成解析
   
0 请登录后投票
时间:2004-08-30
BlueDavy 写道
buaawhl那样理解也没什么问题,只不过那样一定程度上造成了树结构的固定


对。我的那种写法就把html 片断固定写在taglib里面了。
我上面的写法只是一种猜测。希望引出flyisland对weblogic portal的更深入的看法。:)

BlueDavy 写道

象我现在做的View层解析框架,是通过Page-->Layout-->Portlet来构成
Layout又支持嵌套Layout,在Layout类中只负责获取当前用户页面中的一级子节点,并将此节点的相应信息传递至对应层次的标签类,由该标签再次调用View层解析框架完成解析


Layout又支持嵌套Layout,Layout是一个标签类吗?
标签类中调用了include()方法,包括下一级的template。对吗?
能否给出一些简单的关于标签使用方法的说明性例子?比如包含Layout标签类的jsp代码等。
多谢。:-)
   
0 请登录后投票
时间:2004-08-30
buaawhl你的理解还是有些冲突:
1、“让组件来决定自己应该如何展现”,book.jsp不应该干涉page的显示,PageTag里面也不应该有调用Portlet的代码
2、的确如BlueDavy所说,你的做法还是限制了组件树的结构。按照你的写法,下图的结构岂不是要存在三个book.jsp?


我们可以用一个PortalRenderServlet来读取组件树,在遍历的过程中,由PortalRenderServlet调用各个JSP。例如当经过book节点时就调用book.jsp,经过page节点时调用page.jsp,book.jsp和page.jsp之间没有直接的关系,这样就可以支持任意结构的组件树。

至于book.jsp可以这么写法:
[code:1]book.jsp
<portal:begin>
<div>
<h1 class="portal-book-title"><%=getCurrentNode.getTitle()%> </h1>
</portal:begin>

<portal:end>
</div>
</portal:end>[/code:1]

其他JSP文件写法类似,用统一的<portal:begin>,<portal:end>。PortalRenderServlet在调用各个JSP的时候设置相关参数,使得每次只调用<portal:begin>和<portal:end>的其中一个。

回了帖子才发现原来是给引出来的
   
0 请登录后投票
时间:2004-08-31
很关心这个话题,希望此贴能继续。

PageTag里调用Portlet并没有干涉Porlet显示吧
   
0 请登录后投票
时间:2004-08-31
根据flyisland的信息,我研究了一下WebLogic Portal.
(大家可以到bea portal主页下载一个免费的开发试用版本)

WebLogic Portal Layout -- gridlayout.jsp
[code:1]
<%@ page import="com.bea.netuix.servlets.controls.layout.PlaceholderPresentationContext,
com.bea.netuix.servlets.controls.layout.GridLayoutPresentationContext"
%>
<%@ page session="false"%>
<%@ taglib uri="render.tld" prefix="render" %>

<%
GridLayoutPresentationContext layout = GridLayoutPresentationContext.getGridLayoutPresentationContext(request);
%>

<render:beginRender>
<%-- Begin Grid Layout --%>
<table cellspacing="0" cellpadding="2" border="0" style="width:100%; height:100%;">
<%
PlaceholderPresentationContext[][] grid = layout.getPlaceholderGrid();

for (int i = 0; i < grid.length; i++)
{
%>
<tr>
<%
for (int j = 0; j < grid[i].length; j++)
{
%>
<%-- Begin Grid Layout Cell [<%= i %>, <%= j %>] --%>
<td valign="top" style="width:100%;height:100%;">
<render:renderChild presentationContext="<%= grid[i][j] %>"/>
</td>
<%-- End Grid Layout Cell [<%= i %>, <%= j %>] --%>
<%
}
%>
</tr>
<%
}
%>
</table>
<%-- End Grid Layout --%>
</render:beginRender>
<render:endRender>
</render:endRender>

[/code:1]

WebLogic Portal Layout -- gridlayout_two_column.jsp
[code:1]
<%@ page import="com.bea.netuix.servlets.controls.layout.PlaceholderPresentationContext,
com.bea.netuix.servlets.controls.layout.GridLayoutPresentationContext,
com.bea.jsptools.common.Utility,
com.bea.jsptools.common.JavascriptErrorDialogBean"
%>
<%@ taglib uri="render.tld" prefix="render" %>
<%@ taglib uri="i18n.tld" prefix="i18n" %>
<%
GridLayoutPresentationContext layout = GridLayoutPresentationContext.getGridLayoutPresentationContext(request);
String tdWidth;
%>
<render:beginRender>
<%-- Begin common divs and code needed by the tools framework. Moved to this skeleton file to support the new CMS Client Portlet --%>

<!-- The div for the popups -->
<div id="popupPlaceholder" style="position: absolute;left: 0px;top: 0px;visibility: hidden;">&</div>

<!-- i18n text for tree framework -->
<script language="javascript">
// i18n Text
iSelect = "<i18n:getMessage messageName='iSelect' bundleName='gridlayout_two_column' />";
iSelected = "<i18n:getMessage messageName='iSelected' bundleName='gridlayout_two_column' />";
iExpand = "<i18n:getMessage messageName='iExpand' bundleName='gridlayout_two_column' />";
iExpanded = "<i18n:getMessage messageName='iExpanded' bundleName='gridlayout_two_column' />";
iCollapse = "<i18n:getMessage messageName='iCollapse' bundleName='gridlayout_two_column' />";
iCollapsed = "<i18n:getMessage messageName='iCollapsed' bundleName='gridlayout_two_column' />";
iLevel = "<i18n:getMessage messageName='iLevel' bundleName='gridlayout_two_column' />";
</script>

<!-- The dark transparent background for dialog boxes -->
<div id="dialogBackgroundDiv" class="dialogBackgroundDiv">&</div>

<!-- Displays a cursor when the page is getting submitted or updated -->
<div id="waitDiv" class="waiting">&</div>
<%-- End common divs and code--%>
<!-- Begin gridlayout.jsp Skeleton File -->
<table cellspacing="0" cellpadding="2" width="100%" height="100%" <render:writeAttribute name="id" value="<%=layout.getPresentationId()%>" /> <render:writeAttribute name="class" value="<%=layout.getPresentationClass()%>" defaultValue="layout-grid"/> <render:writeAttribute name="style" value="<%=layout.getPresentationStyle()%>" />>
<%
PlaceholderPresentationContext[][] grid = layout.getPlaceholderGrid();

for (int i = 0; i < (grid.length); i++)
{
%>
<tr>
<%
for (int j = 0; j < grid[i].length; j++)
{
/* This layout should only be used with a 1% wide tree */
/* on the left and a 99% wide editor on the right. */
if (j==1)
{
tdWidth = "99%";
} else {
tdWidth = "1%";
}
%>
<td class="layout-placeholder-container" style="width:<%=tdWidth%>;height:100%;" valign="top">
<render:renderChild presentationContext="<%=grid[i][j]%>"/>
</td>
<%
}
%>
</tr>
<%
}
%>
</table>
</render:beginRender>

<render:endRender>
<script language="JavaScript">
function showUserErrorMessages() {
<%
StringBuffer buffer = new StringBuffer();
List errorList =
Utility.getErrorList(request);
if (errorList != null && !errorList.isEmpty()) {
buffer.append("alert(\"");
Iterator errorsIt = errorList.iterator();
while (errorsIt.hasNext()) {
String message = "- " + (String)errorsIt.next();
buffer.append(message);
if(errorsIt.hasNext())
buffer.append("\\n");
}
buffer.append("\");");
}
out.print(buffer.toString());
%>
}

function showUserErrorMessagesWithConfirm () {
<%
JavascriptErrorDialogBean bean =
Utility.getErrorDialogBean (request);

if (bean != null && bean.isValid()) {
buffer = new StringBuffer ();
buffer.append("var prompt = confirm ('");
buffer.append (bean.getMessage()).append("\\n");
//output the optional messages.
List messagesList = bean.getMessages();
if (messagesList != null && !messagesList.isEmpty()) {
Iterator errorsIt = messagesList.iterator();
while (errorsIt.hasNext()) {
String message = "- " + (String)errorsIt.next();
buffer.append(message);
if(errorsIt.hasNext()) { buffer.append("\\n"); }
}
}
buffer.append ("');\n")
.append ("if (prompt) { ").append (bean.getJsFunction())
.append ("}\n");

out.println (buffer.toString());
}

%>
}
</script>
<!-- End gridlayout.jsp Skeleton File -->
</render:endRender>

[/code:1]

下面我把 liferay 和 jetspeed 的 layout 写在下面。

liferay layout -- layout_portlets.jsp
[code:1]
<%
boolean child = ParamUtil.get(request, "child", false);
%>

<table border="0" cellpadding="0" cellspacing="0" <%= child ? "height=\"100%\"" : "" %> width="<%= child ? "100%" : Integer.toString(RES_TOTAL) %>">
<tr>

<%
Portlet[] narrow1Portlets = layout.getNarrow1Portlets();
Portlet[] narrow2Portlets = layout.getNarrow2Portlets();
Portlet[] widePortlets = layout.getWidePortlets();

if (!layoutMaximized) {
for (int i = 0; i < columnOrder.length; i++) {
String curColumnOrder = columnOrder[i];

if (curColumnOrder.equals("n1")) {
portlets = narrow1Portlets;
portletWidth = RES_NARROW;
}
else if (curColumnOrder.equals("n2")) {
portlets = narrow2Portlets;
portletWidth = RES_NARROW;
}
else if (curColumnOrder.equals("w")) {
portlets = widePortlets;
portletWidth = RES_WIDE;
}
%>

<td id="p_l_c_<%= curColumnOrder %>" valign="top">

<%
if (portlets != null) {
for (int j = 0; j < portlets.length; j++) {
Portlet portlet = portlets[j];

%>

<%@ include file="/html/portal/view_portlet.jsp" %>

<%
}
}
%>

<table border="0" cellpadding="0" cellspacing="0" width="<%= portletWidth %>">
<tr>
<td><img border="0" height="1" hspace="0" src="<%= COMMON_IMG %>/spacer.gif" vspace="0" width="<%= portletWidth %>"></td>
</tr>
</table>
</td>

<c:if test="<%= (i + 1) < columnOrder.length %>">
<td width="10">
&
</td>
</c:if>

<%
}
}
else {
String curColumnOrder = "w";
portlets = new Portlet[0];
portletWidth = RES_TOTAL;
int j = 0;

Portlet portlet = PortletManagerUtil.getPortletById(company.getCompanyId(), StringUtil.split(layout.getStateMax())[0]);
%>

<td valign="top">
<%@ include file="/html/portal/view_portlet.jsp" %>
</td>

<%
}
%>

</tr>

<c:if test="<%= GetterUtil.getBoolean(PropsUtil.get(PropsUtil.LAYOUT_ADD_PORTLETS)) && signedIn && !layoutMaximized && !layout.isGroup() && (GetterUtil.getBoolean(PropsUtil.get(PropsUtil.UNIVERSAL_PERSONALIZATION)) || RoleLocalManagerUtil.isPowerUser(user.getUserId())) %>">
<tr>

<%
List allPortlets = PortletManagerUtil.getPortlets(user.getCompanyId());

Collections.sort(allPortlets, new PortletTitleComparator(application, locale));

for (int i = 0; i < columnOrder.length; i++) {
String curColumnOrder = columnOrder[i];
%>


<td>
<table border="0" cellpadding="0" cellspacing="0">

<form name="layout_fm<%= i %>">

<tr>
<td colspan="3">
<font class="bg" size="1">
<%= LanguageUtil.get(pageContext, curColumnOrder.equals("w") ? "add-content-to-wide-column" : "add-content-to-narrow-column") %>
</font>
</td>
</tr>
<tr>
<td colspan="3"><img border="0" height="2" hspace="0" src="<%= COMMON_IMG %>/spacer.gif" vspace="0" width="1"></td>
</tr>
<tr>
<td valign="bottom">
<font size="2">
<select name="add_<%= curColumnOrder %>_sel" style="font-family: Verdana, Arial; font-size: smaller; font-weight: normal;">
<option value=""><%= LanguageUtil.get(pageContext, "select-content") %></option>

<%
for (int j = 0; j < allPortlets.size(); j++) {
Portlet selPortlet = (Portlet)allPortlets.get(j);

String portletTitle = PortalUtil.getPortletConfig(selPortlet, application).getResourceBundle(locale).getString(WebKeys.JAVAX_PORTLET_TITLE);

boolean curColumnOrderBoolean = false;
if (curColumnOrder.equals("w")) {
if (!selPortlet.isNarrow()) {
curColumnOrderBoolean = true;
}
}
else {
if (selPortlet.isNarrow()) {
curColumnOrderBoolean = true;
}
}

if (columnOrder.length == 1) {
curColumnOrderBoolean = true;
}

if (curColumnOrderBoolean && selPortlet.isActive() && !layout.hasPortletId(selPortlet.getPortletId()) && RoleLocalManagerUtil.hasRoles(user.getUserId(), userRoles, selPortlet.getRolesArray())) {
%>

<option value="<%= selPortlet.getPortletId() %>"><%= selPortlet.isNarrow() ? StringUtil.shorten(portletTitle, 15) : portletTitle %></option>

<%
}
}
%>

</select>
</font>
</td>
<td><img border="0" height="1" hspace="0" src="<%= COMMON_IMG %>/spacer.gif" vspace="0" width="4"></td>
<td>
<font size="2">
<input style="font-family: Verdana, Arial; font-size: smaller; font-weight: normal;" type="button" value="<%= LanguageUtil.get(pageContext, "add") %>" onClick="var selPortletId = document.layout_fm<%= i %>.add_<%= curColumnOrder %>_sel[document.layout_fm<%= i %>.add_<%= curColumnOrder %>_sel.selectedIndex].value; if (selPortletId != '') { addPortlet('<%= layout.getLayoutId() %>', selPortletId, '<%= curColumnOrder %>'); };">
</font>
</td>
</tr>

</form>

</table>
</td>

<c:if test="<%= (i + 1) < columnOrder.length %>">
<td width="10">
&
</td>
</c:if>

<%
}
%>

</tr>
</c:if>

</table>

[/code:1]

JetSpeed Layout -- x-multicolumn-customize.jsp

[code:1]
.......
<%
for (int i = 0; i < columns.length; i++)
{
%>
<%= i > 0 ? "," : "" %>[
<%
List portletList = (List) columns[i];
for (int j = 0; j < portletList.size(); j++)
{
%>
<%= j > 0 ? "," : "" %>
<%
PsmlEntry entry = (PsmlEntry) portletList.get(j);
String portletId = entry.getId();
String portletTitle = (String) portletTitleMap.get(entry.getId());
String portletSkinName = entry.getSkin() == null ? "-- Default --" : entry.getSkin().getName();

// security reference
SecurityReference securityReference = jetspeed.getSecurityReference(entry);
String portletSecurityRef;
if (securityReference != null)
{
portletSecurityRef = securityReference.getParent();
if (jetspeed.getSecuritySource(entry) == 1)
{
portletSecurityRef = portletSecurityRef + "<span style=\"color:red\"><br/>" + l10n.get("SECURITY_IS_REGISTRY_LEVEL") + ".</span>";
}
else if (jetspeed.getSecuritySource(entry) == 2)
{
portletSecurityRef = portletSecurityRef + "<span style=\"color:red\"><br/>" + l10n.get("SECURITY_IS_SYSTEM_DEFAULT") + ".</span>";
}
}
else
{
portletSecurityRef = "-- Default --";
}

// decoration
String controlListBox =
(entry.getControl() != null && entry.getControl().getName() != null) ?
JetspeedTool.getPortletParameter(runData, (Portlet) runData.getUser().getTemp("customizer"), "control", entry.getControl().getName()) :
JetspeedTool.getPortletParameter(runData, (Portlet) runData.getUser().getTemp("customizer"), "control");
controlListBox = controlListBox.substring(12);
controlListBox = controlListBox.replace('\n', ' ');
controlListBox = controlListBox.replace('\r', ' ');
%>
{id:'<%= portletId %>', parent:'<%= entry.getParent() %>',title:'<%= portletTitle %>',description:'<%= entry.getDescription() %>',skinName:'<%= portletSkinName %>',securityId:'<%= portletSecurityRef %>',controlListBox:'<%= controlListBox %>'}
<%
}
%>
]
<%
}
%>
.........
[/code:1]

我还没有研究过eXo,谁能给补充一下,给出eXo的Layout实现。

大家可以比较一下这些Portal的Layout实现。
关于WebLogic的renderChild tag, PresentationContext, PresentationControl,我还需要更多的时间研究。
   
0 请登录后投票
时间:2004-08-31
^_^,其实我觉得我们可以另开一贴,因为我们现在讨论的其实是View层解析框架的设计
我大概讲讲我现在所做的View层解析框架的设计思路吧,此框架架构上基于Service FrameWork+Domain Model搭建而成,Service FrameWork基于Spring而实现,Domain Model中的ORM由Hibernate实现,DAO通过服务框架注入各服务bean中。在View层解析过程中由ViewContext携带整个View层所需的数据,此处类似XWork的ActionContext的设计,主要是方便测试。
View层解析框架入口由一个解析服务实现,默认的此解析服务的实现方式为获取当前请求的Page,并由Page引入相应的Layout、Portlet分别载入其对应的View层类以及模板进行解析,在对模板解析上采用根据模板的扩展名采用相应的解析服务进行页面级的解析。
Domain Model层则由用户布局信息、View层模块注册信息共同构成。
该View层解析框架主要具有以下的一些特点:
1、与Webwork 2的结合共同组合成一个类似Turbine的框架,相当于作为一个Portal的一部分。
2、可测试性
3、页面分层灵活
4、支持多种页面类型
5、易用性
代码的示例我看在之后贴一些出来,因为目前此框架暂时还只有个大概的样子,等稍微完善候会公开给大家,希望大家多给些建议。
附件是关于我目前的View层解析框架的一个PPT,感兴趣的可以下去看看,期待大家的指点。
   
0 请登录后投票
时间:2004-08-31
原来可以拆分帖子的。
buaawhl 写道
根据flyisland的信息,我研究了一下WebLogic Portal

to buaawhl, 如果要研究WLP,建议参考这里的帮助,另外gridlayout.jsp不是一个好的研究对象,比较复杂,我觉得看book.jsp等比较容易,而且最好动手去改改他的代码。
intolong 写道
PageTag里调用Portlet并没有干涉Porlet显示吧

事实上这里是一个“创建者”模式的问题,谁负责调用(创建)Portlet,谁就要有“创建哪些?创建多少个?”这些信息。当PageTag调用(创建)Portlet的时候,PageTag就需要知道当前需要创建那些Portlet,而且对于不同的Page实例答案是不一样的。引入组件树的概念重点在于:把创建责任独立出来,由一个引擎在遍历组件树的过程中负责调用各个组件的显示,尽量减少JSP之间的调用。这样负责显示的JSP页面更加干净,而且允许非常灵活的组件树结构。
   
0 请登录后投票
论坛首页 Java版 Webwork

跳转论坛:
JavaEye推荐