Spring Framework 与 BIRT

工程 | Josh Long | 2012 年 1 月 30 日 | ...

作者:Jason Weathersby 和 Josh Long

引言

Eclipse 的商业智能与报表工具 (BIRT) 项目是一个基于流行 Eclipse IDE 的开源项目。BIRT 项目于 2005 年夏天发布了第一个主要版本,自启动以来已获得超过一千万次下载。该项目由 Actuate Corporation 启动,该公司将 BIRT 作为其许多商业产品的基础。BIRT 项目网站包括 BIRT 的介绍、教程、下载和使用示例。

开发人员使用 BIRT 在 Java/Java EE 环境中构建和部署报表。在 BIRT 3.7 中,提供了一个新的基于 POJO 的运行时环境,大大简化了 BIRT 引擎的部署。本文讨论了使用 Spring Framework 组件的几种 BIRT 集成场景。

图 1 – BIRT 拼贴画

BIRT 项目包括以下关键组件:

  • BIRT Designer - 用于设计报表的开发工具。
  • Web Viewer - 用于部署报表的示例 Java Web 应用。此查看器包含一个 JSP 标签库,便于与现有 Web 应用集成。
  • BIRT 引擎 - 用于构建、运行和渲染 BIRT 报表的设计引擎和报表引擎。
  • BIRT Charts - 支持构建和运行高度复杂的交互式图表的软件包。

Spring 框架是架构和实现方法流行的集合,可使企业级 Java 开发更加容易。该框架的核心部分是依赖注入和面向切面编程。利用这些特性可以减少应用程序基础设施特定部分与业务功能(这是其主要目标)之间的耦合。Spring 框架由 VMware 内的 SpringSource 业务部门(原 SpringSource 公司于 2009 年被 VMware 收购)以开源方式开发。它在 Java 开发社区中得到广泛使用,并可在所有平台上运行,包括 VMware 支持的 Tomcat 版本,称为 tcServer。

虽然 Spring Framework 提供了许多有用的功能,但关于与 BIRT 集成的大多数问题都围绕着 Spring MVC 和在报表内访问 Spring Bean。本文涵盖了三种场景:

  • 在 Spring MVC 中集成 BIRT 引擎
  • 从 BIRT Viewer 访问 Spring Bean
  • 使用 Spring Remoting 从 BIRT 报表访问 Spring Bean

本文假定读者具备 BIRT 和 Spring Framework 的编程知识。要了解有关 Spring 框架的更多信息,请查看“Green Beans”博客系列

每种场景都有一个与本文关联的示例,该示例是使用以下组件构建和测试的:

这些示例旨在进行说明,包含很少的错误检查。

示例代码

在整篇文章中,不同的集成场景都引用了一个通用的 Spring Bean,该 Bean 为 BIRT 报表提供数据。Spring Framework 包含许多复杂的数据访问组件,但这个简单的示例是学习更复杂场景的有用工具。在示例代码中,有两个包:org.eclipse.birt.spring.exampleorg.eclipse.birt.spring.core。示例包包含可以替换为您自己的数据访问对象的简单代码。核心包包含用于运行和渲染 BIRT 报表的 Spring View,一个参数转换器类用于将 URL 中输入的报表参数转换为相应的 BIRT 报表参数类型,以及一个 BIRT 引擎工厂,该工厂向其他 Bean 提供 Report Engine。示例类如下所示,并在本文中使用。核心类将在下一节中描述。

我们从一个简单的 POJO 开始,它将在我们的 BIRT 报表中使用。它命名为 Car,包含描述汽车的简单属性。

 
package org.eclipse.birt.spring.example;

