天天看點

運作時和編譯時元程式設計—運作時元程式設計(一)運作時和編譯時元程式設計 第一部分

groovy語言支援兩種風格的元程式設計:運作時元程式設計和編譯時元程式設計。第一種元程式設計支援在程式運作時修改類模型和程式行為,而第二種發生在編譯時。兩種元程式設計有各自的優缺點,在這一章節我們将詳細讨論。

注:譯者也是第一次接觸groovy,由于時間和水準有限(姑且讓譯者使用這個理由吧,對待知識本應該一絲不苟)部分專有名詞可能翻譯不準确甚至有誤(讀者閱讀的過程中最好能參考原文),懇請讀者不吝留言指出,謝謝!

pojo – 一個普通的java對象,它的類可以使用java或其他支援jvm的語言來編寫。

對于每次方法調用,groovy都會檢查對象是一個pojo還是一個pogo。對于pojos,groovy從groovy.lang.metaclassregistry類中攜帶元資訊并且委托方法來調用。對于pogos,groovy有更複雜的不知,我們在下圖示範:

運作時和編譯時元程式設計—運作時元程式設計(一)運作時和編譯時元程式設計 第一部分

<code>01</code>

<code>package groovy.lang;</code>

<code>02</code>

<code>03</code>

<code>public interface groovyobject {</code>

<code>04</code>

<code>05</code>

<code>    </code><code>object invokemethod(string name, object args);</code>

<code>06</code>

<code>07</code>

<code>    </code><code>object getproperty(string propertyname);</code>

<code>08</code>

<code>09</code>

<code>    </code><code>void setproperty(string propertyname, object newvalue);</code>

<code>10</code>

<code>11</code>

<code>    </code><code>metaclass getmetaclass();</code>

<code>12</code>

<code>13</code>

<code>    </code><code>void setmetaclass(metaclass metaclass);</code>

<code>14</code>

<code>}</code>

