解读eXtremeComponents代码结构

原文地址:http://blog.csdn.net/lark3/article/details/1937466

大致整理了去年写的东西,罗列如下:


ec是一系列提供高级显示的开源JSP定制标签,当前的包含的组件为eXtremeTable,用于以表形式显示数据。ec现在的版本是1.0.1,由Jeff Johnston开发的,网址:http://www.extremecomponents.org
应该说eXtremeComponents已经实现了一些较为完善的功能,包括排序、过滤等,现在还支持Ajax功能。
用户通过设置标签(如table,row,column等)的属性(大部分的属性和html中的table、tr,td等的属性相同,另外添加一些用于控制的属性)便可轻松实现数据的列表。ec的强大还在于其良好的可扩展性,因为用户可以方便地对其进行二次开发,以满足一些特殊的需求。
由于本文主要是分析ec的代码设计而不是使用说明,因此如何使用ec可以参考相关的指南和参考文档。以下仅列举了发行包中的一个例子test.jsp:(大部分属性的含义都很明显,这里也不作说明了)

以下内容为程序代码:

<ec:table 
items="pres"
action="${pageContext.request.contextPath}/test.jsp"
imagePath="${pageContext.request.contextPath}/images/table/*.gif"
title="test"
width="60%"
rowsDisplayed="5"
>
<ec:exportXls fileName="resourceList.xls" tooltip="Export Excel"/>
<ec:exportPdf fileName="resourceList.pdf" tooltip="Export PDF"/>
<ec:row highlightRow="true">
         <ec:column property="rowcount" cell="rowCount" onClick="alert('${pres[0]}');" 
            title="No." sortable="false" filterable="false" width="5%" 
            style="text-align:center"/> 
<ec:column property="name" title="姓名" href="#" filterCell="droplist"/>
<ec:column property="nickname" title="昵称" filterable="true" sortable="false"/>
<ec:column property="term"/>
</ec:row>
</ec:table>

ec的精彩点之一是Limit接口及其实现类。
就整个软件的设计架构来说,ec也是非常优秀的。ec完全面向对象,并充分运用了设计模式,重构后的整个代码简洁且高效。

1.  代码结构

1.1四个第一级包:

package org.extremecomponents.table.*;(列表)

package org.extremecomponents.test;(用于测试)

package org.extremecomponents.tree;(树型,尚处于开发中)

package org.extremecomponents.util;(工具类)

其中,util包下的HtmlBuilder类封装了视图输出(如html格式)的各种操作,如函数table()用于输出一个HTML标签<table,该类包含一个StringWriter的私有变量。

而ExtremeUtils类则封装了一些常用的函数,如函数formatDate和formatNumber等。

1.2table包下的主要内容:

(1)package org.extremecomponents.table.bean;

说明:简单的bean类,类似VO。

Attributes抽象类包含一个HashMap的私有变量,用于关联属性及其值。类Column,Export,Row,Table都继承了Attributes并添加了各种的属性变量,如Column中的style变量便是对应HTML中td的style属性,Table中的border变量对应的是HTML中table的border属性,等等。当然,这些bean中还有一些属性是用于控制的,Column中的cell变量用于指明该单元格的输出是用哪个Cell类(其实就是用于控制输出的格式),如果cell=”date”,那么该单元格的输出就采用DateCell类的输出,即日期格式;等等。

还有ColumnDefaults,ExportDefault,RowDefault,TableDefault这几个最终类用于对应的Column,Export,Row,Table初始化时设置一些默认的属性值。这些默认值是由core包下的extremetable.properties文件设置的,在TableModel初始化时通过类TableProperties来读取的。

(2)package org.extremecomponents.table.calc;

说明:用于列的属性(calc,和calcTitle)中,由多个列的值计算而成的值。如:总值,平均值等。

Calc接口,只定义了一个函数getCalcResult(model,column);

类AverageCalc和TotalCalc实现了该接口,分别计算平均值和总值。

(3)package org.extremecomponents.table.callback;

说明:用于检索、过滤和排序行集数据,由TableModel中的execute方法调用。

三个接口:RetrieveRowsCallback, FilterRowsCallback, SortRowsCallback分别定义了函数retrieveRows(model),filterRows(model),sortRows(model),用于检索、过滤和排序数据。FilterRowsCallback的默认实现是得到Beans或Maps的Collection,然后通过实现jakarta Predicate接口来进行过滤。