public class Car{
	private String make;
	private String model;
	private String year;
	public String getMake() {
		return make;
	}
	public void setMake(String make) {
		this.make = make;
	}
	public String getModel() {
		return model;
	}
	public void setModel(String model) {
		this.model = model;
	}
	public String getYear() {
		return year;
	}
	public void setYear(String year) {
		this.year = year;
	}
	public Car() {

	}
	public String toString(){
		return "Make:--"+this.make+" Model:--"+this.model+" Year:--"+this.year;
	}
	public String getCarString(){
		return( this.toString() );
	}
}

示例提供了一个服务类,用于加载多个 Car POJO 以用于报表目的。

 
package org.eclipse.birt.spring.example;
import java.util.*;

public class CarServiceImpl implements CarService {

	public List getAllCars (){
		Car car1 = new Car();
		car1.setYear("2000");
		car1.setMake("Chevrolet");
		car1.setModel("Corvette");
		Car car2 = new Car();
		car2.setYear("2005");
		car2.setMake("Dodge");
		car2.setModel("Viper");
		Car car3 = new Car();
		car3.setYear("2002");
		car3.setMake("Ford");
		car3.setModel("Mustang GT");
		List cars = Arrays.asList(  car1, car2, car3 ) ;
		return cars ; 
		

	}
}

为了在 Spring Context 中使此服务可用,我们使用一个简单的类,该类使用 Spring 注解进行配置。

 
package org.eclipse.birt.spring.example;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class BirtDataServiceConfiguration {

	@Bean 
	public CarService carService(){ 
		return new CarServiceImpl(); 

	}
} 

从 Spring MVC 调用 BIRT 引擎

有许多方法可以将 BIRT 引擎集成到 Spring 中,特别是 Spring MVC。对于本例,我们实现了一个 Spring View,它使用参数来确定用户所需的报表和输出格式。BIRT 报表引擎被注入到 View 中,以执行报表的实际运行和渲染。

BIRT 引擎可以被多个线程使用,但启动通常很耗时。理想情况下,我们只希望在应用程序生命周期内启动 BIRT 引擎一次。我们还希望在应用程序关闭时正确地关闭引擎。考虑到这一点,我们首先创建了一个 BirtEngineFactory 类,它充当工厂 Bean,其他 Bean 可以使用它来返回 BIRT 报表引擎的实例。在 Spring 中,FactoryBean 接口是复杂对象的智能构造器。它有一个重要的契约:从其 getObject 方法返回一个随时可用的对象。

 
package org.eclipse.birt.spring.core;

import java.io.File;
import java.io.IOException;

import org.eclipse.birt.core.exception.BirtException;
import org.eclipse.birt.core.framework.Platform;
import org.eclipse.birt.report.engine.api.EngineConfig;
import org.eclipse.birt.report.engine.api.IReportEngine;
import org.eclipse.birt.report.engine.api.IReportEngineFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;


/**
Factory bean for the instance of the {@link IReportEngine report engine}.
 */
public class BirtEngineFactory implements FactoryBean, ApplicationContextAware, DisposableBean {  

	public boolean isSingleton(){ return true ; } 

	private ApplicationContext context ; 
	private IReportEngine birtEngine ;	
	private Resource logDirectory ;
	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 ( java.io.File f ){ 
		validateLogDirectory(f) ;
		this._resolvedDirectory = f; 
	}

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

	@Override
	public Class getObjectType() {
		return IReportEngine.class;
	}
}

该类还实现了 DisposableBean 接口,以便在应用程序关闭时可以正确关闭引擎。当 Spring ApplicationContext 关闭时,Spring 会调用所有实现 DisposableBean 的 Bean 上的 DisposableBean#destroy 方法。我们的实现调用了 destroy 方法。最后,该类实现了 ApplicationContextAware 接口,以接收 Spring ApplicationContext 的实例。我们存储 ApplicationContext,然后使用以下代码将其传递给 BIRT Report Engine:

 
		EngineConfig config = new EngineConfig();
	
		//This line injects the Spring Context into the BIRT Context
		config.getAppContext().put("spring", this.context );

