java

位置:IT落伍者 >> java >> 浏览文章

Groovy使Spring更出色,第1 部分: 集成的基础知识


发布日期:2020年09月15日
 
Groovy使Spring更出色,第1 部分: 集成的基础知识

Spring Framework 为 Web 和企业应用程序提供了坚实的基础通过支持 Groovy 等动态语言Spring 添加了一些功能从而使应用程序架构更加灵活更具动态性在包含 部分的系列文章 的第一部分中您将学习将 Groovy 集成到 Spring 应用程序的基础知识

Spring 支持将动态语言集成到基于 Spring 的应用程序中Spring 开箱即用地支持 GroovyJRuby 和 BeanShell以 GroovyJRuby 或任何受支持的语言(当然包括 Java? 语言)编写的应用程序部分可以无缝地集成到 Spring 应用程序中应用程序其他部分的代码不需要知道或关心单个 Spring bean 的实现语言

Spring 支持动态语言意味着应用程序可以获得灵活性和动态性并且没有任何附加条件在本系列的第 部分中您将看到如何将 Spring 和 Groovy 一起使用以及这个强大集成如何为应用程序增加有趣的功能例如您可能需要频繁地更改小块的业务逻辑应用程序发出的 email 消息中包含的文本应用程序生成的 PDF 格式和布局等为了进行更改传统的应用程序架构可能需要完全重新部署应用程序Spring 支持 Groovy 之后您可以这样更改一个已部署的应用程序并使这些更改立即生效我将讨论这一功能为应用程序所带来的好处以及可能引发的问题本文中所有例子的完整的源代码(参见 下载)都可以下载

Spring 的动态语言支持

动态语言支持将 Spring 从一个以 Java 为中心的应用程序框架改变成一个以 JVM 为中心的应用程序框架现在Spring 不再只是让 Java 开发变得更容易它还允许将以静态和动态语言编写的代码轻松地插入到 Spring 支持的分层架构方法中从而使 JVM 的开发也变得更加容易如果您已经熟悉 Spring那么您会感到很舒服可以利用 Spring 已经提供的所有特性 — 控制反转(IoC)和依赖项注入面向方面编程(AOP)声明式事务划分Web 和数据访问框架集成远程调用等 — 同时又可以使用灵活动态的语言比如 Groovy

Spring 通过 ScriptFactory 和 ScriptSource 接口支持动态语言集成ScriptFactory 接口定义用于创建和配置脚本 Spring bean 的机制理论上所有在 JVM 上运行语言都受支持因此可以选择特定的语言来创建自己的实现ScriptSource 定义 Spring 如何访问实际的脚本源代码例如通过文件系统或 URLGroovy 语言集成通过 ScriptFactory 的 GroovyScriptFactory 实现得到支持

为什么是 Groovy?

根据官方的 Groovy 站点Groovy 是 用于 Java 虚拟机的一种敏捷的动态语言以 Java 的强大功能为基础同时又包含由 PythonRuby 和 Smalltalk 等语言带来的强大附加功能例如动态类型转换闭包和元编程(metaprogramming)支持(参见 参考资料)它是一种成熟的面向对象编程语言既可以用于面向对象编程又可以用作纯粹的脚本语言我喜欢将它看作是没有讨厌代码但又具有闭包和动态语言中的其他特性的 Java 语言

Groovy 特别适合与 Spring 的动态语言支持一起使用因为它是专门为 JVM 设计的设计时充分考虑了 Java 集成这使 Groovy 与 Java 代码的互操作很容易它的类 Java 语法对于 Java 开发人员来说也很自然

接下来看看如何将 Groovy 代码集成到基于 Spring 的应用程序中

更巧妙的 Spring bean

在 Spring 应用程序中使用 Groovy bean 很容易就像使用 Java bean 一样(但是在后面可以看到对于如何配置它们则有很多选项)首先需要定义一个接口作为 Groovy bean 必须遵从的约定虽然不是非得定义接口不可但是大多数 Spring 应用程序会通过接口(而不是具体实现类)来定义应用程序组件之间的交互和依赖项以促进松散耦合并为测试提供便利