ProcessRowsCallback类实现了这三个接口,也是ec中默认的对数据进行检索,过滤和排序的类。但是,这种功能的正确实现是基于这么一个事实,即ec得到的数据必须是数据库中未经处理(即过滤或排序)的所有原始数据,否则过滤或排序等处理的结果便不是正确的。还有,ec也能处理数据的分页,但现实中我们的数据量往往都很大(成千上万的),不可能未经处理就把所有的数据全部读出让ec来处理!显然,分页、过滤、排序等等处理都是程序在和数据库交互中完成的,ec仅仅接受处理后的数据然后显示而已。而LimitCallback类便实现了这种处理方案,它也实现了上面的那三个接口,但仅仅是直接返回数据而已。这种“Limit”的实现在limit包中再做讨论。

(4)package org.extremecomponents.table.cell;

说明:用于单元格的格式化输出。

Cell接口定义了两个方法:getExportDisplay和getHtmlDisplay。

AbstractCell抽象类实现了Cell,其实也定义了cell的输出框架,其继承类只需实现getCellValue方法即可。其getHtmlDisplay方法实现如下:

public String getHtmlDisplay(TableModel model, Column column) {

        ColumnBuilder columnBuilder = new ColumnBuilder(column);

        columnBuilder.tdStart();

        columnBuilder.tdBody(getCellValue(model, column));

        columnBuilder.tdEnd();

        return columnBuilder.toString();

    }

DisplayCell继承了AbstractCell,是ec中默认的cell。

DateCell继承了AbstractCell,用于输出日期格式化的单元格。

FilterCell实现了Cell,用于在头部输出一个用于过滤的输入框。

FilterDroplistCell实现了Cell,用于在头部输出一个用于过滤的下拉列表。

HeaderCell实现了Cell,用于输出头部标题的单元格内容。

NumberCell继承了AbstractCell,用于输出数字格式化的单元格。

RowCountCell继承了AbstractCell,用于输出数据集合的序号。

SelectAllHeaderCell实现了Cell,用于在头部生成一个选择框,用于选择所有的数据。

(5)package org.extremecomponents.table.context;

说明:ec中用到的上下文类的封装。

Context接口,用于获得Application,Page,Session,Request等上下文的变量。

HttpServletRequestContext实现了Context接口。

ServletRequestContext实现了Context接口。

JspPageContext实现了Context接口。TableModel初始化时(在TableTag中)使用了该类:

model = new TableModelImpl(new JspPageContext(pageContext), TagUtils.evaluateExpressionAsString("locale", this.locale, this, pageContext));

(6)package org.extremecomponents.table.core;

说明:ec列表的核心包,包括表格模型,配置文件,属性文件,参数封装等。

Registry接口,处理所有的参数(),包括用户自定义的。

AbstractRegistry抽象类实现了Registry,保存一些ec的内部参数及用户参数。

TableRegistry继承了AbstractRegistry类,由TableModel中的addTable函数调用。

Messages接口,即支持国际化显示,从Local中(如ZH_CN)获取正确的资源文件。由resource包中的TableResourceBundle类实现。在TableModelImp中初始化:

Messages messages = TableModelUtils.getMessages(this);

messages.init(context, this.locale);

this.messages = messages;


Preferences接口,用于获取配置文件中的设置值。

TableProperties实现了Preferences,初始化时先加载系统默认的配置文件,然后再加载由用户自己配置的文件,如下:

public void init(Context context, String preferencesLocation) {

        try {

            properties.load(this.getClass().getResourceAsStream(EXTREMETABLE_PROPERTIES));

            if (StringUtils.isNotBlank(preferencesLocation)) {

                InputStream input = this.getClass().getResourceAsStream(preferencesLocation);

                if (input != null) {

                    properties.load(input);

                }

            }

        } catch (IOException e) {

            if (logger.isErrorEnabled()) {

                logger.error("Could not load the eXtremeTable preferences.", e);

            }

        }

    }

其在TableModelImpl中被调用:

Preferences preferences = new TableProperties();

        preferences.init(context, TableModelUtils.getPreferencesLocation(context));

        this.preferences = preferences;

为了设置属性文件,你应该如下例所示在/WEB-INF/web.xml文件中声明一个context-param,并 指定你的属性文件的路径:

<context-param>
  <param-name>extremecomponentsPreferencesLocation</param-name>  <param-value>/org/extremesite/resource/extremecomponents.properties</param-value>
</context-param>

 