此代码将允许从 BIRT Scripting 和 Expressions 内部访问 Spring Context 对象。

接下来,我们创建一个 Spring View,它运行并渲染报表。此 View 期望注入 BIRT 报表引擎。该 View 搜索请求中的 ReportName 和 ReportFormat 参数,以决定运行哪个报表以及需要什么格式。请求中还会搜索报表参数名称。如果找到,这些报表参数将设置为相应的值。此 View 的一部分如下所示。有关更多信息,请参阅示例代码。

 
/**
 * BirtView is used to run and render BIRT reports.
 * This class expects the request to contain a ReportName and ReportFormat
 * parameter. In addition Report parameters are automatically searched for in the
 * the request object.
 */
public class BirtView extends AbstractView {


	public static final String PARAM_ISNULL = "__isnull";
	public static final String UTF_8_ENCODE = "UTF-8"; 

	private IReportEngine birtEngine;
	private String reportNameRequestParameter = “ReportName” ; 
	private String reportFormatRequestParameter = “ReportFormat” ; 
	private IRenderOption renderOptions ; 

	public void setRenderOptions(IRenderOption ro) { 
		this.renderOptions = ro;
	} 
	
	public void setReportFormatRequestParameter( String rf ){ 
		Assert.hasText( rf , “the report format parameter must not be null”) ;
		this.reportFormatRequestParameter = rf ;
	}

	public void setReportNameRequestParameter ( String rn ) { 
		Assert.hasText( rn , “the reportNameRequestParameter must not be null”) ;
		this.reportNameRequestParameter = rn ; 
	}

	protected void renderMergedOutputModel(
			Map map, HttpServletRequest request,
			HttpServletResponse response) throws Exception {

		String reportName = request.getParameter( this.reportNameRequestParameter );
		String format = request.getParameter( this.reportFormatRequestParameter );
		ServletContext sc = request.getSession().getServletContext();
		if( format == null ){
			format="html";
		}
		IReportRunnable runnable = null;
		runnable = birtEngine.openReportDesign( sc.getRealPath("/Reports")+"/"+reportName );
		IRunAndRenderTask runAndRenderTask = birtEngine.createRunAndRenderTask(runnable);
		runAndRenderTask.setParameterValues(discoverAndSetParameters( runnable, request ));


		response.setContentType( birtEngine.getMIMEType( format ));
		IRenderOption options =  null == this.renderOptions ? new RenderOption() : this.renderOptions;		
		if( format.equalsIgnoreCase("html")){    
			HTMLRenderOption htmlOptions = new HTMLRenderOption( options);
			htmlOptions.setOutputFormat("html");
			htmlOptions.setOutputStream(response.getOutputStream());
			htmlOptions.setImageHandler(new HTMLServerImageHandler());
			htmlOptions.setBaseImageURL(request.getContextPath()+"/images");
			htmlOptions.setImageDirectory(sc.getRealPath("/images"));
			runAndRenderTask.setRenderOption(htmlOptions);

		}else if( format.equalsIgnoreCase("pdf") ){
			PDFRenderOption pdfOptions = new PDFRenderOption( options );
			pdfOptions.setOutputFormat("pdf");
			pdfOptions.setOption(IPDFRenderOption.PAGE_OVERFLOW, IPDFRenderOption.FIT_TO_PAGE_SIZE);
			pdfOptions.setOutputStream(response.getOutputStream());
			runAndRenderTask.setRenderOption(pdfOptions);
		}else{

			String att  ="download."+format;
			String uReportName = reportName.toUpperCase(); 
			if( uReportName.endsWith(".RPTDESIGN") ){ 
				att = uReportName.replace(".RPTDESIGN", "."+format);
			}	
			response.setHeader(	"Content-Disposition", "attachment; filename=\"" + att + "\"" );
			options.setOutputStream(response.getOutputStream());
			options.setOutputFormat(format);
			runAndRenderTask.setRenderOption(options);
		}
		runAndRenderTask.getAppContext().put( EngineConstants.APPCONTEXT_BIRT_VIEWER_HTTPSERVET_REQUEST, request );
		runAndRenderTask.run();	
		runAndRenderTask.close();		

	}
	public void setBirtEngine(IReportEngine birtEngine) {
		this.birtEngine = birtEngine;
	}
.
.

要设置 Spring MVC(并在我们的应用程序中使用此新 View),我们需要创建一个带有 @EnableWebMVC 注解的 Spring @Configuration 类。要覆盖 Spring MVC 机制的一部分,我们可以简单地扩展一个基类 - WebMvcConfigurerAdapter - 并钩入适当的回调方法。这个类是一个常规的 Spring @Configuration 类,就像我们之前配置服务时看到的那样。我们使用 @ComponentScan 注解来告诉 Spring 将来自我们两个包的带注解的 bean 注册到 Spring context。接下来,我们覆盖 addViewControllers 方法,告诉 Spring 将以“/reports”结尾的 URL 委托给新的 Birt Spring MVC View。birtView 和 BIRT 引擎被创建为 bean,并且设置了 BirtView bean 的 birtEngine 属性。

 
package org.eclipse.birt.spring.example;

import org.eclipse.birt.spring.core.BirtEngineFactory;
import org.eclipse.birt.spring.core.BirtView;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.BeanNameViewResolver;

@EnableWebMvc 
@ComponentScan( {"org.eclipse.birt.spring.core", "org.eclipse.birt.spring.example"})
@Configuration
public class BirtWebConfiguration  extends WebMvcConfigurerAdapter  {