例如假设有一个定义如何从 Invoice 对象生成 PDF 的接口如清单 所示

清单 PdfGenerator 接口

public interface PdfGenerator {

byte[] pdfFor(Invoice invoice);

}

PdfGenerator 接口被用作 Groovy 实现类必须遵从的约定这很容易因为 Groovy 类可以像 Java 类那样实现接口清单 显示了 PdfGenerator 的 Groovy 实现它使用 iText 库(参见 参考资料)完成实际的 PDF 生成它返回一个包含 PDF 内容的字节数组

清单 GroovyPdfGenerator

class GroovyPdfGenerator implements PdfGenerator {

String companyName

public byte[] pdfFor(Invoice invoice) {

Document document = new Document(PageSizeLETTER)

ByteArrayOutputStream output = new ByteArrayOutputStream()

PdfWritergetInstance(document output)

documentopen()

Font headerFont = new Font(family: FontHELVETICA size: style: FontITALIC)

documentadd(new Paragraph($companyName headerFont))

documentadd(new Paragraph(Invoice $invoiceorderNumber))

documentadd(new Paragraph(Total amount: \$ ${invoicetotal}))

documentclose()

outputtoByteArray()

}

}

GroovyPdfGenerator 已准备就绪它定义了一个名为 companyName 的 string 属性该属性在生成的 PDF 发票上与订单号和总额一起使用此时可以将 GroovyPdfGenerator 集成到 Spring 应用程序中使用 Java 语言编写的 bean 必须编译成 class 文件但是在使用基于 Groovy 的 bean 时则有几种选择

将 Groovy 类编译成普通的 Java 类文件

在一个 groovy 文件中定义 Groovy 类或脚本

在 Spring 配置文件中以内联方式编写 Groovy 脚本

可以选择不同的方法在 Spring 应用程序上下文中定义和配置 Groovy bean这取决于 Groovy bean 采用的选项接下来我们将探讨每一种配置选项

Groovy bean 配置

通常可以使用 XML 配置用 Java 代码编写的 Spring bean或者 — 从 Spring (参见 参考资料)开始 — 使用注释进行配置后者可以显着减少 XML 配置当配置 Groovy bean 时可用的选项取决于是使用编译的 Groovy 类还是 groovy 文件中定义的 Groovy 类需要记住的是您可以使用 Groovy 实现 bean然后可以像 Java 编程那样编译它们或者在 groovy 文件中以类似脚本的形式实现它们然后由 Spring 负责在创建应用程序上下文时编译它们

如果选择在 groovy 文件中实现 bean那么您不必 自己编译它们相反Spring 读取文件获得脚本源代码并在运行时编译它们使它们可用于应用程序上下文这比直接编译更灵活性因为不一定必须将 groovy 文件部署在应用程序的 JAR 或 WAR 文件中它们还可以来自文件系统的某个地方或 URL

接下来介绍各种不同的配置选项的应用要记住在构建过程中自己编译的 Groovy 类中定义的 bean 与在 groovy 脚本中定义的 bean 之间的区别

配置编译的 Groovy 类

配置已经编译成 class 文件的 Groovy bean这与配置基于 Java 的 bean 完全一样假设您已经使用 groovyc 编译器编译了 GroovyPdfGenerator那么可以使用常规的 Spring XML 配置定义 bean如清单 所示

清单 使用 XML 配置预编译的 GroovyPdfGenerator

<bean id=pdfGenerator class=groovierspringGroovyPdfGenerator>

<property name=companyName value=Groovy Bookstore/>

</bean>

清单 中的配置是一个简单的旧的 Spring bean 定义它是用 Groovy 实现的但这一点不重要在包含 pdfGenerator bean 的 Spring 应用程序中任何其他组件都可以使用它而不必知道或关心它的实现细节或语言还可以像往常一样使用 <property> 元素在 bean 上设置属性(Spring 引入了 p 名称空间以便更简练地定义属性但是我坚持使用 <property> 元素因为我发现它们可读性更好 — 这完全是个人的喜好)

