Jasper Reports 简明教程

Report Scriptlets

我们在前几章中看到,报告上显示的数据通常从报表参数和字段中获取。可以使用报表变量及其表达式来处理此数据。有时,不能使用报表表达式或变量轻松实现复杂的功能。这方面的示例可能是复杂的字符串操作、构建映射、内存中的对象列表或使用第三方 Java API 执行日期操作。对于这种情况,JasperReports 向我们提供了一种简单而有效的方法,即 Scriptlets

We have seen in our previous chapters, data displayed on the report is usually fetched from report parameters and report fields. This data can be processed using the report variables and their expressions. There are situations when a complex functionality cannot be achieved easily using report expressions or variables. Examples of this may be complex String manipulations, building of Maps, or Lists of objects in memory or manipulations of dates using 3rd party Java APIs. For such situations, JasperReports provides us with a simple and powerful means of doing this with Scriptlets.

脚本是每次发生报告事件时执行的 Java 代码序列。可以通过脚本影响报表变量的值。

Scriptlets are sequences of Java code that are executed every time a report event occurs. Values of report variables can be affected through scriptlets.

Scriptlet Declaration

我们可以通过两种方式声明脚本 -

We can declare a scriptlet in two ways −

  1. Using <*scriptlet*> element. This element has name attribute and class attribute. The class attribute should specify the name of the class, which extends JRAbstractScriptlet class. The class must be available in the classpath at report filling time and must have an empty constructor, so that the engine can instantiate it on the fly.

  2. Using the attribute scriptletClass of the element <*jasperReport*>, in the report template (JRXML). By setting this attribute with fully qualified name of scriptlet (including the entire package name), we indicate that we want to use a scriptlet. The scriptlet instance, created with this attribute, acts like the first scriptlet in the list of scriptlets and has the predefined name REPORT.

Scriptlet class

脚本是 Java 类,它必须扩展以下任一类 -

A scriptlet is a java class, which must extend either of the following classes −

  1. net.sf.jasperreports.engine.JRAbstractScriptlet − This class contains a number of abstract methods that must be overridden in every implementation. These methods are called automatically by JasperReports at the appropriate moment. Developer must implement all the abstract methods.

  2. net.sf.jasperreports.engine.JRDefaultScriptlet − This class contains default empty implementations of every method in JRAbstractScriptlet. A developer is only required to implement those methods he/she needs for his/her project.

下表列出了上面类中的方法。在报表填充阶段,这些方法将在适当的时候由报表引擎调用。

The following table lists the methods in the above class. These methods will be called by the report engine at the appropriate time, during report filling phase.

S.NO

Method and Description

1

public void beforeReportInit() Called before report initialization.

2

public void afterReportInit() Called after report initialization.

3

public void beforePageInit() Called before each page is initialized.

4

public void afterPageInit() Called after each page is initialized.

5

public void beforeColumnInit() Called before each column is initialized.

6

public void afterColumnInit() Called after each column is initialized.

7

public void beforeGroupInit(String groupName) Called before the group specified in the parameter is initialized.

8

public void afterGroupInit(String groupName) Called after the group specified in the parameter is initialized.

9

public void beforeDetailEval() Called before each record in the detail section of the report is evaluated.

10

public void afterDetailEval() Called after each record in the detail section of the report is evaluated.

每个报表可以指定任意数量的脚本。如果未为报表指定脚本,引擎仍会创建单个 JRDefaultScriptlet 实例,并使用内置的 REPORT_SCRIPTLET 参数将其注册。

Any number of scriptlets can be specified per report. If no scriptlet is specified for a report, the engine still creates a single JRDefaultScriptlet instance and registers it with the built-in REPORT_SCRIPTLET parameter.

我们可以向脚本添加我们需要的任何其他方法。报表可以使用内置参数 REPORT_SCRIPTLET 调用这些方法。