	@Override
	public void addViewControllers(ViewControllerRegistry registry) {
		registry.addViewController("/reports").setViewName("birtView");

	}

	@Bean 
	public BirtView birtView(){ 
		BirtView bv = new BirtView(); 
		bv.setBirtEngine( this.engine().getObject() );
		return bv; 
	}


	@Bean public BeanNameViewResolver beanNameResolver(){ 
		BeanNameViewResolver br = new BeanNameViewResolver() ;
		return br; 
	} 

	@Bean
	protected BirtEngineFactory engine(){ 
		BirtEngineFactory factory = new BirtEngineFactory() ;  
		//Enable BIRT Engine Logging
		//factory.setLogLevel( Level.FINEST);
		//factory.setLogDirectory( new FileSystemResource("c:/temp"));

		return factory ; 
	}


}

如前所述,在描述 BIRT 引擎工厂类时,我们从 BIRT 上下文提供了对 Spring ApplicationContext 的引用。BIRT 上下文对象不过是一个对象的 Map,BIRT 引擎将其提供给下游进程,例如 BIRT 表达式、BIRT 事件处理程序等。BIRT 使用 Rhino JavaScript 引擎来处理 BIRT 表达式并评估 JavaScript 事件处理程序。BIRT 上下文与 Rhino 引擎对象一起预加载到脚本环境中。这个对象允许报表开发人员使用以下语法检索 Spring Bean 并在表达式或事件脚本中使用它:

 
var mypojo = spring.getBean("carService");
mypojo.getAllCars() ().get(0);

如果您不想将 Spring 上下文注入 BIRT 应用程序上下文,您总是可以使用类似于以下代码的 JavaScript 来访问 BIRT 表达式和 JavaScript 事件处理程序中的 Spring bean:

 
importPackage(Packages.org.springframework.context);
importPackage(Packages.org.springframework.web.context.support );
//ServletContext
var sc = reportContext.getHttpServletRequest().getSession().getServletContext();
//ApplicationContext 
var spring = WebApplicationContextUtils.getWebApplicationContext(sc);
var mypojo = spring.getBean("carService");
this.text = mypojo.getAllCars().get(0).getMake();

要运行此 Web 应用,请将从下载的 BIRT 3.7.1 运行时(birt-runtime-3_7_1\ReportEngine\lib 目录)中的 JAR 文件添加到 Web 应用的 WEB-INF/lib 目录。您还需要在 WEB-INF/lib 中添加从 Spring Framework 下载的以下 JAR 文件:

