JasperReports 报表脚本Scriptlet

我们在前面的章节中已经看到,报表上显示的数据通常是从报表参数和报表字段中获取的。可以使用报表变量及其表达式来处理此数据。在某些情况下,使用报表表达式或变量无法轻松实现复杂的功能。这方面的示例可能是复杂的字符串操作、构建地图或内存中的对象列表或使用第 3 方 Java API 的日期操作。对于这种情况,JasperReports 为我们提供了一种简单而强大的方法来使用Scriptlets做到这一点。

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

JasperReports Scriptlet 声明

我们可以通过两种方式声明一个 scriptlet :

  • 使用 <scriptlet> 元素。该元素具有name属性和class属性。在类属性应指定类,它扩展名JRAbstractScriptlet类。该类必须在填充报表时在类路径中可用,并且必须具有空构造函数,以便引擎可以即时实例化它。

  • 使用元素<jasperReport>的属性scriptletClass,在报表模板(JRXML)中。通过使用 scriptlet 的完全限定名称(包括整个包名称)设置此属性,我们表明我们要使用 scriptlet。使用此属性创建的 scriptlet 实例的作用类似于 scriptlet 列表中的第一个 scriptlet,并具有预定义的名称 REPORT。

 JasperReports Scriptlet类

Scriptlet 是一个 java 类,它必须扩展以下任一类 :

  • net.sf.jasperreports.engine.JRAbstractScriptlet : 此类包含许多必须在每个实现中覆盖的抽象方法。JasperReports 在适当的时候自动调用这些方法。开发人员必须实现所有抽象方法。

  • net.sf.jasperreports.engine.JRDefaultScriptlet : 此类包含 JRAbstractScriptlet 中每个方法的默认空实现。开发人员只需要为他/她的项目实现他/她需要的那些方法。

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

方法 描述
public void beforeReportInit() 在报表初始化之前调用。
public void afterReportInit() 在报表初始化后调用。
public void beforePageInit() 在每个页面初始化之前调用。
public void afterPageInit() 在每个页面初始化后调用。
public void beforeColumnInit() 在每列初始化之前调用。
public void afterColumnInit() 在每列初始化后调用。
public void beforeGroupInit(String groupName) 在初始化参数中指定的组之前调用。
public void afterGroupInit(String groupName) 在参数中指定的组初始化后调用。
public void beforeDetailEval() 在评估报表的详细信息部分中的每条记录之前调用。
public void afterDetailEval() 在评估报表的详细信息部分中的每条记录后调用。

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

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

JasperReports 全局Scriptlet

我们可以通过另一种方式将 scriptlet 与报表相关联,即通过全局声明 scriptlet。这使得 scriptlet 适用于在给定 JasperReports 部署中填写的所有报表。由于可以将 scriptlet 作为扩展添加到 JasperReports 中,所以这很容易。scriptlet 扩展点由net.sf.jasperreports.engine.scriptlets.ScriptletFactory表示界面。JasperReports 将在运行时通过扩展加载所有可用的 scriptlet 工厂。然后,它会询问他们每个人要应用到正在运行的当前报告的 scriptlet 实例列表。当询问 scriptlet 实例列表时,引擎会提供一些上下文信息,工厂可以使用这些信息来决定哪些 scriptlet 实际应用于当前报表。

JasperReports Report Governors

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

JasperReports 有两个简单的报表管理器,可以根据指定的最大页数或指定的超时间隔停止报告执行。他们是 :

  • net.sf.jasperreports.governors.MaxPagesGovernor : 这是一个全局脚本,它正在寻找两个配置属性来决定它是否适用于当前正在运行的报告。配置属性是 :

    • net.sf.jasperreports.governor.max.pages.enabled=[true|false]

    • net.sf.jasperreports.governor.max.pages=[integer]

  • net.sf.jasperreports.governors.TimeoutGovernor : 这也是一个全局脚本,它正在寻找以下两个配置属性来决定它是否适用。

    配置属性是 :

    • net.sf.jasperreports.governor.timeout.enabled=[true|false]

    • net.sf.jasperreports.governor.timeout=[milliseconds]

