irepot使用简介

一、iReport简介



说到iReport不得不先介绍Jasperreport,Jasperreport是一个报表制作程序,用户需要按照它制定的规则编写一个XML文件,然后得到用户需要输出的格式文件。它支持输出的文件格式包括PDF,HTML,XML,XLS,CVS等等。而iReport就是一个制作Jasperreport的XML文件的可视化开发工具。只是一个UI。


二、安装说明

2.1 基本安装
现在最新的版本是iReport 0.5.1 (Aug 27, 2005).
下载后解压然后在根目录双击iReport.bat就可以用了,但是为了可以使用external ttf font还必须要把 iReport-0.5.1/fonts 加到环境变量里面去。

2.2 了解制作报表用的包
下载后到到 iReport-0.5.1/lib 里看看,你就可以发现iReport的真面目了。
(1)  jasperreports-1.0.1.jar
jasperreports是iReport的核心内容。它是一个强力的报表产生工具,他有能力描述丰富内容到屏幕上、到打印机或到PDF, HTML, XLS, CSV和XML文件。它完全用Java编写的,并可在各种Java应用(包括J2EE或WEB应用)中用来产生动态内容。它的主要目的是以一种简单而灵活的方式来帮助创建导向的页面。
JasperReports组织根据在一个XML文件中定义的报表设计通过JDBC来接受来自一个关系数据库中的数据. 为了以数据来填充报表,报表设计必须首先被编译。
jasperreports的官方网站:http://jasperreports.sourceforge.net/

(2) itext-1.3.1.jar
iText是一个开放源码的Java类库,是用来生成PDF文件的。
iText的官方网站:http://itext.sourceforge.net
如果要在生成的pdf文件显示中文等亚洲字符,还必须下载itext的亚洲字符包。
tTextAsian的官方下载地址:http://itextdocs.lowagie.com/downloads/iTextAsian.jar
  
(3) jfreechart-1.0.0-rc1.jar
jfreechart是一款免费的、功能强大的统计图生成工具,可以直接生成PNG,JPG等各式的文件。
这些图表包括:饼图、柱状图(普通柱状图以及堆栈柱状图)、线图、区域图、分布图、混合图、甘特图以及一些仪表盘等等。
jfreechart的官方下载地址:http://www.jfree.org/jfreechart/
  
(4) jcommon-1.0.0-rc1.jar
JCommon是一组有用的classes集合.它已经用在JFreeChart,JFreeReport与其它项目上. 这个类库包含了以下功能: 文本工具类(text utilities), 用来显示关于应用程序信息的用户界面类,布局定制管理器,一个日期选择面板,序列化工具类,XML解析器支持类.
jcommon的官方下载地址:http://www.jfree.org/jcommon/

(5) poi-2.0-final-20040126.jar
Apache的Jakata项目的POI子项目,目标是处理ole2对象。目前比较成熟的是HSSF接口,处理MS Excel(97-2002)对象。它不象我们仅仅是用csv生成的没有格式的可以由Excel转换的东西,而是真正的Excel对象,你可以控制一些属性如sheet,cell等等。
直接调用poi包的不是ireport,而是jasperreport。
poi的官方下载地址:http://www.apache.org/dyn/closer.cgi/jakarta/poi/

这些是用来制作报表用的包,所以可以用最新的版本来代替原有包。但是务必保持与项目中的包一致,因为很多开源的项目都不向下兼容。(例如jasperreport)


三、制作jrxml、jasper





3.1 选择语言
Tools->Option->General->Language
3.2 连接数据库
资料来源->连结/资料来源
如果要使用mysql以外的数据库,要保证jdbc包在环境变量里。放到iReport-0.5.1/lib 下也可以。name相当于一个JNDI。添好后点击test按钮如果显示成功就可以执行下一步了。
3.3 新建一个Report
定位可以控制报表是横向的还是纵向的。Portrait是纵向,Landscape是横向。
边距是可以调整的。

3.4 基本域
title域用来放报表的总标题
pageHeaher域顾名思义页头
columnHeader域是用来放static text的,也就是不循环的部分。
detail域是用来放text field的,也就是循环部分。
pageFooter域是用来放本页的统计参数的。
summary域是用来放整个表的统计参数的。

可以直接调整每个域的长度,也可以通过Band properties来调整。当然总长度是不会超过页面的原长。

3.5 报表查询
3.5.1 为报表添加SQL查询语句
资料来源->报表查询
在Report SQL query里填写SQl语句。如果语句正确,在下面的field里就会显示正常的表字段。

3.5.2 为报表添加动态字段
预览->报表字段
把fields里的字段直接拖到报表上就行了。

3.5.3 为SQL语句添加参数
预览->报表参数
在parameters里新增一个参数
paratemeter name 是参数名,在SQL语句里写成"$P{参数名}"
paratemeter class type里选择参数类型。
注意:如果是int型的数据,最好在报表字段里将该字段的Class type改成java.lang.String型的。
另外一种办法,不管该字段原来是什么数据类型,直接在paratemeter class type里选择java.lang.String类型,然后在Default value expression 填写"Integer.toString(整数)"。

3.5.4 添加报表变量
预览->报表变量 $V{变量名}

3.5.5 处理字体
3.5.5.1 基本设置
选中字段->右键->properties->font(双击也可以)
Report font 选择全局的字体(仅限于该报表)
Font name 选择在ireport里面显示的字体
Pdf font name 选择在pdf里面显示的字体
Rotation 选择内容是否旋转(很有用的选项)
PDF Encoding 中文要用UniGB-UCS2-H,外部字体要选Identity-H

3.5.5.2 选择外部字体
第一步 先在Pdf font name里选择External TTF font,然后在下面的Ture Type font里选择外部字体,当然要用的外部字体放在iReport-0.5.1/fonts目录下面。
第二步 在web项目的WEB-INF/classes/下面放要用到的外部字体,才能在程序里正常显示。

3.5.5.3 设定该报表的全局字体
预览->报表字型

3.6 编译jrxml
建立->编译
编译后生成一个后缀名为jasper的binary文件,可以直接给程序调用。

 


四、web项目应用


4.1 jsp输出PDF报表

4.1.1 需要放到项目里的包
itext-1.3.1.jar
iTextAsian.jar
jasperreports-1.0.1.jar

4.1.2 输出PDF注意事项:

4.1.3 jsp输出PDF的例子

<%@ page import="java.sql.*" %>
<%@ page import="java.util.*" %>
<%@ page import="java.io.*" %>
<%@ page import="net.sf.jasperreports.engine.JasperFillManager" %>
<%@ page import="net.sf.jasperreports.engine.JasperPrint"%>
<%@ page import="net.sf.jasperreports.engine.JRException"%>
<%@ page import="net.sf.jasperreports.engine.JasperRunManager"%>//pdf

<%
  String rowid = "1";//初始化变量
  
  Connection conn= DriverManager.getConnection("proxool.test");//从数据源连接数据库
  
  //装载jasper文件application
  File exe_rpt = new File(application.getRealPath("/reports/test.jasper"));
  
  //rowid就是iReport的变量$P{rowid}的名称
  Map parameters = new HashMap();
  parameters.put("rowid",rowid);

  try{
   // fill
   JasperPrint jasperPrint = JasperFillManager.fillReport(exe_rpt.getPath(),parameters,conn);
  
   // 生成pdf
   byte[] bytes = JasperRunManager.runReportToPdf(exe_rpt.getPath(),parameters,conn);
  
   response.setContentType("application/pdf");
   response.setContentLength(bytes.length);
   ServletOutputStream ouputStream = response.getOutputStream();
   ouputStream.write(bytes,0,bytes.length);
   ouputStream.flush();
   ouputStream.close();
  
   conn.close();
   }catch(JRException ex){
   out.print("Jasper Output Error:"+ex.getMessage());
  }

%>

4.2 jsp输出EXCEL报表

4.2.1 需要放到项目里的包
jasperreports-1.0.1.jar
poi-2.0-final-20040126.jar

4.2.2 输出excel要注意的:
(1) 输出excel报表必须fields的边界刚好填充满整个页面,不然会有大量的空白出现。
(2) 删除记录最下面的空行需要加上参数
exporter.setParameter(JRXlsExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS,Boolean.TRUE);
(3) 删除多余的ColumnHeader需要加上参数
exporter.setParameter(JRXlsExporterParameter.IS_ONE_PAGE_PER_SHEET,Boolean.FALSE);
(4) 在ireport里给fields加上border,那输出的excel就会有很黑的边框,跟excel默认的灰度边框就会很不协调。但是如果不加border,在输出的excel里就不会显示每个表格的边框。
解决方法是:
第一步 在选中字段->右键->properties->Common->Transparent 打上勾。
第二步 在输出的jsp页面加上参数
exporter.setParameter(JRXlsExporterParameter.IS_WHITE_PAGE_BACKGROUND,Boolean.FALSE);

4.2.3 jsp输出EXCEL的例子
<%@ page import="java.sql.*" %>
<%@ page import="java.util.*" %>
<%@ page import="java.io.*" %>
<%@ page import="net.sf.jasperreports.engine.JasperFillManager" %>
<%@ page import="net.sf.jasperreports.engine.JasperPrint"%>
<%@ page import="net.sf.jasperreports.engine.JRException"%>
<%@ page import="net.sf.jasperreports.engine.JRExporterParameter"%>//excel
<%@ page import="net.sf.jasperreports.engine.export.JRXlsExporterParameter"%>//excel
<%@ page import="net.sf.jasperreports.engine.export.JRXlsExporter"%>//excel

<%
  String rowid = "1";
  Connection conn= DriverManager.getConnection("proxool.test");

  //装载jasper文件application
  File exe_rpt = new File(application.getRealPath("/excel/test_excel.jasper"));
  
  //rowid就是iReport的变量$P{rowid}的名称
  Map parameters = new HashMap();
  parameters.put("rowid",rowid);
  
  try{

   // fill
   JasperPrint jasperPrint = JasperFillManager.fillReport(exe_rpt.getPath(),parameters,conn);
  
   // excel输出
   ByteArrayOutputStream oStream = new ByteArrayOutputStream();
  
    JRXlsExporter exporter = new JRXlsExporter();  
    
    exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
    exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, oStream);
    exporter.setParameter(JRXlsExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS,Boolean.TRUE); // 删除记录最下面的空行
    exporter.setParameter(JRXlsExporterParameter.IS_ONE_PAGE_PER_SHEET,Boolean.FALSE);// 删除多余的ColumnHeader
    exporter.setParameter(JRXlsExporterParameter.IS_WHITE_PAGE_BACKGROUND,Boolean.FALSE);// 显示边框
    exporter.exportReport();
    
   byte[] bytes = oStream.toByteArray();
  
   if(bytes != null && bytes.length > 0) {
    response.reset();
    response.setContentType("application/vnd.ms-excel");
     response.setContentLength(bytes.length);
    ServletOutputStream ouputStream = response.getOutputStream();
    ouputStream.write(bytes,0,bytes.length);
    ouputStream.flush();
    ouputStream.close();
  
   }else{
    out.print("bytes were null!");
   }
  
   conn.close();
   }catch(JRException ex){
   out.print("Jasper Output Error:"+ex.getMessage());
  }