  • cglib-nodep-2.2.2.jar
  • org.springframework.aop-3.1.0.RELEASE.jar
  • org.springframework.asm-3.1.0.RELEASE.jar
  • org.springframework.beans-3.1.0.RELEASE.jar
  • org.springframework.context.support-3.1.0.RELEASE.jar
  • org.springframework.context-3.1.0.RELEASE.jar
  • org.springframework.core-3.1.0.RELEASE.jar
  • org.springframework.expression-3.1.0.RELEASE.jar
  • org.springframework.web.servlet-3.1.0.RELEASE.jar
  • org.springframework.web-3.1.0.RELEASE.jar

本文附带的示例具有以下 web.xml 文件:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
    "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app>

 <servlet>
   <servlet-name>springandbirt</servlet-name>
   <servlet-class>
     org.springframework.web.servlet.DispatcherServlet
   </servlet-class>
   <init-param>
     <param-name>contextClass</param-name>
     <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
   </init-param>   
   <init-param>
     <param-name>contextConfigLocation</param-name>
     <param-value>org.eclipse.birt.spring.example.BirtWebConfiguration</param-value>
   </init-param>
   <load-on-startup>1</load-on-startup>
 </servlet>

 <servlet-mapping>
   <servlet-name>springandbirt</servlet-name>
   <url-pattern>/reports</url-pattern>
 </servlet-mapping>

  <welcome-file-list>
    <welcome-file>
      index.jsp
    </welcome-file>
  </welcome-file-list>
</web-app>

所有 /reports URL 都路由到 Spring DispatcherServlet。index.jsp 中有两个链接,用于运行两个报表示例。一个报表使用 BIRT Sample Derby 数据库,另一个访问 Car Service bean,如以下代码所示:

<html>
<head>
</head>
<body>
<h1>BIRT Report</h1>
<p>
<a href="http://localhost:8080/springandbirt/reports?ReportName=TopNPercent.rptdesign">click here to run BIRT Report</a><br>
<a href="http://localhost:8080/springandbirt/reports?ReportName=SampleSpring.rptdesign">click here to run BIRT Report that calls a Spring Bean</a>
</p>
<%= new java.util.Date() %>
</body>
</html>
图 1 – 查看 BIRT Spring MVC 示例输出

从 BIRT Viewer 访问 Spring Bean

在许多情况下,BIRT 用户希望将 Spring IOC 容器添加到 BIRT Viewer 中。Viewer 是一个基于 AJAX 的 Java Web 应用,用于运行和渲染报表。Viewer 支持分页、目录和导出到 PDF 等其他格式。以这种方式部署时,开发人员需要访问位于 Spring 容器中的 bean 以进行报表处理。此场景实现起来非常简单。

从 BIRT 网站下载并解压 BIRT Runtime。BIRT Viewer 位于运行时下载的 WebViewerExample 目录中。要将 Viewer 部署到 Tomcat,用户只需将 WebViewerExample 目录复制到 tomcatinstall/webapps 目录即可。大多数用户会将此文件夹重命名为更合适的名称,例如 BirtViewer。BIRT 网站提供了将 Viewer 部署到其他应用服务器的更多详细信息。安装 Viewer 后,可以将 Spring ContextLoaderListener 添加到 Viewer 的 web.xml 中。为此,请将以下监听器条目添加到 web.xml 的上下文参数下方:

<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

您还可以指定一个上下文参数来指向 Spring Context 类,如以下代码所示:

   <context-param>
     <param-name>contextClass</param-name>
     <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
   </context-param>   
   <context-param>
     <param-name>contextConfigLocation</param-name>
     <param-value>org.eclipse.birt.spring.webviewer.example.BirtDataServiceConfiguration</param-value>
   </context-param>

在此场景中,我们不需要 BirtWebConfiguration 类,因此我们将 contextConfigLocation 指向 BirtDataServiceConfiguration 类。

本文第一节中描述的相同 POJO 类在此处使用(只是包名不同),并且需要位于 WEB-INF/classes 目录中,或者该包需要位于 JAR 文件中并放置在 Viewer 的 WEB-INF/lib 目录中。最后,将 Spring Framework 下载中的以下 JAR 文件添加到 WebViewer 的 WEB-INF/lib 目录:

  • cglib-nodep-2.2.2.jar
  • org.springframework.aop-3.1.0.RELEASE.jar
  • org.springframework.asm-3.1.0.RELEASE.jar
  • org.springframework.beans-3.1.0.RELEASE.jar
  • org.springframework.context.support-3.1.0.RELEASE.jar
  • org.springframework.context-3.1.0.RELEASE.jar
  • org.springframework.core-3.1.0.RELEASE.jar
  • org.springframework.expression-3.1.0.RELEASE.jar
  • org.springframework.web.servlet-3.1.0.RELEASE.jar
  • org.springframework.web-3.1.0.RELEASE.jar

要在 BIRT 表达式或 BIRT JavaScript 事件处理程序中访问 carService bean,请使用以下语法:

 
//BIRT label report item onCreate Script
importPackage(Packages.org.springframework.context);
importPackage(Packages.org.springframework.web.context.support );
var sc = reportContext.getHttpServletRequest().getSession().getServletContext();
//ApplicationContext 
spring = WebApplicationContextUtils.getWebApplicationContext(sc);
var mypojo = spring.getBean("carService");
this.Text = mypojo.getAllCars().get(0).getMake();
图 2 - 构建 BIRT 表达式

在设计器中预览报表时,BIRT 表达式或脚本会失败。为防止失败,请将表达式或脚本包装在“if”语句中,如以下代码所示:

 
if( !reportContext.getHttpServletRequest().getAttribute("attributeBean").isDesigner()){
//Access Bean 
} 
图 3 – 在 BIRT Viewer 中显示 Spring bean 输出

使用 Remoting 从 BIRT 报表访问 Bean

本文前两节侧重于将 BIRT 引擎和 Viewer 与 Spring 框架集成。虽然这些方法效果很好,但假设您需要访问位于单独上下文或另一台机器上的 Spring bean。可以使用 Spring Remoting 来实现此功能。

虽然任何 Spring Remoting 技术或其他机制,如 SOAP 或 REST 都可以使用,但本节描述了使用 Spring 基于 HTTP Invoker 的服务导出器。为了实现本示例,我们首先构建一个示例 Web 应用,其中包含一个 Car Service bean,该 bean 向远程调用客户端提供 Car POJO。

对于远程调用,我们首先定义 Car POJO,如以下代码所示:

 
package org.eclipse.birt.spring.remoting.example;

public class Car implements ICar{

	private static final long serialVersionUID = 1L;
	private String make;
	private String model;
	private String year;
	public String getMake() {
		return make;
	}
	public void setMake(String make) {
		this.make = make;
	}
	public String getModel() {
		return model;
	}
	public void setModel(String model) {
		this.model = model;
	}
	public String getYear() {
		return year;
	}
	public void setYear(String year) {
		this.year = year;
	}
	public Car() {
		this.year = "2000";
		this.make = "Chevrolet";
		this.model = "Corvette";
	}
	public String toString(){
		return "Make:--"+this.make+" Model:--"+this.model+" Year:--"+this.year;
	}
}

接下来,我们实现 Car Service。

 
package org.eclipse.birt.spring.remoting.example;
import java.util.Arrays;
import java.util.List;

import org.eclipse.birt.spring.remoting.example.Car;
import org.eclipse.birt.spring.remoting.example.CarService;
public class CarServiceImpl implements CarService{

public List getAllCars(){
	Car car1 = new Car();
	car1.setYear("2000");
	car1.setMake("Chevrolet");
	car1.setModel("Corvette");
	Car car2 = new Car();
	car2.setYear("2005");
	car2.setMake("Dodge");
	car2.setModel("Viper");
	Car car3 = new Car();
	car3.setYear("2002");
	car3.setMake("Ford");
	car3.setModel("Mustang GT");
	List cars = Arrays.asList(  car1, car2, car3 ) ;
	return cars ; 

	
}
}

最后,我们实现 BirtDataServiceConfiguration 文件来处理 Spring 上下文的 Java 配置。

 
package org.eclipse.birt.spring.remoting.example;

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

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;


@Configuration
public class BirtDataServiceConfiguration {