<code>class somegroovyclass {</code>

<code>    </code><code>def invokemethod(string name, object args) {</code>

<code>        </code><code>return "called invokemethod $name $args"</code>

<code>    </code><code>}</code>

<code>    </code><code>def test() {</code>

<code>        </code><code>return 'method exists'</code>

<code>def somegroovyclass = new somegroovyclass()</code>

<code>assert somegroovyclass.test() == 'method exists'</code>

<code>15</code>

<code>assert somegroovyclass.somemethod() == 'called invokemethod somemethod []'</code>

通過重載目前對象的getproperty()方法可以使每次讀取屬性時被攔截。下面是一個簡單的示例:

<code>    </code><code>def property1 = 'ha'</code>

<code>    </code><code>def field2 = 'ho'</code>

<code>    </code><code>def field4 = 'hu'</code>

<code>    </code><code>def getfield1() {</code>

<code>        </code><code>return 'getha'</code>

<code>    </code><code>def getproperty(string name) {</code>

<code>        </code><code>if (name != 'field3')</code>

<code>            </code><code>return metaclass.getproperty(this, name)                  //(1)</code>

<code>        </code><code>else</code>

<code>            </code><code>return 'field3'</code>

<code>16</code>

<code>17</code>

<code>18</code>

<code>19</code>

<code>20</code>

<code>assert somegroovyclass.field1 == 'getha'</code>

<code>21</code>

<code>assert somegroovyclass.field2 == 'ho'</code>

<code>22</code>

<code>assert somegroovyclass.field3 == 'field3'</code>

<code>23</code>

<code>assert somegroovyclass.field4 == 'hu'</code>

(1) 将請求的getter轉到除field3之外的所有屬性

你可以重載setproperty()方法來攔截寫屬性:

<code>class pogo {</code>

<code>    </code><code>string property</code>

<code>    </code><code>void setproperty(string name, object value) {</code>

<code>        </code><code>this.@"$name" = 'overriden'</code>

<code>def pogo = new pogo()</code>

<code>pogo.property = 'a'</code>

<code>assert pogo.property == 'overriden'</code>

你可以通路一個對象的metaclass或者通過改變預設的攔截機制來設定實作你自己的metaclass。比如說你通過寫你自己的metaclass實作接口來将一套攔截機制配置設定到一個對象上:

<code>1</code>

<code>// getmetaclass</code>

<code>2</code>

<code>someobject.metaclass</code>

<code>3</code>

<code>4</code>

<code>// setmetaclass</code>

<code>5</code>

<code>someobject.metaclass = new ownmetaclassimplementation()</code>

這個功能和metaclass實作類相關。在該類預設的實作裡,你可以無需調用他們的getter和setters方法來通路屬性。下面是一個示例:

<code>    </code><code>def field1 = 'ha'</code>

<code>assert somegroovyclass.metaclass.getattribute(somegroovyclass, 'field1') == 'ha'</code>

<code>assert somegroovyclass.metaclass.getattribute(somegroovyclass, 'field2') == 'ho'</code>

<code>    </code><code>private string field</code>

<code>    </code><code>string property1</code>

<code>    </code><code>void setproperty1(string property1) {</code>

<code>        </code><code>this.property1 = "setproperty1"</code>

<code>pogo.metaclass.setattribute(pogo, 'field', 'ha')</code>

<code>pogo.metaclass.setattribute(pogo, 'property1', 'ho')</code>

<code>assert pogo.field == 'ha'</code>

<code>assert pogo.property1 == 'ho'</code>

groovy支援methodmissing的概念。這個方法不同于invokemethod,它隻能在方法分發失敗的情況下調用,當給定的名字或給定的參數無法找到時被調用:

<code>class foo {</code>

<code>   </code><code>def methodmissing(string name, def args) {</code>

<code>        </code><code>return "this is me"</code>

<code>   </code><code>}</code>

<code>6</code>

<code>7</code>

<code>8</code>

<code>assert new foo().someunknownmethod(42l) == 'this is me'</code>

當我們使用methodmissing的時候,如果下一次同樣一個方法被調用其傳回的結果可能是緩存的。比如說,考慮在gorm的動态查找器,有一個methodmissing的實作,下面是具體的代碼:

<code>class gorm {</code>

<code>   </code><code>def dynamicmethods = [...] // an array of dynamic methods that use regex</code>

<code>   </code><code>def methodmissing(string name, args) {</code>

<code>       </code><code>def method = dynamicmethods.find { it.match(name) }</code>

<code>       </code><code>if(method) {</code>

<code>          </code><code>gorm.metaclass."$name" = { object[] varargs -&amp;amp;amp;amp;amp;gt;</code>

<code>             </code><code>method.invoke(delegate, name, varargs)</code>

<code>          </code><code>}</code>

<code>          </code><code>return method.invoke(delegate,name, args)</code>

<code>       </code><code>}</code>

<code>       </code><code>else throw new missingmethodexception(name, delegate, args)</code>

groovy支援propertymissing的概念,用于攔截可能存在的屬性擷取失敗。在getter方法裡,propertymissing使用單個string類型的參數來代表屬性名字:

<code>   </code><code>def propertymissing(string name) { name }</code>

<code>assert new foo().boo == 'boo'</code>

在groovy運作時,propertymissing(string)方法隻有在沒有任何getter方法可以被給定的property所找到才會被調用。

對于setter方法,可以添加第二個propertymissing定義來添加一個額外的值參數

<code>   </code><code>def storage = [:]</code>

<code>   </code><code>def propertymissing(string name, value) { storage[name] = value }</code>

<code>   </code><code>def propertymissing(string name) { storage[name] }</code>

<code>def f = new foo()</code>

<code>f.foo = "bar"</code>

<code>assert f.foo == "bar"</code>

methodmissing方法的最适用地方在動态注冊新的屬性時能極大提供查找屬性所花費的性能。

<code>public interface groovyinterceptable extends groovyobject {</code>

當一個groovy對象實作了groovyinterceptable接口,它的invokemethod()将在任何方法調用時被調用。下面是這個類型的一個簡單示例:

<code>class interception implements groovyinterceptable {</code>

<code>    </code><code>def definedmethod() { }</code>

<code>        </code><code>'invokedmethod'</code>

下一塊代碼是一個測試類,不管調用存在的方法還是不存在的方法都将傳回相同的結果。

<code>class interceptabletest extends groovytestcase {</code>

<code>    </code><code>void testcheckinterception() {</code>

<code>        </code><code>def interception = new interception()</code>

<code>        </code><code>assert interception.definedmethod() == 'invokedmethod'</code>

<code>        </code><code>assert interception.somemethod() == 'invokedmethod'</code>

<code>9</code>

我們不能使用預設的groovy方法比如println,因為這些方法是被注入到groovy對象中區,是以它們也會被攔截。

如果我們想攔截所有所有方法但又不想實作groovyinterceptable接口,我們可以在一個對象的metaclass類上實作invokemethod()。對于pogos和pojos,這種方式都是可以的。下面是一個示例:

<code>class interceptionthroughmetaclasstest extends groovytestcase {</code>

<code>    </code><code>void testpojometaclassinterception() {</code>

<code>        </code><code>string invoking = 'ha'</code>

<code>        </code><code>invoking.metaclass.invokemethod = { string name, object args -&amp;amp;amp;amp;amp;gt;</code>

<code>            </code><code>'invoked'</code>

<code>        </code><code>}</code>

<code>        </code><code>assert invoking.length() == 'invoked'</code>

<code>        </code><code>assert invoking.somemethod() == 'invoked'</code>

<code>    </code><code>void testpogometaclassinterception() {</code>

<code>        </code><code>entity entity = new entity('hello')</code>

<code>        </code><code>entity.metaclass.invokemethod = { string name, object args -&amp;amp;amp;amp;amp;gt;</code>

<code>        </code><code>assert entity.build(new object()) == 'invoked'</code>

<code>        </code><code>assert entity.somemethod() == 'invoked'</code>

有這樣一種場景,如果能讓一個類的某些方法不受控制将會是很有用的。為了實作這種可能性,groovy從object-c借用實作了一個特性,叫做categories。

categories特性實作了所謂的category類,一個category類是需要滿足某些特定的預定義的規則來定義一些拓展方法。

下面有幾個categories是在groovy環境中系統提供的一些額外功能:

<a href="http://docs.groovy-lang.org/2.4.5/html/gapi/index.html?groovy/time/timecategory.html">groovy.time.timecategory</a>

<a href="http://docs.groovy-lang.org/2.4.5/html/gapi/index.html?groovy/servlet/servletcategory.html">groovy.servlet.servletcategory</a>

<a href="http://docs.groovy-lang.org/2.4.5/html/gapi/index.html?groovy/xml/dom/domcategory.html">groovy.xml.dom.domcategory</a>

category類預設是不能使用的,要使用這些定義在一個category類的方法需要使用 use 方法,這個方法是gdk提供的一個内置于groovy對象中的執行個體:

<code>use(timecategory)  {</code>

<code>    </code><code>println 1.minute.from.now           //(1)</code>

<code>    </code><code>println 10.hours.ago</code>

<code>    </code><code>def somedate = new date()          //(2)</code>

<code>    </code><code>println somedate - 3.months</code>

(1) timecategory添加一個方法到integer

(2) timecategory添加一個方法到date

use 方法把category類作為第一個參數,一個閉包代碼塊作為第二個參數。在closure裡可以通路catetory。從上面的例子可以看到,即便是jdk的類,比如java.lang.integer或java.util.date也是可以被包含到使用者定義的方法裡的。

一個category不需要直接暴露給使用者代碼,下面的示例說明了這一點:

<code>class jpacategory{</code>

<code>  </code><code>// let's enhance jpa entitymanager without getting into the jsr committee</code>

<code>  </code><code>static void persistall(entitymanager em , object[] entities) { //add an interface to save all</code>

<code>    </code><code>entities?.each { em.persist(it) }</code>

<code>  </code><code>}</code>

<code>def transactioncontext = {</code>

<code>  </code><code>entitymanager em, closure c -&amp;amp;amp;amp;amp;gt;</code>

<code>  </code><code>def tx = em.transaction</code>

<code>  </code><code>try {</code>

<code>    </code><code>tx.begin()</code>

<code>    </code><code>use(jpacategory) {</code>

<code>      </code><code>c()</code>

<code>    </code><code>tx.commit()</code>

<code>  </code><code>} catch (e) {</code>

<code>    </code><code>tx.rollback()</code>

<code>  </code><code>} finally {</code>

<code>    </code><code>//cleanup your resource here</code>

<code>24</code>

<code>// user code, they always forget to close resource in exception, some even forget to commit, let's not rely on them.</code>

<code>25</code>

<code>entitymanager em; //probably injected</code>

<code>26</code>

<code>transactioncontext (em) {</code>

<code>27</code>

<code> </code><code>em.persistall(obj1, obj2, obj3)</code>

<code>28</code>

<code> </code><code>// let's do some logics here to make the example sensible</code>

<code>29</code>

<code> </code><code>em.persistall(obj2, obj4, obj6)</code>

<code>30</code>

如果我們去看groovy.time.timecategory類的嗲嗎我們會發現拓展方法都是被聲明為static方法。事實上,一個category類的方法要能被成功地加到use代碼塊裡必須要這樣寫:

<code>public class timecategory {</code>

<code>    </code><code>public static date plus(final date date, final baseduration duration) {</code>

<code>        </code><code>return duration.plus(date);</code>

<code>    </code><code>public static date minus(final date date, final baseduration duration) {</code>

<code>        </code><code>final calendar cal = calendar.getinstance();</code>

<code>        </code><code>cal.settime(date);</code>

<code>        </code><code>cal.add(calendar.year, -duration.getyears());</code>

<code>        </code><code>cal.add(calendar.month, -duration.getmonths());</code>

<code>        </code><code>cal.add(calendar.day_of_year, -duration.getdays());</code>

<code>        </code><code>cal.add(calendar.hour_of_day, -duration.gethours());</code>

<code>        </code><code>cal.add(calendar.minute, -duration.getminutes());</code>

<code>        </code><code>cal.add(calendar.second, -duration.getseconds());</code>

<code>        </code><code>cal.add(calendar.millisecond, -duration.getmillis());</code>

<code>        </code><code>return cal.gettime();</code>

<code>    </code><code>// ...</code>

另外一個要求是靜态方法的第一個參數必須定義類型,隻要方法被激活。另外一個參數可以作為一個普通的參數當成方法的變量。

因為參數和靜态方法的轉變,category方法的定義可能比一般的方法定義不那麼直覺。不過groovy提供了一個@category注解,可以在編譯時将一個類轉化為category類。

<code>class distance {</code>

<code>    </code><code>def number</code>

<code>    </code><code>string tostring() { "${number}m" }</code>

<code>@category(number)</code>

<code>class numbercategory {</code>

<code>    </code><code>distance getmeters() {</code>

<code>        </code><code>new distance(number: this)</code>

<code>use (numbercategory)  {</code>

<code>    </code><code>assert 42.meters.tostring() == '42m'</code>

使用@category注解可以直接使用示例方法二不必将目标類型作為第一個參數的好處。目标類型類在注解裡作為了一個參數。

(tbd)

delegating metaclass

magic package(maksym stavyskyi)