%>

4.3 jsp输出html报表
没有意义,不写。


五、备注

5.1 下载地址
iReport的官方网站:http://ireport.sourceforge.net/
jasperreports的官方网站:http://jasperreports.sourceforge.net/
tTextAsian的官方下载地址:http://itextdocs.lowagie.com/downloads/iTextAsian.jar
jfreechart的官方下载地址:http://www.jfree.org/jfreechart/
jcommon的官方下载地址:http://www.jfree.org/jcommon/
poi的官方下载地址:http://www.apache.org/dyn/closer.cgi/jakarta/poi/

整理的JasperReport资料 flexer@163.com (flexer) http://www.disound.com/zblog/post/347.html Mon, 03 Jul 2006 12:11:52 +0800 http://www.disound.com/zblog/post/347.html

最近工作中用到报表,而我在学习JasperReport的过程中遇到了很多问题(主要是国内的资料太少了),网上很少找得到,在此我就把我找到的一些资料和大家共享,希望能对大家有所帮助。
1、JasperReport和iReport的资源,最新版本可以到下面官方网站得到

iReport官方网站:
http://ireport.sourceforge.net
     JasperReport官方网站:
http://jasperreports.sourceforge.net

2、安装
   1)、JDK的安装,并配置JAVA_HOME
比如我的JAVA_HOME路径如下:
JAVA_HOME  D:/Program Files/j2sdk1.4.2_03

2)、由于中文的问题,所以还需要下载:itext-1.02b.jar和iTextAsian.jar包
下载地址:http://itext.sourceforge.net/downloads/iTextAsian.jar
并在CLASSPATH中设置
      例如我的CLASSPATH如下:
CLASSPATH
E:/Program Files/Apache Group/Tomcat4.1/webapps/testreport/WEB-INF/lib/itext-1.02b.jar;E:/Program

Files/Apache Group/Tomcat 4.1/webapps/testreport/WEB-INF/lib/iTextAsian.jar;E:/Program Files/Apache

Group/Tomcat 4.1/webapps/testreport/WEB-INF/lib;D:/tools/iReport0.2.3/lib

   3)、iReport的安装iReport只要解压就OK,如果没有安装Ant,可以直接在iReport下的noAnt目录下,
运行startup.bat就可以了,这样iReport就可以启动了

4)、JasperReport
Jasperreport不需要任何配置,你只需将下载以后的jar包放到classpath下即可
5)、数据库的JDBC驱动包
加入到CLASSPATH中

3、详细资源
iReport官方提供了一些关于iReport视频,对于初学者很有帮助:
   地址:http://ireport.sourceforge.net/docs.html

JasperReport官方提供的使用指南
地址:http://jasperreports.sourceforge.net/tutorial/index.html

JasperReport提供的一些例子:
地址:http://jasperreports.sourceforge.net/samples/index.html

4、常见问题
1)、iReport中提示框输入中文是不能正常显示,请将iReport下lib中的这个包删除tinylaf.jar
   2)、在iReport中运行报表时如果出现乱码问题,请检查itext-1.02b.jar和iTextAsian.jar这两个包是否加到CLASSPATH
3)、在jsp或servlet高度报表时出现乱码或不显示,请检查你在报表设计过程中所设置的字体及其编码
比如:pdfname、pdfencoding
5、下面是两个调试例子
  Servlet:
import javax.servlet.*;
import javax.servlet.http.*;
import dori.jasper.engine.*;
import java.io.*;
import java.util.*;
import java.sql.*;

/**
* @author Administrator
*
* To change the template for this generated type comment go to
* Window>Preferences>Java>Code Generation>Code and Comments
*/
public class TestReport extends HttpServlet {

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Connection conn = null;

try {

Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");
conn =
DriverManager.getConnection(

"jdbc:microsoft:sqlserver://192.168.0.10:1433;DatabaseName=am;user=sa;password=sa");

ServletContext servletContext =this.getServletContext();
File reportFile = new File(servletContext.getRealPath("test/iteminfo.jasper"));
Map parameters = new HashMap();
Integer i=new Integer(8);
parameters.put("pjId", i);
byte[] bytes =
JasperRunManager.runReportToPdf(
reportFile.getPath(),
parameters,
conn);
response.setContentType("application/pdf");
response.setContentLength(bytes.length);
ServletOutputStream ouputStream = response.getOutputStream();
ouputStream.write(bytes, 0, bytes.length);
ouputStream.flush();
ouputStream.close();
} catch (JRException jre) {
System.out.println("JRException:" + jre.getMessage());
} catch (Exception e) {
System.out.println("Exception:" + e.getMessage());
}

}

public void doPost(
HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

doGet(request, response);

}

}

J2EEhttp://www.disound.com/zblog/post/347.html#commenthttp://www.disound.com/zblog/xml-rpc/comment.asp?id=347http://www.disound.com/zblog/cmd.asp?act=rss&id=347http://www.disound.com/zblog/cmd.asp?act=tb&id=347 JasperReport 学习之路 flexer@163.com (flexer) http://www.disound.com/zblog/post/346.html Mon, 03 Jul 2006 12:09:10 +0800 http://www.disound.com/zblog/post/346.html

 

1 首先看看

http://plateau.sicool.com/main.html上的三篇
iReport和Jasperreport整合开发报表向导(一)(二)(三)
看完理解并自己运行一下,OK,你入门了

2 去找一份TheJasperReportsUltimateGuide.1.0.pdf

看完之后你就能了解JasperReport生成的基本过程及原理,里面也回答了很多
刚接触这个报表工具会产生的疑问

3 论坛上的经验

Java研究的论坛上有很多关于JasperReport及iReport的经验及问题解答,所以
大家在提问的时候不妨多翻翻论坛上的帖子,很多你要问的问题都能找到答案,
还有很多宝贵的经验
当然说起论坛最正宗还是
https://sourceforge.net/projects/ireport/
https://sourceforge.net/projects/jasperreports/
大家可以到sourceforge.net去看看,论坛上提供搜索,也能翻到很多东西,顺便
练练你的英语

4 学习JasperReport的Samples

解压jasperreports-0.5.0-project.zip 后目录jasperreports/demo/samples
下的例子,看看这些例子是你关于这个报表工具提升能力的最好最快的方式,
我会在下面贴上各个例子的简要说明。
要运行sample下的例子,首先你要安装ant,并设置好环境变量
如ANT_HOME    值为   D:/Install/ant161
然后到dos方式下,到某一个sample的目录,比如到
jasperreports/demo/samples/alterdesign目录下
运行 ant 则会编译报表文件
运行 ant view 则会展现报表

5 比上面更好的资料是什么?

就是JasperReport的源代码,看懂源代码,JasperReport
就是在你脑子里的了,你想干什么都行了,优化代码,覆写,继承他的类,实现个性
化的功能,甚至你可以参考他的思想,升华一下,做一个你自己的报表
 

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,笔者做了一个有参数页面,可以选择html pdf applet
输出方式的报表,有兴趣的 我可以在后面放上代码

下面是一些补充的内容:

概述

Jasperreport是http://www.sourceforge.net上一个优秀的开源的报表工具,其强大的功能及免费的特性得到了广大的认可和赞誉,现在的最新版本是Jasperreport0.6.1。下载地址是
https://sourceforge.net/projects/jasperreports/
iReport是jasperreport的一个IDE的开发工具,使Jasperreport变得更易用,其最新版本是iReport0.3.2支持到Jasperreport0.5.3,和Jasperreport一样也在不断的更新中。下载地址是https://sourceforge.net/projects/ireport/
关于利用这两个工具开发Web报表,这里已经有几份很好的材料:
?    JasperReport与iReport的配置与使用.pdf(该文档可用于一开始入门熟悉这两个工具)
?    TheJasperReportsUltimateGuide.1.0.pdf (这个本是一份收费的文档,讲述了Jasperreport生成的原理方面的知识,如果你打算能熟练的使用Jasperreport这个报表工具,需要好好看这份英文资料的)
?    http://www.javaresearch.org/forum/thread.jsp?column=316&thread=14374
(这是论坛上的一个入门指南)

第一次运行

第一次总是比较痛苦,使用这个报表工具也是,下面是我总结出的一些运行中比较容易出现的问题。
我的总结是建立在使用iReport0.3.2 + Jasperreport0.5.3(没办法,iReport0.4.0还没出来,0.3.2只支持到Jasperreport0.5.3)。至于这两者的组合,我用到现在还是觉得比较顺的。
从souceforge上下载iReport-0.3.2.zip 解压后,要注意一点,把你的JDK的tools.jar拷到你解压后的iReport的lib目录下,做中文的pdf报表还需要把iTextAsian.jar拷到lib下,如果你用的是Oracle的数据库,那么你还需要把class12.jar也就是Oracle的Jdbc包拷到lib下,其他数据库也一样。
好了准备工作做好了,运行iReport.bat。如果比较顺利,应该能看到以下画面,说明
iReport成功运行了。
 
    如果dos界面一闪而过,并且没有出现以上界面,说明没有成功。按以下步骤可解决该问题:
确定系统安装有JDK,而且系统环境设置,path里有JDK的bin目录,并有java_home参数,以下是我的几个参数的配置情况可供参考:
1  java_home   D:/JBuilder9/jdk1.4
2  path        D:/JBuilder9/jdk1.4/bin;D:/Install/ant161/bin
3  classpath    D:/iReport032/fonts;E:/Project/EssV6/webapps/WEB-INF/classes
4  ant_home   D:/Install/ant161
(关于上面参数设置的说明:
?    Report0.3.2及Jasperreport0.5.3都要求JDK1.4以上版本,所以要注意Oracle9装好会自动把java环境设为Oracle自带的JDK1.3.1,会导致无法运行iReport。
?    ant_home及path中ant的设置是为了可以用ant来学习Jasperreport的例子,这些例子在Jasperreport解压后的demo/samples目录下,比如在Dos环境下进入demo/samples/jasper目录下,输入命令ant view 就可以看到该示例报表。
?    classpath中D:/iReport032/fonts;的设置是确保iReport里字体设置的时候能选择一些外部字体,比如要让PDF显示黑体,那么把windows下的simhei.ttf拷到D:/iReport032/fonts下,在iReport中如下设置:pdffontname为外部字体、truetypefont 为simhei.ttf、 编码为unicode with horizontal writing,这样就能使用黑体了
?    E:/Project/EssV6/webapps/WEB-INF/classes,classpath里的这是设置,是让报表里一些数据要经过特殊处理,可以用工程里的函数。

这些设置完成后,到dos环境下的iReport目录下,运行iReport.bat,dos环境下的好处是如果iReport运行失败可以看到失败原因,然后针对性的解决。
iReport可以运行后就是一个摸索使用,慢慢了解熟悉的过程了,直到你能做出像样的一些报表。这个过程可以参考上面提到的
http://www.javaresearch.org/forum/thread.jsp?column=316&thread=14374

常见问题的解决

    具体做报表的时候会碰到许多问题,关键的是我们需要分析出错提示,根据找到解决办法,比如你把报表放到你的工程下,比如运行结果提示java.io.FileNotFoundException,那么你就需要确定你的报表编译后的文件在相应的目录;又比如提示NoClassDefFoundError,那你就要看看是哪个包没有放到你的工程下。以下是一些常见问题:
?    Jasper报表中画的交叉线在html和xls中无法显示?
答:要想在html里面显示的话,线与线之间,线与字框之间不能有任何一点重合
所以你说的交叉线,如果是两条线直接交叉画的话,就只能由一条显示,需要分三条线段画。
    ……
    关键一点就是看出错提示,找出问题解决问题,这在一定程度上是依赖于使用者Java编程的能力。

J2EEhttp://www.disound.com/zblog/post/346.html#commenthttp://www.disound.com/zblog/xml-rpc/comment.asp?id=346http://www.disound.com/zblog/cmd.asp?act=rss&id=346http://www.disound.com/zblog/cmd.asp?act=tb&id=346 iReport整合向量图形 flexer@163.com (flexer) http://www.disound.com/zblog/post/345.html Mon, 03 Jul 2006 12:06:05 +0800 http://www.disound.com/zblog/post/345.html

Svg补丁直接使用了iText对Java Graphics2D的wrap来实现pdf文件的svg图形。

 

iReport主页

JasperReports向量图形patch

补丁方式

通过svg-patch-src修补jasperreports cvs源码,编译后与iReport源码整合。笔者发现了少数中文和xml校验等问题,通过修改源码可以解决。所以建议iText,jasperreports,jfreechart,iReport都使用cvs源码。

中文问题

修改com.lowagie.text.pdf.DefaultFontMapper类的awtToPdf方法:
BaseFont bf = null;
        try {
            bf =
                BaseFont.createFont(
    System.getProperty(“font.chinese.SIMYOU”),"D:/wiki/iReport-0.3.0/fonts/SIMYOU.TTF",
                    BaseFont.IDENTITY_H,
                    BaseFont.EMBEDDED);
        } catch (DocumentException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

在Linux平台上部署的时候需要修改jfreechart相应的字体。

iReport Chart

iReport目前对chart支持很有限,不过通过image标签和scriptlet可以很好的嵌入jfreechart图形, 嵌入其它chart比如jcharts方法一样。

Scriptlet代码

public class ChartScriptlet extends JRDefaultScriptlet
{
    /**
     *
     */
    public void afterReportInit() throws JRScriptletException
    {
        Connection con=null;
        DefaultPieDataset pieDataset = new DefaultPieDataset();
        try {
            Class.forName("oracle.jdbc.driver.OracleDriver");
            System.out.println("oracle.jdbc.driver.OracleDriver");
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        try {

            con =
                DriverManager.getConnection(
                    "jdbc:oracle:thin:@127.0.0.1.38:1521:ora8i",
                    "user",
                    "password");

            String query =
                "select province.name as name,sum(bill.total) as total from province,bill,node where bill.node_id =node.id and node.province_id=province.id and bill.CREATE_TIME > to_date('2004-1-1','yyyy-MM-dd') group by province.name";
            Statement stmt = con.createStatement();

            // Submit a query, creating a ResultSet object

            ResultSet rs = stmt.executeQuery(query);

            while (rs.next()) {
                System.out.println(rs.getString("name"));
                pieDataset.setValue(
                    rs.getString("name"),
                    rs.getDouble("total"));
            }
            rs.close();
            stmt.close();
            con.close();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    
        JFreeChart chart =
            ChartFactory.createPieChart(
                "2004-1-1至今各省帐单数据统计图",
                pieDataset,
                true,
                true,
                false);
        JCommonDrawableWrapper chartWrapper = new JCommonDrawableWrapper(chart);

        this.setVariableValue("ChartImage", chartWrapper);

        BufferedImage image = chart.createBufferedImage(246, 303);
        this.setVariableValue("ChartImage2", image);
    
    }
}

生成pdf

作为对比,笔者同时使用了svg和位图,svg在编辑模式下可以copy文本,并且放大显示后不会出现像那样位图模糊不清的情况。图形渲染质量和打印效果也非常好。
Example pdf下载

J2EEhttp://www.disound.com/zblog/post/345.html#commenthttp://www.disound.com/zblog/xml-rpc/comment.asp?id=345http://www.disound.com/zblog/cmd.asp?act=rss&id=345http://www.disound.com/zblog/cmd.asp?act=tb&id=345 Jasperreport 报表开发入门指南 flexer@163.com (flexer) http://www.disound.com/zblog/post/344.html Mon, 03 Jul 2006 09:31:29 +0800 http://www.disound.com/zblog/post/344.html

概述


Jasperreport是http://www.sourceforge.net上一个优秀的开源的报表工具,其强大的功能及免费的特性得到了广大的认可和赞誉,现在的最新版本是Jasperreport0.6.1。下载地址是
asperreports/">https://sourceforge.net/projects/jasperreports/。
iReport是jasperreport的一个IDE的开发工具,使Jasperreport变得更易用,其最新版本是iReport0.4.0支持到Jasperreport0.6.1,和Jasperreport一样也在不断的更新中。下载地址是https://sourceforge.net/projects/ireport/
关于利用这两个工具开发Web报表,这里已经有几份很好的材料:
?    JasperReport与iReport的配置与使用.pdf(该文档可用于一开始入门熟悉这两个工具)
?    TheJasperReportsUltimateGuide.1.0.pdf (这个本是一份收费的文档,讲述了Jasperreport生成的原理方面的知识,如果你打算能熟练的使用Jasperreport这个报表工具,需要好好看这份英文资料的)
?    http://www.javaresearch.org/forum/thread.jsp?column=316&thread=14374
(这是我在论坛上写的一个另一篇入门指南)

 


第一次运行


第一次总是比较痛苦,使用这个报表工具也是,下面是我总结出的一些运行中比较容易出现的问题。
我的总结是建立在使用iReport0.4.0 + Jasperreport0.6.1)。至于这两者的组合,我用到现在还是觉得比较顺的。
从souceforge上下载iReport-0.4.0.zip 解压后,要注意一点,把你的JDK的tools.jar拷到你解压后的iReport的lib目录下,做中文的pdf报表还需要把iTextAsian.jar拷到lib下,如果你用的是Oracle的数据库,那么你还需要把class12.jar也就是Oracle的Jdbc包拷到lib下,其他数据库也一样。
好了准备工作做好了,运行iReport.bat。如果比较看到UI界面,说明
iReport成功运行了。
 
    如果dos界面一闪而过,说明没有成功。按以下步骤可解决该问题:
确定系统安装有JDK,而且系统环境设置,path里有JDK的bin目录,并有java_home参数,以下是我的几个参数的配置情况可供参考:
1  java_home   D:/JBuilder9/jdk1.4
2  path        D:/JBuilder9/jdk1.4/bin;D:/Install/ant161/bin
3  classpath    D:/iReport042/fonts;E:/Project/EssV6/webapps/WEB-INF/classes
4  ant_home   D:/Install/ant161
(关于上面参数设置的说明:
?    iReport0.4.0要求JDK1.4以上版本,所以要注意Oracle9装好会自动把java环境设为Oracle自带的JDK1.3.1,会导致无法运行iReport。
?    ant_home及path中ant的设置是为了可以用ant来学习Jasperreport的例子,这些例子在Jasperreport解压后的demo/samples目录下,比如在Dos环境下进入demo/samples/jasper目录下,输入命令ant view 就可以看到该示例报表。
?    classpath中D:/iReport042/fonts;的设置是确保iReport里字体设置的时候能选择一些外部字体,比如要让PDF显示黑体,那么把windows下的simhei.ttf拷到D:/iReport042/fonts下,在iReport中如下设置:pdffontname为外部字体、truetypefont 为simhei.ttf、 编码为unicode with horizontal writing,这样就能使用黑体了
?    E:/Project/EssV6/webapps/WEB-INF/classes,classpath里的这是设置,是让报表里一些数据要经过特殊处理,可以用工程里的函数。

这些设置完成后,到dos环境下的iReport目录下,运行iReport.bat,dos环境下的好处是如果iReport运行失败可以看到失败原因,然后针对性的解决。
iReport可以运行后就是一个摸索使用,慢慢了解熟悉的过程了,直到你能做出像样的一些报表。这个过程可以参考上面提到的
http://www.javaresearch.org/forum/thread.jsp?column=316&thread=14374

 

常见问题的解决


    具体做报表的时候会碰到许多问题,关键的是我们需要分析出错提示,根据找到解决办法,比如你把报表放到你的工程下,比如运行结果提示java.io.FileNotFoundException,那么你就需要确定你的报表编译后的文件在相应的目录;又比如提示NoClassDefFoundError,那你就要看看是哪个包没有放到你的工程下。
    需要非常强调的是:好好看Jasperreport自带的例子,看懂那些例子,至少模仿一下,就能做出你想要的报表了。


我的报表Faq

1.java.io.FileNotFoundException
    Jasperreport自带的webapp的例子中,用于定位.jasper报表模板文件的方法是:
      File reportFile = new File(application.getRealPath("/reports/WebappReport.jasper"));
    这个时候,如果你的web服务器是tomcat,wabapp中的文件就放在root目录下
    那么这个报表的实际位置是:D:/Tomcat41/webapps/ROOT/reports/WebappReport.jasper
    然而当你使用子报表时,情况有所不同,首先在子报表中没有 application 这个对象
    所以你不能使用 new File(application.getRealPath("/reports/WebappReport.jasper"))的方法来定位子报表。
    jasperreport支持三种方式定位文件,具体参看 JasperreportUtimateGuide.pdf
    这里说一下,如果你是用String的类型来关联子报表,并且String的值是 subreport.jasper
    那么Jasperreport会到 D:/Tomcat41/webapps/ROOT/WEB-INF/classes目录下查找是否有subreport.jasper这个文件
    知道这点就能采取各种办法了

J2EEhttp://www.disound.com/zblog/post/344.html#commenthttp://www.disound.com/zblog/xml-rpc/comment.asp?id=344http://www.disound.com/zblog/cmd.asp?act=rss&id=344http://www.disound.com/zblog/cmd.asp?act=tb&id=344 提升JSP应用程序的七大绝招 flexer@163.com (flexer) http://www.disound.com/zblog/post/339.html Sun, 21 May 2006 12:41:28 +0800 http://www.disound.com/zblog/post/339.html     你时常被客户抱怨JSP页面响应速度很慢吗?你想过当客户访问次数剧增时,你的WEB应用能承受日益增加的访问量吗?本文讲述了调整JSP和servlet的一些非常实用的方法,它可使你的servlet和JSP页面响应更快,扩展性更强。而且在用户数增加的情况下,系统负载会呈现出平滑上长的趋势。在本文中,我将通过一些实际例子和配置方法使得你的应用程序的性能有出人意料的提升。其中,某些调优技术是在你的编程工作中实现的。而另一些技术是与应用服务器的配置相关的。在本文中,我们将详细地描述怎样通过调整servlet和JSP页面,来提高你的应用程序的总体性能。在阅读本文之前,假设你有基本的servlet和JSP的知识。

  方法一:在servlet的init()方法中缓存数据

  当应用服务器初始化servlet实例之后,为客户端请求提供服务之前,它会调用这个servlet的init()方法。在一个servlet的生命周期中,init()方法只会被调用一次。通过在init()方法中缓存一些静态的数据或完成一些只需要执行一次的、耗时的操作,就可大大地提高系统性能。

  例如,通过在init()方法中建立一个JDBC连接池是一个最佳例子,假设我们是用jdbc2.0的DataSource接口来取得数据库连接,在通常的情况下,我们需要通过JNDI来取得具体的数据源。我们可以想象在一个具体的应用中,如果每次SQL请求都要执行一次JNDI查询的话,那系统性能将会急剧下降。解决方法是如下代码,它通过缓存DataSource,使得下一次SQL调用时仍然可以继续利用它:



 
 
 public class ControllerServlet extends HttpServlet
{
 private javax.sql.DataSource testDS = null; 
 public void init(ServletConfig config) throws ServletException
 {
  super.init(config); 
  Context ctx = null;
  try
  { 
   ctx = new InitialContext();
   testDS = (javax.sql.DataSource)ctx.lookup("jdbc/testDS");
  }
  catch(NamingException ne)
  {
   ne.printStackTrace(); 
  }
  catch(Exception e)
  {
   e.printStackTrace();
  }
 }

 public javax.sql.DataSource getTestDS()
 {
  return testDS;
 }
 ...
 ... 


  方法 2:禁止servlet和JSP 自动重载(auto-reloading)

  Servlet/JSP提供了一个实用的技术,即自动重载技术,它为开发人员提供了一个好的开发环境,当你改变servlet和JSP页面后而不必重启应用服务器。然而,这种技术在产品运行阶段对系统的资源是一个极大的损耗,因为它会给JSP引擎的类装载器(classloader)带来极大的负担。因此关闭自动重载功能对系统性能的提升是一个极大的帮助。

  方法 3: 不要滥用HttpSession 

  在很多应用中,我们的程序需要保持客户端的状态,以便页面之间可以相互联系。但不幸的是由于HTTP具有天生无状态性,从而无法保存客户端的状态。因此一般的应用服务器都提供了session来保存客户的状态。在JSP应用服务器中,是通过HttpSession对像来实现session的功能的,但在方便的同时,它也给系统带来了不小的负担。因为每当你获得或更新session时,系统者要对它进行费时的序列化操作。你可以通过对HttpSession的以下几种处理方式来提升系统的性能:

  ? 如果没有必要,就应该关闭JSP页面中对HttpSession的缺省设置: 如果你没有明确指定的话,每个JSP页面都会缺省地创建一个HttpSession。如果你的JSP中不需要使用session的话,那可以通过如下的JSP页面指示符来禁止它:



 
 
 <%@ page session="false"%>  

  ? 不要在HttpSession中存放大的数据对像:如果你在HttpSession中存放大的数据对像的话,每当对它进行读写时,应用服务器都将对其进行序列化,从而增加了系统的额外负担。你在HttpSession中存放的数据对像越大,那系统的性能就下降得越快。

  ? 当你不需要HttpSession时,尽快地释放它:当你不再需要session时,你可以通过调用HttpSession.invalidate()方法来释放它。

  ? 尽量将session的超时时间设得短一点:在JSP应用服务器中,有一个缺省的session的超时时间。当客户在这个时间之后没有进行任何操作的话,系统会将相关的session自动从内存中释放。超时时间设得越大,系统的性能就会越低,因此最好的方法就是尽量使得它的值保持在一个较低的水平。


  方法 4: 将页面输出进行压缩

  压缩是解决数据冗余的一个好的方法,特别是在网络带宽不够发达的今天。有的浏览器支持gzip(GNU zip)进行来对HTML文件进行压缩,这种方法可以戏剧性地减少HTML文件的下载时间。因此,如果你将servlet或JSP页面生成的HTML页面进行压缩的话,那用户就会觉得页面浏览速度会非常快。但不幸的是,不是所有的浏览器都支持gzip压缩,但你可以通过在你的程序中检查客户的浏览器是否支持它。下面就是关于这种方法实现的一个代码片段:



 
 
 public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException 
{
 OutputStream out = null
 String encoding = request.getHeader("Accept-Encoding"); 
 if (encoding != null && encoding.indexOf("gzip") != -1)
 {
  request.setHeader("Content-Encoding" , "gzip");
  out = new GZIPOutputStream(request.getOutputStream());
 }
 else if (encoding != null && encoding.indexOf("compress") != -1)
 {
  request.setHeader("Content-Encoding" , "compress");
  out = new ZIPOutputStream(request.getOutputStream());
 } 
 else
 {
  out = request.getOutputStream();
 }
 ...
 ... 
}  

  方法 5: 使用线程池

  应用服务器缺省地为每个不同的客户端请求创建一个线程进行处理,并为它们分派service()方法,当service()方法调用完成后,与之相应的线程也随之撤消。由于创建和撤消线程会耗费一定的系统资源,这种缺省模式降低了系统的性能。但所幸的是我们可以通过创建一个线程池来改变这种状况。另外,我们还要为这个线程池设置一个最小线程数和一个最大线程数。在应用服务器启动时,它会创建数量等于最小线程数的一个线程池,当客户有请求时,相应地从池从取出一个线程来进行处理,当处理完成后,再将线程重新放入到池中。如果池中的线程不够地话,系统会自动地增加池中线程的数量,但总量不能超过最大线程数。通过使用线程池,当客户端请求急剧增加时,系统的负载就会呈现的平滑的上升曲线,从而提高的系统的可伸缩性。

  方法 6: 选择正确的页面包含机制

  在JSP中有两种方法可以用来包含另一个页面:1、使用include指示符(<%@ includee file=”test.jsp” %>)。2、使用jsp指示符(<jsp:includee page=”test.jsp” flush=”true”/>)。在实际中我发现,如果使用第一种方法的话,可以使得系统性能更高。

  方法 7:正确地确定javabean的生命周期

  JSP的一个强大的地方就是对javabean的支持。通过在JSP页面中使用<jsp:useBean>标签,可以将javabean直接插入到一个JSP页面中。它的使用方法如下:



 
 
 <jsp:useBean id="name" scope="page|request|session|application" class=
"package.className" type="typeName">
</jsp:useBean> 

  其中scope属性指出了这个bean的生命周期。缺省的生命周期为page。如果你没有正确地选择bean的生命周期的话,它将影响系统的性能。

  举例来说,如果你只想在一次请求中使用某个bean,但你却将这个bean的生命周期设置成了session,那当这次请求结束后,这个bean将仍然保留在内存中,除非session超时或用户关闭浏览器。这样会耗费一定的内存,并无谓的增加了JVM垃圾收集器的工作量。因此为bean设置正确的生命周期,并在bean的使命结束后尽快地清理它们,会使用系统性能有一个提高。

  其它一些有用的方法 

  ? 在字符串连接操作中尽量不使用“+”操作符:在java编程中,我们常常使用“+”操作符来将几个字符串连接起来,但你或许从来没有想到过它居然会对系统性能造成影响吧?由于字符串是常量,因此JVM会产生一些临时的对像。你使用的“+”越多,生成的临时对像就越多,这样也会给系统性能带来一些影响。解决的方法是用StringBuffer对像来代替“+”操作符。

  ? 避免使用System.out.println()方法:由于System.out.println()是一种同步调用,即在调用它时,磁盘I/O操作必须等待它的完成,因此我们要尽量避免对它的调用。但我们在调试程序时它又是一个必不可少的方便工具,为了解决这个矛盾,我建议你最好使用Log4j工具(http://Jakarta.apache.org ),它既可以方便调试,而不会产生System.out.println()这样的方法。

  ? ServletOutputStream 与 PrintWriter的权衡:使用PrintWriter可能会带来一些小的开销,因为它将所有的原始输出都转换为字符流来输出,因此如果使用它来作为页面输出的话,系统要负担一个转换过程。而使用ServletOutputStream作为页面输出的话就不存在一个问题,但它是以二进制进行输出的。因此在实际应用中要权衡两者的利弊。

  总结

  本文的目的是通过对servlet和JSP的一些调优技术来极大地提高你的应用程序的性能,并因此提升整个J2EE应用的性能。通过这些调优技术,你可以发现其实并不是某种技术平台(比如J2EE和.NET之争)决定了你的应用程序的性能,重要是你要对这种平台有一个较为深入的了解,这样你才能从根本上对自己的应用程序做一个优化! J2EEhttp://www.disound.com/zblog/post/339.html#commenthttp://www.disound.com/zblog/xml-rpc/comment.asp?id=339http://www.disound.com/zblog/cmd.asp?act=rss&id=339http://www.disound.com/zblog/cmd.asp?act=tb&id=339 用AJAX+J2EE实现网上会议室系统 flexer@163.com (flexer) http://www.disound.com/zblog/post/337.html Tue, 16 May 2006 16:07:42 +0800 http://www.disound.com/zblog/post/337.html

  今年大家都在炒作Web2.0,其中的一门技术Ajax也是跟着火了起来,因此前面我写了一篇名为《忽悠一下AJAX》的文章,简单地分析了一下Ajax的技术的实质。虽然笔者不太喜欢跟风,但Ajax有一些地方还是比较有用的。前段时间做了EasyJF开源团队的网上会议系统,就用到了Ajax技术,下面把设计思路发出来跟大家分享一下。
 
一、系统实现的功能
 
  本会议室系统主要用于EasyJF开源团队的成员网上会议使用,会议系统模拟传统的会议形式,可以同时开设多个不同主题的会议室,每个会议室需要提供访问权限控制功能,会议中能够指定会议发言模式(分为排队发言、自由发言两种),系统能自动记录每个会议室的发言信息,可以供参会人员长期查阅。
会议系统的用户支持游客帐号参加会议,同时也提供跟其它用户系统的接口,比如EasyJF官网中的开源论坛系统。
会议系统暂时使用文字聊天的方式,并提供语音及视频的接口。
 
二、技术体系
 
  服务器端使用Java语言,MVC使用EasyJWeb框架;
  客户端使用AJAX技术与服务器端交互数据;
  会议历史信息储存格式使用文本格式,方便系统安装运行,也便于管理。
 
三、会议室服务器端设计
 
  会议室服务器端是整个会议系统的核心部分,服务器端程序设计的好坏影响到整个系统的质量。
  首先,根据会议室要实现的功能进行抽象分析。一个会议室对象,应该包括会议主题、会议简介、参会人数限制、公告、会议室类型、访问权限设定、房间密码、当前参会的人员、当前发言的人员、排队等待发言的人员等参数信息。我们把他封装一个Java对象当中。如下面的ChatRoom代码所示:
public class ChatRoom{
 private String cid;//主键
 private String title;//会议室主题
 private String intro;//会议室简介
 private String announce;//会议室公告
 private String owner;//会议室创建人
 private Integer maxUser;//最大在线人数
 private Integer intervals;//最大刷新时间间隔
 private String vrtype;//访问权限
 private String vrvalue;//访问值
 private Integer status;//会议室状态
 private Date inputTime;
}
 
  需要一个管理会议室的类,与会议有关的操作(如启动会议、关闭会议)等都直接找他。该类还应该即有自动定时检测用户在线情况(防止用户意外退出)、把内存中的会议历史发言信息保存到文本文件中等功能。这里可以考虑使用一个ChatService类提供这些功能:
public class ChatService implements Runnable {
private static final Map service=new HashMap();//会议室服务,系统中的当前会议室存放到该表集合中
private static final int maxServices=10;//可以同时开的最大会议室数
private static final SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd");
private final List msgs;//会议发言信息Chat
private final List users;//在线用户,ChatUser
private final List talkers;//排队发言人数Talker
private final List manager;//会议室管理员
private Talker currentTalker;//当前发言人
public ChatService()
{
 this.msgs=new ArrayList();
 this.users=new ArrayList();
 this.talkers=new ArrayList();
 this.manager=new ArrayList();
 this.maxUser=1000;//最大1000人同时
 this.interval=1000*60*5;//5分钟以前的信息
}
}
 
  会议发言信息也需要封装成一个类,表示发言人、接收人、内容、发言时间、类型等,大致如下面的Chat类:
public class Chat  {
private String cid;
private String sender;
private String reciver;
private String content;
private Date vdate;
private Integer types;
private Integer status;
}
 
  还有表示参加会议的人的信息,包括参会人名称、IP地址、状态等,如下面的ChatUser类所示:
public class ChatUser {
private String ip;
private String port;
private String userName;
private Date lastAccessTime;
private Integer status;
}
 
  另外还需要一个表示当前发言人的Talker类,表示当前的发言人,发言开始时间,发言预计结束时间等。
  在服务器端的设计中,会议室信息服务器应该能以多线程的方式运行,即启动一个会议就新开一个线程,每个会议线程维护自己的会议状态,如参会人、发言人,保存会议历史发言信息以及清空内存中的数据等操作。
 
 
四、客户端设计
 
  会议室客户端包括两个部分,一个部分是会议室的管理界面,主要包会议室的“添删改查”及“启动”或“关闭”会议服务的操作。这部分我们直接使用EasyJWeb Tools中的添删改查业务引擎AbstractCrudAction可以快速实现。界面也比较简单,直接使用EasyJWeb Tools代码生成工具引擎生成即可。会议室管理的客户端是传统的Java Web技术,因此没有什么要考虑的。
  客户端的第二个部分也即会议系统的主要部分,该部分主要有两个界面,第一个页面是会议室进入的选择页面。也即把已经启动的会议室列出来,用户选择一个会议室进入,这个页面也是使用传统的Java Web技术。第二个页面是进入会议室后的主界面,这个界面是整个会议系统的主要界面,所有参与会议的操作都在这里运行的。这个界面需要不断的与服务器端交互传输数据,传输的内容包括用户的发言、其它人给用户的发言、会议室的状态等。有的传输信息需要即时响应(如用户发言),有的信息可以设置成定时响应(如会议室状态)。
  Java Web程序中与服务器端交互数据主要有两种方式,一种是直接刷新页面,另外一种是使用Socket直接跟Web服务器端口通讯。由于Socket编程相对复杂,我们选择第一种直接刷新页面的方式,这种方式又可以分为几种,包括传统的Form提交,传统的自动刷新网页取得数据以及使用ActiveXObject对象(如xmlhttp)直接与服务器交互数据,也即AJAX方式。由于使用AJAX方式用户感觉不到页面在刷新,表现起来好于手动或自动刷新页面的方式,因此我们决定选择AJAX方式实现客户端与服务器端进行数据交互。
  用户发言的时候,直接使用xmlhttp对象Post数据到服务器。为了能不断接收到别人的发言信息,需要定时不断的从服务器端读取数据,因此,需要在客户端启动一个定时器,每隔一定的时候自动使用xmlhttp对象到服务器端下载别人的发言信息,并显示到会议室信息主界面中。另外还要定时刷新参会的人数、会议室当前发言人、会议室的公告等会议状态信息,这也可以通过从客户端启动一个定时器,通过xmlhttp对象与服务器交互得到。
  另外还有一些操作,锁定会议室、踢人、指定发言人的发言时间、给会议室加密码等功能,也通过xmlhttp的方式与服务器传输命令实现。
 
五、核心代码说明

1、服务器端核心代码
  在EasyJF开源团队的会议系统中,由于是以EasyJF官网的论坛系统、后台管理等是集成一起的。服务器ChatService与ChatRoom共同合并到了一个ChatService.java类中,实现会议室管理及会议服务功能。ChatService类的部分主要代码如下:
package com.easyjf.chat.business;
public class ChatService implements Runnable {
private static final Map service=new HashMap();//会议室服务,系统中的当前会议室存放到该表集合中
private static final int maxServices=10;//可以同时开的最大会议室数
private static final SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd");
private final List msgs;//会议发言信息Chat
private final List users;//在线用户,ChatUser
private final List talkers;//排队发言人数Talker
private final List manager;//会议室管理员
private Talker currentTalker;//当前发言人
private String cid;//会议室id
private String title;//会议室主题
private String intro;//会议室简介
private String owner;//会议室创建人
private int maxUser;//最大在线人数
private int interval;//最大刷新时间间隔
private String vrtype;//访问权限
private String vrvalue;//访问值
private String announce;
private String password;//房间进入密码
private int status;//会议室状态
private String filePath;
//private Thread thread;
private boolean isStop=false;
public ChatService()
{
 this.msgs=new ArrayList();
 this.users=new ArrayList();
 this.talkers=new ArrayList();
 this.manager=new ArrayList();
 this.maxUser=1000;//最大1000人同时
 this.interval=1000*60*5;//5分钟以前的信息
}
/**
 * 停止所有会议室
 *
 */
public  static void clear()
{
 if(!service.isEmpty())
 {
  Iterator it=service.values().iterator();
  while(it.hasNext())
  {
   ChatService chat=(ChatService)it.next();
   chat.stop();
  }
 }
  service.clear();
}
/**
 * 创建一个会议室
 * @param name 会议室ID
 * @return
 */
public static ChatService create(String name)
{
ChatService ret=null;
if(service.containsKey(name))
{
 ChatService s=(ChatService)service.get(name);
 s.stop();
 service.remove(name);
}
if(service.size()<maxServices)
{
 ret=new ChatService(); 
 service.put(name,ret);
}
return ret;
}
/**
 * 停止某个会议室
 * @param name 会议室ID
 * @return
 */
public static boolean close(String name)
{
 ChatService chatRoom=ChatService.get(name);
 if(chatRoom!=null)
  {
  chatRoom.stop();
  service.remove(name);
  }
 return true;
}
/**
 * 获得一个会议室信息
 * @param name 会议室ID
 * @return
 */
public static ChatService get(String name)
{
 if(service.containsKey(name))return (ChatService)service.get(name);
 else return null;
}
public void run() {
 // TODO Auto-generated method stub
 //this.thread=Thread.currentThread();
 while(!isStop)
 {
 //System.out.println("开始监控一个会议室!"+this.title);
 this.flash();
 try{
 Thread.sleep(5000);
 }
 catch(Exception e)
 {
  e.printStackTrace();  
 } 
 }
 //System.out.println("结束!");
}
public void stop()
{
 this.flashAll();
 isStop=true;
}
//会议室中有人发言
public boolean talk(Chat chat)
{
 boolean ret=false;
 if(canTalk(chat.getSender()))
  {  
  this.msgs.add(chat);
      ret=true;
  }
 return ret;
}
public boolean exit(ChatUser user)
{    
  talk(geneSystemMsg(user.getUserName()+"退出了会议室!"));
  return this.users.remove(user);
}
}
//刷新信息,保存会议信息
public void flash()
{
 flashChatMsg();
 flashChatUser();
}
}
 
 
2、MVC处理部分的Action代码
 

  在EasyJF的会议系统中,由于使用EasyJWeb作为MVC框架,因此处理Ajax比较简单,下面是会议室系统的核心Action主要代码。
package com.easyjf.chat.action;
public class ChatAction extends AbstractCmdAction {
 private ChatService chatRoom;
 public Object doBefore(WebForm form, Module module) {
 // TODO Auto-generated method stub
  if(chatRoom==null)chatRoom=ChatService.get((String)form.get("cid"));
  return super.doBefore(form, module);
 }
 public Page doInit(WebForm form, Module module) {
  // TODO Auto-generated method stub  
  return doMain(form,module);
 } 
 //用户登录进入会议室
 public Page doMain(WebForm form, Module module) {   
  if(chatRoom!=null){
  ChatUser user=getChatUser();  
  if(!chatRoom.join(user))form.addResult("msg","不能加入房间,可能是权限不够!");
  form.addResult("chatRoom",chatRoom);
  form.addResult("user",user);
  }
  else
  {
   form.addResult("msg","会议未启动或者会议室不存在!");
  }  
  return module.findPage("main");
 } 
 //处理用户发言信息
 public Page doSend(WebForm form, Module module) {  
  if(chatRoom==null)return new Page("err","/err.html","thml");//返回会议室不存在的错误
  Chat chat=(Chat)form.toPo(Chat.class);
  chat.setCid(chatRoom.geneId());
  chatRoom.talk(chat);
  return doRecive(form,module);
 } 
 //用户接收发言信息
 public Page doRecive(WebForm form, Module module) {  
  if(chatRoom==null)return new Page("err","/err.html","thml");//返回会议室不存在的错误
  String lastReadId=CommUtil.null2String(form.get("lastReadId"));
  //System.out.println(lastReadId);
  form.addResult("list", chatRoom.getNewestMsg(getChatUser(),lastReadId));  
  return module.findPage("msgList");
 }
 //用户刷新会议状态信息
 public Page doLoadConfig(WebForm form, Module module) {  
  if(chatRoom==null)return new Page("err","/err.html","thml");//返回会议室不存在的错误  
  form.addResult("userList", chatRoom.getUsers());
  form.addResult("talkerList", chatRoom.getTalkers());
  return module.findPage("config");
 }
 //用户退出
 public Page doExit(WebForm form, Module module) {  
  if(chatRoom==null)return new Page("err","/err.html","thml");//返回会议室不存在的错误
  chatRoom.exit(getChatUser());
  form.addResult("msg","退出成功");
  ActionContext.getContext().getSession().removeAttribute("chatUser");
  return new Page("msg","/chat/xmlMsg.xml",Globals.PAGE_TEMPLATE_TYPE);
 }
 
 
3、客户端AJAX部分核心代码
 

  EasyJF会议系统中,服务器发送给客户端的都是格式化的xml文档数据。下面是核心的AJAX函数及发送接收会议信息的客户端代码。
function newXMLHttpRequest() {
  var xmlreq = false;
  if (window.XMLHttpRequest) {  
    xmlreq = new XMLHttpRequest();
  } else if (window.ActiveXObject) {   
    try {     
      xmlreq = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e1) {     
      try {      
        xmlreq = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (e2) {     
      }
    }
  }
  return xmlreq;
}
//处理返回信息
//xmlHttp返回值,
//method:方法名 方法必须带一个参数如doRecive(xNode);
function handleAjaxResult(req,method) {
  return function () { 
    if (req.readyState == 4) {    
      if (req.status == 200) {
      // 将载有响应信息的XML传递到处理函数
    var objXMLDoc=new ActiveXObject("Microsoft.XMLDOM");
       objXMLDoc.loadXML(req.responseText);     
       eval("if(objXMLDoc.firstChild)"+method+"(objXMLDoc.firstChild.nextSibling);");
      } else {      
        //alert("HTTP error: "+req.status);
      }
    }
  }
}
//执行客户端Ajax命令
//url 数据post地址
//postData 发送的数据包
//handleMethod 处理返回的方法
function executeAjaxCommand(url,postData,handleMethod)
{
   var req = newXMLHttpRequest();
   req.onreadystatechange =handleAjaxResult(req,handleMethod);   
   req.open("POST", url, true);
   req.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
   req.setRequestHeader("charset","utf-8"); 
   req.send(postData);
}
//用户发言
unction doSend()
{
 
   if(!check())return false;
   var msg=EditForm.content.value;
   var reciver=EditForm.reciver.value;  
   var url="/chat.ejf?easyJWebCommand=send&cid="+roomId+"&lastReadId="+lastReadId;
   var postData="sender="+myName+"&reciver="+reciver+"&content="+msg;
   clearTimeout(reciveTime);
   executeAjaxCommand(url,postData,"recive");
   EditForm.content.value="";
}
//接收发言信息
function doRecive()
{  
   var reciver=EditForm.reciver.value;  
   var url="/chat.ejf?easyJWebCommand=recive&cid="+roomId+"&lastReadId="+lastReadId;
   executeAjaxCommand(url,"","recive"); 
}
//处理接收到的发言信息
function recive(list)
{
    var id="";  
    for(var oNode=list.firstChild;oNode;oNode=oNode.nextSibling) // 依次分析每个节点
 {
    chatContent.innerHTML+=showMsg(oNode);
    id=oNode.getAttribute("cid");
 }
    if(id!="") lastReadId=id;
    chatContent.scrollTop=chatContent.scrollHeight;
    reciveTime=setTimeout("doRecive();",5000); 
}
 

六、系统演示
 
  大家可以到EasyJF开源团队的官方网站看程序演示效果,地址是:

   http://www.easyjf.com/chatRoom.ejf?easyJWebCommand=show&ejid=2538093638804337

结束语

  Ajax从技术上讲主要就是javascript、dhtml、css、xmldom、xmlhttp等一些我们很早就接触了的技术。而xmldom及xmlhttp也没有什么东西,写程序的时候把参考文档打开Copy就OK,dhtml及javascript涉及的东西就多了,不能只是看参考文档,需要把他真正消化,并能灵活动用,这就需要大家都练习了。笔者建议大家不要滥用Ajax。对于高手建议多研究一些业务及系统级算法设计等,对于新手嘛,把基本的技术(客户端的包括dhtml、css、javascript、xml等,J2EE服务器端的设计模式、UML建模、Servlet、JDBC或ORM系统、XML、EJB及一些框架、工具等)学好才是硬道理。

J2EEhttp://www.disound.com/zblog/post/337.html#commenthttp://www.disound.com/zblog/xml-rpc/comment.asp?id=337http://www.disound.com/zblog/cmd.asp?act=rss&id=337http://www.disound.com/zblog/cmd.asp?act=tb&id=337 面向 Java 开发人员的 Ajax: 构建动态的 Java 应用程序 flexer@163.com (flexer) http://www.disound.com/zblog/post/326.html Fri, 28 Apr 2006 17:08:33 +0800 http://www.disound.com/zblog/post/326.html

在 Web 应用程序开发中,页面重载循环是最大的一个使用障碍,对于 Java&#8482; 开发人员来说也是一个严峻的挑战。在这个系列中,作者 Philip McCarthy 介绍了一种创建动态应用程序体验的开创性方式。Ajax(异步 JavaScript 和 XML)是一种编程技术,它允许为基于 Java 的 Web 应用程序把 Java 技术、XML 和 JavaScript 组合起来,从而打破页面重载的范式。
Ajax(即异步 JavaScript 和 XML)是一种 Web 应用程序开发的手段,它采用客户端脚本与 Web 服务器交换数据。所以,不必采用会中断交互的完整页面刷新,就可以动态地更新 Web 页面。使用 Ajax,可以创建更加丰富、更加动态的 Web 应用程序用户界面,其即时性与可用性甚至能够接近本机桌面应用程序。

Ajax 不是一项技术,而更像是一个 模式 —— 一种识别和描述有用的设计技术的方式。Ajax 是新颖的,因为许多开发人员才刚刚开始知道它,但是所有实现 Ajax 应用程序的组件都已经存在若干年了。它目前受到重视是因为在 2004 和 2005 年出现了一些基于 Ajax 技术的非常棒的动态 Web UI,最著名的就是 Google 的 GMail 和 Maps 应用程序,以及照片共享站点 Flickr。这些用户界面具有足够的开创性,有些开发人员称之为“Web 2.0”,因此对 Ajax 应用程序的兴趣飞速上升。

在这个系列中,我将提供使用 Ajax 开发应用程序需要的全部工具 。在第一篇文章中,我将解释 Ajax 背后的概念,演示为基于 Java 的 Web 应用程序创建 Ajax 界面的基本步骤。我将使用代码示例演示让 Ajax 应用程序如此动态的服务器端 Java 代码和客户端 JavaScript。最后,我将指出 Ajax 方式的一些不足,以及在创建 Ajax 应用程序时应当考虑的一些更广的可用性和访问性问题。

更好的购物车

可以用 Ajax 增强传统的 Web 应用程序,通过消除页面装入从而简化交互。为了演示这一点,我采用一个简单的购物车示例,在向里面添加项目时,它会动态更新。这项技术如果整合到在线商店,那么用户可以持续地浏览和向购物车中添加项目,而不必在每次点击之后都等候完整的页面更新。虽然这篇文章中的有些代码特定于购物车示例,但是演示的技术可以应用于任何 Ajax 应用程序。清单 1 显示了购物车示例使用的有关 HTML 代码,整篇文章中都会使用这个 HTML。


清单1. 购物车示例的有关片断

<!-- Table of products from store's catalog, one row per item -->
<th>Name</th> <th>Description</th> <th>Price</th> <th></th>
...
<tr>
  <!-- Item details -->
  <td>Hat</td> <td>Stylish bowler hat</td> <td>$19.99</td>
  <td>
    <!-- Click button to add item to cart via Ajax request -->
    <button οnclick="addToCart('hat001')">Add to Cart</button>
  </td>
</tr>
...

<!-- Representation of shopping cart, updated asynchronously -->
<ul id="cart-contents">

  <!-- List-items will be added here for each item in the cart -->
  
</ul>

<!-- Total cost of items in cart displayed inside span element -->
Total cost: <span id="total">$0.00</span>



Ajax 往返过程

Ajax 交互开始于叫作 XMLHttpRequest 的 JavaScript 对象。顾名思义,它允许客户端脚本执行 HTTP 请求,并解析 XML 服务器响应。Ajax 往返过程的第一步是创建 XMLHttpRequest 的实例。在 XMLHttpRequest 对象上设置请求使用的 HTTP 方法(GET 或 POST)以及目标 URL。

现在,您还记得 Ajax 的第一个 a 是代表 异步(asynchronous) 吗?在发送 HTTP 请求时,不想让浏览器挂着等候服务器响应。相反,您想让浏览器继续对用户与页面的交互进行响应,并在服务器响应到达时再进行处理。为了实现这个要求,可以在 XMLHttpRequest 上注册一个回调函数,然后异步地分派 XMLHttpRequest。然后控制就会返回浏览器,当服务器响应到达时,会调用回调函数。

在 Java Web 服务器上,请求同其他 HttpServletRequest 一样到达。在解析了请求参数之后,servlet 调用必要的应用程序逻辑,把响应序列化成 XML,并把 XML 写入 HttpServletResponse。

回到客户端时,现在调用注册在 XMLHttpRequest 上的回调函数,处理服务器返回的 XML 文档。最后,根据服务器返回的数据,用 JavaScript 操纵页面的 HTML DOM,把用户界面更新。图 1 是 Ajax 往返过程的顺序图。


图 1. Ajax 往返过程


现在您对 Ajax 往返过程有了一个高层面的认识。下面我将放大其中的每一步骤,进行更详细的观察。如果过程中迷了路,请回头看图 1 —— 由于 Ajax 方式的异步性质,所以顺序并非十分简单。

分派 XMLHttpRequest

我将从 Ajax 序列的起点开始:创建和分派来自浏览器的 XMLHttpRequest。不幸的是,不同的浏览器创建 XMLHttpRequest 的方法各不相同。清单 2 的 JavaScript 函数消除了这些依赖于浏览器的技巧,它可以检测当前浏览器要使用的正确方式,并返回一个可以使用的 XMLHttpRequest。最好是把它当作辅助代码:只要把它拷贝到 JavaScript 库,并在需要 XMLHttpRequest 的时候使用它就可以了。


清单 2. 创建跨浏览器的 XMLHttpRequest

/*
* Returns a new XMLHttpRequest object, or false if this browser
* doesn't support it
*/
function newXMLHttpRequest() {

  var xmlreq = false;

  if (window.XMLHttpRequest) {

    // Create XMLHttpRequest object in non-Microsoft browsers
    xmlreq = new XMLHttpRequest();

  } else if (window.ActiveXObject) {

    // Create XMLHttpRequest via MS ActiveX
    try {
      // Try to create XMLHttpRequest in later versions
      // of Internet Explorer

      xmlreq = new ActiveXObject("Msxml2.XMLHTTP");

    } catch (e1) {

      // Failed to create required ActiveXObject

      try {
        // Try version supported by older versions
        // of Internet Explorer

        xmlreq = new ActiveXObject("Microsoft.XMLHTTP");

      } catch (e2) {

        // Unable to create an XMLHttpRequest with ActiveX
      }
    }
  }

  return xmlreq;
}

  


稍后我将讨论处理那些不支持 XMLHttpRequest 的浏览器的技术。目前,示例假设清单 2 的 newXMLHttpRequest 函数总能返回 XMLHttpRequest 实例。

返回示例的购物车场景,我想要当用户在目录项目上点击 Add to Cart 时启动 Ajax 交互。名为 addToCart() 的 onclick 处理函数负责通过 Ajax 调用来更新购物车的状态(请参阅 清单 1)。正如清单 3 所示,addToCart() 需要做的第一件事是通过调用清单 2 的 newXMLHttpRequest() 函数得到 XMLHttpRequest 对象。接下来,它注册一个回调函数,用来接收服务器响应(我稍后再详细解释这一步;请参阅 清单 6)。

因为请求会修改服务器上的状态,所以我将用 HTTP POST 做这个工作。通过 POST 发送数据要求三个步骤。第一,需要打开与要通信的服务器资源的 POST 连接 —— 在这个示例中,服务器资源是一个映射到 URL cart.do 的 servlet。然后,我在 XMLHttpRequest 上设置一个头,指明请求的内容是表单 编码的数据。最后,我用表单编码的数据作为请求体发送请求。

清单 3 把这些步骤放在了一起。


清单 3. 分派 Add to Cart XMLHttpRequest

/*
* Adds an item, identified by its product code, to the shopping cart
* itemCode - product code of the item to add.
*/
function addToCart(itemCode) {

  // Obtain an XMLHttpRequest instance
  var req = newXMLHttpRequest();

  // Set the handler function to receive callback notifications
  // from the request object
  var handlerFunction = getReadyStateHandler(req, updateCart);
  req.onreadystatechange = handlerFunction;
  
  // Open an HTTP POST connection to the shopping cart servlet.
  // Third parameter specifies request is asynchronous.
  req.open("POST", "cart.do", true);

  // Specify that the body of the request contains form data
  req.setRequestHeader("Content-Type",
                       "application/x-www-form-urlencoded");

  // Send form encoded data stating that I want to add the
  // specified item to the cart.
  req.send("action=add&item="+itemCode);
}




这就是建立 Ajax 往返过程的第一部分,即创建和分派来自客户机的 HTTP 请求。接下来是用来处理请求的 Java servlet 代码。


servlet 请求处理

用 servlet 处理 XMLHttpRequest,与处理普通的浏览器 HTTP 请求一样。可以用 HttpServletRequest.getParameter() 得到在 POST 请求体中发送的表单编码数据。Ajax 请求被放进与来自应用程序的常规 Web 请求一样的 HttpSession 中。对于示例购物车场景来说,这很有用,因为这让我可以把购物车状态封装在 JavaBean 中,并在请求之间在会话中维持这个状态。

清单 4 是处理 Ajax 请求、更新购物车的简单 servlet 的一部分。Cart bean 是从用户会话中获得的,并根据请求参数更新它的状态。然后 Cart 被序列化成 XML,XML 又被写入 ServletResponse。重要的是把响应的内容类型设置为 application/xml,否则 XMLHttpRequest 不会把响应内容解析成 XML DOM。


清单 4. 处理 Ajax 请求的 servlet 代码

public void doPost(HttpServletRequest req, HttpServletResponse res)
                        throws java.io.IOException {

  Cart cart = getCartFromSession(req);

  String action = req.getParameter("action");
  String item = req.getParameter("item");
  
  if ((action != null)&&(item != null)) {

    // Add or remove items from the Cart
    if ("add".equals(action)) {
      cart.addItem(item);

    } else if ("remove".equals(action)) {
      cart.removeItems(item);

    }
  }

  // Serialize the Cart's state to XML
  String cartXml = cart.toXml();

  // Write XML to response.
  res.setContentType("application/xml");
  res.getWriter().write(cartXml);
}




清单 5 显示了 Cart.toXml() 方法生成的示例 XML。它很简单。请注意 cart 元素的 generated 属性,它是 System.currentTimeMillis() 生成的一个时间戳。


清单 5. Cart 对象的XML 序列化示例

<?xml version="1.0"?>
<cart generated="1123969988414" total="$171.95">
  <item code="hat001">
    <name>Hat</name>
    <quantity>2</quantity>
  </item>
  <item code="cha001">
    <name>Chair</name>
    <quantity>1</quantity>
  </item>
  <item code="dog001">
    <name>Dog</name>
    <quantity>1</quantity>
  </item>
</cart>




如果查看应用程序源代码(可以从 下载 一节得到)中的 Cart.java,可以看到生成 XML 的方式只是把字符串添加在一起。虽然对这个示例来说足够了,但是对于从 Java 代码生成 XML 来说则是最差的方式。我将在这个系列的下一期中介绍一些更好的方式。

现在您已经知道了 CartServlet 响应 XMLHttpRequest 的方式。下一件事就是返回客户端,查看如何用 XML 响应更新页面状态。


用 JavaScript 进行响应处理

XMLHttpRequest 的 readyState 属性是一个数值,它指出请求生命周期的状态。它从 0(代表“未初始化”)变化到 4(代表“完成”)。每次 readyState 变化时,readystatechange 事件就触发,由 onreadystatechange 属性指定的事件处理函数就被调用。

在 清单 3 中已经看到了如何调用 getReadyStateHandler() 函数创建事件处理函数。然后把这个事件处理函数分配给 onreadystatechange 属性。getReadyStateHandler() 利用了这样一个事实:函数是 JavaScript 中的一级对象。这意味着函数可以是其他函数的参数,也可以创建和返回其他函数。getReadyStateHandler() 的工作是返回一个函数,检查 XMLHttpRequest 是否已经完成,并把 XML 响应传递给调用者指定的事件处理函数。清单 6 是 getReadyStateHandler() 的代码。


清单 6. getReadyStateHandler() 函数

/*
* Returns a function that waits for the specified XMLHttpRequest
* to complete, then passes its XML response to the given handler function.
* req - The XMLHttpRequest whose state is changing
* responseXmlHandler - Function to pass the XML response to
*/
function getReadyStateHandler(req, responseXmlHandler) {

  // Return an anonymous function that listens to the
  // XMLHttpRequest instance
  return function () {

    // If the request's status is "complete"
    if (req.readyState == 4) {
      
      // Check that a successful server response was received
      if (req.status == 200) {

        // Pass the XML payload of the response to the
        // handler function
        responseXmlHandler(req.responseXML);

      } else {

        // An HTTP problem has occurred
        alert("HTTP error: "+req.status);
      }
    }
  }
}



HTTP 状态码

在清单 6 中,检查 XMLHttpRequest 的 status 属性以查看请求是否成功完成。status 包含服务器响应的 HTTP 状态码。在执行简单的 GET 和 POST 请求时,可以假设任何大于 200 (OK)的码都是错误。如果服务器发送重定向响应(例如 301 或 302),浏览器会透明地进行重定向并从新的位置获取资源;XMLHttpRequest 看不到重定向状态码。而且,浏览器会自动添加 Cache-Control: no-cache 头到所有 XMLHttpRequest,这样客户代码永远也不用处理 304(未经修改)服务器响应。


关于 getReadyStateHandler()

getReadyStateHandler() 是段相对复杂的代码,特别是如果您不习惯阅读 JavaScript 的话。但是通过把这个函数放在 JavaScript 库中,就可以处理 Ajax 服务器响应,而不必处理 XMLHttpRequest 的内部细节。重要的是要理解如何在自己的代码中使用 getReadyStateHandler()。

在 清单 3 中看到了 getReadyStateHandler() 像这样被调用:handlerFunction = getReadyStateHandler(req, updateCart)。在这个示例中,getReadyStateHandler() 返回的函数将检查在 req 变量中的 XMLHttpRequest 是否已经完成,然后用响应的 XML 调用名为 updateCart 的函数。

提取购物车数据

清单 7 是 updateCart() 本身的代码。函数用 DOM 调用检查购物车的 XML 文档,然后更新 Web 页面(请参阅 清单 1),反映新的购物车内容。这里的重点是用来从 XML DOM 提取数据的调用。cart 元素的 generated 属性是在 Cart 序列化为 XML 时生成的一个时间戳,检查它可以保证新的购物车数据不会被旧的数据覆盖。Ajax 请求天生是异步的,所以这个检查可以处理服务器响应未按次序到达的情况。


清单 7. 更新页面,反映购物车的 XML 文档

function updateCart(cartXML) {

// Get the root "cart" element from the document
var cart = cartXML.getElementsByTagName("cart")[0];

// Check that a more recent cart document hasn't been processed
// already
var generated = cart.getAttribute("generated");
if (generated > lastCartUpdate) {
   lastCartUpdate = generated;

   // Clear the HTML list used to display the cart contents
   var contents = document.getElementById("cart-contents");
   contents.innerHTML = "";

   // Loop over the items in the cart
   var items = cart.getElementsByTagName("item");
   for (var I = 0 ; I < items.length ; I++) {

     var item = items[I];

     // Extract the text nodes from the name and quantity elements
     var name = item.getElementsByTagName("name")[0]
                                               .firstChild.nodeValue;
                                              
     var quantity = item.getElementsByTagName("quantity")[0]
                                               .firstChild.nodeValue;

     // Create and add a list item HTML element for this cart item
     var li = document.createElement("li");
     li.appendChild(document.createTextNode(name+" x "+quantity));
     contents.appendChild(li);
   }
}

// Update the cart's total using the value from the cart document
document.getElementById("total").innerHTML =
                                          cart.getAttribute("total");
}




到此,整个 Ajax 往返过程完成了,但是您可能想让 Web 应用程序运行一下查看实际效果(请参阅 下载 一节)。这个示例非常简单,有很多需要改进之处。例如,我包含了从购物车中清除项目的服务器端代码,但是无法从 UI 访问它。作为一个好的练习,请试着在应用程序现有的 JavaScript 代码之上构建出能够实现这个功能的代码。

使用 Ajax 的挑战

就像任何技术一样,使用 Ajax 也有许多出错的可能性。我目前在这里讨论的问题还缺乏容易的解决方案,但是会随着 Ajax 的成熟而改进。随着开发人员社区增加开发 Ajax 应用程序的经验,将会记录下最佳实践和指南。

XMLHttpRequest 的可用性

Ajax 开发人员面临的一个最大问题是:在没有 XMLHttpRequest 可用时该如何响应?虽然主要的现代浏览器都支持 XMLHttpRequest,但仍然有少数用户的浏览器不支持,或者浏览器的安全设置阻止使用 XMLHttpRequest。如果开发的 Web 应用程序要部署在企业内部网,那么可能拥有指定支持哪种浏览器的权力,从而可以认为 XMLHttpRequest 总能使用。但是,如果要部署在公共 Web 上,那么就必须当心,如果假设 XMLHttpRequest 可用,那么就可能会阻止那些使用旧的浏览器、残疾人专用浏览器和手持设备上的轻量级浏览器的用户使用您的应用程序。

所以,您应当努力让应用程序“平稳降级”,在没有 XMLHttpRequest 支持的浏览器中也能够工作。在购物车的示例中,把应用程序降级的最好方式可能是让 Add to Cart 按钮执行一个常规的表单提交,刷新页面来反映购物车更新后的状态。Ajax 的行为应当在页面装入的时候就通过 JavaScript 添加到页面,只有在 XMLHttpRequest 可用时才把 JavaScript 事件处理函数附加到每个 Add to Cart 按钮。另一种方式是在用户登录时检测 XMLHttpRequest 是否可用,然后相应地提供应用程序的 Ajax 版本或基于表单的普通版本。

可用性考虑

关于 Ajax 应用程序的某些可用性问题比较普遍。例如,让用户知道他们的输入已经注册了可能是重要的,因为沙漏光标和 spinning 浏览器的常用反馈机制“throbber”对 XMLHttpRequest 不适用。一种技术是用“Now updating...”类型的信息替换 Submit 按钮,这样用户在等候响应期间就不会反复单击按钮了。

另一个问题是,用户可能没有注意到他们正在查看的页面的某一部分已经更新了。可以使用不同的可视技术,把用户的眼球带到页面的更新区域,从而缓解这个问题。由 Ajax 更新页面造成的其他问题还包括:“破坏了”浏览器的后退按钮,地址栏中的 URL 也无法反映页面的整个状态,妨碍了设置书签。请参阅 参考资料 一节,获得专门解决 Ajax 应用程序可用性问题的文章。

服务器负载

用 Ajax 实现代替普通的基于表单的 UI,会大大提高对服务器发出的请求数量。例如,一个普通的 Google Web 搜索对服务器只有一个请求,是在用户提交搜索表单时出现的。而 Google Suggest 试图自动完成搜索术语,它要在用户输入时向服务器发送多个请求。在开发 Ajax 应用程序时,要注意将要发送给服务器的请求数量以及由此造成的服务器负荷。降低服务器负载的办法是,在客户机上对请求进行缓冲并且缓存服务器响应(如果可能的话)。还应该尝试将 Ajax Web 应用程序设计为在客户机上执行尽可能多的逻辑,而不必联络服务器。

处理异步

非常重要的是,要理解无法保证 XMLHttpRequest 会按照分派它们的顺序完成。实际上,应当假设它们不会按顺序完成,并且在设计应用程序时把这一点记在心上。在购物车的示例中,使用最后更新的时间戳来确保新的购物车数据不会被旧的数据覆盖(请参阅 清单 7)。这个非常基本的方式可以用于购物车场景,但是可能不适合其他场景。所以在设计时请考虑如何处理异步的服务器响应。


结束语

现在您对 Ajax 的基本原则应当有了很好的理解,对参与 Ajax 交互的客户端和服务器端组件也应当有了初步的知识。这些是基于 Java 的 Ajax Web 应用程序的构造块。另外,您应当理解了伴随 Ajax 方式的一些高级设计问题。创建成功的 Ajax 应用程序要求整体考虑,从 UI 设计到 JavaScript 设计,再到服务器端架构;但是您现在应当已经武装了考虑其他这些方面所需要的核心 Ajax 知识。

如果使用这里演示的技术编写大型 Ajax 应用程序的复杂性让您觉得恐慌,那么有好消息给您。由于 Struts、Spring 和 Hibernate 这类框架的发展把 Web 应用程序开发从底层 Servlet API 和 JDBC 的细节中抽象出来,所以正在出现简化 Ajax 开发的工具包。其中有些只侧重于客户端,提供了向页面添加可视效果的简便方式,或者简化了对 XMLHttpRequest 的使用。有些则走得更远,提供了从服务器端代码自动生成 Ajax 接口的方式。这些框架替您完成了繁重的任务,所以您可以采用更高级的方式进行 Ajax 开发。我在这个系列中将研究其中的一些。

Ajax 社区正在快速前进,所以会有大量有价值的信息涌现。在阅读这个系列的下一期之前,我建议您参考 参考资料 一节中列出的文章,特别是如果您是刚接触 Ajax 或客户端开发的话。您还应当花些时间研究示例源代码并考虑一些增强它的方式。

在这个系列的下一篇文章中,我将深入讨论 XMLHttpRequest API,并推荐一些从 JavaBean 方便地创建 XML 的方式。我还将介绍替代 XML 进行 Ajax 数据传递的方式,例如 JSON(JavaScript Object Notation)轻量级数据交换格式。 J2EEhttp://www.disound.com/zblog/post/326.html#commenthttp://www.disound.com/zblog/xml-rpc/comment.asp?id=326http://www.disound.com/zblog/cmd.asp?act=rss&id=326http://www.disound.com/zblog/cmd.asp?act=tb&id=326 AJAX基础教程 flexer@163.com (flexer) http://www.disound.com/zblog/post/325.html Fri, 28 Apr 2006 16:12:26 +0800 http://www.disound.com/zblog/post/325.html
这篇文章将带您浏览整个AJAX的基本概貌,并展示两个简单的例子让您轻松上路.

  什么是 AJAX?
  AJAX (异步 JavaScript 和 XML) 是个新产生的术语,专为描述JavaScript的两项强大性能.这两项性能在多年来一直被网络开发者所忽略,直到最近Gmail, Google suggest和google Maps的横空出世才使人们开始意识到其重要性.

  这两项被忽视的性能是:
  无需重新装载整个页面便能向服务器发送请求.
  对XML文档的解析和处理.

步骤 1 – "请!" --- 如何发送一个HTTP请求

  为了用JavaScript向服务器发送一个HTTP请求, 需要一个具备这种功能的类实例. 这样的类首先由Internet Explorer以ActiveX对象引入, 被称为XMLHTTP. 后来Mozilla, Safari 和其他浏览器纷纷仿效, 提供了XMLHttpRequest类,它支持微软的ActiveX对象所提供的方法和属性.

  因此, 为了创建一个跨浏览器的这样的类实例(对象), 可以应用如下代码:
if (window.XMLHttpRequest) { // Mozilla, Safari, ...
   http_request = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE
   http_request = new ActiveXObject("Microsoft.XMLHTTP");
}
  (上例对代码做了一定简化,这是为了解释如何创建XMLHTTP类实例. 实际的代码实例可参阅本篇步骤3.)

  如果服务器的响应没有XML mime-type header,某些Mozilla浏览器可能无法正常工作. 为了解决这个问题, 如果服务器响应的header不是text/xml,可以调用其它方法修改该header.
http_request = new XMLHttpRequest();
http_request.overrideMimeType('text/xml');
  接下来要决定当收到服务器的响应后,需要做什么.这需要告诉HTTP请求对象用哪一个JavaScript函数处理这个响应.可以将对象的onreadystatechange属性设置为要使用的JavaScript的函数名,如下所示:

http_request.onreadystatechange = nameOfTheFunction;

  注意:在函数名后没有括号,也无需传递参数.另外还有一种方法,可以在扉页(fly)中定义函数及其对响应要采取的行为,如下所示:
http_request.onreadystatechange = function(){
   // do the thing
};
  在定义了如何处理响应后,就要发送请求了.可以调用HTTP请求类的open()和send()方法, 如下所示:
http_request.open('GET', 'http://www.example.org/some.file';, true);
http_request.send(null);
  open()的第一个参数是HTTP请求方式 – GET, POST, HEAD 或任何服务器所支持的您想调用的方式. 按照HTTP规范,该参数要大写;否则,某些浏览器(如Firefox)可能无法处理请求.有关HTTP请求方法的详细信息可参考http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html W3C specs
  第二个参数是请求页面的URL.由于自身安全特性的限制,该页面不能为第三方域名的页面.同时一定要保证在所有的页面中都使用准确的域名,否则调用open()会得到"permission denied"的错误提示.一个常见的错误是访问站点时使用domain.tld,而当请求页面时,却使用www.domain.tld.
  第三个参数设置请求是否为异步模式.如果是TRUE, JavaScript函数将继续执行,而不等待服务器响应.这就是"AJAX"中的"A".
  如果第一个参数是"POST",send()方法的参数可以是任何想送给服务器的数据. 这时数据要以字符串的形式送给服务器,如下所示:
name=value&anothername=othervalue&so=on

步骤 2 – "收到!" --- 处理服务器的响应

  当发送请求时,要提供指定处理响应的JavaScript函数名.
http_request.onreadystatechange = nameOfTheFunction;
  我们来看看这个函数的功能是什么.首先函数会检查请求的状态.如果状态值是4,就意味着一个完整的服务器响应已经收到了,您将可以处理该响应.
if (http_request.readyState == 4) {
   // everything is good, the response is received
} else {
   // still not ready
}
  readyState的取值如下:
  0 (未初始化)
  1 (正在装载)
  2 (装载完毕)
  3 (交互中)
  4 (完成)

  接着,函数会检查HTTP服务器响应的状态值. 完整的状态取值可参见 W3C site. 我们着重看值为200 OK的响应.
if (http_request.status == 200) {
   // perfect!
} else {
   // there was a problem with the request,
   // for example the response may be a 404 (Not Found)
   // or 500 (Internal Server Error) response codes
}
  在检查完请求的状态值和响应的HTTP状态值后, 您就可以处理从服务器得到的数据了.有两种方式可以得到这些数据:
http_request.responseText – 以文本字符串的方式返回服务器的响应
http_request.responseXML – 以XMLDocument对象方式返回响应.处理XMLDocument对象可以用JavaScript DOM函数

步骤 3 – "万事俱备!" - 简单实例

  我们现在将整个过程完整地做一次,发送一个简单的HTTP请求. 我们用JavaScript请求一个HTML文件, test.html, 文件的文本内容为"I'm a test.".然后我们"alert()"test.html文件的内容.
<script type="text/javascript" language="javascript">
   var http_request = false;
   function makeRequest(url) {

       http_request = false;

       if (window.XMLHttpRequest) { // Mozilla, Safari,...
           http_request = new XMLHttpRequest();
           if (http_request.overrideMimeType) {
               http_request.overrideMimeType('text/xml');
           }
       } else if (window.ActiveXObject) { // IE
           try {
               http_request = new ActiveXObject("Msxml2.XMLHTTP");
           } catch (e) {
               try {
                   http_request = new ActiveXObject("Microsoft.XMLHTTP");
               } catch (e) {}
           }
       }

       if (!http_request) {
           alert('Giving up Cannot create an XMLHTTP instance');
           return false;
       }
       http_request.onreadystatechange = alertContents;
       http_request.open('GET', url, true);
       http_request.send(null);

   }

   function alertContents() {

       if (http_request.readyState == 4) {
           if (http_request.status == 200) {
               alert(http_request.responseText);
           } else {
               alert('There was a problem with the request.');
           }
       }

   }
</script>
<span
   style="cursor: pointer; text-decoration: underline"
   οnclick="makeRequest('test.html')">
       Make a request
</span>

  本例中:
  用户点击浏览器上的"请求"链接;
  接着函数makeRequest()将被调用.其参数 – HTML文件test.html在同一目录下;
  这样就发起了一个请求.onreadystatechange的执行结果会被传送给alertContents();
  alertContents()将检查服务器的响应是否成功地收到,如果是,就会"alert()"test.html文件的内容.

步骤 4 – "X-文档" --- 处理XML响应

  在前面的例子中,当服务器对HTTP请求的响应被收到后,我们会调用请求对象的reponseText属性.该属性包含了test.html文件的内容.现在我们来试试responseXML属性.

  首先,我们新建一个有效的XML文件,后面我们将使用这个文件.该文件(test.xml)源代码如下所示:
<?xml version="1.0" ?>
<root>
   I'm a test.
</root>
  在该脚本中,我们只需修改请求部分:
...
οnclick="makeRequest('test.xml')">
...
  接着,在alertContents()中,我们将alert()的代码alert(http_request.responseText);换成:
  var xmldoc = http_request.responseXML;
  var root_node = xmldoc.getElementsByTagName('root').item;
  alert(root_node.firstChild.data);


  这里,我们使用了responseXML提供的XMLDocument对象并用DOM方法获取存于XML文件中的内容.



 
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值