	@Bean 
	public CarService carService(){ 
		 
		return  new CarServiceImpl();
	}

	@Bean 
	public HttpInvokerServiceExporter myServiceExporter(){ 
		HttpInvokerServiceExporter hse = new HttpInvokerServiceExporter();
		hse.setService( this.carService()) ;
		hse.setServiceInterface( CarService.class); 
		return hse; 
	}

	@Bean
	public SimpleUrlHandlerMapping myUrlMapping(){

		SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();         
		Map urlMap = new HashMap();         
		urlMap.put("/carService", myServiceExporter());                  
		mapping.setUrlMap(urlMap);         
		mapping.setAlwaysUseFullPath(true);         
		return mapping; 		
	}


}

这个类只是将 /carService URL 映射到 HttpInvokerServiceExporter 对象,该对象将 carService bean 暴露给远程调用客户端。

接下来,我们可以为这个应用程序创建一个使用 Spring DispatcherServlet 的 web.xml 文件,如以下代码所示:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
    "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app>

 <servletgt;
   <servlet-name>springandbirt</servlet-name>
   <servlet-class>
     org.springframework.web.servlet.DispatcherServlet
   </servlet-class>
   <init-param>
     <param-namegt;contextClass</param-name>
     <param-valuegt;org.springframework.web.context.support.AnnotationConfigWebApplicationContextlt;/param-value>
   </init-param>   
   <init-param>
     <param-name>contextConfigLocation</param-name>
     <param-value>org.eclipse.birt.spring.remoting.example.BirtDataServiceConfiguration</param-value>
   </init-param>
   <load-on-startup>1</load-on-startup>
 </servlet>

 <servlet-mapping>
   <servlet-name>springandbirt</servlet-name>
   <url-pattern>/carService</url-pattern>
 </servlet-mapping>

  <welcome-file-list>
    <welcome-file>
      index.jsp
    </welcome-file>
  </welcome-file-list>

</web-app>

该 Web 应用程序需要在 WEB-INF/lib 目录中包含 Spring Framework 的以下 JAR 文件:

  • cglib-nodep-2.2.2.jar
  • org.springframework.aop-3.1.0.RELEASE.jar
  • org.springframework.asm-3.1.0.RELEASE.jar
  • org.springframework.beans-3.1.0.RELEASE.jar
  • org.springframework.context.support-3.1.0.RELEASE.jar
  • org.springframework.context-3.1.0.RELEASE.jar
  • org.springframework.core-3.1.0.RELEASE.jar
  • org.springframework.expression-3.1.0.RELEASE.jar
  • org.springframework.web.servlet-3.1.0.RELEASE.jar
  • org.springframework.web-3.1.0.RELEASE.jar
  • aopalliance.jar (Spring 依赖)

现在可以构建和部署该应用程序了。

接下来,我们需要构建一个远程调用客户端来访问之前构建的应用程序。在此示例中,它只是一个 JAR 文件,我们可以将其包含在 BIRT Viewer 中,以便在单独的上下文访问 carService。

客户端 JAR 文件应该包含 CarService 接口和 CarPojo/ICarPojo 类/接口。除了这三个类之外,我们还需要一个配置类来处理 Spring Context 的 Java 配置。

此类使用 AnnotationConfigApplicationContext 类指定一个 Java 类来处理 Spring Context 的配置。ContextConfig 类如下所示,并使用 HttpInvokerProxyFactoryBean 连接到服务器并检索 car service bean。

 
package org.eclipse.birt.spring.remoting.client.example;

import org.eclipse.birt.spring.remoting.example.CarService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean;

@Configuration
public class ContextConfig {