另外如果使用 Spring 或更高版本还可以使用基于注释的 GroovyPdfGenerator 的配置在此情况下不必在 XML 应用程序上下文中实际定义 bean相反可以用 @Component 构造型注释来注释类如清单 所示

清单 用 @Component 注释 GroovyPdfGenerator

@Component(pdfGenerator)

class GroovyPdfGenerator implements PdfGenerator {

}

然后在 Spring 应用程序上下文 XML 配置中启用注释配置和组件扫描如清单 所示

清单 启用 Spring 注释配置和组件扫描

<context:annotationconfig/>

<context:componentscan basepackage=groovierspring/>

不管使用 XML 还是注释来配置编译后的 Groovy bean这种配置与普通的基于 Java bean 的配置是一样的

配置来自 Groovy 脚本的 bean

配置来自 groovy 脚本的 Groovy bean 与配置编译后的 Groovy bean 大不相同在这里事情开始变得更加有趣将 Groovy 脚本转换为 bean 的机制包括读取并编译 Groovy 脚本然后使之可以在 Spring 应用程序上下文中作为 bean 使用第一步是定义一个 bean它的类型可以认为是 GroovyScriptFactory并且指向 Groovy 脚本的位置如清单 所示

清单 定义 GroovyScriptFactory bean

<bean id=pdfGenerator

class=orgspringframeworkscriptinggroovyGroovyScriptFactory>

<constructorarg value=classpath:groovierspring/GroovyPdfGeneratorgroovy/>

<property name=companyName value=Groovier Bookstore/>

</bean>

在这个清单中pdfGenerator bean 被定义为 GroovyScriptFactory<constructorarg> 元素定义要配置的 Groovy 脚本的位置特别要注意这指向一个 Groovy 脚本而不是一个已编译的 Groovy 类可以使用定义 Spring bean 的语法设置用脚本编写的对象的属性正如您预期的那样清单 中的 <property> 元素设置 companyName 属性

GroovyPdfGeneratorgroovy 脚本 必须包含至少一个实现接口的类通常最好的做法是遵从标准 Java 实现每个 groovy 文件定义一个 Groovy 类但是您可能想在脚本中实现用于确定创建哪种类型的 bean 的逻辑例如可以在 GroovyPdfGeneratorgroovy 中定义 PdfGenerator 接口的两种不同的实现并直接在脚本中执行确定应该返回哪种实现的逻辑清单 定义两种不同的 PdfGenerator 实现并根据系统的属性选择使用一种实现

清单 Groovy 脚本中的多个类定义

class SimpleGroovyPdfGenerator implements PdfGenerator {

}

class ComplexGroovyPdfGenerator implements PdfGenerator {

}

def type = Systemproperties[generatorType]

if (type == simple)

return new SimpleGroovyPdfGenerator()

}

else {

return new ComplexGroovyPdfGenerator()

}

如这段代码所示可以通过用脚本编写的 bean 根据系统属性选择不同的实现当 generatorType 系统属性为 simple 时该脚本创建并返回一个 SimpleGroovyPdfGenerator否则它返回一个 ComplexGroovyPdfGenerator由于简单和复杂的实现都实现了 PdfGenerator 接口因此 Spring 应用程序中使用 pdfGenerator bean 的代码不必知道也不必关心实际的实现是什么

注意仍然可以像 清单 那样在从脚本返回的 bean 上设置属性所以如果脚本返回一个 ComplexGroovyPdfGenerator则设置该 bean 上的 companyName 属性如果不需要定义多个实现那么可以在 Groovy 脚本文件中仅定义一个类如清单 所示在这种情况下Spring 发现并实例化这个惟一的类

清单 典型的 Groovy 脚本实现

class GroovyPdfGenerator implements PdfGenerator {

}

