JFreeChart类:
void setAntiAlias(boolean flag) 字体模糊边界
void setBackgroundImage(Image image) 背景图片
void setBackgroundImageAlignment(int alignment) 背景图片对齐方式(参数常量在org.jfree.ui.Align类中定义)
void setBackgroundImageAlpha(float alpha) 背景图片透明度(0.0~1.0)
void setBackgroundPaint(Paint paint) 背景色
void setBorderPaint(Paint paint) 边界线条颜色
void setBorderStroke(Stroke stroke) 边界线条笔触
void setBorderVisible(boolean visible) 边界线条是否可见
--------------------------------------------------------------------------------------
TextTitle类:
void setFont(Font font) 标题字体
void setPaint(Paint paint) 标题字体颜色
void setText(String text) 标题内容
-----------------------------------------------------------------------------------
StandardLegend(Legend)类:
void setBackgroundPaint(Paint paint) 图示背景色
void setTitle(String title) 图示标题内容
void setTitleFont(Font font) 图示标题字体
void setBoundingBoxArcWidth(int arcWidth) 图示边界圆角宽
void setBoundingBoxArcHeight(int arcHeight) 图示边界圆角高
void setOutlinePaint(Paint paint) 图示边界线条颜色
void setOutlineStroke(Stroke stroke) 图示边界线条笔触
void setDisplaySeriesLines(boolean flag) 图示项是否显示横线(折线图有效)
void setDisplaySeriesShapes(boolean flag) 图示项是否显示形状(折线图有效)
void setItemFont(Font font) 图示项字体
void setItemPaint(Paint paint) 图示项字体颜色
void setAnchor(int anchor) 图示在图表中的显示位置(参数常量在Legend类中定义)
-------------------------------------------------------------------------------------------
Axis类:
void setVisible(boolean flag) 坐标轴是否可见
void setAxisLinePaint(Paint paint) 坐标轴线条颜色(3D轴无效)
void setAxisLineStroke(Stroke stroke) 坐标轴线条笔触(3D轴无效)
void setAxisLineVisible(boolean visible) 坐标轴线条是否可见(3D轴无效)
void setFixedDimension(double dimension) (用于复合表中对多坐标轴的设置)
void setLabel(String label) 坐标轴标题
void setLabelFont(Font font) 坐标轴标题字体
void setLabelPaint(Paint paint) 坐标轴标题颜色
void setLabelAngle(double angle)` 坐标轴标题旋转角度(纵坐标可以旋转)
void setTickLabelFont(Font font) 坐标轴标尺值字体
void setTickLabelPaint(Paint paint) 坐标轴标尺值颜色
void setTickLabelsVisible(boolean flag) 坐标轴标尺值是否显示
void setTickMarkPaint(Paint paint) 坐标轴标尺颜色
void setTickMarkStroke(Stroke stroke) 坐标轴标尺笔触
void setTickMarksVisible(boolean flag) 坐标轴标尺是否显示
--------------------------------------------------------------------------
ValueAxis(Axis)类:
void setAutoRange(boolean auto) 自动设置数据轴数据范围
void setAutoRangeMinimumSize(double size) 自动设置数据轴数据范围时数据范围的最小跨度
void setAutoTickUnitSelection(boolean flag) 数据轴的数据标签是否自动确定(默认为true)
void setFixedAutoRange(double length) 数据轴固定数据范围(设置100的话就是显示MAXVALUE到MAXVALUE-100那段数据范围)
void setInverted(boolean flag) 数据轴是否反向(默认为false)
void setLowerMargin(double margin) 数据轴下(左)边距
void setUpperMargin(double margin) 数据轴上(右)边距
void setLowerBound(double min) 数据轴上的显示最小值
void setUpperBound(double max) 数据轴上的显示最大值
void setPositiveArrowVisible(boolean visible) 是否显示正向箭头(3D轴无效)
void setNegativeArrowVisible(boolean visible) 是否显示反向箭头(3D轴无效)
void setVerticalTickLabels(boolean flag) 数据轴数据标签是否旋转到垂直
void setStandardTickUnits(TickUnitSource source) 数据轴的数据标签(可以只显示整数标签,需要将AutoTickUnitSelection设false)
---------------------------------------------------------------------------
NumberAxis(ValueAxis)类:
void setAutoRangeIncludesZero(boolean flag) 是否强制在自动选择的数据范围中包含0
void setAutoRangeStickyZero(boolean flag) 是否强制在整个数据轴中包含0,即使0不在数据范围中
void setNumberFormatOverride(NumberFormat formatter) 数据轴数据标签的显示格式
void setTickUnit(NumberTickUnit unit) 数据轴的数据标签(需要将AutoTickUnitSelection设false)
DateAxis(ValueAxis)类:
void setMaximumDate(Date maximumDate) 日期轴上的最小日期
void setMinimumDate(Date minimumDate) 日期轴上的最大日期
void setRange(Date lower,Date upper) 日期轴范围
void setDateFormatOverride(DateFormat formatter) 日期轴日期标签的显示格式
void setTickUnit(DateTickUnit unit) 日期轴的日期标签(需要将AutoTickUnitSelection设false)
void setTickMarkPosition(DateTickMarkPosition position) 日期标签位置(参数常量在org.jfree.chart.axis.DateTickMarkPosition类中定义)
CategoryAxis(Axis)类:
void setCategoryMargin(double margin) 分类轴边距
void setLowerMargin(double margin) 分类轴下(左)边距
void setUpperMargin(double margin) 分类轴上(右)边距
void setVerticalCategoryLabels(boolean flag) 分类轴标题是否旋转到垂直
void setMaxCategoryLabelWidthRatio(float ratio) 分类轴分类标签的最大宽度
-----------------------------------------------------------------------------------------------------------
Plot类:
void setBackgroundImage(Image image) 数据区的背景图片
void setBackgroundImageAlignment(int alignment) 数据区的背景图片对齐方式(参数常量在org.jfree.ui.Align类中定义)
void setBackgroundPaint(Paint paint) 数据区的背景图片背景色
void setBackgroundAlpha(float alpha) 数据区的背景透明度(0.0~1.0)
void setForegroundAlpha(float alpha) 数据区的前景透明度(0.0~1.0)
void setDataAreaRatio(double ratio) 数据区占整个图表区的百分比
void setOutLinePaint(Paint paint) 数据区的边界线条颜色
void setOutLineStroke(Stroke stroke) 数据区的边界线条笔触
void setNoDataMessage(String message) 没有数据时显示的消息
void setNoDataMessageFont(Font font) 没有数据时显示的消息字体
void setNoDataMessagePaint(Paint paint) 没有数据时显示的消息颜色
CategoryPlot(Plot)类:
void setDataset(CategoryDataset dataset) 数据区的2维数据表
void setColumnRenderingOrder(SortOrder order) 数据分类的排序方式
void setAxisOffset(Spacer offset) 坐标轴到数据区的间距
void setOrientation(PlotOrientation orientation) 数据区的方向(PlotOrientation.HORIZONTAL或PlotOrientation.VERTICAL)
void setDomainAxis(CategoryAxis axis) 数据区的分类轴
void setDomainAxisLocation(AxisLocation location) 分类轴的位置(参数常量在org.jfree.chart.axis.AxisLocation类中定义)
void setDomainGridlinesVisible(boolean visible) 分类轴网格是否可见
void setDomainGridlinePaint(Paint paint) 分类轴网格线条颜色
void setDomainGridlineStroke(Stroke stroke) 分类轴网格线条笔触
void setRangeAxis(ValueAxis axis) 数据区的数据轴
void setRangeAxisLocation(AxisLocation location) 数据轴的位置(参数常量在org.jfree.chart.axis.AxisLocation类中定义)
void setRangeGridlinesVisible(boolean visible) 数据轴网格是否可见
void setRangeGridlinePaint(Paint paint) 数据轴网格线条颜色
void setRangeGridlineStroke(Stroke stroke) 数据轴网格线条笔触
void setRenderer(CategoryItemRenderer renderer) 数据区的表示者(详见Renderer组)
void addAnnotation(CategoryAnnotation annotation) 给数据区加一个注释
void addRangeMarker(Marker marker,Layer layer) 给数据区加一个数值范围区域
PiePlot(Plot)类:
void setDataset(PieDataset dataset) 数据区的1维数据表
void setIgnoreNullValues(boolean flag) 忽略无值的分类
void setCircular(boolean flag) 饼图是否一定是正圆
void setStartAngle(double angle) 饼图的初始角度
void setDirection(Rotation direction) 饼图的旋转方向
void setExplodePercent(int section,double percent) 抽取的那块(1维数据表的分类下标)以及抽取出来的距离(0.0~1.0),3D饼图无效
void setLabelBackgroundPaint(Paint paint) 分类标签的底色
void setLabelFont(Font font) 分类标签的字体
void setLabelPaint(Paint paint) 分类标签的字体颜色
void setLabelLinkMargin(double margin) 分类标签与图的连接线边距
void setLabelLinkPaint(Paint paint) 分类标签与图的连接线颜色
void setLabelLinkStroke(Stroke stroke) 分类标签与图的连接线笔触
void setLabelOutlinePaint(Paint paint) 分类标签边框颜色
void setLabelOutlineStroke(Paint paint) 分类标签边框笔触
void setLabelShadowPaint(Paint paint) 分类标签阴影颜色
void setMaximumLabelWidth(double width) 分类标签的最大长度(0.0~1.0)
void setPieIndex(int index) 饼图的索引(复合饼图中用到)
void setSectionOutlinePaint(int section,Paint paint) 指定分类饼的边框颜色
void setSectionOutlineStroke(int section,Stroke stroke) 指定分类饼的边框笔触
void setSectionPaint(int section,Paint paint) 指定分类饼的颜色
void setShadowPaint(Paint paint) 饼图的阴影颜色
void setShadowXOffset(double offset) 饼图的阴影相对图的水平偏移
void setShadowYOffset(double offset) 饼图的阴影相对图的垂直偏移
void setLabelGenerator(PieSectionLabelGenerator generator) 分类标签的格式,设置成null则整个标签包括连接线都不显示
void setToolTipGenerator(PieToolTipGenerator generator) MAP中鼠标移上的显示格式
void setURLGenerator(PieURLGenerator generator) MAP中钻取链接格式
PiePlot3D(PiePlot)类:
void setDepthFactor(double factor) 3D饼图的Z轴高度(0.0~1.0)
MultiplePiePlot(Plot)类:
void setLimit(double limit) 每个饼图之间的数据关联(详细比较复杂)
void setPieChart(JFreeChart pieChart) 每个饼图的显示方式(见JFreeChart类个PiePlot类)
-----------------------------------------------------------------------------------------------------------
AbstractRenderer类:
void setItemLabelAnchorOffset(double offset) 数据标签的与数据点的偏移
void setItemLabelsVisible(boolean visible) 数据标签是否可见
void setItemLabelFont(Font font) 数据标签的字体
void setItemLabelPaint(Paint paint) 数据标签的字体颜色
void setItemLabelPosition(ItemLabelPosition position) 数据标签位置
void setPositiveItemLabelPosition(ItemLabelPosition position) 正数标签位置
void setNegativeItemLabelPosition(ItemLabelPosition position) 负数标签位置
void setOutLinePaint(Paint paint) 图形边框的线条颜色
void setOutLineStroke(Stroke stroke) 图形边框的线条笔触
void setPaint(Paint paint) 所有分类图形的颜色
void setShape(Shape shape) 所有分类图形的形状(如折线图的点)
void setStroke(Stroke stroke) 所有分类图形的笔触(如折线图的线)
void setSeriesItemLabelsVisible(int series,boolean visible) 指定分类的数据标签是否可见
void setSeriesItemLabelFont(int series,Font font) 指定分类的数据标签的字体
void setSeriesItemLabelPaint(int series,Paint paint) 指定分类的数据标签的字体颜色
void setSeriesItemLabelPosition(int series,ItemLabelPosition position) 数据标签位置
void setSeriesPositiveItemLabelPosition(int series,ItemLabelPosition position) 正数标签位置
void setSeriesNegativeItemLabelPosition(int series,ItemLabelPosition position) 负数标签位置
void setSeriesOutLinePaint(int series,Paint paint) 指定分类的图形边框的线条颜色
void setSeriesOutLineStroke(int series,Stroke stroke) 指定分类的图形边框的线条笔触
void setSeriesPaint(int series,Paint paint) 指定分类图形的颜色
void setSeriesShape(int series,Shape shape) 指定分类图形的形状(如折线图的点)
void setSeriesStroke(int series,Stroke stroke) 指定分类图形的笔触(如折线图的线)
AbstractCategoryItemRenderer(AbstractRenderer)类:
void setLabelGenerator(CategoryLabelGenerator generator) 数据标签的格式
void setToolTipGenerator(CategoryToolTipGenerator generator) MAP中鼠标移上的显示格式
void setItemURLGenerator(CategoryURLGenerator generator) MAP中钻取链接格式
void setSeriesLabelGenerator(int series,CategoryLabelGenerator generator) 指定分类的数据标签的格式
void setSeriesToolTipGenerator(int series,CategoryToolTipGenerator generator) 指定分类的MAP中鼠标移上的显示格式
void setSeriesItemURLGenerator(int series,CategoryURLGenerator generator) 指定分类的MAP中钻取链接格式
BarRenderer(AbstractCategoryItemRenderer)类:
void setDrawBarOutline(boolean draw) 是否画图形边框
void setItemMargin(double percent) 每个BAR之间的间隔
void setMaxBarWidth(double percent) 每个BAR的最大宽度
void setMinimumBarLength(double min) 最短的BAR长度,避免数值太小而显示不出
void setPositiveItemLabelPositionFallback(ItemLabelPosition position) 无法在BAR中显示的正数标签位置
void setNegativeItemLabelPositionFallback(ItemLabelPosition position) 无法在BAR中显示的负数标签位置
BarRenderer3D(BarRenderer)类:
void setWallPaint(Paint paint) 3D坐标轴的墙体颜色
StackedBarRenderer(BarRenderer)类:
没有特殊的设置
StackedBarRenderer3D(BarRenderer3D)类:
没有特殊的设置
GroupedStackedBarRenderer(StackedBarRenderer)类:
void setSeriesToGroupMap(KeyToGroupMap map) 将分类自由的映射成若干个组(KeyToGroupMap.mapKeyToGroup(series,group))
LayeredBarRenderer(BarRenderer)类:
void setSeriesBarWidth(int series,double width) 设定每个分类的宽度(注意设置不要使某分类被覆盖)
WaterfallBarRenderer(BarRenderer)类:
void setFirstBarPaint(Paint paint) 第一个柱图的颜色
void setLastBarPaint(Paint paint) 最后一个柱图的颜色
void setPositiveBarPaint(Paint paint) 正值柱图的颜色
void setNegativeBarPaint(Paint paint) 负值柱图的颜色
IntervalBarRenderer(BarRenderer)类:
需要传IntervalCategoryDataset作为数据源
GanttBarRenderer(IntervalBarRenderer)类:
void setCompletePaint(Paint paint) 完成进度颜色
void setIncompletePaint(Paint paint) 未完成进度颜色
void setStartPercent(double percent) 设置进度条在整条中的起始位置(0.0~1.0)
void setEndPercent(double percent) 设置进度条在整条中的结束位置(0.0~1.0)
StatisticBarRenderer(BarRenderer)类:
需要传StatisticCategoryDataset作为数据源
LineAndShapeRenderer(AbstractCategoryItemRenderer)类:
void setDrawLines(boolean draw) 是否折线的数据点之间用线连
void setDrawShapes(boolean draw) 是否折线的数据点根据分类使用不同的形状
void setShapesFilled(boolean filled) 所有分类是否填充数据点图形
void setSeriesShapesFilled(int series,boolean filled) 指定分类是否填充数据点图形
void setUseFillPaintForShapeOutline(boolean use) 指定是否填充数据点的Paint也被用于画数据点形状的边框
LevelRenderer(AbstractCategoryItemRenderer)类:
void setItemMargin(double percent) 每个分类之间的间隔
void setMaxItemWidth(double percent) 每个分类的最大宽度
CategoryStepRenderer(AbstractCategoryItemRenderer)类:
void setStagger(boolean shouldStagger) 不同分类的图是否交错
MinMaxCategoryRenderer(AbstractCategoryItemRenderer)类:
void setDrawLines(boolean drawLines) 是否在每个分类线间画连接线
void setGroupPaint(Paint groupPaint) 一组图形连接线的颜色
void setGroupStroke(Stroke groupStroke) 一组图形连接线的笔触
void setMaxIcon(Icon maxIcon) 最大值的ICON
void setMinIcon(Icon minIcon) 最小值的ICON
void setObjectIcon(Icon objectIcon) 所有值的ICON
AreaRender(AbstractCategoryItemRenderer)类:
没有特殊的设置
StackedAreaRender(AreaRender)类:
没有特殊的设置
要运行sample下的例子,首先你要安装ant,并设置好环境变量 。然后到dos方式下,到某一个sample的目录,运行 ant view 则会展现报表
1. alterdesign
该例子演示了报表编译后,在报表展现的时候如何动态的控制其中的元素比如让某一个矩形变色或其他
2. antcompile
演示如何让 ant 来编译
3. chart
演示了如何在报表中添加图像,JasperReport是用Scriptlet的方式往报表中添加图像,而Scriptlet是调用也是开源的jfreechart的Api来生成图形,去jfreechart看一下,该工具能的图形生成能力也很强
4. datasource
演示了如何在报表中使用各种数据源,能够使用beanarray beancollection,也可以用自定义的数据源,只要继承了JRDataSource的
两个接口,这个能给用户提供非常大的灵活性,报表的数据不用局限于一条Sql语句,也可以使用存储过程,对生成报表中的数据也可以排序,二 次检索,等等
5. fonts
各种字体的演示
6. horizontal
演示了水平分栏的报表,演示报表中分了三栏,其中还用到了textFieldExpression,就像if语句的效果来选择输出的内容
7. hyperlink
演示了各种样式的链接
8. images
演示了如何在报表中加入图像以及图像的显示方式
9. jasper
演示了分组分栏的报表,演示中用了2次group
10. jcharts
演示了调用另一个开源的API jcharts来往报表中加入分析图形,原理同上chart,如果jfreechart都还不能满足你分析图形的要求,那到jcharts里找找看吧,说不定有
11. landscape
演示横向的报表
12. nopagebreak
演示比如在IE中不分页的方式打印出报表的内容,通过这个演示也可以了解报表输出如何配置参数
13. noreport
演示了如何直接通过java程序生成JasperPrint对象来输出
14. noxmldesign
演示了如何直接通过java程序生成JasperDesign对象来动态的生成报表,根据这个例子,用户可以作出自定义选列的报表,当然比较麻烦,而且肯定自己要补充他的API库(JasperReport真是强大啊,呵呵)
15. pdfencrypt
演示了pdf的输出方式,可以给pdf文件加密码,其实就是pdf输出方式的参数配置,具体有那些参数可配置,去看看API吧
16. printservice
演示了如何直接打印报表
17. query
演示了如何让查询的sql动态起来,比如你可以通过一个Jsp页面传报表的sql的where条件,order条件,甚至整个sql语句
18. rotation
演示了文字纵向显示的报表
19. scriptlet
演示了如何继承JRDefaultScriptlet,并加入自己的处理语句,这个功能可是很强大的哦,看看这些接口
beforeReportInit()
afterReportInit()
beforePageInit()
afterPageInit()
beforeColumnInit()
afterColumnInit()
beforeGroupInit(String groupName)
afterGroupInit(String groupName)
看看这些名字就知道你能完成那些功能,比如显示一列数据后,马上跟上该列数据的分析图形,当然你也可以加上自己的方法并在报表中调用
20. shapes
演示了JasperReport中自带的图形,及能配置的参数当然你也能继承或者覆写JasperReport中的Api生成你要的图形,
21. stretch
演示了如何处理报表中数据拉伸以及带来周围的线及框的拉伸,你能了解到虽然黑框式表格不是JasperReport中缺省的展现方式,
但在JasperReport中不难实现
22. subreport
演示了子报表,还告诉你一个报表中可以有n个子报表,子报表中还可以嵌套子报表
23. tableofcontents
演示了如何生成一个有目录的,复杂的报表
24. unicode
演示了各种 字符编码
25. webapp
演示了如何把报表放到一个JavaWeb项目中,可以用Jsp Servlet applet
JasperReports 是什么
JasperReports是一个面向开发人员设计的开源Java类库, 通过它可以为Java应用程序增加报表功能。由于 JasperReports 不是独立的工具,所以不能对它进行独立安装。而是要通过应用程序的 CLASSPATH 来包含其类库,从而把它嵌入到 Java应用程序中。JasperReports 是一个 Java类库,也就是说它不是为最终用户准备的。它的目标用户是那些需要为应用程序添加报表功能的Java
开发人员。
JasperReports采用 Lesser GNU Public Library (LGPL)许可协议,所以开放源代码的或不开放源代码的应用程序都可以使用它。通过链接来使用JasperReports 类库的应用程序不需要开放源代码,而需要对现有JasperReports 源代码进行修改的,那么所修改的内容必须也遵循 LGPL 进行发布。更详细的说明可参考 http://www.gnu.org/copyleft/lesser.html。尽管 JasperReports 主要用于通过 Servlet API 来为基于 Web 的应用程序增加报表功能,但它并不是完全依赖于 Servlet API或任何 Java EE类库。因此,它并不仅限于 Web 应用程序。用 JasperReports 来建立独立的桌面程序或命令行程序来生成报表的开发从未停止过。可是,话说又回来,JasperReports除了是一个 Java类库之外,什么都不是。它做的事情只是通过提供 API来为各种Java应用程序增加生成报表的功能。
JasperReports需要 Java Development Kit (JDK) 1.4或更新的版本来进行编译,以便和 JasperReports 的 Java 类库一同工作。同时还需要 Java Runtime Environment (JRE) 1.3或更新的版本来运行这些应用程序。早期版本的 JasperReports 需要 JDK 来运行 JasperReports 应用程序 (严格地讲,JasperReports 需要 tools.jar 被设置在 CLASSPATH 环境变量中,JDK包含了 tools.jar,而 JRE 中没有)。然而,从 0.6.4 版以后,JasperReports把 Eclipse Java Development Tools (JDT)编译器捆绑在一起,因此不再需
要 JDK 来运行部署后的应用程序。本书的例子是用 JDK1.6 开发的,但它们在JasperReports支持的任何其它 JDK 或 JRE上也应该能够顺利地编译和运行。
JasperReports 的特点
JasperReports 除了以文本数据方式生成报表外,还可以生成包含图片、图表和图形的专业报表。JasperReports的主要特点包括:
• 灵活的报表排版
• 多样的数据表现方式
• 多样的数据提供方式
• 支持从多种数据源接收数据
• 能够生成水印
• 能够生成子报表
此外,它还可以用许多种格式来输出报表。下面的各小节将对这些特点做简要介绍。
类库依赖
JasperReports借用了其它的开源Java类库来实现其部分功能,其中包括:
iText: 一个用于生成和处理 PDF的类库。另外,它还可以生成和处理 RTF、XML和 HTML文档。JasperReports用它来导出 PDF和 RTF 格式的报表。要获得有关 iText 的详细介绍,可以访问 http://www.lowagie.com/iText/。
JFreeChart: 一个 Java类库,可用于生成各种图表,包括:饼图、条形图、线形图、区域图、等等。JasperReports通过 JFreeChart 来实现其图表功能。有关 JFreeChart 的详细介绍可以查阅http://www.jfree.org/jfreechart/。
Apache POI: 一个Java类库, 用于创建和处理各种建立在Microsoft的OLE2混合文档格式基础上的Microsoft Office格式的文档。 JasperReports通过POI来导出XLS 格式的报表,更多的 Jakarta POI有关介绍可查阅http://poi.apache.org/。
JAXP: 用于解析和转换XML文档的 Java API,JasperReports用它来解析XML文件。JAXP 包含在 Java SE 5.0中。如果使用更早版本的Java SE,也可以要独立地下载它。有关 JAXP的详细介绍可以查阅https://jaxp.dev.java.net/。
Apache Commons: 一套 Java类库,提供了大量的可重用组件。JasperReports使用了其中的 Commons Digester、BeanUtils、Logging组件来辅助JAXP 解析XML。关于 Apache Commons的详细介绍可查阅http://commons.apache.org/。
典型的开发流程
下面的图形给出了用 JasperReports创建报表的典型开发流程:
用 JasperReports进行开发时,第一步要创建报表模板,它是一个 XML文件。它可以通过手工编码来完成,也可以用图形化的报表设计软件完成。虽然JasperReports的报表模板是 XML文件,但其文件名却用.jrxml 来作为扩展名。JasperReports XML模板通常就是指 JRXML文件,本书中也使用这一术语。
摘要:
1 前言 1
2 阅读本篇的基础准备 2
2.1 概念的基础
2.2 环境的基础
3 什么是...
阅读全文
jbpm概念
1 : process definition(流程定义):
工作流的流程的完整定义,包括节点和节点之间的走向等关键信息。通常以xml格式提供。一个具体的系统往往是由许多个流程组成的。
2 : process instance(流程实例):
每个process defination生成的业务层的实例。当process instance创建以后,代表流程的执行路径,并被定义到开始节点。
3 : token(令牌):
表示了一个执行的路径,它是运行时产生的。当实例建立以后,令牌也就产生了。
4 : node:
表示流程中的一个节点。
5 : transition:
关联两个节点,用于表示节点的走向
6 : signal:
让一个token执行下一步。process instance也有signal,当用process instance的signal时,其实就是运行process instance根令牌(root token)的signal. 当token进入到一个node时,node会被执行,并产生一些事件,比如进入、离开节点等,这也是执行业务逻辑的地方。事件由action来表示。
7 : 事件Event
Event反映的是流程执行中的各个时刻。在流程执行中JBPM引擎会在计算下一个状态的时候触发各种事件。一个事件通常和流程定义中的一个元素相关联,比如流程定义本身,节点或者转移。大部分的元素能够触发不同类型的事件,比如一个节点可以触发节点进入事件,节点离开事件。事件其实是和动作连接在一起的。每个事件维护一个动作列表。当JBPM引擎触发一个事件的时候,该事件维护的动作列表中的动作将被执行。
事件类型
在JBPM中事件类型是写死在事件类中的,共有16种:
EVENTTYPE_TRANSITION = "transition"; // 转移
EVENTTYPE_BEFORE_SIGNAL = "before-signal"; // 发信号前
EVENTTYPE_AFTER_SIGNAL = "after-signal"; // 发信号后
EVENTTYPE_PROCESS_START = "process-start"; // 处理开始状态
EVENTTYPE_PROCESS_END = "process-end"; // 处理结束状态
EVENTTYPE_NODE_ENTER = "node-enter"; // 进入节点
EVENTTYPE_NODE_LEAVE = "node-leave"; // 离开节点
EVENTTYPE_SUPERSTATE_ENTER = "superstate-enter"; // 进入超级状态
EVENTTYPE_SUPERSTATE_LEAVE = "superstate-leave"; // 离开超级状态
EVENTTYPE_SUBPROCESS_CREATED = "subprocess-created"; // 子流程创建
EVENTTYPE_SUBPROCESS_END = "subprocess-end"; // 子流程结束
EVENTTYPE_TASK_CREATE = "task-create"; // 任务创建
EVENTTYPE_TASK_ASSIGN = "task-assign"; // 任务分派
EVENTTYPE_TASK_START = "task-start"; // 任务启动
EVENTTYPE_TASK_END = "task-end"; // 任务结束
EVENTTYPE_TIMER = "timer"; // 定时器
常用API
ProcessInstance是ProcessDefinition的一个执行实例,想象一下对于订票流程,每个客户的订票动作都会根据订票流程定义而创建一个流程实例,也就是执行实例ProcessInstance.当一个ProcessInstance被创建后,负责执行主路径的token也被创建,这个token就是根token(root token),根token此时位于流程定义的开始状态start state.
创建执行实例很简单有2种方式 :
1 : 通过 ProcessDefinition 类的 createProcessInstance() 方法
//得到 processDefinition
ProcessDefinition processDefinition = ProcessDefinition.parseXmlResource("processdefinition.xml");
//通过 processDefinition 创建 出 processInstance
ProcessInstance processInstance = processDefinition.createProcessInstance();
2 :通过 ProcessInstance 类的 构造函数
//得到 jbpmContext
JbpmContext jbpmContext = JbpmConfiguration.getInstance().createJbpmContext();
//得到 processDefinition
ProcessDefinition processDefinition = jbpmContext.getGraphSession().findLatestProcessDefinition("baoxiao");
//得到 processInstance
ProcessInstance processInstance = new ProcessInstance(processDefinition);
本文转载:http://www.ibm.com/developerworks/cn/opensource/os-cn-easymock/
EasyMock 是一套通过简单的方法对于指定的接口或类生成 Mock 对象的类库,它能利用对接口或类的模拟来辅助单元测试。本文将对 EasyMock 的功能和原理进行介绍,并通过示例来说明如何使用 EasyMock 进行单元测试。
Mock 方法是单元测试中常见的一种技术,它的主要作用是模拟一些在应用中不容易构造或者比较复杂的对象,从而把测试与测试边界以外的对象隔离开。
编写自定义的 Mock 对象需要额外的编码工作,同时也可能引入错误。EasyMock 提供了根据指定接口动态构建 Mock 对象的方法,避免了手工编写 Mock 对象。本文将向您展示如何使用 EasyMock 进行单元测试,并对 EasyMock 的原理进行分析。
1.Mock 对象与 EasyMock 简介
单元测试与 Mock 方法
单元测试是对应用中的某一个模块的功能进行验证。在单元测试中,我们常遇到的问题是应用中其它的协同模块尚未开发完成,或者被测试模块需要和一些不容易构造、比较复杂的对象进行交互。另外,由于不能肯定其它模块的正确性,我们也无法确定测试中发现的问题是由哪个模块引起的。
Mock 对象能够模拟其它协同模块的行为,被测试模块通过与 Mock 对象协作,可以获得一个孤立的测试环境。此外,使用 Mock 对象还可以模拟在应用中不容易构造(如 HttpServletRequest 必须在 Servlet 容器中才能构造出来)和比较复杂的对象(如 JDBC 中的 ResultSet 对象),从而使测试顺利进行。
EasyMock 简介
手动的构造 Mock 对象会给开发人员带来额外的编码量,而且这些为创建 Mock 对象而编写的代码很有可能引入错误。目前,有许多开源项目对动态构建 Mock 对象提供了支持,这些项目能够根据现有的接口或类动态生成,这样不仅能避免额外的编码工作,同时也降低了引入错误的可能。
EasyMock 是一套用于通过简单的方法对于给定的接口生成 Mock 对象的类库。它提供对接口的模拟,能够通过录制、回放、检查三步来完成大体的测试过程,可以验证方法的调用种类、次数、顺序,可以令 Mock 对象返回指定的值或抛出指定异常。通过 EasyMock,我们可以方便的构造 Mock 对象从而使单元测试顺利进行。
安装 EasyMock
EasyMock 是采用 MIT license 的一个开源项目,您可以在 Sourceforge 上下载到相关的 zip 文件。目前您可以下载的 EasyMock 最新版本是2.3,它需要运行在 Java 5.0 平台上。如果您的应用运行在 Java 1.3 或 1.4 平台上,您可以选择 EasyMock1.2。在解压缩 zip 包后,您可以找到 easymock.jar 这个文件。如果您使用 Eclipse 作为 IDE,把 easymock.jar 添加到项目的 Libraries 里就可以使用了(如下图所示)。此外,由于我们的测试用例运行在 JUnit 环境中,因此您还需要 JUnit.jar(版本3.8.1以上)。
图1:Eclipse 项目中的 Libraries
2.使用 EasyMock 进行单元测试
通过 EasyMock,我们可以为指定的接口动态的创建 Mock 对象,并利用 Mock 对象来模拟协同模块或是领域对象,从而使单元测试顺利进行。这个过程大致可以划分为以下几个步骤:
- 使用 EasyMock 生成 Mock 对象;
- 设定 Mock 对象的预期行为和输出;
- 将 Mock 对象切换到 Replay 状态;
- 调用 Mock 对象方法进行单元测试;
- 对 Mock 对象的行为进行验证。
接下来,我们将对以上的几个步骤逐一进行说明。除了以上的基本步骤外,EasyMock 还对特殊的 Mock 对象类型、特定的参数匹配方式等功能提供了支持,我们将在之后的章节中进行说明。
使用 EasyMock 生成 Mock 对象
根据指定的接口或类,EasyMock 能够动态的创建 Mock 对象(EasyMock 默认只支持为接口生成 Mock 对象,如果需要为类生成 Mock 对象,在 EasyMock 的主页上有扩展包可以实现此功能),我们以 ResultSet
接口为例说明EasyMock的功能。java.sql.ResultSet
是每一个 Java 开发人员都非常熟悉的接口:
清单1:ResultSet 接口
public interface java.sql.ResultSet {
......
public abstract java.lang.String getString(int arg0) throws java.sql.SQLException;
public abstract double getDouble(int arg0) throws java.sql.SQLException;
......
}
|
通常,构建一个真实的 RecordSet
对象需要经过一个复杂的过程:在开发过程中,开发人员通常会编写一个 DBUtility
类来获取数据库连接 Connection
,并利用 Connection
创建一个 Statement
。执行一个 Statement
可以获取到一个或多个 ResultSet
对象。这样的构造过程复杂并且依赖于数据库的正确运行。数据库或是数据库交互模块出现问题,都会影响单元测试的结果。
我们可以使用 EasyMock 动态构建 ResultSet
接口的 Mock 对象来解决这个问题。一些简单的测试用例只需要一个 Mock 对象,这时,我们可以用以下的方法来创建 Mock 对象:
ResultSet mockResultSet = createMock(ResultSet.class);
|
其中 createMock
是 org.easymock.EasyMock
类所提供的静态方法,你可以通过 static import 将其引入(注:static import 是 java 5.0 所提供的新特性)。
如果需要在相对复杂的测试用例中使用多个 Mock 对象,EasyMock 提供了另外一种生成和管理 Mock 对象的机制:
IMocksControl control = EasyMock.createControl();
java.sql.Connection mockConnection = control.createMock(Connection.class);
java.sql.Statement mockStatement = control.createMock(Statement.class);
java.sql.ResultSet mockResultSet = control.createMock(ResultSet.class);
|
EasyMock
类的 createControl
方法能创建一个接口 IMocksControl
的对象,该对象能创建并管理多个 Mock 对象。如果需要在测试中使用多个 Mock 对象,我们推荐您使用这一机制,因为它在多个 Mock 对象的管理上提供了相对便捷的方法。
如果您要模拟的是一个具体类而非接口,那么您需要下载扩展包 EasyMock Class Extension 2.2.2。在对具体类进行模拟时,您只要用 org.easymock.classextension.EasyMock
类中的静态方法代替 org.easymock.EasyMock
类中的静态方法即可。
设定 Mock 对象的预期行为和输出
在一个完整的测试过程中,一个 Mock 对象将会经历两个状态:Record 状态和 Replay 状态。Mock 对象一经创建,它的状态就被置为 Record。在 Record 状态,用户可以设定 Mock 对象的预期行为和输出,这些对象行为被录制下来,保存在 Mock 对象中。
添加 Mock 对象行为的过程通常可以分为以下3步:
- 对 Mock 对象的特定方法作出调用;
- 通过
org.easymock.EasyMock
提供的静态方法 expectLastCall
获取上一次方法调用所对应的 IExpectationSetters 实例;
- 通过
IExpectationSetters
实例设定 Mock 对象的预期输出。
设定预期返回值
Mock 对象的行为可以简单的理解为 Mock 对象方法的调用和方法调用所产生的输出。在 EasyMock 2.3 中,对 Mock 对象行为的添加和设置是通过接口 IExpectationSetters
来实现的。Mock 对象方法的调用可能产生两种类型的输出:(1)产生返回值;(2)抛出异常。接口 IExpectationSetters
提供了多种设定预期输出的方法,其中和设定返回值相对应的是 andReturn 方法:
IExpectationSetters<T> andReturn(T value);
|
我们仍然用 ResultSet
接口的 Mock 对象为例,如果希望方法 mockResult.getString(1)
的返回值为 "My return value",那么你可以使用以下的语句:
mockResultSet.getString(1);
expectLastCall().andReturn("My return value");
|
以上的语句表示 mockResultSet
的 getString
方法被调用一次,这次调用的返回值是 "My return value"。有时,我们希望某个方法的调用总是返回一个相同的值,为了避免每次调用都为 Mock 对象的行为进行一次设定,我们可以用设置默认返回值的方法:
void andStubReturn(Object value);
|
假设我们创建了 Statement
和 ResultSet
接口的 Mock 对象 mockStatement 和 mockResultSet,在测试过程中,我们希望 mockStatement 对象的 executeQuery
方法总是返回 mockResultSet,我们可以使用如下的语句
mockStatement.executeQuery("SELECT * FROM sales_order_table");
expectLastCall().andStubReturn(mockResultSet);
|
EasyMock 在对参数值进行匹配时,默认采用 Object.equals()
方法。因此,如果我们以 "select * from sales_order_table"
作为参数,预期方法将不会被调用。如果您希望上例中的 SQL 语句能不区分大小写,可以用特殊的参数匹配器来解决这个问题,我们将在 "在 EasyMock 中使用参数匹配器" 一章对此进行说明。
设定预期异常抛出
对象行为的预期输出除了可能是返回值外,还有可能是抛出异常。IExpectationSetters
提供了设定预期抛出异常的方法:
IExpectationSetters<T> andThrow(Throwable throwable);
|
和设定默认返回值类似,IExpectationSetters
接口也提供了设定抛出默认异常的函数:
void andStubThrow(Throwable throwable);
|
设定预期方法调用次数
通过以上的函数,您可以对 Mock 对象特定行为的预期输出进行设定。除了对预期输出进行设定,IExpectationSetters
接口还允许用户对方法的调用次数作出限制。在 IExpectationSetters
所提供的这一类方法中,常用的一种是 times
方法:
IExpectationSetters<T>times(int count);
|
该方法可以 Mock 对象方法的调用次数进行确切的设定。假设我们希望 mockResultSet 的 getString
方法在测试过程中被调用3次,期间的返回值都是 "My return value",我们可以用如下语句:
mockResultSet.getString(1);
expectLastCall().andReturn("My return value").times(3);
|
注意到
andReturn
和
andThrow
方法的返回值依然是一个
IExpectationSetters
实例,因此我们可以在此基础上继续调用
times
方法。
除了设定确定的调用次数,IExpectationSetters
还提供了另外几种设定非准确调用次数的方法:
times(int minTimes, int maxTimes)
:该方法最少被调用 minTimes 次,最多被调用 maxTimes 次。
atLeastOnce()
:该方法至少被调用一次。
anyTimes()
:该方法可以被调用任意次。
某些方法的返回值类型是 void,对于这一类方法,我们无需设定返回值,只要设置调用次数就可以了。以 ResultSet
接口的 close
方法为例,假设在测试过程中,该方法被调用3至5次:
mockResultSet.close();
expectLastCall().times(3, 5);
|
为了简化书写,EasyMock 还提供了另一种设定 Mock 对象行为的语句模式。对于上例,您还可以将它写成:
expect(mockResult.close()).times(3, 5);
|
这个语句和上例中的语句功能是完全相同的。
将 Mock 对象切换到 Replay 状态
在生成 Mock 对象和设定 Mock 对象行为两个阶段,Mock 对象的状态都是 Record 。在这个阶段,Mock 对象会记录用户对预期行为和输出的设定。
在使用 Mock 对象进行实际的测试前,我们需要将 Mock 对象的状态切换为 Replay。在 Replay 状态,Mock 对象能够根据设定对特定的方法调用作出预期的响应。将 Mock 对象切换成 Replay 状态有两种方式,您需要根据 Mock 对象的生成方式进行选择。如果 Mock 对象是通过 org.easymock.EasyMock
类提供的静态方法 createMock 生成的(第1节中介绍的第一种 Mock 对象生成方法),那么 EasyMock
类提供了相应的 replay 方法用于将 Mock 对象切换为 Replay 状态:
如果 Mock 对象是通过 IMocksControl
接口提供的 createMock
方法生成的(第1节中介绍的第二种Mock对象生成方法),那么您依旧可以通过 IMocksControl
接口对它所创建的所有 Mock 对象进行切换:
以上的语句能将在第1节中生成的 mockConnection、mockStatement 和 mockResultSet 等3个 Mock 对象都切换成 Replay 状态。
调用 Mock 对象方法进行单元测试
为了更好的说明 EasyMock 的功能,我们引入 src.zip 中的示例来解释 Mock 对象在实际测试阶段的作用。其中所有的示例代码都可以在 src.zip 中找到。如果您使用的 IDE 是 Eclipse,在导入 src.zip 之后您可以看到 Workspace 中增加的 project(如下图所示)。
图2:导入 src.zip 后的 Workspace
下面是示例代码中的一个接口 SalesOrder
,它的实现类 SalesOrderImpl
的主要功能是从数据库中读取一个 Sales Order 的 Region 和 Total Price,并根据读取的数据计算该 Sales Order 的 Price Level(完整的实现代码都可以在 src.zip 中找到):
清单2:SalesOrder 接口
public interface SalesOrder
{
……
public void loadDataFromDB(ResultSet resultSet) throws SQLException;
public String getPriceLevel();
}
|
其实现类 SalesOrderImpl
中对 loadDataFromDB
的实现如下:
清单3:SalesOrderImpl 实现
public class SalesOrderImpl implements SalesOrder
{
......
public void loadDataFromDB(ResultSet resultSet) throws SQLException
{
orderNumber = resultSet.getString(1);
region = resultSet.getString(2);
totalPrice = resultSet.getDouble(3);
}
......
}
|
方法 loadDataFromDB
读取了 ResultSet
对象包含的数据。当我们将之前定义的 Mock 对象调整为 Replay 状态,并将该对象作为参数传入,那么 Mock 对象的方法将会返回预先定义的预期返回值。完整的 TestCase 如下:
清单4:完整的TestCase
public class SalesOrderTestCase extends TestCase {
public void testSalesOrder() {
IMocksControl control = EasyMock.createControl();
......
ResultSet mockResultSet = control.createMock(ResultSet.class);
try {
......
mockResultSet.next();
expectLastCall().andReturn(true).times(3);
expectLastCall().andReturn(false).times(1);
mockResultSet.getString(1);
expectLastCall().andReturn("DEMO_ORDER_001").times(1);
expectLastCall().andReturn("DEMO_ORDER_002").times(1);
expectLastCall().andReturn("DEMO_ORDER_003").times(1);
mockResultSet.getString(2);
expectLastCall().andReturn("Asia Pacific").times(1);
expectLastCall().andReturn("Europe").times(1);
expectLastCall().andReturn("America").times(1);
mockResultSet.getDouble(3);
expectLastCall().andReturn(350.0).times(1);
expectLastCall().andReturn(1350.0).times(1);
expectLastCall().andReturn(5350.0).times(1);
control.replay();
......
int i = 0;
String[] priceLevels = { "Level_A", "Level_C", "Level_E" };
while (mockResultSet.next()) {
SalesOrder order = new SalesOrderImpl();
order.loadDataFromDB(mockResultSet);
assertEquals(order.getPriceLevel(), priceLevels[i]);
i++;
}
control.verify();
} catch (Exception e) {
e.printStackTrace();
}
}
}
|
在这个示例中,我们首先创建了 ResultSet
的 Mock 对象 moResultSet,并记录该 Mock 对象的预期行为。之后我们调用了 control.replay()
,将 Mock 对象的状态置为 Replay 状态。在实际的测试阶段,Sales Order 对象的 loadDataFromDB
方法调用了 mockResultSet 对象的 getString
和 getDouble
方法读取 mockResultSet 中的数据。Sales Order 对象根据读取的数据计算出 Price Level,并和预期输出进行比较。
对 Mock 对象的行为进行验证
在利用 Mock 对象进行实际的测试过程之后,我们还有一件事情没有做:对 Mock 对象的方法调用的次数进行验证。
为了验证指定的方法调用真的完成了,我们需要调用 verify
方法进行验证。和 replay
方法类似,您需要根据 Mock 对象的生成方式来选用不同的验证方式。如果 Mock 对象是由 org.easymock.EasyMock
类提供的 createMock
静态方法生成的,那么我们同样采用 EasyMock
类的静态方法 verify
进行验证:
如果Mock对象是有 IMocksControl
接口所提供的 createMock
方法生成的,那么采用该接口提供的 verify
方法,例如第1节中的 IMocksControl
实例 control:
将对 control 实例所生成的 Mock 对象 mockConnection、mockStatement 和 mockResultSet 等进行验证。如果将上例中 expectLastCall().andReturn(false).times(1)
的预期次数修改为2,在 Eclipse 中将可以看到:
图3:Mock对象验证失败
Mock 对象的重用
为了避免生成过多的 Mock 对象,EasyMock 允许对原有 Mock 对象进行重用。要对 Mock 对象重新初始化,我们可以采用 reset 方法。和 replay 和 verify 方法类似,EasyMock 提供了两种 reset 方式:(1)如果 Mock 对象是由 org.easymock.EasyMock
类中的静态方法 createMock
生成的,那么该 Mock 对象的可以用 EasyMock
类的静态方法 reset
重新初始化;(2)如果 Mock 方法是由 IMocksControl
实例的 createMock
方法生成的,那么该 IMocksControl
实例方法 reset
的调用将会把所有该实例创建的 Mock 对象重新初始化。
在重新初始化之后,Mock 对象的状态将被置为 Record 状态。
3.在 EasyMock 中使用参数匹配器
EasyMock 预定义的参数匹配器
在使用 Mock 对象进行实际的测试过程中,EasyMock 会根据方法名和参数来匹配一个预期方法的调用。EasyMock 对参数的匹配默认使用 equals()
方法进行比较。这可能会引起一些问题。例如在上一章节中创建的mockStatement对象:
mockStatement.executeQuery("SELECT * FROM sales_order_table");
expectLastCall().andStubReturn(mockResultSet);
|
在实际的调用中,我们可能会遇到 SQL 语句中某些关键字大小写的问题,例如将 SELECT 写成 Select,这时在实际的测试中,EasyMock 所采用的默认匹配器将认为这两个参数不匹配,从而造成 Mock 对象的预期方法不被调用。EasyMock 提供了灵活的参数匹配方式来解决这个问题。如果您对 mockStatement 具体执行的语句并不关注,并希望所有输入的字符串都能匹配这一方法调用,您可以用 org.easymock.EasyMock
类所提供的 anyObject
方法来代替参数中的 SQL 语句:
mockStatement.executeQuery( anyObject() );
expectLastCall().andStubReturn(mockResultSet);
|
anyObject
方法表示任意输入值都与预期值相匹配。除了 anyObject
以外,EasyMock还提供了多个预先定义的参数匹配器,其中比较常用的一些有:
aryEq(X value)
:通过Arrays.equals()
进行匹配,适用于数组对象;
isNull()
:当输入值为Null时匹配;
notNull()
:当输入值不为Null时匹配;
same(X value)
:当输入值和预期值是同一个对象时匹配;
lt(X value), leq(X value), geq(X value), gt(X value)
:当输入值小于、小等于、大等于、大于预期值时匹配,适用于数值类型;
startsWith(String prefix), contains(String substring), endsWith(String suffix)
:当输入值以预期值开头、包含预期值、以预期值结尾时匹配,适用于String类型;
matches(String regex)
:当输入值与正则表达式匹配时匹配,适用于String类型。
自定义参数匹配器
预定义的参数匹配器可能无法满足一些复杂的情况,这时你需要定义自己的参数匹配器。在上一节中,我们希望能有一个匹配器对 SQL 中关键字的大小写不敏感,使用 anyObject
其实并不是一个好的选择。对此,我们可以定义自己的参数匹配器 SQLEquals。
要定义新的参数匹配器,需要实现 org.easymock.IArgumentMatcher
接口。其中,matches(Object actual)
方法应当实现输入值和预期值的匹配逻辑,而在 appendTo(StringBuffer buffer)
方法中,你可以添加当匹配失败时需要显示的信息。以下是 SQLEquals 实现的部分代码(完整的代码可以在 src.zip 中找到):
清单5:自定义参数匹配器SQLEquals
public class SQLEquals implements IArgumentMatcher {
private String expectedSQL = null;
public SQLEquals(String expectedSQL) {
this.expectedSQL = expectedSQL;
}
......
public boolean matches(Object actualSQL) {
if (actualSQL == null && expectedSQL == null)
return true;
else if (actualSQL instanceof String)
return expectedSQL.equalsIgnoreCase((String) actualSQL);
else
return false;
}
}
|
在实现了 IArgumentMatcher
接口之后,我们需要写一个静态方法将它包装一下。这个静态方法的实现需要将 SQLEquals 的一个对象通过 reportMatcher
方法报告给EasyMock:
清单6:自定义参数匹配器 SQLEquals 静态方法
public static String sqlEquals(String in) {
reportMatcher(new SQLEquals(in));
return in;
}
|
这样,我们自定义的 sqlEquals 匹配器就可以使用了。我们可以将上例中的 executeQuery
方法设定修改如下:
mockStatement.executeQuery(sqlEquals("SELECT * FROM sales_order_table"));
expectLastCall().andStubReturn(mockResultSet);
|
在使用
executeQuery("select * from sales_order_table")
进行方法调用时,该预期行为将被匹配。
4.特殊的 Mock 对象类型
到目前为止,我们所创建的 Mock 对象都属于 EasyMock 默认的 Mock 对象类型,它对预期方法的调用次序不敏感,对非预期的方法调用抛出 AssertionError。除了这种默认的 Mock 类型以外,EasyMock 还提供了一些特殊的 Mock 类型用于支持不同的需求。
Strick Mock 对象
如果 Mock 对象是通过 EasyMock.createMock()
或是 IMocksControl.createMock()
所创建的,那么在进行 verify 验证时,方法的调用顺序是不进行检查的。如果要创建方法调用的先后次序敏感的 Mock 对象(Strick Mock),应该使用 EasyMock.createStrickMock()
来创建,例如:
ResultSet strickMockResultSet = createStrickMock(ResultSet.class);
|
类似于 createMock,我们同样可以用 IMocksControl
实例来创建一个 Strick Mock 对象:
IMocksControl control = EasyMock.createStrictControl();
ResultSet strickMockResultSet = control.createMock(ResultSet.class);
|
Nice Mock 对象
使用 createMock()
创建的 Mock 对象对非预期的方法调用默认的行为是抛出 AssertionError,如果需要一个默认返回0,null 或 false 等"无效值"的 "Nice Mock" 对象,可以通过 EasyMock
类提供的 createNiceMock()
方法创建。类似的,你也可以用 IMocksControl
实例来创建一个 Nice Mock 对象。
5.EasyMock 的工作原理
EasyMock 是如何为一个特定的接口动态创建 Mock 对象,并记录 Mock 对象预期行为的呢?其实,EasyMock 后台处理的主要原理是利用 java.lang.reflect.Proxy
为指定的接口创建一个动态代理,这个动态代理,就是我们在编码中用到的 Mock 对象。EasyMock 还为这个动态代理提供了一个 InvocationHandler
接口的实现,这个实现类的主要功能就是将动态代理的预期行为记录在某个映射表中和在实际调用时从这个映射表中取出预期输出。下图是 EasyMock 中主要的功能类:
图4:EasyMock主要功能类
和开发人员联系最紧密的是 EasyMock
类,这个类提供了 createMock、replay、verify
等方法以及所有预定义的参数匹配器。
我们知道 Mock 对象有两种创建方式:一种是通过 EasyMock
类提供的 createMock
方法创建,另一种是通过 EasyMock
类的 createControl
方法得到一个 IMocksControl
实例,再由这个 IMocksControl
实例创建 Mock 对象。其实,无论通过哪种方法获得 Mock 对象,EasyMock 都会生成一个 IMocksControl
的实例,只不过第一种方式中的 IMocksControl
的实例对开发人员不可见而已。这个 IMocksControl
的实例,其实就是 MocksControl
类的一个对象。MocksControl
类提供了 andReturn、andThrow、times、createMock
等方法。
MocksControl
类中包含了两个重要的成员变量,分别是接口 IMocksBehavior
和 IMocksControlState
的实例。其中,IMocksBehavior
的实现类 MocksBehavior
是 EasyMock 的核心类,它保存着一个 ExpectedInvocationAndResult
对象的一个列表,而 ExpectedInvocationAndResult
对象中包含着 Mock 对象方法调用和预期结果的映射。MocksBehavior
类提供了 addExpected
和 addActual
方法用于添加预期行为和实际调用。
MocksControl
类中包含的另一个成员变量是 IMocksControlState
实例。IMocksControlState
拥有两个不同的实现类:RecordState
和 ReplayState
。顾名思义,RecordState
是 Mock 对象在 Record 状态时的支持类,它提供了 invoke
方法在 Record 状态下的实现。此外,它还提供了 andReturn、andThrow、times
等方法的实现。ReplayState
是 Mock 对象在 Replay 状态下的支持类,它提供了 invoke
方法在 Replay 状态下的实现。在 ReplayState 中,andReturn、andThrow、times
等方法的实现都是抛出IllegalStateException,因为在 Replay 阶段,开发人员不应该再调用这些方法。
当我们调用 MocksControl
的 createMock
方法时,该方法首先会生成一个 JavaProxyFactory
类的对象。JavaProxyFactory
是接口 IProxyFactory
的实现类,它的主要功能就是通过 java.lang.reflect.Proxy
对指定的接口创建动态代理实例,也就是开发人员在外部看到的 Mock 对象。
在创建动态代理的同时,应当提供 InvocationHandler
的实现类。MockInvocationHandler
实现了这个接口,它的 invoke
方法主要的功能是根据 Mock 对象状态的不同而分别调用 RecordState
的 invoke
实现或是 ReplayState
的 invoke
实现。
创建 Mock 对象
下图是创建 Mock 对象的时序图:
图5:创建 Mock 对象时序图
当 EasyMock
类的 createMock
方法被调用时,它首先创建一个 MocksControl
对象,并调用该对象的 createMock
方法创建一个 JavaProxyFactory
对象和一个 MockInvocationHandler
对象。JavaProxyFactory
对象将 MockInvocationHandler
对象作为参数,通过 java.lang.reflect.Proxy
类的 newProxyInstance
静态方法创建一个动态代理。
记录 Mock 对象预期行为
记录 Mock 的预期行为可以分为两个阶段:预期方法的调用和预期输出的设定。在外部程序中获得的 Mock 对象,其实就是由 JavaProxyFactory
创建的指定接口的动态代理,所有外部程序对接口方法的调用,都会指向 InvocationHandler
实现类的 invoke
方法。在 EasyMock 中,这个实现类是 MockInvocationHandler
。下图是调用预期方法的时序图:
图6:调用预期方法时序图
当 MockInvocationHandler
的 invoke
方法被调用时,它首先通过 reportLastControl
静态方法将 Mock 对象对应的 MocksControl
对象报告给 LastControl
类,LastControl
类将该对象保存在一个 ThreadLocal 变量中。接着,MockInvocationHandler
将创建一个 Invocation 对象,这个对象将保存预期调用的 Mock 对象、方法和预期参数。
在记录 Mock 对象预期行为时,Mock 对象的状态是 Record 状态,因此 RecordState
对象的 invoke
方法将被调用。这个方法首先调用 LastControl
的 pullMatchers
方法获取参数匹配器。如果您还记得自定义参数匹配器的过程,应该能想起参数匹配器被调用时会将实现类的实例报告给 EasyMock,而这个实例最终保存在 LastControl
中。如果没有指定参数匹配器,默认的匹配器将会返回给 RecordState
。
根据 Invocation
对象和参数匹配器,RecordState
将创建一个 ExpectedInvocation
对象并保存下来。
在对预期方法进行调用之后,我们可以对该方法的预期输出进行设定。我们以
expectLastCall().andReturn(X value).times(int times)
|
为例说明。如果
times
方法未被显式的调用,EasyMock 会默认作为
times(1)
处理。下图是设定预期输出的时序图:
图7:设定预期输出时序图
在预期方法被调用时,Mock 对象对应的 MocksControl
对象引用已经记录在 LastControl
中,expectLastCall
方法通过调用 LastControl
的 lastControl
方法可以获得这个引用。MocksControl
对象的 andReturn
方法在 Mock 对象 Record 状态下会调用 RecordState
的 andReturn
方法,将设定的预期输出以 Result
对象的形式记录下来,保存在 RecordState
的 lastResult 变量中。
当 MocksControl
的 times
方法被调用时,它会检查 RecordState
的 lastResult 变量是否为空。如果不为空,则将 lastResult 和预期方法被调用时创建的 ExpectedInvocation
对象一起,作为参数传递给 MocksBehavior
的 addExpected
方法。MocksBehavior
的 addExpected
方法将这些信息保存在数据列表中。
在 Replay 状态下调用 Mock 对象方法
EasyMock
类的 replay
方法可以将 Mock 对象切换到 Replay 状态。在 Replay 状态下,Mock 对象将根据之前的设定返回预期输出。下图是 Replay 状态下 Mock 对象方法调用的时序图:
图8:调用 Mock 对象方法时序图
在 Replay 状态下,MockInvocationHandler
会调用 ReplayState
的 invoke
方法。该方法会把 Mock 对象通过 MocksBehavior
的 addActual
方法添加到实际调用列表中,该列表在 verify
方法被调用时将被用到。同时,addActual
方法会根据实际方法调用与预期方法调用进行匹配,返回对应的 Result
对象。调用 Result
对象的 answer
方法就可以获取该方法调用的输出。
6.使用 EasyMock 进行单元测试小结
如果您需要在单元测试中构建 Mock 对象来模拟协同模块或一些复杂对象,EasyMock 是一个可以选用的优秀框架。EasyMock 提供了简便的方法创建 Mock 对象:通过定义 Mock 对象的预期行为和输出,你可以设定该 Mock 对象在实际测试中被调用方法的返回值、异常抛出和被调用次数。通过创建一个可以替代现有对象的 Mock 对象,EasyMock 使得开发人员在测试时无需编写自定义的 Mock 对象,从而避免了额外的编码工作和因此引入错误的机会。
一、介绍
Struts-menu是一个基于Struts框架的菜单生成应用框架,它是开源软件,可以从http://www.sourceforge.net上获 得。Struts-menu在没有权限控制时最简单,只需配置文件就可以生成需要的菜单;在要菜单权限控制时,可以和Tomcat(或其他J2EE容器)的认证机制结合实现权限控制,也可以利用后台数据库的方式实现权限控制。
从sourceforge.net获得Struts-menu后,将其解压,可以得到如下图1所示的一些文件:
- 其中struts-menu-2.3.jar为其核心包,struts-menu.tld为其对应的标签;struts-menu.war为Struts-menu自带的sample;doc目录下为帮助文档。
二、使用Struts-menu(无权限控制)
1. 将struts-menu.jar拷贝到WEB-INFlib目录下;
2. 将struts-menu.tld拷贝到WEB-INF目录下;
3. 将struts-menu.war中的scripts、styles、images目录拷贝到你的应用"/"目录下;
4. 此外,Struts-menu需要Log4j的支持,因此需要将log4j的包也拷贝到WEB-INF/lib;
5. 修改 web.xml 加入 taglib 的调用:
<taglib>
<taglib-uri>/WEB-INF/struts-menu.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-menu.tld</taglib-location>
</taglib>
6. 修改struts-config.xml,在其中添加如下plugin代码:
<plug-in className="net.sf.navigator.menu.MenuPlugIn">
<set-property property="menuConfig" value="/WEB-INF/menu-config.xml" />
</plug-in>
7. 配置menu-config.xml(此文件位于WEB-INF目录下)
<?xml version="1.0" encoding="UTF-8" ?>
<MenuConfig>
<Displayers>
<Displayer name="CoolMenu" type="net.sf.navigator.displayer.CoolMenuDisplayer"/>
<Displayer name="ListMenu" type="net.sf.navigator.displayer.ListMenuDisplayer"/>
<Displayer name="DropDown" type="net.sf.navigator.displayer.DropDownMenuDisplayer"/>
<Displayer name="Simple" type="net.sf.navigator.displayer.SimpleMenuDisplayer"/>
<Displayer name="CoolMenu4" type="net.sf.navigator.displayer.CoolMenuDisplayer4"/>
<Displayer name="MenuForm" type="net.sf.navigator.example.PermissionsFormMenuDisplayer"/>
<Displayer name="TabbedMenu" type="net.sf.navigator.displayer.TabbedMenuDisplayer"/>
<Displayer name="Velocity" type="net.sf.navigator.displayer.VelocityMenuDisplayer"/>
</Displayers>
<Menus>
<!======== To Do List Menus ==============>
<Menu name="ToDoListMenuFile" title="OPERATOR" description="this is a file menu test" width="50" >
<Item name="TDLnew" title="ADMIN">
<Item name="TDLnewcase" title="GO TO ADMIN PAGE" image="images/case-new.png" location="/webModule/admin/admin.jsp"/>
<Item name="TDLnewitem" title="NEW ITEM" image="images/item-new.png" location="index.jsp"/>
<Item name="TDLnewparty" title="NEW PARTY" image="images/party-new.png" location="index.jsp"/>
<Item name="TDLopen" title="OPEN">
<Item name="TDLopencase" title="OPEN CASE" image="images/case-open.png" location="index.jsp"/>
<Item name="TDLopenitem" title="OPEN ITEM" image="images/item-open.png" location="index.jsp"/>
<Item name="TDLopenparty" title="OPEN PARTY" image="images/party-open.png" location="index.jsp"/>
</Item>
<Item name="TDLexit" title="EXIT" image="images/exit.png" location="index.jsp"/>
</Menu>
<! =========== To Do List Menu Edit =============>
<Menu name="ToDoListMenuEdit" title="EDIT">
<Item name="TDLselect" title="SELECT_ALL" image="images/select-all.png" location="index.jsp" width="100"/>
<Item name="TDLprefs" title="USER_PREFERENCES" image="images/prefs.png" location="index.jsp" width="150"/>
</Menu>
<!============ Permissions Menu ==========>
<Menu name="Permissions" title="Permissions">
<Item title="Change" location="permissionsForm.jsp?username='test'" />
</Menu>
</Menus>
</MenuConfig>
注解:
<Displayers>与</Displayers>之间的代码定义了菜单的格式。
<Menus>与</Menus>之间的代码定义了要显示的菜单
<Menu>与<Menu>之间的代码具有定义一个菜单的各种属性,如:
<Menu name="ToDoListMenuFile" title="OPERATOR" description="this is a file menu test" width="50" >
其中name指定了在JSP页面上显示此菜单时使用的name,title属性定义了菜单的显示名称,description属性定义了当鼠标移动到此菜单上时显示的描述信息,width属性定义了此菜单所占的宽度。
在<Menu>下的<Item/>标记用于定义此菜单的子项,如:
<Item title="Change" location="permissionsForm.jsp?username='test'" />
其中title为子项显示的名称,location为当点击此项时所要执行的操作,如此处当点击“Change”菜单时,它将转向permissionsForm.jsp页面。
8. 在JSP页面中加入如下代码:
<menu:useMenuDisplayer name="ListMenu" bundle="org.apache.struts.action.MESSAGE">
<menu:displayMenu name="ToDoListMenuFile"/>
<menu:displayMenu name="ToDoListMenuEdit"/>
<menu:displayMenu name="Permissions"/>
</menu:useMenuDisplayer>
注解:
name属性指定要显示菜单的外观样式,由<Displayers></Displayers>中指定。
bundle为显示的菜单名称,必须为org.apache.struts.action.MESSAGE。
<menu:displayMenu />定义要显示的菜单,此处的name为要显示菜单的名称,由<Menu/>指定。
此外,对于各种不同的菜单外观,需要引用其指定的CSS及JavaScript代码。
效果如下图所示:
三、基于Tomcat的权限控制的菜单显示
在Struts中要启用基于容器的安全认证:
1. 需要在web.xml中进行配置(请参考Struts相关书籍):
<security-constraint>
<web-resource-collection>
<web-resource-name>AdminPages</web-resource-name>
<description>Administroat only access</description>
<url-pattern>/security.jsp</url-pattern>
<http-method>POST</http-method>
<http-method>GET</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>tomcat</role-name>
<role-name>role1</role-name>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>adminRealm</realm-name>
</login-config>
<security-role>
<description>Administrator</description>
<role-name>tomcat</role-name>
</security-role>
<security-role>
<description>A Second Role (to prove a comma-delimited list works)</description>
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/donkeyzheng/archive/2006/03/28/640821.aspx
转载:http://www.javaeye.com/topic/24869
DisplayTag是一个非常好用的表格显示标签,适合MVC模式,其主页在http://
displaytag.sourceforge.net/
一、最简单的情况,未使用<display:column/>标签
<%request.setAttribute( "test", new ReportList(6) );%>
<display:table name="test" />
标签遍历List里的每一个对象,并将对象里的所有属性显示出来。一般用于开发的时候检查对象数据的完整性。
二、使用<display:column/>标签的情况
<display:table name="test">
<display:column property="id" title="ID" />
<display:column property="name" />
<display:column property="email" />
<display:column property="status" />
<display:column property="description" title="Comments"/>
</display:table>
property对应List里对象的属性(用getXXX()方法取得),title则对应表格表头里的列名。定义列有两种方式:
A、<display:column property="email" />
使用<display:column/>标签里的property属性来定义
B、<display:column title="email">email@it.com</display:column>
在<display:column/>标签体里增加内容,可以是常量,也可以用其他标签等等
两种方式比较,用property属性来定义更加快速和利于排序。
三、表格显示样式的定义
A、在<display:table/>和<display:column/>标签里指定标准的html属性,烦琐
B、修改样式表
<display:table name="test" class="mars">
<display:column property="id" title="ID" class="idcol"/>
<display:column property="name" />
<display:column property="email" />
<display:column property="status" class="tableCellError" />
<display:column property="description" title="Comments"/>
</display:table>
通过class属性来指定所要应用的样式。可以在其默认样式表里(./css/screen.css)直接修改
四、标签取得数据的数据源
有四种范围
pageScope
requestScope (默认) <display:table name="test2" >
sessionScope <display:table name="sessionScope.holder.list" > 注意,这里要指定范围,非默认
applicationScope
五、通过增加id属性创建隐含的对象
<display:table name="test" id="testit">
<display:column property="id" title="ID" />
<display:column property="name" />
<display:column title="static value">static</display:column>
<display:column title="row number (testit_rowNum)"><%=pageContext.getAttribute("testit_rowNum")%></display:column>
<display:column title="((ListObject)testit).getMoney()"><%=((ListObject)pageContext.getAttribute("testit")).getMoney()%></display:column>
</display:table>
注意到在<display:table/>里增加了id属性,这时就在page context里创建了一个隐含对象,指向List里的当前对象,
可以通过(ListObject)pageContext.getAttribute("id")来捕获这个对象。同时还创建了一个id_rowNum对象,同样,可
通过pageContext.getAttribute("testit_rowNum")来捕获,它仅仅代表当前行的行数。
有了这两个隐含对象,就可以通过其他标签来访问,例如Jstl:
<display:table id="row" name="mylist">
<display:column title="row number" >
<c:out value="${row_rowNum}"/>
</display:column>
<display:column title="name" >
<c:out value="${row.first_name}"/>
<c:out value="${row.last_name}"/>
</display:column>
</display:table>
六、显示部分数据
显示开始五条数据:通过设定length属性
<display:table name="test" length="5">
<display:column property="id" title="ID" />
<display:column property="email" />
<display:column property="status" />
</display:table>
显示第三到第八条数据:通过设定offset和length属性
<display:table name="test" offset="3" length="5">
<display:column property="id" title="ID" />
<display:column property="email" />
<display:column property="status" />
</display:table>
七、对email和url地址的直接连接
<display:table name="test" >
<display:column property="id" title="ID" />
<display:column property="email" autolink="true" />
<display:column property="url" autolink="true" />
</display:table>
如果要显示的对象里包含email和url地址,则可以在display:column里直接设定autolink="true"来直接连接
八、使用装饰模式转换数据显示(写自己的 decorator )
A、对整个表格应用decorator
<display:table name="test" decorator="org.
displaytag.sample.Wrapper" >
<display:column property="id" title="ID" />
<display:column property="email" />
<display:column property="status" />
<display:column property="date" />
<display:column property="money" />
</display:table>
org.
displaytag.sample.Wrapper即自己写的decorator,它要继承TableDecorator类,看看它的一个方法:
public String getMoney()
{
return this.moneyFormat.format(((ListObject) this.getCurrentRowObject()).getMoney());
}
很明显,它通过父类的getCurrentRowObject()方法获得当前对象,然后对其getMoney()方法进行‘油漆’
B、对单独的column应用decorator
<display:table name="test">
<display:column property="id" title="ID" />
<display:column property="email" />
<display:column property="status" />
<display:column property="date" decorator="org.
displaytag.sample.LongDateWrapper" />
</display:table>
org.
displaytag.sample.LongDateWrapper要实现ColumnDecorator接口,它的方法:
public final String decorate(Object columnValue)
{
Date date = (Date) columnValue;
return this.dateFormat.format(date);
}
显然,它获得不了当前对象(因为它实现的是接口),仅仅是获得该对象的columnValue,然后‘油漆’
九、创建动态连接
有两种方法创建动态连接:
A、在<display:column/>里通过增加href、paramId、paramName、paramScope、paramProperty属性
href 基本的URL 地址
paramId 加在URL 地址后的参数名称
paramName 数据bean的名称,一般为null(即使用当前List里的对象)
paramScope 数据bean的范围,一般为null
paramProperty 数据bean的属性名称,用来填充URL 地址后的参数值
<display:table name="sessionScope.details">
<display:column property="id" title="ID" href="details.jsp" paramId="id" />
<display:column property="email" href="details.jsp" paramId="action" paramName="testparam" paramScope="request" />
<display:column property="status" href="details.jsp" paramId="id" paramProperty="id" />
</display:table>
这种方法简便直接,但缺点是无法产生类似details.jsp?id=xx&action=xx的复合URL
B、应用decorator 创建动态连接:
<display:table name="sessionScope.details" decorator="org.
displaytag.sample.Wrapper" >
<display:column property="link1" title="ID" />
<display:column property="email" />
<display:column property="link2" title="Actions" />
</display:table>
org.
displaytag.sample.Wrapper里的方法:
public String getLink1()
{
ListObject lObject= (ListObject)getCurrentRowObject();
int lIndex= getListIndex();
return "<a href=\"details.jsp?index=" + lIndex + "\">" + lObject.getId() + "</a>";
}
public String getLink2()
{
ListObject lObject= (ListObject)getCurrentRowObject();
int lId= lObject.getId();
return "<a href=\"details.jsp?id=" + lId
+ "&action=view\">View</a> | "
+ "<a href=\"details.jsp?id=" + lId
+ "&action=edit\">Edit</a> | "
+ "<a href=\"details.jsp?id=" + lId
+ "&action=delete\">Delete</a>";
}
十、分页
实现分页非常的简单,增加一个pagesize属性指定一次想显示的行数即可
<display:table name="sessionScope.test" pagesize="10">
<display:column property="id" title="ID" />
<display:column property="name" />
<display:column property="email" />
<display:column property="status" />
</display:table>
十一、排序
排序实现也是很简单,在需要排序的column里增加sortable="true"属性,headerClass="sortable"仅仅是
指定显示的样式。column里的属性对象要实现Comparable接口,如果没有的话可以应用decorator
defaultsort="1" 默认第一个column排序
defaultorder="descending" 默认递减排序
<display:table name="sessionScope.stest" defaultsort="1" defaultorder="descending">
<display:column property="id" title="ID" sortable="true" headerClass="sortable" />
<display:column property="name" sortable="true" headerClass="sortable"/>
<display:column property="email" />
<display:column property="status" sortable="true" headerClass="sortable"/>
</display:table>
注意的是,当同时存在分页时如果不指定sort=list,则排序仅仅针对的是当前页面,而不是整个List都进行排序
十二、column 分组
分组只是需要在column里增加group属性
<display:table name="test" class="simple">
<display:column property="city" title="CITY" group="1"/>
<display:column property="project" title="PROJECT" group="2"/>
<display:column property="amount" title="HOURS"/>
<display:column property="task" title="TASK"/>
</display:table>
十三、导出数据到其他格式(页面溢出filter??)
在<display:table/>里设定export="true"
在<display:column/>里设定media="csv excel xml pdf" 决定该字段在导出到其他格式时被包不包含,不设定则都包含
<display:setProperty name="export.csv" value="false" />
决定该种格式能不能在页面中导出
<display:table name="test" export="true" id="currentRowObject">
<display:column property="id" title="ID"/>
<display:column property="email" />
<display:column property="status" />
<display:column property="longDescription" media="csv excel xml pdf" title="Not On HTML"/>
<display:column media="csv excel" title="URL" property="url"/>
<display:setProperty name="export.pdf" value="true" />
<display:setProperty name="export.csv" value="false" />
</display:table>
十四、配置属性,覆盖默认
两种方法:
A、在程序classpath下新建
displaytag.properties文件
B、对于单个表格,应用<display:setProperty>标签
具体可配置的属性:http://
displaytag.sourceforge.net/configuration.html
十五、一个完整的例子
<display:table name="test" export="true" sort="list" pagesize="8">
<display:column property="city" title="CITY" group="1" sortable="true" headerClass="sortable"/>
<display:column property="project" title="PROJECT" group="2" sortable="true" headerClass="sortable"/>
<display:column property="amount" title="HOURS"/>
<display:column property="task" title="TASK"/>
</display:table>
sort="list" 对整个list进行排序
导出数据到其他格式时,group无效
项目中需要将查询结果导出成Excel,由于使用了DisplayTag,知道这个标签库具有导出Excel的功能,不过之前一直没有使用过,刚好今天体验一把。
使用实在是太Easy了,只需要在display:table标签上设置一下参数,export="true",就可以在表格下边看到导出Excel的链接了,其他什么都不用做!
对他的实现方式有点好奇,遂查看一下这个导出功能的链接,乖乖,那是相当简洁啊,就是把我这个页面的全部参数添加到URL里,然后附加上了几个奇怪数字组成的参数,估计就是DisplayTag用来识别我要做导出操作的了。从这个链接来看,做导出操作时还是要到我这个Action去进行逻辑处理,但我这个Action输出的内容可是一个完整的HTML,而不是Excel啊,他是怎么实现的呢?带着个大大的问号,开始去挖源代码。顺便提一下,m2eclipse确实方便,不但帮我管理了项目的依赖,还能帮我把源代码拽下来关联到相应的jar,所以我直接在项目依赖里面找到displaytag.jar,就可以查看源代码了。
代码其实很简单,其实是利用了ServletRsponse的缓存机制,当我们调用response.getWriter().print()方法时,打印的内容是不会立即发送到客户端的,在发送到客户端之前,还可以对其进行操作,哈哈,有点明白了吧?DisplayTag就是利用了这个原理,在TableTag这个类中,导出Excel时,趁ServletResponse还没有提交到客户端,先执行了一下response.reset()和pageContext.getOut().clearBuffer(),这样就把前面jsp里面输出的所有内容都清除掉了~然后再使用response.setContentType()重新设置文件类型,输出表格内容,完事了再返回Tag.SKIP_PAGE,这样JSP里面剩下的内容也不会再输出了,客户端得到的就完全是在DisplayTag控制下的内容了,牛吧!
其实这个response.reset以前也见过,读servlet API文档时也看过方法的解释,但如果不是今天看这个Display的源代码,还真不会想到这样子来应用,看来这就是理论和实践的差距呀!!!
PS:上面这种方法,只适用于输出文本内容。DisplayTag自带的Excel导出工具其实就是输出了一个csv文件,把扩展名改成xls了而已。如果要输出真正的xls文件获者PDF这样的二进制文件,使用上面的方法在某些中间件上就会有问题了。因为原则上response.getOutputStream方法是只能被调用一次的,这在进入jsp处理时就被调用了,而输出PDF文件这样二进制流时又必须使用这个方法,那就出错了。DisplayTag很巧妙的提供了一个Filter,对标准的ServletRepsonse做了一个包装(Wrap),在执行Export导出时,如果是jsp等其他请求执行的response.getOutputStream,就返回自己的一个缓存的OutputStream,而不是真正去调用容器的这个方法。直到执行Export时,才去调用容器的方法,保证这个response.getOutputStream只执行一次。
DisplayTag的地址:http://displaytag.sourceforge.net/1.2/index.html,很简单,很强大,强烈推荐,可以少写N多表格和分页标签
displaytag.properties文件位于displaytag-1.x.jar中的org.displaytag.properties下
打开这个文件,复制一份并作相应修改保存到classes目录下,就可以对View中的表格显示形式做相应的设置
Java代码
basic.empty.showtable=false #设置当数据为空时,是不是显示表格 true表示显示表格
basic.show.header=true #设置当数据为空时,显示的表格是否有表头 true表示显示
# page | list 设置排序的数据量 相当于jsp页面中display标签中的page属性
sort.amount=page
export.amount=list #导出的数据量
export.decorated=true
paging.banner.group_size=8 #前台显示的最多页数 8 表示最多显示8页
paging.banner.placement=top #前台显示中"上一页/下一页"文字的位置 top表示上面 bottom表示下面
css.tr.even=even #偶数行的css标识 就是偶数行的css类 下面几个也是设置相应的css class
css.tr.odd=odd
css.th.sorted=sorted
css.th.ascending=order1
css.th.descending=order2
css.table=
css.th.sortable=sortable
# factory classes for extensions
factory.requestHelper=org.displaytag.util.DefaultRequestHelperFactory
# factory class for decorators
factory.decorator=org.displaytag.decorator.DefaultDecoratorFactory
# locale provider (Jstl provider by default)
locale.provider=org.displaytag.localization.I18nJstlAdapter
# locale.resolver (nothing by default, simply use locale from request)
#locale.resolver=
export.types=csv excel xml pdf
export.csv.class=org.displaytag.export.CsvView
export.excel.class=org.displaytag.export.ExcelView
export.xml.class=org.displaytag.export.XmlView
export.pdf.class=org.displaytag.export.PdfView
#export.***设置为true表示显示这种导出方式 false为不采用
export.csv=true
export.csv.label=<span class="export csv">CSV </span>
export.csv.include_header=false
export.csv.filename=
export.excel=true
export.excel.label=<span class="export excel">Excel </span>
export.excel.include_header=true
export.excel.filename=
export.xml=true
export.xml.label=<span class="export xml">XML </span>
export.xml.filename=
export.pdf=false
export.pdf.label=<span class="export pdf">PDF </span>
export.pdf.include_header=true
export.pdf.filename=
export.rtf=false
export.rtf.label=<span class="export rtf">RTF </span>
export.rtf.include_header=true
export.rtf.filename=
# messages
#相应的显示信息 包括空数据时候显示的 下两个显示的位置不一样 一个是在table中 一个是在下面
basic.msg.empty_list=Nothing found to display.
basic.msg.empty_list_row=<tr class="empty"><td colspan="{0}">Nothing found to display.</td></tr>
error.msg.invalid_page=invalid page
export.banner=<div class="exportlinks">Export options: {0}</div>#导出处的提示文字
export.banner.sepchar= | #导出处的提示文字分隔符
paging.banner.item_name=item
paging.banner.items_name=items
paging.banner.no_items_found=<span class="pagebanner">No {0} found.</span>
paging.banner.one_item_found=<span class="pagebanner">One {0} found.</span>
paging.banner.all_items_found=<span class="pagebanner">{0} {1} found, displaying all {2}.</span>
paging.banner.some_items_found=<span class="pagebanner">{0} {1} found, displaying {2} to {3}.</span>
paging.banner.full=<span class="pagelinks">[<a href="{1}">First</a>/<a href="{2}">Prev</a>] {0} [<a href="{3}">Next</a>/<a href="{4}">Last</a>]</span>
paging.banner.first=<span class="pagelinks">[First/Prev] {0} [<a href="{3}">Next</a>/<a href="{4}">Last</a>]</span>
paging.banner.last=<span class="pagelinks">[<a href="{1}">First</a>/<a href="{2}">Prev</a>] {0} [Next/Last]</span>
paging.banner.onepage=<span class="pagelinks">{0}</span>
paging.banner.page.selected=<strong>{0}</strong>
paging.banner.page.link=<a href="{1}" title="Go to page {0}">{0}</a>
paging.banner.page.separator=, \
# external sort and pagination
pagination.sort.param=sort
pagination.sortdirection.param=dir
pagination.pagenumber.param=page
pagination.searchid.param=searchid
pagination.sort.asc.value=asc
pagination.sort.desc.value=desc
pagination.sort.skippagenumber=true
# unused
save.excel.banner=<a href="{0}" rel="external">save ({1} bytes)</a>
save.excel.filename=export.xls
basic.empty.showtable=false #设置当数据为空时,是不是显示表格 true表示显示表格
basic.show.header=true #设置当数据为空时,显示的表格是否有表头 true表示显示
# page | list 设置排序的数据量 相当于jsp页面中display标签中的page属性
sort.amount=page
export.amount=list #导出的数据量
export.decorated=true
paging.banner.group_size=8 #前台显示的最多页数 8 表示最多显示8页
paging.banner.placement=top #前台显示中"上一页/下一页"文字的位置 top表示上面 bottom表示下面
css.tr.even=even #偶数行的css标识 就是偶数行的css类 下面几个也是设置相应的css class
css.tr.odd=odd
css.th.sorted=sorted
css.th.ascending=order1
css.th.descending=order2
css.table=
css.th.sortable=sortable
# factory classes for extensions
factory.requestHelper=org.displaytag.util.DefaultRequestHelperFactory
# factory class for decorators
factory.decorator=org.displaytag.decorator.DefaultDecoratorFactory
# locale provider (Jstl provider by default)
locale.provider=org.displaytag.localization.I18nJstlAdapter
# locale.resolver (nothing by default, simply use locale from request)
#locale.resolver=
export.types=csv excel xml pdf
export.csv.class=org.displaytag.export.CsvView
export.excel.class=org.displaytag.export.ExcelView
export.xml.class=org.displaytag.export.XmlView
export.pdf.class=org.displaytag.export.PdfView
#export.***设置为true表示显示这种导出方式 false为不采用
export.csv=true
export.csv.label=<span csv">CSV </span>
export.csv.include_header=false
export.csv.filename=
export.excel=true
export.excel.label=<span excel">Excel </span>
export.excel.include_header=true
export.excel.filename=
export.xml=true
export.xml.label=<span xml">XML </span>
export.xml.filename=
export.pdf=false
export.pdf.label=<span pdf">PDF </span>
export.pdf.include_header=true
export.pdf.filename=
export.rtf=false
export.rtf.label=<span rtf">RTF </span>
export.rtf.include_header=true
export.rtf.filename=
# messages
#相应的显示信息 包括空数据时候显示的 下两个显示的位置不一样 一个是在table中 一个是在下面
basic.msg.empty_list=Nothing found to display.
basic.msg.empty_list_row=<tr ><td colspan="{0}">Nothing found to display.</td></tr>
error.msg.invalid_page=invalid page
export.banner=<div >Export options: {0}</div>#导出处的提示文字
export.banner.sepchar= | #导出处的提示文字分隔符
paging.banner.item_name=item
paging.banner.items_name=items
paging.banner.no_items_found=<span >No {0} found.</span>
paging.banner.one_item_found=<span >One {0} found.</span>
paging.banner.all_items_found=<span >{0} {1} found, displaying all {2}.</span>
paging.banner.some_items_found=<span >{0} {1} found, displaying {2} to {3}.</span>
paging.banner.full=<span >[<a href="{1}">First</a>/<a href="{2}">Prev</a>] {0} [<a href="{3}">Next</a>/<a href="{4}">Last</a>]</span>
paging.banner.first=<span >[First/Prev] {0} [<a href="{3}">Next</a>/<a href="{4}">Last</a>]</span>
paging.banner.last=<span >[<a href="{1}">First</a>/<a href="{2}">Prev</a>] {0} [Next/Last]</span>
paging.banner.onepage=<span >{0}</span>
paging.banner.page.selected=<strong>{0}</strong>
paging.banner.page.link=<a href="{1}" title="Go to page {0}">{0}</a>
paging.banner.page.separator=, \
# external sort and pagination
pagination.sort.param=sort
pagination.sortdirection.param=dir
pagination.pagenumber.param=page
pagination.searchid.param=searchid
pagination.sort.asc.value=asc
pagination.sort.desc.value=desc
pagination.sort.skippagenumber=true
# unused
save.excel.banner=<a href="{0}" rel="external">save ({1} bytes)</a>
save.excel.filename=export.xls
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/ZHOUJIAOSHOU/archive/2009/06/17/4277400.aspx