TableCache类用于获得一些缓存的对象,包括Cell,State,Callback,Interceptor等,因此这些类都是singleton,并且不再线程安全。

 

TableModel接口,是系统的核心接口,包括其实现类TableModelImpl,因为它们把系统中的所有变量都联系了起来。

TableModelImpl实现了TableModel,初始化时获取Context,Preferences及Messages实例;通过addTable函数获取Registry及LimitFactory,Limit实例。

定义的变量有:Context,Preferences,Messages,Registry, TableHandler,RowHandler,ColumnHandler,ViewHandler,ExportHandler,Limit,Locale等。

变量currentRowBean保存当前处理的bean,并在上下文中设置var变量(table中的var属性)的值指向该bean,这样的话,Row和Column标签中便可以通过var变量来应用这个当前的bean对象,获得一些有意义的值。如下:

public void setCurrentRowBean(Object bean) {

        int rowcount = rowHandler.increaseRowCount();

        this.currentRowBean = bean;

        context.setPageAttribute(TableConstants.ROWCOUNT, String.valueOf(rowcount));

        context.setPageAttribute(tableHandler.getTable().getVar(), bean);

    }

而collectionOfBeans、collectionOfFilteredBeans、collectionOfPageBeans则分别保存了所有的bean、过滤后的bean、当前页的bean。

TableModelImpl中的execute函数在标签第一次迭代时被调用,先过滤,后排序,然后通过ViewHandler.setView()来设置输出的视图。


(7)package org.extremecomponents.table.filter;

说明:过滤器,用于导出时的过滤,实现了javax.servlet.Filter。

(8)package org.extremecomponents.table.handler;

说明:各种处理句柄,帮助TableModel处理对应的bean,即关联model和bean。

类有:ColumnHandler, ExportHandler, RowHandler, TableHandle, ViewHandler。

(9)package org.extremecomponents.table.interceptor;

说明:拦截器,用于运行时添加和修改对应bean的属性。

接口有:TableInterceptor, RowInterceptor, ColumnInterceptor, ExportInterceptor。

用户可以实现自己的Interceptor,然后在对应的标签中使用Interceptor属性来设置并使用。所有的拦截器接口都定义了一个add方法, add方法被用来处理模型bean第一次创建时的属性。行和列的拦截器还有一个modify 方法,在当行和类进行处理是对属性值进行操作。

(10)package org.extremecomponents.table.limit;

说明:封装排序,过滤及分页的一些信息,用于向后台程序传递Limit对象。

LimitFactory接口,Limit的工厂接口。

AbstractLimitFactory抽象类实现LimitFactory,用于获取是否导出、当前页面数、排序字段及值及过滤集合等。

TableLimitFactory继承了AbstractLimitFactory。

ModelLimitFactory也继承了AbstractLimitFactory。

Filter最终类,值对象,三个String型私有变量:alias, property, value。

FilterSet类,内含一个Filter数组。

Limit接口,定义了一些用于获取limit信息的函数,如排序值、过滤字段及值、等等。

TableLimit最终类,实现了Limit。其构造函数的参数是LimitFactory,即Limit的值是由工厂类得到的。

Sort最终类,值对象,三个String型私有变量:alias,property,value。


(11)package org.extremecomponents.table.resource;

说明:资源文件及操作资源的类。

TableResourceBundle实现了Messages接口,初始化时会加载特定的资源文件以及用户自定义的资源文件,通过在web.xml中定义extremecomponentsMessagesLocation值来获取。

public void init(Context context, Locale locale) {

        this.locale = locale;

        defaultResourceBundle = findResourceBundle(EXTREMETABLE_RESOURCE_BUNDLE, locale);

        String messagesLocation = TableModelUtils.getMessagesLocation(context);

        if (StringUtils.isNotBlank(messagesLocation)) {

            customResourceBundle = findResourceBundle(messagesLocation, locale);

        }

    }

(12)package org.extremecomponents.table.state;

说明:处理表格的状态。

State接口,定义了saveParameters和getParameters两个函数。

AbstractState抽象类,实现了State接口,定义了saveParameters函数。

DefaultState类实现了State接口,默认两个函数为空。


(13)package org.extremecomponents.table.tag;

说明:标签类,是ec开始的地方。

ColumnsTag继承TagSupport,用于生成自动产生的类。


ColumnTag继承BodyTagSupport并实现了ColumnInterceptor拦截器。