至此您可能想知道为什么 清单 将 bean 定义为一个 GroovyScriptFactory那是因为 Spring 通过一个与 ScriptFactoryPostProcessor bean 结合的 ScriptFactory 实现(在这里是一个 Groovy 工厂)创建脚本对象ScriptFactoryPostProcessor bean 负责用由工厂创建的实际对象替换工厂 bean清单 显示添加后处理器 bean 的附加配置

清单 定义 ScriptFactoryPostProcessor bean

<bean class=orgspringframeworkscriptingsupportScriptFactoryPostProcessor/>

当 Spring 装载应用程序上下文时它首先创建工厂 bean(例如 GroovyScriptFactory bean)然后执行 ScriptFactoryPostProcessor bean用实际的脚本对象替换所有的工厂 bean例如清单 和 清单 中的配置产生一个名为 pdfGenerator 的 bean它的类型是 groovierspringGroovyPdfGenerator(如果启用 Spring 中的 debug 级日志记录并观察应用程序上下文的启动将会看到 Spring 首先创建一个名为 scriptFactorypdfGenerator 的工厂 bean然后 ScriptFactoryPostProcessor 从该工厂 bean 创建 pdfGenerator bean)

现在您已知道使用 GroovyScriptFactory 和 ScriptFactoryPostProcessor 配置脚本编写的 Groovy bean 的底层细节接下来我将展示一种更简单更整洁的方法这种方法可以得到相同结果Spring 专门为创建脚本 bean 提供了 lang XML 模式清单 使用 lang 模式定义 pdfGenerator bean

清单 使用 <langgroovy> 定义脚本 bean

<lang:groovy id=pdfGenerator

scriptsource=classpath:groovierspring/GroovyPdfGeneratorgroovy>

<lang:property name=companyName value=Really Groovy Bookstore/>

</lang:groovy>

这段代码产生的 pdfGenerator bean 与 清单 和 清单 中更冗长的配置产生的 bean 是一样的但是它更整洁更简练而且意图更清晰<langgroovy> bean 定义需要 scriptsource 属性这告诉 Spring 如何找到 Groovy 脚本源代码此外可以使用 <langproperty> 元素为脚本 bean 设置属性使用 <langgroovy> 定义基于 Groovy 的 bean 是一种更好的选择对阅读 Spring 配置的人而言这种选项也更加清晰

配置内联 Groovy 脚本

为了实现完整性我将介绍Spring 还支持直接在 bean 定义中编写 Groovy 脚本清单 使用一个内联脚本创建 pdfGenerator

清单 内联定义脚本 bean

<lang:groovy id=pdfGenerator>

<lang:inlinescript>

<![CDATA[

class GroovyPdfGenerator implements PdfGenerator {

}

]]>

</lang:inlinescript>

<lang:property name=companyName value=Icky Groovy Bookstore/>

</lang:groovy>

这段代码使用 <langgroovy> 和 <langinlinescript> 标记定义 pdfGenerator bean它包含定义类的 Groovy 脚本可以像前面一样使用 <langproperty> 设置属性您可能已经猜到我不建议在 XML 配置文件中定义脚本 bean(或这一方面的任何类型的代码)

使用 Grails Bean Builder 配置 bean

Grails Web framework 在幕后依赖于 SpringGrails 提供了 Bean Builder这是一个很棒的特性让您可以使用 Groovy 代码编程式地 定义 Spring bean(参见 参考资料)编程式地定义 bean 比 XML 配置更灵活因为可以在 bean 定义脚本中嵌入逻辑而这在 XML 中是不可能的通过使用 Bean Builder可以为已编译 Groovy 类和用脚本编写的 Groovy bean 创建 bean 定义清单 使用已编译的 Groovy 类定义 pdfGenerator bean

清单 使用 Bean Builder 定义已编译的 Groovy bean

def builder = new grailsspringBeanBuilder()

builderbeans {

pdfGenerator(GroovyPdfGenerator) {

companyName = Compiled BeanBuilder Bookstore

}

}

