Grails 1.2参考文档 - 插件

Grails是一个插件架构,这一点我们已经在前面体会到了,最典型的就是GORM一节中,我们明明没有定义crud操作,但在运行时却可以使用它,造成这一结果的“元凶”就是我们预先安装的Hibernate插件。在这一节里,我们将对这背后的机制进行一番探究,对插件有一个基本的了解。

Plugin是Grails的主要扩展点,其工程跟普通Grails工程并无区别,只是多了一个描述文件。描述文件位于位于工程根目录,是以GrailsPlugin结尾的Groovy文件。同时,工程中缺省没有URL Mappings,因此Controller无法马上工作,如果需要则必须手工添加该文件。

插件相关的命令如下:grails create-plugin grails run-app grails package-plugin grails install-plugin url(或路径) grails list-plugins:列出插件仓库中插件 grails plugin-info 插件名:列出插件信息 grails release-plugin插件的描述文件定义了插件的相关信息,主要的属性有:version:版本 title:短描述 author:作者 authorEmail:作者的Email description:长描述 documentation:文档URL插件完成之后,需要进行打包发布,在打包时需要注意,以下内容会被排除:

grails-app/conf/DataSource.groovy
grails-app/conf/UrlMappings.groovy
build.xml

/web-app/WEB-INF目录中所有内容

插件文件中pluginExcludes定义的内容,如: def pluginExcludes = [ "grails-app/views/error.gsp" ]虽然UrlMappings.groovy会被排除,但是xxxUrlMappings.groovy并不在此列。同时,如果需要,你还可以修改工程的_Install.groovy脚本改变打包行为。

插件最终的发布位置是“仓库”。缺省仓库是:http://plugins.grails.org,Grails也支持配置多个仓库。在Grails中,自定义仓库的主要目的有2个:发现和发布,前者相对于插件的使用者,后者相对于插件的创作者。配置文件是grails-app/conf/BuildConfig.groovy或USER_HOME/.grails/settings.groovy,前者针对于单个项目,后者则是多项目间共享。配置内容:grails.plugin.repos.discovery.myRepository= "http://svn.codehaus.org/grails/trunk/grails-test-plugin-repo" grails.plugin.repos.distribution.myRepository= "https://svn.codehaus.org/grails/trunk/grails-test-plugin-repo"如果仓库设有用户名/口令,URL格式:PROTOCOL://USERNAME:PASSWORD@SERVER……

Grails的命令一般是自动解析仓库位置,也可以在命令中指定使用哪个仓库:

在指定仓库中查找,grails list-plugins -repository=myRepository

在指定仓库中发布,grails release-plugin -repository=myRepository

命令缺省的仓库搜索顺序为:default、core、自定义仓库。改变顺序则在配置文件中书写:grails.plugin.repos.resolveOrder=['myRepository','default','core']如果想只解析指定仓库:grails.plugin.repos.resolveOrder=['myRepository']安装插件时,需要注意其对使用工程的影响。它会影响使用工程的目录结构:

原Plugin工程的grails-app目录内容移至plugins/plugin-version/grails-app。

原Plugin工程中web-app的静态内容移至web-app/plugins/plugin-version。

原Plugin工程的Java和Groovy内容编译到web-app/classes

鉴于以上目录改变,在Plugin工程中使用pluginContextPath引用目录。<g:createLinkTo dir="${pluginContextPath}/js" file="mycode.js" />Plugin工程中的一些部件的创建方法和普通Grails工程一样:Script、Controller、Tag Lib、Server、DomainClass等。其中view的查找顺序是:首先查找所安装App中的view,再查看插件中的。如果使用的template是插件的,那么写成:<g:render template="fooTemplate" plugin="amazon"/>每个Plugin工程都有一个application变量,它是GrailsApplication接口实例。每个GrailsApplication接口提供了计算项目内部惯例的方法,内部使用GrailsClass保存引用。GrailsClass代表一个物理的Grails资源,如一个Controller或一个标签库。application主要的动态方法有:

*Classes,获得某artefact的所有类:application.controllerClasses

get*Class,获得某artefact的指定类:application.getControllerClass(“ExampleController”)

is*Class,判断指定类是否是某artefact:application.isControllerClass(ExampleController.class)

GrailsClass的主要接口:

getPropertyValue
hasProperty
newInstance
getName
getShortName
getFullName
getPropertyName
getLogicalPropertyName
getNaturalName
getPackageName

插件工程中的脚本:

scripts/_Install.groovy,plugin被安装后触发
scripts/ _Update.groovy,update命令触发

以下的内容将说明,插件的一些“神迹”,如上面Domain Class中自动出现的CRUD,实现的原因。所有这些都要归因于插件参与了动态配置。在描述文件中定义以下闭包,将定义插件在动态配置时的行为:

doWithSpring:Spring配置,使用Spring Bean Builder。

doWithWebDescriptor:web.xml,使用XmlSlurper。

doWithApplicationContext:ApplicationContext构建之后。

doWithDynamicMethods:动态增加动态方法

导致DomainClass中凭空多出的CRUD方法的正是doWithDynamicMethods,使用例子:class ExamplePlugin { def doWithDynamicMethods = { applicationContext -> application.controllerClasses.metaClass.each { metaClass -> metaClass.myNewMethod = {-> println "hello world" } } } }以上代码将给所有Controller都添加一个myNewMethod,其作用就是打印一行“hello world”。建议下载Grails的源码,然后搜一搜插件的描述文件,读一读,或许你会有其他的收获。

Grails也为插件自动重载提供了支持。描述文件中以下几个属性(或闭包)涉及Plugin的自动重载:

watchedResources属性:监测的资源。

onChange闭包:监视资源发生变化时调用,传入event对象,它的主要属性:event.source,事件源,重载的类或Spring资源;event.ctx,Spring的ApplicationContext实例;event.plugin,管理资源的plugin对象(通常是this);event.application,GrailsApplication实例
influences属性:定义影响的plugin,在重载时会一并载入。方向:由此及彼。 def influences = ['controllers']observe属性:定义观察的plugin,当那个插件变化时,获得变化通知事件。方向:由彼及此。 def observe = ["hibernate"] def observe =["*"]看看例子:class ServicesGrailsPlugin { def watchedResources = "file:./grails-app/services/*Service.groovy" def onChange = { event -> if(event.source) { def serviceClass = application.addServiceClass(event.source) def serviceName = "${serviceClass.propertyName}" def beans = beans { "$serviceName"(serviceClass.getClazz()) { bean -> bean.autowire = true } } if(event.ctx) { event.ctx.registerBeanDefinition(serviceName, beans.getBeanDefinition(serviceName)) } }}}插件之间可以有依赖关系,因此这就引出了跟插件加载顺序相关的话题。这其中涉及两个属性:dependsOn和loadAfter。

dependsOn:定义了强依赖,如果依赖没有得到满足,Plugin不会加载。

例1: def dependsOn = [dataSource:1.0, core: 1.0]

例2: def dependsOn = [foo:“1.0 > 1.1”]。该表达式表示1.0~1.1版,包括1.0和1.1。可以使用代表任意版本,如[foo:" > 1.0"]、[foo:“1.0 > *”] 。

loadAfter:定义了弱依赖,如依赖没有满足,也会加载。但是Plugin可以在程序内部查看依赖是否满足(使用GrailsPluginManager),以决定是否提供高级操作。

以上就是插件的基本情况,除了这些,如今Grails社区已经就一点达成共识:使用插件作为一种模块化的机制,通过给主程序安装不同插件,逐步达成应用需求。