We can add any additional methods that we need to our scriptlets. Reports can call these methods by using the built-in parameter REPORT_SCRIPTLET.

Global Scriptlets

我们可以通过另一种方式为报告关联脚本,即全局声明脚本。这使得脚本适用于给定 JasperReports 部署中正在填充的所有报告。由于可以将脚本小程序作为扩展添加到 JasperReports,因此可以轻松实现这一点。脚本扩展点表示为 net.sf.jasperreports.engine.scriptlets.ScriptletFactory 接口。运行时,JasperReports 将加载通过扩展提供的可用脚本工厂。然后,它会向每个脚本工厂询问他们要应用于当前正在运行的报表的脚本实例列表。在询问脚本实例列表时,引擎会提供一些上下文信息,工厂可以使用这些信息来决定哪些脚本实际上适用于当前报表。

We can associate scriptlets in another way to reports, which is by declaring the scriptlets globally. This makes the scriptlets apply to all reports being filled in the given JasperReports deployment. This is made easy by the fact that scriptlets can be added to JasperReports as extensions. The scriptlet extension point is represented by the net.sf.jasperreports.engine.scriptlets.ScriptletFactory interface. JasperReports will load all scriptlet factories available through extensions at runtime. Then, it will ask each one of them for the list of scriptlets instances that they want to apply to the current report that is being run. When asking for the list of scriptlet instances, the engine gives some context information that the factory could use in order to decide, which scriptlets actually apply to the current report.

Report Governors

总督只是全局脚本的扩展,它使我们能够解决在生成报表时报表引擎在运行时进入无限循环的问题。无效的报表模板无法在设计时检测到,因为大多数时候,进入无限循环的条件取决于在运行时馈入引擎的实际数据。报表总督有助于决定某个报表是否进入了无限循环,并且可以停止它。这可以防止为运行报表的机器耗尽资源。

Governors are just an extension of global scriptlets that enable us to tackle a problem of report engine entering infinite loop at runtime, while generating reports. Invalid report templates cannot be detected at design time, because most of the time, the conditions for entering the infinite loops depend on the actual data that is fed into the engine at runtime. Report Governors help in deciding whether a certain report has entered an infinite loop and they can stop it. This prevents resource exhaustion for the machine that runs the report.

JasperReports 具有两个简单的报表总督,它们将根据指定的最大页数或指定的超时间隔来停止报表执行。它们是 −

JasperReports has two simple report governors that would stop a report execution based on a specified maximum number of pages or a specified timeout interval. They are −

  1. net.sf.jasperreports.governors.MaxPagesGovernor − This is a global scriptlet that is looking for two configuration properties to decide if it applies or not to the report currently being run. The configuration properties are − net.sf.jasperreports.governor.max.pages.enabled=[true|false] net.sf.jasperreports.governor.max.pages=[integer]

  2. net.sf.jasperreports.governors.TimeoutGovernor− This is also a global scriptlet that is looking for the following two configuration properties to decide if it applies or not. The configuration properties are − net.sf.jasperreports.governor.timeout.enabled=[true|false] net.sf.jasperreports.governor.timeout=[milliseconds]

这两个调节器的属性可以作为自定义报告属性在 jasperreports.properties 文件中全局设置,或者在报告级别设置。由于不同的报告可以具有不同的估计大小或超时限制,并且你可能希望对所有报告启用调节器,同时对某些报告禁用,反之亦然,因此此功能非常有用。

The properties for both governors can be set globally, in the jasperreports.properties file, or at report level, as custom report properties. This is useful because different reports can have different estimated size or timeout limits and also because you might want turn on the governors for all reports, while turning it off for some, or vice-versa.

Example

我们编写一个脚本小类 ( MyScriptlet )。C:\tools\jasperreports-5.0.1\test\src\com\tutorialspoint\MyScriptlet.java 文件的内容如下 −

Let’s write a scriptlet class (MyScriptlet). The contents of file C:\tools\jasperreports-5.0.1\test\src\com\tutorialspoint\MyScriptlet.java are as follows −