首次迭代时并不生成视图代码,而是:

Column column = new Column(model);

//设置一些属性。。。

addColumnAttributes(model, column);

model.getColumnHandler().addColumn(column);

第2次迭代开始后便执行真正的视图输出:

if (column != null) { // null if view not allowed

     Object bean = TagUtils.getModel(this).getCurrentRowBean();

     Object propertyValue = TableModelUtils.getColumnPropertyValue(bean, property);

     column.setValue(getColumnValue(propertyValue));

     column.setPropertyValue(propertyValue);

 

     modifyColumnAttributes(model, column);

     model.getColumnHandler().modifyColumnAttributes(column);

     model.getViewHandler().addColumnValueToView(column);

}

最后那个语句的函数代码如下:

public void addColumnValueToView(Column column) {

        Cell cell = TableModelUtils.getCell(column);

        boolean isExported = model.getLimit().isExported();

        if (!isExported) {

            column.setCellDisplay(cell.getHtmlDisplay(model, column));

        } else {

            column.setCellDisplay(cell.getExportDisplay(model, column));

        }

        getView().body(model, column);

    }

通过getView().body()函数的调用便完成了视图的输出。

 

RowTag继承了TagSupport并实现了RowInterceptor。

和ColumnTag类似,首次迭代时也仅仅是new一个Row对象,然后设置属性并添加到model中。但RowTag并不产生视图的输出,而是在ColumnTag视图输出时判断是否第一个或最后一个Column,若是,则这时才输出Row的视图数据。如下(抽象类AbstractHtmlView中:)

public void body(TableModel model, Column column) {

        if (column.isFirstColumn()) {

            rowBuilder.rowStart();

        }

        html.append(column.getCellDisplay());

        if (column.isLastColumn()) {

            rowBuilder.rowEnd();

        }

    }

 

TableTag继承了TagSupport,实现TryCatchFinally和TableInterceptor接口。

在doStartTag()函数中:

初始化TableModel的实例为TableModelImpl类,再实例化一个Table类并设置属性,最后通过model.addTable(table)把Table添加到model中,在该addTable函数中完成TableRegistry和TableLimit的初始化。

在doAfterBody()函数中:

在doEndTag()函数中:

pageContext.getOut().println(model.getViewData());

以上这语句便完成了视图的输出,而model.getViewData()函数的代码如下:

public Object getViewData() throws Exception {

    Object viewData = viewHandler.getView().afterBody(this);

    if (limit.isExported()) {

       context.setRequestAttribute(TableConstants.VIEW_DATA, viewData);

       context.setRequestAttribute(TableConstants.VIEW_RESOLVER, exportHandler.getCurrentExport().getViewResolver());

       context.setRequestAttribute(TableConstants.EXPORT_FILE_NAME, exportHandler.getCurrentExport().getFileName());

            return "";

        }

        return viewData;

    }

还有几个Tag:ExportCsvTag, ExportPdfTag, ExportTag, ExportXlsTag,ParameterTag.等。

而TagUtils类则封装了几个处理函数,如利用ExpressionEvaluatorManager类完成属性的设置?


(14)package org.extremecomponents.table.view;

说明:视图部分,包括HTML,toolbar,pdf,xsl等

View接口,定义三个函数:beforeBody, body, afterBody。

AbstractHtmlView抽象类实现了View,其实也便定义了表格输出的框架,继承类只需实现beforeBodyInternal和afterBodyInternal两个函数即可,分别用于输出表格的表头数据及表尾数据,而其body函数则由ColumnTag标签处理时调用。在beforeBody函数中,该抽象类实例化HtmlBuilder、FormBuilder、TableBuilder、RowBuilder等用于构建相应视图的类,如FormBuilder完成Html中form表单等参数的设置等。

HtmlView继承了AbstractHtmlView类,是ec中默认的视图。

(15)package org.extremecomponents.table.view.html;

说明:用于帮助视图构建输出的类,如ColumnBuilder,FormBuilder,RowBuilder,TableBuilder等,如ColumnBuilder.tdEnd()函数生成的代码是“</td>”。

TableActions类封装了一些js的动作代码,主要用于form动作。


(16) package org.extremecomponents.table.html.toobar;

说明:工具条,类型有:按钮,字符,图形等。

ToolbarItem接口,

AbstractItem抽象类。

ButtonItem,ImageItem,TextItem继承AbstractItem实现了ToolbarItem接口。

你可能感兴趣的:(component)