	@Bean
	public HttpInvokerProxyFactoryBean client() {     
		HttpInvokerProxyFactoryBean proxy = new HttpInvokerProxyFactoryBean();     
		proxy.setServiceInterface(CarService.class);     
		proxy.setServiceUrl("http://localhost:8080/springandbirtremote/carService");
		return proxy;
	} 

}

这个配置文件定义了一个 bean,它充当 HTTP invoker 代理。服务 URL 定义了我们之前定义的远程调用服务器的位置,服务接口定义了返回的对象。要构建客户端 JAR 文件,将为服务器添加的相同 JAR 文件添加到 classpath。

要从 BIRT 查看器调用 car service,请在 BIRT Expression Builder 或 JavaScript 事件处理程序中使用以下 JavaScript 代码片段,如以下代码所示:

 
importPackage(Packages.org.springframework.context);
importPackage(Packages.org.springframework.web.context.support );
var sc = reportContext.getHttpServletRequest().getSession().getServletContext();
//ApplicationContext 
var spring = WebApplicationContextUtils.getWebApplicationContext(sc);
var mypojo = spring.getBean("client");
mypojo.getAllCars().get(0).getMake();

将构建远程调用服务器一节中描述的所有 JAR 文件添加到 BIRT Viewer 的 WEB-INF/lib 目录。此外,将远程调用客户端 JAR 文件添加到 BIRT Viewer 的 WEB-INF/lib 目录。最后,将以下内容添加到已部署 Viewer 的 web.xml 中。这些设置在本文“从 BIRT Viewer 访问 Spring Bean”一节中进行了讨论。请注意,contextConfigLocation 已更改为 ContextConfig 类。

<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

   <context-param>
     <param-name>contextClass</param-name>
     <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
   </context-param>   
   <context-param>
     <param-name>contextConfigLocation</param-name>
     <param-value>org.eclipse.birt.spring.webviewer.example.ContextConfig</param-value>
   </context-param>

这种方法允许已部署的 BIRT Viewer 访问单独的机器/上下文来访问远程 bean。最后,如果您使用中间 Java 类来访问远程 Bean,请使用以下代码。

 
.
.
private final CarService carPojoService;

	public CarPojoClient(){
		final ApplicationContext context = new AnnotationConfigApplicationContext( ContextConfig.class);      
		this.carPojoService = (CarService) context.getBean("client");   
.
.

总结

Spring 和 BIRT 框架在 Java 社区中非常流行。许多 BIRT 技术的用户也在其企业应用程序代码中使用 Spring 功能来处理数据源、报表呈现和访问控制等功能。这些只是将 Spring Framework 与 BIRT 结合使用的一些好处。本文介绍了如何集成 BIRT 和 Spring。虽然还存在其他场景,但本文可以作为入门工具,帮助您同时使用这两种技术。

附件

希望利用本文所述集成类的开发人员可以使用下面的 SpringandBirtCoreJar.zip 下载。对于示例,请参阅 SpringBirtArticleSamples.zip 下载。

SpringandBirtCoreJar.zip SpringBirtArticleSamples.zip

获取 Spring 时事通讯

通过 Spring 时事通讯保持联系

订阅

领先一步

VMware 提供培训和认证,为您的进步加速。

了解更多

获取支持

Tanzu Spring 通过一个简单的订阅提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部