package com.tutorialspoint;

import net.sf.jasperreports.engine.JRDefaultScriptlet;
import net.sf.jasperreports.engine.JRScriptletException;


public class MyScriptlet extends JRDefaultScriptlet {

   public void afterReportInit() throws JRScriptletException{
      System.out.println("call afterReportInit()");
      // this.setVariableValue("AllCountries", sbuffer.toString());
      this.setVariableValue("someVar", new String("This variable value
         was modified by the scriptlet."));
   }

   public String hello() throws JRScriptletException {
      return "Hello! I'm the report's scriptlet object.";
   }

}

上述脚本小类详细信息如下 −

Details of the above scriptlet class are as follows −

  1. In the afterReportInit method, we set a value to the variable "someVar" this.setVariableValue("someVar", new String("This variable value was modified by the scriptlet.")).

  2. At the end of the class, an extra method called 'hello' has been defined. This is an example of a method that can be added to the Scriptlet that actually returns a value, rather than setting a Variable.

接下来,我们在现有的报告模板(章节 Report Designs )中添加脚本小类引用。修订后的报告模板(jasper_report_template.jrxml)如下。将其保存到 C:\tools\jasperreports-5.0.1\test 目录 −

Next, we will add the scriptlet class reference in our existing report template (Chapter Report Designs). The revised report template (jasper_report_template.jrxml) are as follows. Save it to C:\tools\jasperreports-5.0.1\test directory −

<?xml version = "1.0"?>
<!DOCTYPE jasperReport PUBLIC
   "//JasperReports//DTD Report Design//EN"
   "http://jasperreports.sourceforge.net/dtds/jasperreport.dtd">

<jasperReport xmlns = "http://jasperreports.sourceforge.net/jasperreports"
   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation = "http://jasperreports.sourceforge.net/jasperreports
   http://jasperreports.sourceforge.net/xsd/jasperreport.xsd"
   name = "jasper_report_template" pageWidth = "595"
   pageHeight = "842" columnWidth = "515"
   leftMargin = "40" rightMargin = "40" topMargin = "50" bottomMargin = "50"
   scriptletClass = "com.tutorialspoint.MyScriptlet">

   <style name = "alternateStyle" fontName = "Arial" forecolor = "red">

      <conditionalStyle>
         <conditionExpression>
            <![CDATA[new Boolean($V{countNumber}.intValue() % 2 == 0)]]>
         </conditionExpression>

         <style forecolor = "blue" isBold = "true"/>
      </conditionalStyle>
   </style>

   <parameter name = "ReportTitle" class = "java.lang.String"/>
   <parameter name = "Author" class = "java.lang.String"/>

   <queryString>
      <![CDATA[]]>
   </queryString>

   <field name = "country" class = "java.lang.String">
      <fieldDescription>
         <![CDATA[country]]>
      </fieldDescription>
   </field>

   <field name = "name" class = "java.lang.String">
      <fieldDescription>
         <![CDATA[name]]>
      </fieldDescription>
   </field>

   <variable name = "countNumber" class = "java.lang.Integer"
      calculation = "Count">
      <variableExpression><
         ![CDATA[Boolean.TRUE]]>
      </variableExpression>
   </variable>

   <variable name = "someVar" class = "java.lang.String">
      <initialValueExpression>
        <![CDATA["This is the initial variable value."]]>
      </initialValueExpression>
   </variable>

   <title>
      <band height = "100">

         <line>
            <reportElement x = "0" y = "0" width = "515" height = "1"/>
         </line>

         <textField isBlankWhenNull = "true" bookmarkLevel = "1">
            <reportElement x = "0" y = "10" width = "515" height = "30"/>

            <textElement textAlignment = "Center">
              <font size = "22"/>
            </textElement>

            <textFieldExpression class = "java.lang.String">
              <![CDATA[$P{ReportTitle}]]>
            </textFieldExpression>

            <anchorNameExpression>
               <![CDATA["Title"]]>
            </anchorNameExpression>
         </textField>

         <textField isBlankWhenNull = "true">
            <reportElement  x = "0" y = "40" width = "515" height = "20"/>

            <textElement textAlignment = "Center">
               <font size = "10"/>
            </textElement>

            <textFieldExpression class = "java.lang.String">
               <![CDATA[$P{Author}]]>
            </textFieldExpression>
         </textField>

         <textField isBlankWhenNull = "true">
            <reportElement  x = "0" y = "50" width = "515"
               height = "30" forecolor = "#993300"/>

            <textElement textAlignment = "Center">
               <font size = "10"/>
            </textElement>

            <textFieldExpression class = "java.lang.String">
               <![CDATA[$V{someVar}]]>
            </textFieldExpression>

         </textField>

      </band>
   </title>

   <columnHeader>
      <band height = "23">

         <staticText>
            <reportElement mode = "Opaque" x = "0" y = "3"
               width = "535" height = "15"
               backcolor = "#70A9A9" />

            <box>
               <bottomPen lineWidth = "1.0" lineColor = "#CCCCCC" />
            </box>

            <textElement />

            <text>
               <![CDATA[]]>
            </text>

         </staticText>

         <staticText>
            <reportElement x = "414" y = "3" width = "121" height = "15" />

            <textElement textAlignment = "Center" verticalAlignment = "Middle">
               <font isBold = "true" />
            </textElement>

            <text><![CDATA[Country]]></text>
         </staticText>

         <staticText>
            <reportElement x = "0" y = "3" width = "136" height = "15" />

            <textElement textAlignment = "Center" verticalAlignment = "Middle">
               <font isBold = "true" />
            </textElement>

            <text><![CDATA[Name]]></text>
         </staticText>

      </band>
   </columnHeader>

   <detail>
      <band height = "16">

         <staticText>
            <reportElement mode = "Opaque" x = "0" y = "0"
               width = "535"	height = "14"
               backcolor = "#E5ECF9" />

            <box>
               <bottomPen lineWidth = "0.25" lineColor = "#CCCCCC" />
            </box>

            <textElement />

            <text>
               <![CDATA[]]>
            </text>
         </staticText>

         <textField>
            <reportElement style = "alternateStyle" x="414" y = "0"
               width = "121" height = "15" />

            <textElement textAlignment = "Center" verticalAlignment = "Middle">
               <font size = "9" />
            </textElement>


            <textFieldExpression class = "java.lang.String">
               <![CDATA[$F{country}]]>
            </textFieldExpression>
         </textField>

         <textField>
            <reportElement x = "0" y = "0" width = "136" height = "15" />
            <textElement textAlignment = "Center" verticalAlignment = "Middle" />

            <textFieldExpression class = "java.lang.String">
               <![CDATA[$F{name}]]>
            </textFieldExpression>
         </textField>

      </band>
   </detail>

   <summary>
      <band height = "45">

         <textField isStretchWithOverflow = "true">
            <reportElement x = "0" y = "10" width = "515" height = "15" />
            <textElement textAlignment = "Center"/>

            <textFieldExpression class = "java.lang.String">
               <![CDATA["There are " + String.valueOf($V{REPORT_COUNT}) +
                  " records on this report."]]>
            </textFieldExpression>
         </textField>

         <textField isStretchWithOverflow = "true">
            <reportElement positionType = "Float" x = "0" y = "30" width = "515"
               height = "15" forecolor = "# 993300" />

            <textElement textAlignment = "Center">
               <font size = "10"/>
            </textElement>

            <textFieldExpression class = "java.lang.String">
               <![CDATA[$P{REPORT_SCRIPTLET}.hello()]]>
            </textFieldExpression>

         </textField>

      </band>
   </summary>

</jasperReport>

修订后的报告模板的详细信息如下 −

The details of the revised report template is given below −

  1. We have referenced the MyScriptlet class in the attribute scriptletClass of <jasperReport> element.

  2. Scriptlets can only access, but not modify the report fields and parameters. However, scriptlets can modify report variable values. This can be accomplished by calling the setVariableValue() method. This method is defined in JRAbstractScriptlet class, which is always the parent class of any scriptlet. Here, we have defined a variable someVar, which will be modified by the MyScriptlet to have the value This value was modified by the scriptlet.

  3. The above report template has a method call in the Summary band that illustrates how to write new methods (in scriptlets) and use them in the report template. ($P{REPORT_SCRIPTLET}.hello())

报表填充的 java 代码保持不变。文件 C:\tools\jasperreports-5.0.1\test\src\com\tutorialspoint\JasperReportFill.java 的内容如下 -

The java codes for report filling remain unchanged. The contents of the file C:\tools\jasperreports-5.0.1\test\src\com\tutorialspoint\JasperReportFill.java are as given below −

package com.tutorialspoint;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;

public class JasperReportFill {
   @SuppressWarnings("unchecked")
   public static void main(String[] args) {
      String sourceFileName =
         "C://tools/jasperreports-5.0.1/test/jasper_report_template.jasper";

      DataBeanList DataBeanList = new DataBeanList();
      ArrayList<DataBean> dataList = DataBeanList.getDataBeanList();

      JRBeanCollectionDataSource beanColDataSource = new
         JRBeanCollectionDataSource(dataList);

      Map parameters = new HashMap();
      /**
       * Passing ReportTitle and Author as parameters
       */
      parameters.put("ReportTitle", "List of Contacts");
      parameters.put("Author", "Prepared By Manisha");

      try {
         JasperFillManager.fillReportToFile(
         sourceFileName, parameters, beanColDataSource);
      } catch (JRException e) {
         e.printStackTrace();
      }
   }
}

C:\tools\jasperreports-5.0.1\test\src\com\tutorialspoint\DataBean.java POJO 文件的内容如下 −

The contents of the POJO file C:\tools\jasperreports-5.0.1\test\src\com\tutorialspoint\DataBean.java are as given below −

package com.tutorialspoint;

public class DataBean {
   private String name;
   private String country;

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }

   public String getCountry() {
      return country;
   }

   public void setCountry(String country) {
      this.country = country;
   }
}

C:\tools\jasperreports-5.0.1\test\src\com\tutorialspoint\DataBeanList.java 文件的内容如下 −

The contents of the file C:\tools\jasperreports-5.0.1\test\src\com\tutorialspoint\DataBeanList.java are as given below −

package com.tutorialspoint;

import java.util.ArrayList;

public class DataBeanList {
   public ArrayList<DataBean> getDataBeanList() {
      ArrayList<DataBean> dataBeanList = new ArrayList<DataBean>();

      dataBeanList.add(produce("Manisha", "India"));
      dataBeanList.add(produce("Dennis Ritchie", "USA"));
      dataBeanList.add(produce("V.Anand", "India"));
      dataBeanList.add(produce("Shrinath", "California"));

      return dataBeanList;
   }

