美文网首页
SpringBoot中集成Birt

SpringBoot中集成Birt

作者: 乱七八糟谈技术 | 来源:发表于2020-10-21 17:12 被阅读0次

最近在整理项目上的报表功能,因为项目比较久远,留存的文档也少,因此重新部署过程中也遇到了不少的坑,记录下来方便后来者。项目中用的是Birt来自定义报表,当时选择Birt的原因主要是因为产品的需要,为了满足用户个性化报表的需求,而且以最小的开发量来快速的定制化报表,预览报表以及生成报表文件等功能。但由于Birt也是一个比较老的技术,熟悉.net的朋友应该都有过水晶报表的开发经验,Birt很类似于水晶报表。Birt现在的更新停留在2016年,因此这几年也没有任何的更新,会存在一些问题:

- JDK版本的支持,JDK的版本目前测试只能到1.8,而且其他类型的JDK,比如corretto JDK 1.8有些功能不能正常运行

- 由于很多库的版本比较低,一些涉及到安全的库升级比较困难。

- 另外它是基于eclipse插件开发,因此暂时在IntellJ上还不支持。

- 当前的很多实例都是基于JAVA EE开发,新的SpringBoot实例比较少。因此,本文在总计Birt的基本使用的基础上,将使用一个实际的实例来详细演示如何在SpringBoot上集成Birt,并提供报表的在线和离线生成功能。

Birt介绍

BIRT是一个Eclipse-based开放源代码的报表系统,它主要是用在基于Java和J2ee的web应用程序上。BIRT主要由两部分组成:一个是基于Eclipse的报表设计器和一个可以添加到应用服务器的运行组建。BIRT同时提供一个图形报表制作引擎。BIRT可以生成图片、导出Excel、pdf等。

基本概念

1)数据源,数据的提供者。如xml数据源、jdbc数据源等。也支持Hive和Cassandra等大数据源。也可以支持脚本数据源。
2)数据集,数据集合,即查询的结果。
3)报表以及报表项,报表可视为是一组数据集的表现形式,而报表项是具体形式表现的单元。
4)报表参数,用于查询报表的查询参数5)模板和库,主要用于复用报表,提高报表开发的效率。

报表类型

列表

列表是最简单的报表。当列表变长时,你可以把相关数据增加到同一分组。如果数据是数字类型的,你可以添加到“总数”、“平均”、或其他汇总中。

图表

当需要图表表现时,数字型数据比较好理解。BIRT 也提供饼状、线状以及柱状图标等。

交叉表

交叉表(也叫做十字表格或矩阵)用两种维度展示数据。

信函和文档

通知、信件、以及其他文本文档都很容易通过 BIRT 方便建立。文档包括正文、格式、列表、图表等。

混合报表

可以组合各种类型的报表到一个报表上。

报表设计器

包括布局视图(Layout),属性编辑器(Property Editor),报表预览(Preview),代码编辑器(Script)等。
整体。如下图:

准备环境

下载download birt开发工具 http://download.eclipse.org/birt/downloads 选All in one,安装后打开eclipse.exe或者自行下载eclipe安装,然后安装Birt runtime和design tool。

报表设计

第一步,创建报表

File->new->New Report , 新建立一个report,命名为book.rptdesign,放在resouces/report目录下

第二步,创建数据源

在Outline里找到Data source,新建DataSource,在下拉框里选Scripted Data Source,Scripted Data Source是指通过script来获取到数据源。

第三步,创建dataset

在Outline里找到刚刚建立Data set,双击找到DataSource,关联上刚刚建立的DataSource。Data set 是指从DataSource来组装出需要的数据集合。

第四步,设置dataset输出列

在Outline里找到Data set,新建DataSet,在OutPut columns填入Year type选string,name type选String,author type选String。

第五步,设置报表参数

在Outline里找到Report Parameters,新建立一个参数为year,Data type为String,Display type为Text Box。

第六步,设置body

在Outline里找到Body,在Outline里找到Rport Items里,拖动一个Table到右侧的layout里,会自动弹出对话框,DataSet选刚才的建立的DataSet,选中binding columns。并输入表头并绑定cell到dataset的输出参数。

第七步,预览报表

预览report,可以在birt里选window->preference->web browser,勾上use external web browser ,在下面的选项框里选你的浏览器。然后在工具栏里选择播放标志的下拉选view report as html或其它的选项。预览时,会提示输入年份,输入年份后,就可以看到报表的雏形。

SpringBoot服务开发

使用SpringBoot开发后端服务,来为报表数据提供数据源。下面以一个简单的实例来演示如果生成报表。

引入依赖包

<dependency>
    <groupId>org.eclipse.birt.runtime</groupId>
    <artifactId>org.eclipse.birt.runtime</artifactId>
    <version>4.4.2</version>
    <exclusions>
        <exclusion>
            <groupId>org.eclipse.birt.runtime</groupId>
            <artifactId>org.eclipse.orbit.mongodb</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.eclipse.birt.runtime</groupId>
    <artifactId>viewservlets</artifactId>
    <version>4.4.2</version>
