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社区已经就一点达成共识:使用插件作为一种模块化的机制,通过给主程序安装不同插件,逐步达成应用需求。
转载于:https://blog.51cto.com/bcptdtptp/306638