   /**
    * This method returns a DataBean object,
    * with name and country set in it.
    */
   private DataBean produce(String name, String country) {
      DataBean dataBean = new DataBean();
      dataBean.setName(name);
      dataBean.setCountry(country);

      return dataBean;
   }
}

Report Generation

我们将使用常规 ANT 构建流程编译并执行上述文件。build.xml 文件(保存在 C:\tools\jasperreports-5.0.1\test 目录下)的内容如下。

We will compile and execute the above file using our regular ANT build process. The contents of the file build.xml (saved under directory C:\tools\jasperreports-5.0.1\test) are as given below.

导入文件 - baseBuild.xml 从第 Environment Setup 章中获取,并应放置在与 build.xml 相同的目录中。

The import file - baseBuild.xml is picked up from the chapter Environment Setup and should be placed in the same directory as the build.xml.

<?xml version = "1.0" encoding = "UTF-8"?>
<project name = "JasperReportTest" default = "viewFillReport" basedir = ".">
   <import file = "baseBuild.xml" />

   <target name = "viewFillReport" depends = "compile,compilereportdesing,run"
      description = "Launches the report viewer to preview
      the report stored in the .JRprint file.">

      <java classname = "net.sf.jasperreports.view.JasperViewer" fork = "true">
         <arg value = "-F${file.name}.JRprint" />
         <classpath refid = "classpath" />
      </java>
   </target>