</dependency>
<dependency>
    <groupId>org.apache.axis</groupId>
    <artifactId>axis</artifactId>
    <version>1.4</version>
</dependency>

注意:org.eclipse.birt.runtime:org.eclipse.orbit.mongodb排除掉,不然会启动报错

如果需要预览报表,需要引入更多的包,axis,commons-discovery,

viewservlets,jaxrpc-api

BirtEngineFactory

public class BirtEngineFactory implements FactoryBean<IReportEngine>, ApplicationContextAware, DisposableBean {  

    public boolean isSingleton(){ return true ; } 

    private ApplicationContext context ; 
    private IReportEngine birtEngine ;  
    private File _resolvedDirectory ;
    private java.util.logging.Level logLevel ; 

    public void setApplicationContext(ApplicationContext ctx){
        this.context = ctx;     
    }

    public void destroy() throws Exception {
        birtEngine.destroy();
        Platform.shutdown() ;
    }

    public void setLogLevel(  java.util.logging.Level  ll){
        this.logLevel = ll ;
    }

    public void setLogDirectory( org.springframework.core.io.Resource resource ){
        File f=null;
        try {
            f = resource.getFile();
            validateLogDirectory(f);
            this._resolvedDirectory = f ;
        } catch (IOException e) {
            throw new RuntimeException("�couldn'�t set the log directory�");
        } 


    }

    private void validateLogDirectory (File f) {
        Assert.notNull ( f ,  " the directory must not be null");
        Assert.isTrue(f.isDirectory() , " the path given must be a directory");
        Assert.isTrue(f.exists() , "the path specified must exist!");   
    } 

    public void setLogDirectory ( File f ){
        validateLogDirectory(f) ;
        this._resolvedDirectory = f; 
    }

    @SuppressWarnings("unchecked")
    public IReportEngine getObject(){ 

        EngineConfig config = new EngineConfig();

        //This line injects the Spring Context into the BIRT Context
        config.getAppContext().put("spring", this.context );
        config.setLogConfig( null != this._resolvedDirectory ? this._resolvedDirectory.getAbsolutePath() : null  , this.logLevel);
        try {
            Platform.startup( config );
        }
        catch ( BirtException e ) {
            throw new RuntimeException ( "Could not start the Birt engine!", e) ;
        }

        IReportEngineFactory factory = (IReportEngineFactory) Platform.createFactoryObject( IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY );
        IReportEngine be = factory.createReportEngine( config );
        this.birtEngine = be ; 


        return be ;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public Class getObjectType() {
        return IReportEngine.class;
    }
}

这个类也实现了DisposableBean接口确保当应用程序关闭时报表引擎也能关闭,destroy方法将被调用。此类也实现了ApplicationContextAware接口来获取Spring ApplicationContext实例,存储ApplicationContext并传递到报表引擎。使用下面代码段

config.getAppContext().put("spring", this.context );

在报表里就可以使用下面的语句来获取ApplicationContext,进而获取bean实例。

spring = reportContext.getAppContext().get("spring");
var bookService = spring.getBean("bookService");

BirtConfiguration

@Configuration
public class BirtConfiguration {
    @Value("${birt.log.location}")
    String logLocation;

    //产生FactoryBean<IReportEngine>
    //在使用的依赖IReportEngine注入时候会调用到
    @Bean
    protected BirtEngineFactory engine(){ 
        BirtEngineFactory factory = new BirtEngineFactory() ;  
        //Enable BIRT Engine Logging
        factory.setLogLevel( Level.INFO);
        factory.setLogDirectory( new FileSystemResource(logLocation));
        return factory ; 
    }
}

BirtController

@RestController
public class ReportController {
    @Autowired
    BirtReportGenerator birtReportGenerator;

    Logger logger = LoggerFactory.getLogger(ReportController.class);

    @PostMapping("/report/book/{searchBy}/{seachValue}")
    public String searchCarReport(@PathVariable("searchBy") String searchBy,
            @PathVariable("seachValue") String searchValue) {
        ReportParameter rm = new ReportParameter("book", "PDF");
        rm.setParameter(searchBy, searchValue);
        try {
            ByteArrayOutputStream baos = birtReportGenerator.generate(rm);
            FileOutputStream fops = new FileOutputStream("c:/report/bookreport_" + System.currentTimeMillis() + ".pdf");
            fops.write(baos.toByteArray());
            fops.close();
            baos.close();
        } catch (Exception e) {
            logger.error("Error: " + e.getMessage());
        }
        return "success";
    }
}

BirtGenerator

@Component
public class BirtReportGenerator {
    @Autowired
    private IReportEngine birtEngine;

    public ByteArrayOutputStream generate(ReportParameter rptParam) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        IReportRunnable runnable = null;
        ClassPathResource cpr = new ClassPathResource("report/" + rptParam.getReportName() + ".rptdesign");
        runnable = birtEngine.openReportDesign(cpr.getInputStream());
        IRunAndRenderTask runAndRenderTask = birtEngine.createRunAndRenderTask(runnable);
        runAndRenderTask.setParameterValues(setParameters(runnable, rptParam.getParameter()));