def appContext = buildercreateApplicationContext()

def generator = contextpdfGenerator

清单 中的代码首先实例化一个 BeanBuilder然后通过方法调用创建 bean每个方法调用和可选的闭包参数定义一个 bean并设置 bean 属性例如pdfGenerator(GroovyPdfGenerator) 定义一个名为 pdfGenerator 的 bean其类型为 GroovyPdfGenerator闭包中的代码则设置 companyName 属性当然在 beans 闭包中可以定义多个 bean

通过使用 Bean Builder还可以从 Groovy 脚本而不是已编译的 Groovy 类创建 bean但是Bean Builder 没有 <langgroovy> 配置中的语法糖(syntactic sugar即在计算机语言中添加的某种语法这种语法对语言的功能并没有影响但是更方便程序员使用)所以需要将 bean 定义为 GroovyScriptFactory并创建一个 ScriptFactoryPostProcessor bean清单 是一个例子展示如何使用 Bean Builder 配置用脚本编写的 Groovy bean

清单 使用 Bean Builder 定义用脚本编写的 Groovy bean

def builder = new grailsspringBeanBuilder()

builderbeans {

pdfGenerator(GroovyScriptFactory

classpath:groovierspring/GroovyPdfGeneratorgroovy) {

companyName = Scripted BeanBuilder Bookstore

}

scriptFactoryPostProcessor(ScriptFactoryPostProcessor)

}

def appContext = buildercreateApplicationContext()

def generator = contextpdfGenerator

清单 中的代码在逻辑上等同于 清单 和 清单 中的 XML 配置当然清单 是使用 Groovy 代码来定义 bean为了定义 pdfGenerator bean清单 将类型指定为 GroovyScriptFactory第二个参数指定脚本源代码的位置和前面一样在闭包中设置 companyName 属性它还定义一个名为 scriptFactoryPostProcessor 的 bean其类型为 ScriptFactoryPostProcessor它将用实际的用脚本编写的对象替换工厂 bean

哪种配置选项最好?

至此您已经看到配置基于 Groovy 的 bean(无论是已编译的还是用脚本编写的)的几种不同的方式如果您仅是使用 Groovy 替代 Java 作为应用程序的主要语言那么配置这些 bean 与配置基于 Java 的 bean 没有区别对于已编译的 Groovy 类可以使用 XML 或基于注释的配置进行配置

对于用脚本编写的 Groovy 对象虽然可以用几种不同的方式来配置它们但是 <langgroovy> 选项却是最简洁的方式与使用 GroovyScriptFactory 和 ScriptFactoryPostProcessor 或者使用 <langinlinescript> 进行配置相比这种选项能够最清晰地表现意图

您还看到了 Grails Bean Builder它以完全不同的方式创建大多数 Spring 应用程序使用的 Spring 应用程序上下文如果要用 Groovy 创建所有的 bean并且要能够添加逻辑到 bean 构建过程中Bean Builder 必须很好地符合要求另一方面使用 Bean Builder 定义 Groovy bean 时需要使用 GroovyScriptFactory 和 ScriptFactoryPostProcessor 来定义 bean

使用 Groovy bean

bean 配置和可用的几个选项是集成 Groovy 和 Spring 的难点(但是如您所见这并不是很难)实际上在 Spring 应用程序中使用 Groovy bean 很容易Spring 的动态语言支持使得 bean 的使用对于应用程序代码是完全透明的应用程序代码不需要知道也不需要关心实现细节您可以像平常开发 Spring 应用程序一样编写应用程序代码并且可以利用 Spring 提供的所有特性例如依赖项注入AOP 和与第三方框架集成

清单 展示了一个简单的 Groovy 脚本它从 XML 配置文件创建一个 Spring 应用程序上下文获取 PDF 生成器 bean并使用它生成一个发票的 PDF 版本

清单 在脚本中使用 Groovy bean

def context = new ClassPathXmlApplicationContext(applicationContextxml)

def generator = contextgetBean(pdfGenerator)