这两个调控器的属性可以在 jasperreports.properties 文件中全局设置,也可以在报表级别设置为自定义报表属性。这很有用,因为不同的报表可能有不同的估计大小或超时限制,还因为您可能希望为所有报表打开调控器,同时为某些报表关闭它,反之亦然。

JasperReports Scriptlet的示例

pom.xml文件

<dependencies>
        <!-- https://mvnrepository.com/artifact/net.sf.jasperreports/jasperreports -->
        <dependency>
            <groupId>net.sf.jasperreports</groupId>
            <artifactId>jasperreports</artifactId>
            <version>6.17.0</version>
        </dependency>
        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>2.4.7</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.17</version>
        </dependency>
    </dependencies>

DataBean实体类:

package com.yiidian;

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;
   }
}

DataBeanList类:

package com.yiidian;

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;
   }
}

让我们编写一个 scriptlet 类 ( MyScriptlet ):

package com.yiidian;

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.";
   }

}

上述scriptlet类的详细信息如下 :

  • 在afterReportInit方法中,我们为变量"someVar" this.setVariableValue("someVar", new String("This variable value was modified by the scriptlet."))设置了一个值。

  • 在最后,定义了一个名为“hello”的额外方法。这是一个可以添加到实际返回值的 Scriptlet 的方法示例,而不是设置变量。

jasper_report_template.jrxml模板文件:

<?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.yiidian.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 = "#FF0000"/>

                <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 = "#FF0000" />

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

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

            </textField>

        </band>
    </summary>

</jasperReport>

报表模板的详细信息如下 :

  • 我们在<jasperReport> 元素的属性scriptletClass中引用了 MyScriptlet 类。

  • Scriptlet 只能访问但不能修改报表字段和参数。但是,Scriptlet 可以修改报告变量值。这可以通过调用 setVariableValue() 方法来完成。该方法在JRAbstractScriptlet 类中定义,该类始终是任何scriptlet 的父类。在这里,我们定义了一个变量someVar,它将被 MyScriptlet 修改为具有值This value was modified by the scriptlet。

  • 上面的报表模板在摘要带中有一个方法调用,它说明了如何编写新方法(在 scriptlet 中)并在报表模板中使用它们。( $P{REPORT_SCRIPTLET}.hello() )

先编译报表文件:

package com.yiidian;

import net.sf.jasperreports.engine.*;
import net.sf.jasperreports.view.JasperViewer;

public class JasperReportFill {

   public static void main(String[] args) {
      String filePath = "D:/idea_codes/jasper_reports/jasper_report_template.jrxml";
      try {
         JasperCompileManager.compileReportToFile(filePath,"d:/jasper_report_template.jasper");
         System.out.println("编译成功");
      } catch (JRException e) {
         e.printStackTrace();
      }
   }
}

编译后在D:/盘根目录下生成jasper_report_template.jasper文件,然后再填充数据:

package com.yiidian;

import net.sf.jasperreports.engine.*;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;

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

public class JasperReportFill {

   public static void main(String[] args) {
         String sourceFileName = "d:/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();
         }
      }
}

填充数据完毕后,在D:/盘根目录下生成jasper_report_template.jrprint文件,最后使用预览程序预览:

package com.yiidian;

import net.sf.jasperreports.engine.*;
import net.sf.jasperreports.view.JasperViewer;

public class JasperReportFill {

   public static void main(String[] args) {
      String filePath = "D:/jasper_report_template.jrprint";
      try {
         JasperViewer.viewReport(filePath,false);
      } catch (JRException e) {
         e.printStackTrace();
      }
   }
}

最后输出结果如下:

在这里,我们看到 MyScriptlet 类显示了两条消息 :

  • 在标题部分 :This variable value was modified by the scriptlet
  • 在底部 :Hello! I'm the report's scriptlet object.

热门文章

优秀文章