        IRenderOption options = new RenderOption();
        if (rptParam.getFormat().equalsIgnoreCase("pdf")) {
            PDFRenderOption pdfOptions = new PDFRenderOption(options);
            pdfOptions.setOutputFormat("pdf");
            pdfOptions.setOption(IPDFRenderOption.PAGE_OVERFLOW, IPDFRenderOption.FIT_TO_PAGE_SIZE);
            pdfOptions.setOutputStream(baos);
            runAndRenderTask.setRenderOption(pdfOptions);
        }
        runAndRenderTask.getAppContext().put(EngineConstants.APPCONTEXT_CLASSLOADER_KEY,
                this.getClass().getClassLoader());
        runAndRenderTask.run();
        runAndRenderTask.close();
        return baos;
    }

    protected HashMap<String, Object> setParameters(IReportRunnable report, Map<String, Object> m) throws Exception {

        HashMap<String, Object> parms = new HashMap<String, Object>();

        IGetParameterDefinitionTask task = birtEngine.createGetParameterDefinitionTask(report);
        // 拿到birt里所有的parameter定义
        Collection<IParameterDefnBase> params = task.getParameterDefns(true);
        Iterator<IParameterDefnBase> iter = params.iterator();
        while (iter.hasNext()) {
            IParameterDefnBase param = (IParameterDefnBase) iter.next();
            Object val = m.get(param.getName());
            // 如果拿到birt的parameter有定义
            if (val != null) {
                parms.put(param.getName(), val);
            }
        }
        task.close();
        return parms;
    }
}

bean service

@Service
public class BookService {
    static List<Book> books;
    static{
        Book book1 = new Book();
        book1.setYear("2000");
        book1.setAuthor("谭浩强");
        book1.setName("C++程序设计");
        Book book2 = new Book();
        book2.setYear("2005");
        book2.setAuthor("[美] Bruce Eckel");
        book2.setName("JAVA编程思想");
        Book book3 = new Book();
        book3.setYear("2002");
        book3.setAuthor("严蔚敏");
        book3.setName("数据结构");
        books = Arrays.asList(  book1, book2, book3 ) ;
    }

    public List getAllBooks (){
        return this.books ;
    }

    public List getBooksByYear(String year){
        List list=books.stream().filter(book->
            book.getYear().equals(year)
        ).collect(Collectors.toList());
        return list;
    }
}

Report脚本开发

Birt脚本定义在Report Object, Report Elements, and Data Source(Sets)这三种对象上。不同阶段有不同的事件,如下图,

initialize脚本

在Birt里打开book.rptdesign,在Outline里找到Scripts,点book.rptdesign,然后在右边的窗口里切换到script,在左上方的script下来里找达到initialize,然后输入以下代码

spring = reportContext.getAppContext().get("spring");
var bookService = spring.getBean("bookService");

open脚本

在Birt里打开book.rptdesign,在Outline里找到dataset,然后在右边的窗口里切换到script,在左上的script下来里找到open,然后输入以下代码

var bookService = spring.getBean("bookService");
listdata = bookService.getBooksByYear(params["year"]);
count = 0;

fetch脚本

在Birt里打开book.rptdesign,在Outline里找到dataset,然后在右边的窗口里切换到script,在左上的script下来里找到fetch,然后输入以下代码

if(listdata.size() > count){
    book = listdata.get(count);
    row.year = book.getYear();
    row.name = book.getName();
    row.author = book.getAuthor();
    count++;
    return true;
}
return false;

运行报表

eclipse运行此报表程序,或者使用java -jar来运行此报表程序。打开postman,选择post请求,输入http://127.0.0.1:8080/report/book/year/2005,在c:/report下面将生成一个pdf文件。

 报表预览

报表预览需要在SpringBoot中引入Java Web环境,并拷贝一些资源文件和模板,配置相应的属性才能实现在web页面上浏览报表,详细可以参考文章

https://www.ibm.com/developerworks/library/ba-birt-viewer-java-webapps/index.html#list3

常见问题

  • 在开发过程中,在eclipse中经常修改后不能生效,应该是eclipse的问题,在eclipse中clean也不能生效,解决办法是在命令行中运行mvn clean package来生成包。

  • 要理解脚本的执行顺序,为了提高效率,可以使用变量来缓存一些数据,比如在报表的脚本中需要调用后端的服务来获取数据,最好的办法是获取一次并存到global variable中,后续的脚本可以直接从reportContext中取获取这个全局变量来提高报表的渲染效率。使用方式如下:

  • reportContext.setPersistentGlobalVariable("变量名", 变量值);
    reportContext.getPersistentGlobalVariable("变量名");
  • 如果需要调试报表脚本,可以引入日志功能,简单输出日志的方法如下:

  • importPackage(Packages.java.lang);
    System.out.println("你的日志信息");

    相关文章

    网友评论

        本文标题:SpringBoot中集成Birt

        本文链接:https://www.haomeiwen.com/subject/cmsemktx.html