Invoice invoice = new Invoice(orderNumber: orderDate: new Date())

invoicelineItems = [

new LineItem(quantity: description: Groovy in Action (ebook) price: )

new LineItem(quantity: description: Programming Erlang price: )

new LineItem(quantity: description: iText in Action (ebook) price: )

]

byte[] invoicePdf = generatorpdfFor(invoice)

FileOutputStream file = new FileOutputStream(Invoice${invoiceorderNumber}pdf)

filewithStream {

filewrite(invoicePdf)

}

println Generated invoice $invoiceorderNumber

在 清单 大部分代码用于创建 Spring ApplicationContext创建发票并将它写出到一个文件使用 pdfGenerator bean 生成发票仅需一行代码在通常的 Spring 应用程序中在应用程序启动时引导一次应用程序上下文然后应用程序中的组件只需使用 Spring 为它们提供的依赖项在 Spring Web 应用程序中可以配置一个 servlet 上下文侦听器在应用程序启动时引导 Spring例如可以定义一个 PDF 发票生成服务如清单 所示

清单 使用 PDF 生成器的服务类

@Service

public class InvoicePdfServiceImpl implements InvoicePdfService {

@Autowired

private PdfGenerator pdfGenerator;

public byte[] generatePdf(Long invoiceId) {

Invoice invoice = getInvoiceSomehow(invoiceId);

return pdfGeneratorpdfFor(invoice);

}

// Rest of implementation

}

清单 中的 InvoicePdfServiceImpl 类刚好被实现为一个 Java 类它依赖于 PdfGenerator可以很方便地将它实现为 Groovy bean可以通过任何以编译的或用脚本编写的 bean 配置来使用 GroovyPdfGenerator 实现而 InvoicePdfServiceImpl 对此一无所知因此使用 Groovy(或任何动态语言)对应用程序代码而言是透明的这样很好因为实现了组件之间的松散耦合从而使单元测试更加容易并且可以使用最适合的实现语言

结束语

您已经看到了配置 Groovy 语言 bean 的一些不同的方式以及在基于 Spring 的应用程序中使用它们是多么容易您可以像使用 Java 类一样使用已编译的 Groovy 类您还看到了配置用脚本编写的 Groovy 对象的一些不同的方式应该选择的选项取决于如何在应用程序中使用 Groovy还可以在同一个应用程序中结合使用已编译的和用脚本编写的 Groovy bean实际上如果希望的话还可以在同一个应用程序中同时使用 JavaGroovyJRuby 和 BeanShell bean但我不建议这样做作为开发人员必须权衡在同一应用程序中使用多种语言的优点和缺点

作为一种语言Groovy 比 Java 更灵活这使它成为很有吸引力的选择即使仅选择编译 Groovy 类也是如此Spring 可以集成用脚本编写的动态语言 bean这使人们更加喜欢选择 Groovy因为可以在用脚本编写的 bean 中引入附加的逻辑和灵活性例如正如前面看到的那样可以根据业务逻辑添加确定应用程序启动时应该实例化的 bean 类型的逻辑或者可以将用脚本编写的对象部署到 groovy 文件中使 Web 应用程序的部署更加灵活groovy 文件位于应用程序的 CLASSPATH 中或文件系统中的某个地方而不是打包在 WAR 文件中

到目前为止您看到的所有东西都为 Spring 工具箱增加了灵活性和威力但是Spring 动态语言支持中最引人注目的特性可能是在应用程序运行时 监视和检测对动态语言脚本的更改并在 Spring 应用程序上下文中自动重新装载 更改后的 bean 部分将深入探索这个功能包含 bean 的静态配置在运行时不能更改与之对比这个功能提供了很大的灵活性

下载

描述名字大小下载样例代码jgroovierspringcodezipMB点击

参考资料

您可以参阅本文在 developerWorks 全球网站上的 英文原文

               

上一篇:部署Spring与数据库的应用到CloudFoundry

下一篇:Hibernate持久化技术中多对多关系应用