   <target name = "compilereportdesing" description = "Compiles the JXML file and
      produces the .jasper file.">

      <taskdef name = "jrc" classname = "net.sf.jasperreports.ant.JRAntCompileTask">
         <classpath refid = "classpath" />
      </taskdef>

      <jrc destdir = ".">
         <src>
            <fileset dir = ".">
               <include name = "*.jrxml" />
            </fileset>
         </src>
         <classpath refid = "classpath" />
      </jrc>

   </target>

</project>

接下来,让我们打开命令行窗口并进入放置 build.xml 的目录。最后,执行命令 ant -Dmain-class=com.tutorialspoint.JasperReportFill (viewFullReport 是默认目标),如下所示 −

Next, let’s open command line window and go to the directory where build.xml is placed. Finally, execute the command ant -Dmain-class=com.tutorialspoint.JasperReportFill (viewFullReport is the default target) as −

C:\tools\jasperreports-5.0.1\test>ant -Dmain-class=com.tutorialspoint.JasperReportFill
Buildfile: C:\tools\jasperreports-5.0.1\test\build.xml

clean-sample:
   [delete] Deleting directory C:\tools\jasperreports-5.0.1\test\classes
   [delete] Deleting: C:\tools\jasperreports-5.0.1\test\jasper_report_template.jasper
   [delete] Deleting: C:\tools\jasperreports-5.0.1\test\jasper_report_template.jrprint

compile:
   [mkdir] Created dir: C:\tools\jasperreports-5.0.1\test\classes
   [javac] C:\tools\jasperreports-5.0.1\test\baseBuild.xml:28:
   warning: 'includeantruntime' was not set, defaulting to bu
   [javac] Compiling 4 source files to C:\tools\jasperreports-5.0.1\test\classes

compilereportdesing:
   [jrc] Compiling 1 report design files.
   [jrc] log4j:WARN No appenders could be found for logger
   (net.sf.jasperreports.engine.xml.JRXmlDigesterFactory).
   [jrc] log4j:WARN Please initialize the log4j system properly.
   [jrc] log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
   [jrc] File : C:\tools\jasperreports-5.0.1\test\jasper_report_template.jrxml ... OK.

run:
   [echo] Runnin class : com.tutorialspoint.JasperReportFill
   [java] log4j:WARN No appenders could be found for logger
   (net.sf.jasperreports.extensions.ExtensionsEnvironment).
   [java] log4j:WARN Please initialize the log4j system properly.
   [java] call afterReportInit()
   [java] call afterReportInit()

viewFillReport:
   [java] log4j:WARN No appenders could be found for logger
   (net.sf.jasperreports.extensions.ExtensionsEnvironment).
   [java] log4j:WARN Please initialize the log4j system properly.

BUILD SUCCESSFUL
Total time: 18 minutes 49 seconds

作为上述编译的结果,一个 JasperViewer 窗口会打开,如以下给出的屏幕截图所示 -

As a result of above compilation, a JasperViewer window opens up as shown in the screen given below −

report scriptlet example

此处我们看到从 MyScriptlet 类显示了两条消息 −

Here we see two messages are displayed from MyScriptlet class −

  1. In title section − This variable value was modified by the scriptlet

  2. At the bottom − Hello! I’m the report’s scriptlet object.