EricGu's Java-Record-Space

          專注 學(xué)習(xí) 實(shí)踐 創(chuàng)新

          2009年6月18日

               遇到System.Data.OracleClient 需要 Oracle 客戶端軟件 8.1.7 或更高版本。一般第一反映都是會(huì)出處理  oracle_home 文件夾權(quán)限。可是有時(shí)時(shí)候 不管你怎么擺弄權(quán)限,怎么iisreset,怎么重啟電腦都解決不了,cmd  path 明明可以看到有oracle_home 路徑啊。問題在于。環(huán)境變量中, ora10gInstant 精簡客戶端默認(rèn)把變量添加到 Administrator 的用戶變量了,我們要做的是把  用戶變量中的 Path 值 轉(zhuǎn)到 系統(tǒng)變量中的 Path 中。
          posted @ 2012-04-19 13:09 Eric Gu 閱讀(445) | 評(píng)論 (0)編輯 收藏

          在項(xiàng)目中使用Spring的注解,關(guān)于spring的注解,由兩種注解方式,

          基于注釋(Annotation)的配置有越來越流行的趨勢,Spring 2.5 順應(yīng)這種趨勢,提供了完全基于注釋配置 Bean、裝配 Bean 的功能,您可以使用基于注釋的 Spring IoC 替換原來基于 XML 的配置。本文通過實(shí)例詳細(xì)講述了 Spring 2.5 基于注釋 IoC 功能的使用。

          <!--START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--END RESERVED FOR FUTURE USE INCLUDE FILES-->

          概述

          注釋配置相對(duì)于 XML 配置具有很多的優(yōu)勢:

          • 它可以充分利用 Java 的反射機(jī)制獲取類結(jié)構(gòu)信息,這些信息可以有效減少配置的工作。如使用 JPA 注釋配置 ORM 映射時(shí),我們就不需要指定 PO 的屬性名、類型等信息,如果關(guān)系表字段和 PO 屬性名、類型都一致,您甚至無需編寫任務(wù)屬性映射信息——因?yàn)檫@些信息都可以通過 Java 反射機(jī)制獲取。
          • 注釋和 Java 代碼位于一個(gè)文件中,而 XML 配置采用獨(dú)立的配置文件,大多數(shù)配置信息在程序開發(fā)完成后都不會(huì)調(diào)整,如果配置信息和 Java 代碼放在一起,有助于增強(qiáng)程序的內(nèi)聚性。而采用獨(dú)立的 XML 配置文件,程序員在編寫一個(gè)功能時(shí),往往需要在程序文件和配置文件中不停切換,這種思維上的不連貫會(huì)降低開發(fā)效率。

          因此在很多情況下,注釋配置比 XML 配置更受歡迎,注釋配置有進(jìn)一步流行的趨勢。Spring 2.5 的一大增強(qiáng)就是引入了很多注釋類,現(xiàn)在您已經(jīng)可以使用注釋配置完成大部分 XML 配置的功能。在這篇文章里,我們將向您講述使用注釋進(jìn)行 Bean 定義和依賴注入的內(nèi)容。

           

           

           

          使用 @Autowired 注釋

          Spring 2.5 引入了 @Autowired 注釋,它可以對(duì)類成員變量、方法及構(gòu)造函數(shù)進(jìn)行標(biāo)注,完成自動(dòng)裝配的工作。

          Spring 通過一個(gè) BeanPostProcessor 對(duì) @Autowired 進(jìn)行解析,所以要讓 @Autowired 起作用必須事先在 Spring 容器中聲明 AutowiredAnnotationBeanPostProcessor Bean。

           

           

          在Spring中配置如下:

          <!-- 該 BeanPostProcessor 將自動(dòng)起作用,對(duì)標(biāo)注 @Autowired 的 Bean 進(jìn)行自動(dòng)注入 -->
              <bean class="org.springframework.beans.factory.annotation.
                  AutowiredAnnotationBeanPostProcessor"/>
          當(dāng) Spring 容器啟動(dòng)時(shí),AutowiredAnnotationBeanPostProcessor 將掃描 Spring 容器中所有 Bean,當(dāng)發(fā)現(xiàn) Bean 中擁有 @Autowired 注釋時(shí)就找到和其匹配(默認(rèn)按類型匹配)的 Bean,并注入到對(duì)應(yīng)的地方中去。

          按照上面的配置,Spring 將直接采用 Java 反射機(jī)制對(duì) Boss 中的 caroffice 這兩個(gè)私有成員變量進(jìn)行自動(dòng)注入。所以對(duì)成員變量使用 @Autowired 后,您大可將它們的 setter 方法(setCar()setOffice())從 Boss 中刪除。

          當(dāng)然,您也可以通過 @Autowired 對(duì)方法或構(gòu)造函數(shù)進(jìn)行標(biāo)注,

           

          當(dāng)候選 Bean 數(shù)目不為 1 時(shí)的應(yīng)對(duì)方法

          在默認(rèn)情況下使用 @Autowired 注釋進(jìn)行自動(dòng)注入時(shí),Spring 容器中匹配的候選 Bean 數(shù)目必須有且僅有一個(gè)。當(dāng)找不到一個(gè)匹配的 Bean 時(shí),Spring 容器將拋出 BeanCreationException 異常,并指出必須至少擁有一個(gè)匹配的 Bean。

           

          當(dāng)不能確定 Spring 容器中一定擁有某個(gè)類的 Bean 時(shí),可以在需要自動(dòng)注入該類 Bean 的地方可以使用 @Autowired(required = false),這等于告訴 Spring:在找不到匹配 Bean 時(shí)也不報(bào)錯(cuò)。

           

          一般情況下,使用 @Autowired 的地方都是需要注入 Bean 的,使用了自動(dòng)注入而又允許不注入的情況一般僅會(huì)在開發(fā)期或測試期碰到(如為了快速啟動(dòng) Spring 容器,僅引入一些模塊的 Spring 配置文件),所以 @Autowired(required = false) 會(huì)很少用到。

          和找不到一個(gè)類型匹配 Bean 相反的一個(gè)錯(cuò)誤是:如果 Spring 容器中擁有多個(gè)候選 Bean,Spring 容器在啟動(dòng)時(shí)也會(huì)拋出 BeanCreationException 異常。

           

          Spring 允許我們通過 @Qualifier 注釋指定注入 Bean 的名稱,這樣歧義就消除了,可以通過下面的方法解決異常:


          清單 13. 使用 @Qualifier 注釋指定注入 Bean 的名稱

                      @Autowired
                      public void setOffice(@Qualifier("office")Office office) {
                      this.office = office;
                      }
                      

          @Qualifier("office") 中的 office 是 Bean 的名稱,所以 @Autowired@Qualifier 結(jié)合使用時(shí),自動(dòng)注入的策略就從 byType 轉(zhuǎn)變成 byName 了。@Autowired 可以對(duì)成員變量、方法以及構(gòu)造函數(shù)進(jìn)行注釋,而 @Qualifier 的標(biāo)注對(duì)象是成員變量、方法入?yún)ⅰ?gòu)造函數(shù)入?yún)ⅰU怯捎谧⑨寣?duì)象的不同,所以 Spring 不將 @Autowired@Qualifier 統(tǒng)一成一個(gè)注釋類。下面是對(duì)成員變量和構(gòu)造函數(shù)入?yún)⑦M(jìn)行注釋的代碼:

          對(duì)成員變量進(jìn)行注釋:

           

          對(duì)成員變量使用 @Qualifier 注釋

                      public class Boss {
                      @Autowired
                      private Car car;
                      @Autowired
                      @Qualifier("office")
                      private Office office;
                      …
                      }
                      

          對(duì)構(gòu)造函數(shù)入?yún)⑦M(jìn)行注釋:


          清單 15. 對(duì)構(gòu)造函數(shù)變量使用 @Qualifier 注釋

                      public class Boss {
                      private Car car;
                      private Office office;
                      @Autowired
                      public Boss(Car car , @Qualifier("office")Office office){
                      this.car = car;
                      this.office = office ;
                      }
                      }
                      

          @Qualifier 只能和 @Autowired 結(jié)合使用,是對(duì) @Autowired 有益的補(bǔ)充。一般來講,@Qualifier 對(duì)方法簽名中入?yún)⑦M(jìn)行注釋會(huì)降低代碼的可讀性,而對(duì)成員變量注釋則相對(duì)好一些。

           

          使用 JSR-250 的注釋

          Spring 不但支持自己定義的 @Autowired 的注釋,還支持幾個(gè)由 JSR-250 規(guī)范定義的注釋,它們分別是 @Resource@PostConstruct 以及 @PreDestroy

          @Resource

          @Resource 的作用相當(dāng)于 @Autowired,只不過 @Autowired 按 byType 自動(dòng)注入,面 @Resource 默認(rèn)按 byName 自動(dòng)注入罷了。@Resource 有兩個(gè)屬性是比較重要的,分別是 name 和 type,Spring 將 @Resource 注釋的 name 屬性解析為 Bean 的名字,而 type 屬性則解析為 Bean 的類型。所以如果使用 name 屬性,則使用 byName 的自動(dòng)注入策略,而使用 type 屬性時(shí)則使用 byType 自動(dòng)注入策略。如果既不指定 name 也不指定 type 屬性,這時(shí)將通過反射機(jī)制使用 byName 自動(dòng)注入策略。

          Resource 注釋類位于 Spring 發(fā)布包的 lib/j2ee/common-annotations.jar 類包中,因此在使用之前必須將其加入到項(xiàng)目的類庫中。來看一個(gè)使用 @Resource 的例子:


          清單 16. 使用 @Resource 注釋的 Boss.java

                      package com.baobaotao;
                      import javax.annotation.Resource;
                      public class Boss {
                      // 自動(dòng)注入類型為 Car 的 Bean
                      @Resource
                      private Car car;
                      // 自動(dòng)注入 bean 名稱為 office 的 Bean
                      @Resource(name = "office")
                      private Office office;
                      }
                      

          一般情況下,我們無需使用類似于 @Resource(type=Car.class) 的注釋方式,因?yàn)?Bean 的類型信息可以通過 Java 反射從代碼中獲取。

          要讓 JSR-250 的注釋生效,除了在 Bean 類中標(biāo)注這些注釋外,還需要在 Spring 容器中注冊(cè)一個(gè)負(fù)責(zé)處理這些注釋的 BeanPostProcessor

           

          <bean
                      class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>
                      

          CommonAnnotationBeanPostProcessor 實(shí)現(xiàn)了 BeanPostProcessor 接口,它負(fù)責(zé)掃描使用了 JSR-250 注釋的 Bean,并對(duì)它們進(jìn)行相應(yīng)的操作。

          @PostConstruct 和 @PreDestroy

          Spring 容器中的 Bean 是有生命周期的,Spring 允許在 Bean 在初始化完成后以及 Bean 銷毀前執(zhí)行特定的操作,您既可以通過實(shí)現(xiàn) InitializingBean/DisposableBean 接口來定制初始化之后 / 銷毀之前的操作方法,也可以通過 <bean> 元素的 init-method/destroy-method 屬性指定初始化之后 / 銷毀之前調(diào)用的操作方法。關(guān)于 Spring 的生命周期,筆者在《精通 Spring 2.x—企業(yè)應(yīng)用開發(fā)精解》第 3 章進(jìn)行了詳細(xì)的描述,有興趣的讀者可以查閱。

          JSR-250 為初始化之后/銷毀之前方法的指定定義了兩個(gè)注釋類,分別是 @PostConstruct 和 @PreDestroy,這兩個(gè)注釋只能應(yīng)用于方法上。標(biāo)注了 @PostConstruct 注釋的方法將在類實(shí)例化后調(diào)用,而標(biāo)注了 @PreDestroy 的方法將在類銷毀之前調(diào)用。


          清單 17. 使用 @PostConstruct 和 @PreDestroy 注釋的 Boss.java

                      package com.baobaotao;
                      import javax.annotation.Resource;
                      import javax.annotation.PostConstruct;
                      import javax.annotation.PreDestroy;
                      public class Boss {
                      @Resource
                      private Car car;
                      @Resource(name = "office")
                      private Office office;
                      @PostConstruct
                      public void postConstruct1(){
                      System.out.println("postConstruct1");
                      }
                      @PreDestroy
                      public void preDestroy1(){
                      System.out.println("preDestroy1");
                      }
                      …
                      }
                      

          您只需要在方法前標(biāo)注 @PostConstruct@PreDestroy,這些方法就會(huì)在 Bean 初始化后或銷毀之前被 Spring 容器執(zhí)行了。

          我們知道,不管是通過實(shí)現(xiàn) InitializingBean/DisposableBean 接口,還是通過 <bean> 元素的 init-method/destroy-method 屬性進(jìn)行配置,都只能為 Bean 指定一個(gè)初始化 / 銷毀的方法。但是使用 @PostConstruct@PreDestroy 注釋卻可以指定多個(gè)初始化 / 銷毀方法,那些被標(biāo)注 @PostConstruct@PreDestroy 注釋的方法都會(huì)在初始化 / 銷毀時(shí)被執(zhí)行。

          通過以下的測試代碼,您將可以看到 Bean 的初始化 / 銷毀方法是如何被執(zhí)行的:


          清單 18. 測試類代碼

                      package com.baobaotao;
                      import org.springframework.context.support.ClassPathXmlApplicationContext;
                      public class AnnoIoCTest {
                      public static void main(String[] args) {
                      String[] locations = {"beans.xml"};
                      ClassPathXmlApplicationContext ctx =
                      new ClassPathXmlApplicationContext(locations);
                      Boss boss = (Boss) ctx.getBean("boss");
                      System.out.println(boss);
                      ctx.destroy();// 關(guān)閉 Spring 容器,以觸發(fā) Bean 銷毀方法的執(zhí)行
                      }
                      }
                      

          這時(shí),您將看到標(biāo)注了 @PostConstructpostConstruct1() 方法將在 Spring 容器啟動(dòng)時(shí),創(chuàng)建 Boss Bean 的時(shí)候被觸發(fā)執(zhí)行,而標(biāo)注了 @PreDestroy 注釋的 preDestroy1() 方法將在 Spring 容器關(guān)閉前銷毀 Boss Bean 的時(shí)候被觸發(fā)執(zhí)行。





          回頁首

          使用 <context:annotation-config/> 簡化配置

          Spring 2.1 添加了一個(gè)新的 context 的 Schema 命名空間,該命名空間對(duì)注釋驅(qū)動(dòng)、屬性文件引入、加載期織入等功能提供了便捷的配置。我們知道注釋本身是不會(huì)做任何事情的,它僅提供元數(shù)據(jù)信息。要使元數(shù)據(jù)信息真正起作用,必須讓負(fù)責(zé)處理這些元數(shù)據(jù)的處理器工作起來。

          而我們前面所介紹的 AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor 就是處理這些注釋元數(shù)據(jù)的處理器。但是直接在 Spring 配置文件中定義這些 Bean 顯得比較笨拙。Spring 為我們提供了一種方便的注冊(cè)這些 BeanPostProcessor 的方式,這就是 <context:annotation-config/>。請(qǐng)看下面的配置:


          清單 19. 調(diào)整 beans.xml 配置文件

                      <?xml version="1.0" encoding="UTF-8" ?>
                      <beans xmlns="http://www.springframework.org/schema/beans"
                      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                      xmlns:context="http://www.springframework.org/schema/context"
                      xsi:schemaLocation="http://www.springframework.org/schema/beans
                      http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                      http://www.springframework.org/schema/context
                      http://www.springframework.org/schema/context/spring-context-2.5.xsd">
                      <context:annotation-config/>
                      <bean id="boss" class="com.baobaotao.Boss"/>
                      <bean id="office" class="com.baobaotao.Office">
                      <property name="officeNo" value="001"/>
                      </bean>
                      <bean id="car" class="com.baobaotao.Car" scope="singleton">
                      <property name="brand" value=" 紅旗 CA72"/>
                      <property name="price" value="2000"/>
                      </bean>
                      </beans>
                      

          <context:annotationconfig/> 將隱式地向 Spring 容器注冊(cè) AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessorPersistenceAnnotationBeanPostProcessor 以及 equiredAnnotationBeanPostProcessor 這 4 個(gè) BeanPostProcessor。

          在配置文件中使用 context 命名空間之前,必須在 <beans> 元素中聲明 context 命名空間。





          回頁首

          使用 @Component

          雖然我們可以通過 @Autowired@Resource 在 Bean 類中使用自動(dòng)注入功能,但是 Bean 還是在 XML 文件中通過 <bean> 進(jìn)行定義 —— 也就是說,在 XML 配置文件中定義 Bean,通過 @Autowired@Resource 為 Bean 的成員變量、方法入?yún)⒒驑?gòu)造函數(shù)入?yún)⑻峁┳詣?dòng)注入的功能。能否也通過注釋定義 Bean,從 XML 配置文件中完全移除 Bean 定義的配置呢?答案是肯定的,我們通過 Spring 2.5 提供的 @Component 注釋就可以達(dá)到這個(gè)目標(biāo)了。

          下面,我們完全使用注釋定義 Bean 并完成 Bean 之間裝配:


          清單 20. 使用 @Component 注釋的 Car.java

                      package com.baobaotao;
                      import org.springframework.stereotype.Component;
                      @Component
                      public class Car {
                      …
                      }
                      

          僅需要在類定義處,使用 @Component 注釋就可以將一個(gè)類定義了 Spring 容器中的 Bean。下面的代碼將 Office 定義為一個(gè) Bean:


          清單 21. 使用 @Component 注釋的 Office.java

                      package com.baobaotao;
                      import org.springframework.stereotype.Component;
                      @Component
                      public class Office {
                      private String officeNo = "001";
                      …
                      }
                      

          這樣,我們就可以在 Boss 類中通過 @Autowired 注入前面定義的 CarOffice Bean 了。


          清單 22. 使用 @Component 注釋的 Boss.java

                      package com.baobaotao;
                      import org.springframework.beans.factory.annotation.Autowired;
                      import org.springframework.beans.factory.annotation.Required;
                      import org.springframework.beans.factory.annotation.Qualifier;
                      import org.springframework.stereotype.Component;
                      @Component("boss")
                      public class Boss {
                      @Autowired
                      private Car car;
                      @Autowired
                      private Office office;
                      …
                      }
                      

          @Component 有一個(gè)可選的入?yún)ⅲ糜谥付?Bean 的名稱,在 Boss 中,我們就將 Bean 名稱定義為“boss”。一般情況下,Bean 都是 singleton 的,需要注入 Bean 的地方僅需要通過 byType 策略就可以自動(dòng)注入了,所以大可不必指定 Bean 的名稱。

          在使用 @Component 注釋后,Spring 容器必須啟用類掃描機(jī)制以啟用注釋驅(qū)動(dòng) Bean 定義和注釋驅(qū)動(dòng) Bean 自動(dòng)注入的策略。Spring 2.5 對(duì) context 命名空間進(jìn)行了擴(kuò)展,提供了這一功能,請(qǐng)看下面的配置:


          清單 23. 簡化版的 beans.xml

                      <?xml version="1.0" encoding="UTF-8" ?>
                      <beans xmlns="http://www.springframework.org/schema/beans"
                      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                      xmlns:context="http://www.springframework.org/schema/context"
                      xsi:schemaLocation="http://www.springframework.org/schema/beans
                      http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                      http://www.springframework.org/schema/context
                      http://www.springframework.org/schema/context/spring-context-2.5.xsd">
                      <context:component-scan base-package="com.baobaotao"/>
                      </beans>
                      

          這里,所有通過 <bean> 元素定義 Bean 的配置內(nèi)容已經(jīng)被移除,僅需要添加一行 <context:component-scan/> 配置就解決所有問題了——Spring XML 配置文件得到了極致的簡化(當(dāng)然配置元數(shù)據(jù)還是需要的,只不過以注釋形式存在罷了)。<context:component-scan/> 的 base-package 屬性指定了需要掃描的類包,類包及其遞歸子包中所有的類都會(huì)被處理。

          <context:component-scan/> 還允許定義過濾器將基包下的某些類納入或排除。Spring 支持以下 4 種類型的過濾方式,通過下表說明:


          表 1. 掃描過濾方式

          過濾器類型 說明
          注釋 假如 com.baobaotao.SomeAnnotation 是一個(gè)注釋類,我們可以將使用該注釋的類過濾出來。
          類名指定 通過全限定類名進(jìn)行過濾,如您可以指定將 com.baobaotao.Boss 納入掃描,而將 com.baobaotao.Car 排除在外。
          正則表達(dá)式 通過正則表達(dá)式定義過濾的類,如下所示: com\.baobaotao\.Default.*
          AspectJ 表達(dá)式 通過 AspectJ 表達(dá)式定義過濾的類,如下所示: com. baobaotao..*Service+

          下面是一個(gè)簡單的例子:

           

          <context:component-scan base-package="com.baobaotao">
                      <context:include-filter type="regex"
                      expression="com\.baobaotao\.service\..*"/>
                      <context:exclude-filter type="aspectj"
                      expression="com.baobaotao.util..*"/>
                      </context:component-scan>
                      

          值得注意的是 <context:component-scan/> 配置項(xiàng)不但啟用了對(duì)類包進(jìn)行掃描以實(shí)施注釋驅(qū)動(dòng) Bean 定義的功能,同時(shí)還啟用了注釋驅(qū)動(dòng)自動(dòng)注入的功能(即還隱式地在內(nèi)部注冊(cè)了 AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor),因此當(dāng)使用 <context:component-scan/> 后,就可以將 <context:annotation-config/> 移除了。

          默認(rèn)情況下通過 @Component 定義的 Bean 都是 singleton 的,如果需要使用其它作用范圍的 Bean,可以通過 @Scope 注釋來達(dá)到目標(biāo),如以下代碼所示:


          清單 24. 通過 @Scope 指定 Bean 的作用范圍

                      package com.baobaotao;
                      import org.springframework.context.annotation.Scope;
                      …
                      @Scope("prototype")
                      @Component("boss")
                      public class Boss {
                      …
                      }
                      

          這樣,當(dāng)從 Spring 容器中獲取 boss Bean 時(shí),每次返回的都是新的實(shí)例了。





          回頁首

          采用具有特殊語義的注釋

          Spring 2.5 中除了提供 @Component 注釋外,還定義了幾個(gè)擁有特殊語義的注釋,它們分別是:@Repository@Service@Controller。在目前的 Spring 版本中,這 3 個(gè)注釋和 @Component 是等效的,但是從注釋類的命名上,很容易看出這 3 個(gè)注釋分別和持久層、業(yè)務(wù)層和控制層(Web 層)相對(duì)應(yīng)。雖然目前這 3 個(gè)注釋和 @Component 相比沒有什么新意,但 Spring 將在以后的版本中為它們添加特殊的功能。所以,如果 Web 應(yīng)用程序采用了經(jīng)典的三層分層結(jié)構(gòu)的話,最好在持久層、業(yè)務(wù)層和控制層分別采用 @Repository@Service@Controller 對(duì)分層中的類進(jìn)行注釋,而用 @Component 對(duì)那些比較中立的類進(jìn)行注釋。





          回頁首

          注釋配置和 XML 配置的適用場合

          是否有了這些 IOC 注釋,我們就可以完全摒除原來 XML 配置的方式呢?答案是否定的。有以下幾點(diǎn)原因:

          • 注釋配置不一定在先天上優(yōu)于 XML 配置。如果 Bean 的依賴關(guān)系是固定的,(如 Service 使用了哪幾個(gè) DAO 類),這種配置信息不會(huì)在部署時(shí)發(fā)生調(diào)整,那么注釋配置優(yōu)于 XML 配置;反之如果這種依賴關(guān)系會(huì)在部署時(shí)發(fā)生調(diào)整,XML 配置顯然又優(yōu)于注釋配置,因?yàn)樽⑨屖菍?duì) Java 源代碼的調(diào)整,您需要重新改寫源代碼并重新編譯才可以實(shí)施調(diào)整。
          • 如果 Bean 不是自己編寫的類(如 JdbcTemplateSessionFactoryBean 等),注釋配置將無法實(shí)施,此時(shí) XML 配置是唯一可用的方式。
          • 注釋配置往往是類級(jí)別的,而 XML 配置則可以表現(xiàn)得更加靈活。比如相比于 @Transaction 事務(wù)注釋,使用 aop/tx 命名空間的事務(wù)配置更加靈活和簡單。

          所以在實(shí)現(xiàn)應(yīng)用中,我們往往需要同時(shí)使用注釋配置和 XML 配置,對(duì)于類級(jí)別且不會(huì)發(fā)生變動(dòng)的配置可以優(yōu)先考慮注釋配置;而對(duì)于那些第三方類以及容易發(fā)生調(diào)整的配置則應(yīng)優(yōu)先考慮使用 XML 配置。Spring 會(huì)在具體實(shí)施 Bean 創(chuàng)建和 Bean 注入之前將這兩種配置方式的元信息融合在一起。





          回頁首

          小結(jié)

          Spring 在 2.1 以后對(duì)注釋配置提供了強(qiáng)力的支持,注釋配置功能成為 Spring 2.5 的最大的亮點(diǎn)之一。合理地使用 Spring 2.5 的注釋配置,可以有效減少配置的工作量,提高程序的內(nèi)聚性。但是這并不意味著傳統(tǒng) XML 配置將走向消亡,在第三方類 Bean 的配置,以及那些諸如數(shù)據(jù)源、緩存池、持久層操作模板類、事務(wù)管理等內(nèi)容的配置上,XML 配置依然擁有不可替代的地位。

          posted @ 2010-12-15 14:40 Eric Gu 閱讀(277) | 評(píng)論 (0)編輯 收藏

          什么叫控制反轉(zhuǎn)呢?套用好萊塢的一句名言就是:你呆著別動(dòng),到時(shí)我會(huì)找你。
          什么意思呢?就好比一個(gè)皇帝和太監(jiān)
          有一天皇帝想寵幸某個(gè)美女,于是跟太監(jiān)說,今夜我要寵幸美女
          皇帝往往不會(huì)告訴太監(jiān),今晚幾點(diǎn)會(huì)回宮,會(huì)回哪張龍床,他只會(huì)告訴太監(jiān)他要哪位美女

          其它一切都交由太監(jiān)去安排,到了晚上皇帝回宮時(shí),自然會(huì)有美女出現(xiàn)在皇帝的龍床上
          這就是控制反轉(zhuǎn),而把美女送到皇帝的寢宮里面去就是注射

          太監(jiān)就是是框架里面的注射控制器類BeanFactory,負(fù)責(zé)找到美女并送到龍床上去
          整個(gè)后宮可以看成是Spring框架,美女就是Spring控制下的JavaBean
          而傳統(tǒng)的模式就是一個(gè)饑渴男去找小姐出臺(tái)
          找領(lǐng)班,幫助給介紹一個(gè)云云,于是領(lǐng)班就開始給他張羅
          介紹一個(gè)合適的給他,完事后,再把小姐還給領(lǐng)班,下次再來

          這個(gè)過程中,領(lǐng)班就是查詢上下文Context,領(lǐng)班的一個(gè)職能就是給客戶找到他們所要的小姐
          這就是lookup()方法,領(lǐng)班手中的小姐名錄就是JNDI//Java Naming and Directory Interface
          小姐就是EJB饑渴男是客戶端青樓是EJB容器
          看到區(qū)別了么?

          饑渴男去找小姐出臺(tái)很麻煩,不僅得找,用完后還得把小姐給還回去

          而皇帝爽翻了,什么都不用管,交給太監(jiān)去處理,控制權(quán)轉(zhuǎn)移到太監(jiān)手中去了而不是皇帝,

          必要時(shí)候由太監(jiān)給注射進(jìn)去就可以了


          posted @ 2010-12-15 11:39 Eric Gu 閱讀(1000) | 評(píng)論 (0)編輯 收藏
          /**
           *
           * @author liuguangyi
           * @content  ejb3注解的API定義在javax.persistence.*包里面。
           *
           * 注釋說明:
           * @Entity —— 將一個(gè)類聲明為一個(gè)實(shí)體bean(即一個(gè)持久化POJO類)
           * @Id —— 注解聲明了該實(shí)體bean的標(biāo)識(shí)屬性(對(duì)應(yīng)表中的主鍵)。
           * @Table —— 注解聲明了該實(shí)體bean映射指定的表(table),目錄(catalog)和schema的名字
           * @Column —— 注解聲明了屬性到列的映射。該注解有如下的屬性
           *  name  可選,列名(默認(rèn)值是屬性名)
           *  unique 可選,是否在該列上設(shè)置唯一約束(默認(rèn)值false)
           *  nullable 可選,是否設(shè)置該列的值可以為空(默認(rèn)值false)
           *  insertable 可選,該列是否作為生成的insert語句中的一個(gè)列(默認(rèn)值true)
           *  updatable 可選,該列是否作為生成的update語句中的一個(gè)列(默認(rèn)值true)
           *  columnDefinition 可選,為這個(gè)特定列覆蓋sql ddl片段(這可能導(dǎo)致無法在不同數(shù)據(jù)庫間移植)
           *  table 可選,定義對(duì)應(yīng)的表(默認(rèn)為主表)
           *  length 可選,列長度(默認(rèn)值255)
           *  precision 可選,列十進(jìn)制精度(decimal precision)(默認(rèn)值0)
           *  scale 可選,如果列十進(jìn)制數(shù)值范圍(decimal scale)可用,在此設(shè)置(默認(rèn)值0)
           * @GeneratedValue —— 注解聲明了主鍵的生成策略。該注解有如下屬性
           *  strategy 指定生成的策略(JPA定義的),這是一個(gè)GenerationType。默認(rèn)是GenerationType. AUTO
           *   GenerationType.AUTO 主鍵由程序控制
           *   GenerationType.TABLE 使用一個(gè)特定的數(shù)據(jù)庫表格來保存主鍵
           *   GenerationType.IDENTITY 主鍵由數(shù)據(jù)庫自動(dòng)生成(主要是自動(dòng)增長類型)
           *   GenerationType.SEQUENCE 根據(jù)底層數(shù)據(jù)庫的序列來生成主鍵,條件是數(shù)據(jù)庫支持序列。(這個(gè)值要與generator一起使用)
           *  generator 指定生成主鍵使用的生成器(可能是orcale中的序列)。
           * @SequenceGenerator —— 注解聲明了一個(gè)數(shù)據(jù)庫序列。該注解有如下屬性
           *  name 表示該表主鍵生成策略名稱,它被引用在@GeneratedValue中設(shè)置的“gernerator”值中
           *  sequenceName 表示生成策略用到的數(shù)據(jù)庫序列名稱。
           *  initialValue 表示主鍵初始值,默認(rèn)為0.
           *  allocationSize 每次主鍵值增加的大小,例如設(shè)置成1,則表示每次創(chuàng)建新記錄后自動(dòng)加1,默認(rèn)為50.
           * @GenericGenerator —— 注解聲明了一個(gè)hibernate的主鍵生成策略。支持十三種策略。該注解有如下屬性
           *  name 指定生成器名稱
           *  strategy 指定具體生成器的類名(指定生成策略)。
           *  parameters 得到strategy指定的具體生成器所用到的參數(shù)。
           *    其十三種策略(strategy屬性的值)如下:
           *   1.native 對(duì)于orcale采用Sequence方式,對(duì)于MySQL和SQL Server采用identity(處境主鍵生成機(jī)制),
           *     native就是將主鍵的生成工作將由數(shù)據(jù)庫完成,hibernate不管(很常用)
           *     例:@GeneratedValue(generator = "paymentableGenerator")   
           *          @GenericGenerator(name = "paymentableGenerator", strategy = "native")
           *      2.uuid 采用128位的uuid算法生成主鍵,uuid被編碼為一個(gè)32位16進(jìn)制數(shù)字的字符串。占用空間大(字符串類型)。
           *       例:@GeneratedValue(generator = "paymentableGenerator")   
           *          @GenericGenerator(name = "paymentableGenerator", strategy = "uuid")
           *      3.hilo 要在數(shù)據(jù)庫中建立一張額外的表,默認(rèn)表名為hibernate_unque_key,默認(rèn)字段為integer類型,名稱是next_hi(比較少用)
           *       例:@GeneratedValue(generator = "paymentableGenerator")   
           *          @GenericGenerator(name = "paymentableGenerator", strategy = "hilo")
           *      4.assigned 在插入數(shù)據(jù)的時(shí)候主鍵由程序處理(很常用),這是<generator>元素沒有指定時(shí)的默認(rèn)生成策略。等同于JPA中的AUTO。
           *       例:@GeneratedValue(generator = "paymentableGenerator")   
           *          @GenericGenerator(name = "paymentableGenerator", strategy = "assigned")
           *      5.identity 使用SQL Server和MySQL的自增字段,這個(gè)方法不能放到Oracle中,Oracle不支持自增字段,要設(shè)定sequence(MySQL和SQL Server中很常用)。等同于JPA中的IDENTITY
           *       例:@GeneratedValue(generator = "paymentableGenerator")   
           *          @GenericGenerator(name = "paymentableGenerator", strategy = "identity")
           *      6.select 使用觸發(fā)器生成主鍵(主要用于早期的數(shù)據(jù)庫主鍵生成機(jī)制,少用)
           *       例:@GeneratedValue(generator = "paymentableGenerator")   
           *          @GenericGenerator(name = "paymentableGenerator", strategy = "select")
           *      7.sequence 調(diào)用謹(jǐn)慎數(shù)據(jù)庫的序列來生成主鍵,要設(shè)定序列名,不然hibernate無法找到。
           *       例:@GeneratedValue(generator = "paymentableGenerator")  
           *   @GenericGenerator(name = "paymentableGenerator", strategy = "sequence",   
           *    parameters = { @Parameter(name = "sequence", value = "seq_payablemoney") })
           *  8.seqhilo 通過hilo算法實(shí)現(xiàn),但是主鍵歷史保存在Sequence中,適用于支持Sequence的數(shù)據(jù)庫,如Orcale(比較少用)
           *  例:@GeneratedValue(generator = "paymentableGenerator")  
           *    @GenericGenerator(name = "paymentableGenerator", strategy = "seqhilo",   
           *    parameters = { @Parameter(name = "max_lo", value = "5") })
           *  9.increnment 插入數(shù)據(jù)的時(shí)候hibernate會(huì)給主鍵添加一個(gè)自增的主鍵,但是一個(gè)hibernate實(shí)例就維護(hù)一個(gè)計(jì)數(shù)器,所以在多個(gè)實(shí)例運(yùn)行的時(shí)候不能使用這個(gè)方法。
           *   例:@GeneratedValue(generator = "paymentableGenerator")   
           *          @GenericGenerator(name = "paymentableGenerator", strategy = "increnment")
           *      10.foreign 使用另一個(gè)相關(guān)的對(duì)象的主鍵。通常和<one-to-one>聯(lián)合起來使用。
           *      例:@Id   
           *    @GeneratedValue(generator = "idGenerator")  
           *    @GenericGenerator(name = "idGenerator", strategy = "foreign",   
           *         parameters = { @Parameter(name = "property", value = "info") })   
           *    Integer id;
           *   @OneToOne 
           *   EmployeeInfo info;
           *  11.guid 采用數(shù)據(jù)庫底層的guid算法機(jī)制,對(duì)應(yīng)MySQL的uuid()函數(shù),SQL Server的newid()函數(shù),ORCALE的rawtohex(sys_guid())函數(shù)等
           *  例:@GeneratedValue(generator = "paymentableGenerator")    
           *    @GenericGenerator(name = "paymentableGenerator", strategy = "guid")
           *  12.uuid.hex 看uudi,建議用uuid替換
           *    例:@GeneratedValue(generator = "paymentableGenerator")    
           *    @GenericGenerator(name = "paymentableGenerator", strategy = "uuid.hex")
           *  13.sequence-identity sequence策略的擴(kuò)展,采用立即檢索策略來獲取sequence值,需要JDBC3.0和JDK4以上(含1.4)版本
           *       例:@GeneratedValue(generator = "paymentableGenerator")  
           *   @GenericGenerator(name = "paymentableGenerator", strategy = "sequence-identity",   
           *         parameters = { @Parameter(name = "sequence", value = "seq_payablemoney") }) 
           *     
           * @OneToOne 設(shè)置一對(duì)一個(gè)關(guān)聯(lián)。cascade屬性有五個(gè)值(只有CascadeType.ALL好用?很奇怪),分別是CascadeType.PERSIST(級(jí)聯(lián)新建),CascadeType.REMOVE(級(jí)聯(lián)刪除),CascadeType.REFRESH(級(jí)聯(lián)刷新),CascadeType.MERGE(級(jí)聯(lián)更新),CascadeType.ALL(全部四項(xiàng))
           *    方法一
           *     主表: ?@OneToOne(cascade = CascadeType.ALL)
           *      @PrimaryKeyJoinColumn
           *      public 從表類 get從表類(){return 從表對(duì)象}
           *      從表:沒有主表類。
           *      注意:這種方法要求主表與從表的主鍵值想對(duì)應(yīng)。
           *   方法二
           *      主表:?@OneToOne(cascade = CascadeType.ALL)
           *          @JoinColumn(name="主表外鍵") //這里指定的是數(shù)據(jù)庫中的外鍵字段。
           *          public 從表類 get從表類(){return 從表類}
           *      從表:@OneToOne(mappedBy = "主表類中的從表屬性")//例主表User中有一個(gè)從表屬性是Heart類型的heart,這里就填heart
           *          public 主表類 get主表類(){return 主表對(duì)象}
           *      注意:@JoinColumn是可選的。默認(rèn)值是從表變量名+"_"+從表的主鍵(注意,這里加的是主鍵。而不是主鍵對(duì)應(yīng)的變量)。
           * 方法三
           *      主表:@OneToOne(cascade=CascadeType.ALL)
           *          @JoinTable( name="關(guān)聯(lián)表名",
           *    joinColumns = @JoinColumn(name="主表外鍵"),
           *    inverseJoinColumns = @JoinColumns(name="從表外鍵")
           *          )
           *      從表:@OneToOne(mappedBy = "主表類中的從表屬性")//例主表User中有一個(gè)從表屬性是Heart類型的heart,這里就填heart
           *          public 主表類 get主表類(){return 主表對(duì)象}   
           *  @ManyToOne 設(shè)置多對(duì)一關(guān)聯(lián)
           *    方法一      
           *    @ManyToOne(cascade={CasCadeType.PERSIST,CascadeType.MERGE})
           *    @JoinColumn(name="外鍵")
           *    public 主表類 get主表類(){return 主表對(duì)象}
           *    方法二
           *    @ManyToOne(cascade={CascadeType.PERSIST,CascadeType.MERGE})
           *    @JoinTable(name="關(guān)聯(lián)表名",
           *     joinColumns = @JoinColumn(name="主表外鍵"),
           *     inverseJoinColumns = @JoinColumns(name="從表外鍵")
           *    )
           *  @OneToMany 設(shè)置一對(duì)多關(guān)聯(lián)。cascade屬性指定關(guān)聯(lián)級(jí)別,參考@OneToOne中的說明。fetch指定是否延遲加載,值為FetchType.LAZY表示延遲,為FetchType.EAGER表示立即加載
           *    方法一     使用這種配置,在為“一端”添加“多端”時(shí),不會(huì)修改“多端”的外鍵。在“一端”加載時(shí),不會(huì)得到“多端”。如果使用延遲加載,在讀“多端”列表時(shí)會(huì)出異常,立即加載在得到多端時(shí),是一個(gè)空集合(集合元素為0)。
           *    “一端”配置
           *    @OneToMany(mappedBy="“多端”的屬性")
           *    public List<“多端”類> get“多端”列表(){return “多端”列表}
           *    “多端”配置參考@ManyToOne.    
           *    方法二
           *    “一端”配置
           *    @OneToMany(mappedBy="“多端”的屬性")
           *    @MapKey(name="“多端”做為Key的屬性")
           *    public Map<“多端”做為Key的屬性的類,主表類> get“多端”列表(){return “多端”列表}
           *    “多端”配置參考@ManyToOne.
           *    方法三 使用這種配置,在為“一端”添加“多端”時(shí),可以修改“多端”的外鍵。
           *    “一端”配置
           *    @OneToMany
           *    @JoinColumn(name="“多端”外鍵")
           *    public List<“多端”類> get“多端”列表(){return “多端”列表}
           *    “多端”配置參考@ManyToOne.    
           *   
           *  
           */
          posted @ 2010-12-14 15:31 Eric Gu 閱讀(3106) | 評(píng)論 (0)編輯 收藏
          一、方法的重寫。

          1、重寫只能出現(xiàn)在繼承關(guān)系之中。當(dāng)一個(gè)類繼承它的父類方法時(shí),都有機(jī)會(huì)重寫該父類的方法。一個(gè)特例是父類的方法被標(biāo)識(shí)為final。重寫的主要優(yōu)點(diǎn)是能夠定義某個(gè)子類型特有的行為。
              class Animal {
                  public void eat(){
                      System.out.println ("Animal is eating.");
                  }
              }
              
              class Horse extends Animal{
                  public void eat(){
                      System.out.println ("Horse is eating.");
                  }
              }

          2、對(duì)于從父類繼承來的抽象方法,要么在子類用重寫的方式設(shè)計(jì)該方法,要么把子類也標(biāo)識(shí)為抽象的。所以抽象方法可以說是必須要被重寫的方法。

          3、重寫的意義。
          重寫方法可以實(shí)現(xiàn)多態(tài),用父類的引用來操縱子類對(duì)象,但是在實(shí)際運(yùn)行中對(duì)象將運(yùn)行其自己特有的方法。
              public class Test {
                  public static void main (String[] args) {
                      Animal h = new Horse();
                      h.eat();    
                  }
              }

              class Animal {
                  public void eat(){
                      System.out.println ("Animal is eating.");
                  }
              }
              
              class Horse extends Animal{
                  public void eat(){
                      System.out.println ("Horse is eating.");
                  }
                  public void buck(){
                  }
              }

          一個(gè)原則是:使用了什么引用,編譯器就會(huì)只調(diào)用引用類所擁有的方法。如果調(diào)用子類特有的方法,如上例的h.buck(); 編譯器會(huì)抱怨的。也就是說,編譯器只看引用類型,而不是對(duì)象類型。

          4、重寫方法的規(guī)則。
          若想實(shí)現(xiàn)一個(gè)合格重寫方法,而不是重載,那么必須同時(shí)滿足下面的要求!

          A、重寫規(guī)則之一:重寫方法不能比被重寫方法限制有更嚴(yán)格的訪問級(jí)別。
          (但是可以更廣泛,比如父類方法是包訪問權(quán)限,子類的重寫方法是public訪問權(quán)限。)
          比如:Object類有個(gè)toString()方法,開始重寫這個(gè)方法的時(shí)候我們總?cè)菀淄沺ublic修飾符,編譯器當(dāng)然不會(huì)放過任何教訓(xùn)我們的機(jī)會(huì)。出錯(cuò)的原因就是:沒有加任何訪問修飾符的方法具有包訪問權(quán)限,包訪問權(quán)限比public當(dāng)然要嚴(yán)格了,所以編譯器會(huì)報(bào)錯(cuò)的。

          B、重寫規(guī)則之二:參數(shù)列表必須與被重寫方法的相同。
          重寫有個(gè)孿生的弟弟叫重載,也就是后面要出場的。如果子類方法的參數(shù)與父類對(duì)應(yīng)的方法不同,那么就是你認(rèn)錯(cuò)人了,那是重載,不是重寫。

          C、重寫規(guī)則之三:返回類型必須與被重寫方法的返回類型相同。
          父類方法A:void eat(){}  子類方法B:int eat(){}  兩者雖然參數(shù)相同,可是返回類型不同,所以不是重寫。
          父類方法A:int eat(){}   子類方法B:long eat(){}  返回類型雖然兼容父類,但是不同就是不同,所以不是重寫。

          D、重寫規(guī)則之四:重寫方法不能拋出新的異常或者比被重寫方法聲明的檢查異常更廣的檢查異常。但是可以拋出更少,更有限或者不拋出異常。
              import java.io.*;
              public class Test {
                  public static void main (String[] args) {
                      Animal h = new Horse();
                      try {
                          h.eat();    
                      }
                      catch (Exception e) {
                      }
                  }
              }

              class Animal {
                  public void eat() throws Exception{
                      System.out.println ("Animal is eating.");
                      throw new Exception();
                  }
              }
              
              class Horse extends Animal{
                  public void eat() throws IOException{
                      System.out.println ("Horse is eating.");
                      throw new IOException();
                  }
              }
          這個(gè)例子中,父類拋出了檢查異常Exception,子類拋出的IOException是Exception的子類,也即是比被重寫的方法拋出了更有限的異常,這是可以的。如果反過來,父類拋出IOException,子類拋出更為寬泛的Exception,那么不會(huì)通過編譯的。
          注意:這種限制只是針對(duì)檢查異常,至于運(yùn)行時(shí)異常RuntimeException及其子類不再這個(gè)限制之中。

          E、重寫規(guī)則之五:不能重寫被標(biāo)識(shí)為final的方法。

          F、重寫規(guī)則之六:如果一個(gè)方法不能被繼承,則不能重寫它。
          比較典型的就是父類的private方法。下例會(huì)產(chǎn)生一個(gè)有趣的現(xiàn)象。
              public class Test {
                  public static void main (String[] args) {
                      //Animal h = new Horse();
                      Horse h = new Horse();
                      h.eat();
                  }
              }

              class Animal {
                  private void eat(){
                      System.out.println ("Animal is eating.");
                  }
              }
              
              class Horse extends Animal{
                  public void eat(){
                      System.out.println ("Horse is eating.");
                  }
              }
          這段代碼是能通過編譯的。表面上看來違反了第六條規(guī)則,但實(shí)際上那是一點(diǎn)巧合。Animal類的eat()方法不能被繼承,因此Horse類中的eat()方法是一個(gè)全新的方法,不是重寫也不是重載,只是一個(gè)只屬于Horse類的全新的方法!這點(diǎn)讓很多人迷惑了,但是也不是那么難以理解。
          main()方法如果是這樣:
              Animal h = new Horse();
              //Horse h = new Horse();
              h.eat();
          編譯器會(huì)報(bào)錯(cuò),為什么呢?Horse類的eat()方法是public的啊!應(yīng)該可以調(diào)用啊!請(qǐng)牢記,多態(tài)只看父類引用的方法,而不看子類對(duì)象的方法!


          二、方法的重載。
          重載是有好的,它不要求你在調(diào)用一個(gè)方法之前轉(zhuǎn)換數(shù)據(jù)類型,它會(huì)自動(dòng)地尋找匹配的方法。方法的重載是在編譯時(shí)刻就決定調(diào)用哪個(gè)方法了,和重寫不同。最最常用的地方就是構(gòu)造器的重載。

          1、基本數(shù)據(jù)類型參數(shù)的重載。
              public class Test {
                  static void method(byte b){
                      System.out.println ("method:byte");
                  }
                  static void method(short s){
                      System.out.println ("method:short");
                  }
                  static void method(int i){
                      System.out.println ("method:int");
                  }
                  static void method(float f){
                      System.out.println ("method:float");
                  }
                  static void method(double d){
                      System.out.println ("method:double");
                  }
                  public static void main (String[] args) {
                      method((byte)1);
                      method('c');
                      method(1);
                      method(1L);
                      method(1.1);
                      method(1.1f);
                  }
              }
          輸出結(jié)果:
          method:byte
          method:int
          method:int
          method:float
          method:double
          method:float

          可以看出:首先要尋找的是數(shù)據(jù)類型正好匹配方法。如果找不到,那么就提升為表達(dá)能力更強(qiáng)的數(shù)據(jù)類型,如上例沒有正好容納long的整數(shù)類型,那么就轉(zhuǎn)換為float類型的。如果通過提升也不能找到合適的兼容類型,那么編譯器就會(huì)報(bào)錯(cuò)。反正是不會(huì)自動(dòng)轉(zhuǎn)換為較小的數(shù)據(jù)類型的,必須自己強(qiáng)制轉(zhuǎn)換,自己來承擔(dān)轉(zhuǎn)變后果。

          char類型比較特殊,如果找不到正好匹配的類型,它會(huì)轉(zhuǎn)化為int而不是short,雖然char是16位的。


          2、重載方法的規(guī)則。

          A、被重載的方法必須改變參數(shù)列表。
          參數(shù)必須不同,這是最重要的!不同有兩個(gè)方面,參數(shù)的個(gè)數(shù),參數(shù)的類型,參數(shù)的順序。

          B、被重載的方法與返回類型無關(guān)。
          也就是說,不能通過返回類型來區(qū)分重載方法。

          C、被重載的方法可以改變?cè)L問修飾符。
          沒有重寫方法那樣嚴(yán)格的限制。

          D、被重載的方法可以聲明新的或者更廣的檢查異常。
          沒有重寫方法那樣嚴(yán)格的限制。

          E、方法能夠在一個(gè)類中或者在一個(gè)子類中被重載。


          3、帶對(duì)象引用參數(shù)的方法重載。
              class Animal {}
              class Horse extends Animal{}
              
              public class Test {
                  static void method(Animal a){
                      System.out.println ("Animal is called.");
                  }
                  static void method(Horse h){
                      System.out.println ("Horse is called.");
                  }
                  public static void main (String[] args) {
                      Animal a = new Animal();
                      Horse h = new Horse();
                      Animal ah = new Horse();
                      
                      method(a);
                      method(h);
                      method(ah);
                  }
              }
          輸出結(jié)果是:
          Animal is called.
          Horse is called.
          Animal is called.
          前兩個(gè)輸出沒有任何問題。第三個(gè)方法為什么不是輸出“Horse is called.”呢?還是那句老話,要看引用類型而不是對(duì)象類型,方法重載是在編譯時(shí)刻就決定的了,引用類型決定了調(diào)用哪個(gè)版本的重載方法。


          4、重載和重寫方法區(qū)別的小結(jié)。
          如果能徹底弄明白下面的例子,說明你對(duì)重載和重寫非常了解了,可以結(jié)束這節(jié)的復(fù)習(xí)了。
              class Animal {
                  public void eat(){
                      System.out.println ("Animal is eating.");    
                  }
              }
              class Horse extends Animal{
                  public void eat(){
                      System.out.println ("Horse is eating.");    
                  }
                  public void eat(String food){
                      System.out.println ("Horse is eating " + food);
                  }
              }
              
              public class Test {
                  public static void main (String[] args) {
                      Animal a = new Animal();
                      Horse h = new Horse();
                      Animal ah = new Horse();
                      
                      a.eat();
                      h.eat();
                      h.eat("apple");
                      ah.eat();
                      //a.eat("apple");
                      //ah.eat("apple");
                  }
              }

          四個(gè)輸出分別是什么?被注釋的兩條語句為什么不能通過編譯?
          第一條:a.eat(); 普通的方法調(diào)用,沒有多態(tài),沒什么技術(shù)含量。調(diào)用了Animal類的eat()方法,輸出:Animal is eating.
          第二條:h.eat(); 普通的方法調(diào)用,也沒什么技術(shù)含量。調(diào)用了Horse類的eat()方法,輸出:Horse is eating.
          第三條:h.eat("apple"); 重載。Horse類的兩個(gè)eat()方法重載。調(diào)用了Horse類的eat(String food)方法,輸出:Horse is eating apple
          第四條:ah.eat(); 多態(tài)。前面有例子了,不難理解。輸出:Horse is eating.
          第五條:a.eat("apple"); 低級(jí)的錯(cuò)誤,Animal類中沒有eat(String food)方法。因此不能通過編譯。
          第六條:ah.eat("apple"); 關(guān)鍵點(diǎn)就在這里。解決的方法還是那句老話,不能看對(duì)象類型,要看引用類型。Animal類中沒有eat(String food)方法。因此不能通過編譯。

          小結(jié)一下:多態(tài)不決定調(diào)用哪個(gè)重載版本;多態(tài)只有在決定哪個(gè)重寫版本時(shí)才起作用。
          重載對(duì)應(yīng)編譯時(shí),重寫對(duì)應(yīng)運(yùn)行時(shí)。夠簡潔的了吧!


          三、構(gòu)造方法。
          構(gòu)造方法是一種特殊的方法,沒有構(gòu)造方法就不能創(chuàng)建一個(gè)新對(duì)象。實(shí)際上,不僅要調(diào)用對(duì)象實(shí)際類型的構(gòu)造方法,還要調(diào)用其父類的構(gòu)造方法,向上追溯,直到Object類。構(gòu)造方法不必顯式地調(diào)用,當(dāng)使用new關(guān)鍵字時(shí),相應(yīng)的構(gòu)造方法會(huì)自動(dòng)被調(diào)用。

          1、構(gòu)造方法的規(guī)則。
          A、構(gòu)造方法能使用任何訪問修飾符。包括private,事實(shí)上java類庫有很多都是這樣的,設(shè)計(jì)者不希望使用者創(chuàng)建該類的對(duì)象。

          B、構(gòu)造方法的名稱必須與類名相同。這樣使得構(gòu)造方法與眾不同,如果我們遵守sun的編碼規(guī)范,似乎只有構(gòu)造方法的首字母是大寫的。

          C、構(gòu)造方法不能有返回類型。
          反過來說,有返回類型的不是構(gòu)造方法
              public class Test {
                  int Test(){
                      return 1;
                  }
              }
          這個(gè)方法是什么東西?一個(gè)冒充李逵的李鬼而已,int Test()和其他任何普通方法沒什么兩樣,就是普通的方法!只不過看起來很惡心,類似惡心的東西在考試卷子里比較多。

          D、如果不在類中創(chuàng)建自己的構(gòu)造方法,編譯器會(huì)自動(dòng)生成默認(rèn)的不帶參數(shù)的構(gòu)造函數(shù)。
          這點(diǎn)很容易驗(yàn)證!寫一個(gè)這樣簡單的類,編譯。
          class Test {
          }
          對(duì)生成的Test.class文件反編譯:javap Test,可以看到:
          D:"JavaCode"bin>javap Test
          Compiled from "Test.java"
          class Test extends java.lang.Object{
              Test();
          }
          看到編譯器自動(dòng)添加的默認(rèn)構(gòu)造函數(shù)了吧!

          E、如果只創(chuàng)建了帶參數(shù)的構(gòu)造方法,那么編譯器不會(huì)自動(dòng)添加無參的構(gòu)造方法的!

          F、在每個(gè)構(gòu)造方法中,如果使用了重載構(gòu)造函數(shù)this()方法,或者父類的構(gòu)造方法super()方法,那么this()方法或者super()方法必須放在第一行。而且這兩個(gè)方法只能選擇一個(gè),因此它們之間沒有順序問題。

          G、除了編譯器生成的構(gòu)造方法,而且沒有顯式地調(diào)用super()方法,那么編譯器會(huì)插入一個(gè)super()無參調(diào)用。

          H、抽象類有構(gòu)造方法。


          四、靜態(tài)方法的重載與重寫(覆蓋)。

          1、靜態(tài)方法是不能被覆蓋的。可以分兩種情況討論:

          A、子類的非靜態(tài)方法“覆蓋”父類的靜態(tài)方法。
          這種情況下,是不能通過編譯的。

          class Father{
              
          static void print(){
                  System.out.println (
          "in father  method");
              }
          }
          class Child extends Father{
              
          void print(){
                  System.out.println (
          "in child method");
              }
          }

          static方法表示該方法不關(guān)聯(lián)具體的類的對(duì)象,可以通過類名直接調(diào)用,也就是編譯的前期就綁定了,不存在后期動(dòng)態(tài)綁定,也就是不能實(shí)現(xiàn)多態(tài)。子類的非靜態(tài)方法是與具體的對(duì)象綁定的,兩者有著不同的含義。

          B、子類的靜態(tài)方法“覆蓋”父類靜態(tài)方法。
          這個(gè)覆蓋依然是帶引號(hào)的。事實(shí)上把上面那個(gè)例子Child類的print方法前面加上static修飾符,確實(shí)能通過編譯!但是不要以為這就是多態(tài)!多態(tài)的特點(diǎn)是動(dòng)態(tài)綁定,看下面的例子:

          class Father{
              
          static void print(){
                  System.out.println (
          "in father  method");
              }
          }
          class Child extends Father{
              
          static void print(){
                  System.out.println (
          "in child method");
              }
          }

          class Test{
              
          public static void main (String[] args) {
                  Father f 
          =new Child();
                  f.print();
              }
          }

          輸出結(jié)果是:in father  method
          從這個(gè)結(jié)果可以看出,并沒有實(shí)現(xiàn)多態(tài)。
          但是這種形式很迷惑人,貌似多態(tài),實(shí)際編程中千萬不要這樣搞,會(huì)把大家搞懵的!
          它不符合覆蓋表現(xiàn)出來的特性,不應(yīng)該算是覆蓋!
          總而言之,靜態(tài)方法不能被覆蓋。

          2、靜態(tài)方法可以和非靜態(tài)方法一樣被重載。
          這樣的例子太多了,我不想寫例程了。看看java類庫中很多這樣的例子。
          如java.util.Arrays類的一堆重載的binarySearch方法。
          在這里提一下是因?yàn)椴橘Y料時(shí)看到這樣的話“sun的SL275課程說,靜態(tài)方法只能控制靜態(tài)變量(他們本身沒有),靜態(tài)方法不能被重載和覆蓋……”
          大家不要相信啊!可以重載的。而且靜態(tài)與非靜態(tài)方法可以重載。

          從重載的機(jī)制很容易就理解了,重載是在編譯時(shí)刻就決定的了,非靜態(tài)方法都可以,靜態(tài)方法怎么可能不會(huì)呢?
          posted @ 2009-06-30 17:17 Eric Gu 閱讀(274) | 評(píng)論 (0)編輯 收藏
          Java的String太特別了,也太常用了,所以重要。我初學(xué)Java就被它搞蒙了,太多混淆的概念了,比如它的不變性。所以必須深入機(jī)制地去理解它。


          1、String中的每個(gè)字符都是一個(gè)16位的Unicode字符,用Unicode很容易表達(dá)豐富的國際化字符集,比如很好的中文支持。甚至Java的標(biāo)識(shí)符都可以用漢字,但是沒人會(huì)用吧(只在一本清華的《Java2實(shí)用教程》看過)。

          2、判斷空字符串。根據(jù)需要自己選擇某個(gè)或者它們的組合
              if ( s == null )    //從引用的角度
              if ( s.length() == 0 )     //從長度判別
              if ( s.trim().length () == 0 )     //是否有多個(gè)空白字符
          trim()方法的作用是是移除前導(dǎo)和尾部的Unicode值小于'"u0020'的字符,并返回“修剪”好的字符串。這種方法很常用,比如需要用戶輸入用戶名,用戶不小心加了前導(dǎo)或者尾部空格,一個(gè)好的程序應(yīng)該知道用戶不是故意的,即使是故意的也應(yīng)該智能點(diǎn)地處理。
          判斷空串是很常用的操作,但是Java類庫直到1.6才提供了isEmpty()方法。當(dāng)且僅當(dāng) length() 為 0 時(shí)返回 true。

          3、未初始化、空串""與null。它們是不同的概念。對(duì)未初始化的對(duì)象操作會(huì)被編譯器擋在門外;null是一個(gè)特殊的初始化值,是一個(gè)不指向任何對(duì)象的引用,對(duì)引用為null的對(duì)象操作會(huì)在運(yùn)行時(shí)拋出異常NullPointerException;而空串是長度為0的字符串,和別的字符串的唯一區(qū)別就是長度為0。
          例子:
              public class StringTest{
                  static String s1;
                  public static void main(String[] args) {
                      String s2;
                      String s3 = "";
                      System.out.print(s1.isEmpty());     //運(yùn)行時(shí)異常
                      System.out.print(s2.isEmpty());     //編譯出錯(cuò)
                      System.out.print(s3.isEmpty());     //ok!輸出true
                  }
              }

          4、String類的方法很多,在編寫相關(guān)代碼的時(shí)候看看JDK文檔時(shí)有好處的,要不然花了大量時(shí)間實(shí)現(xiàn)一個(gè)已經(jīng)存在的方法是很不值得的,因?yàn)榫帉憽y試、維護(hù)自己的代碼使項(xiàng)目的成本增加,利潤減少,嚴(yán)重的話會(huì)導(dǎo)致開不出工資……

          5、字符串的比較。
          Java不允許自定義操作符重載,因此字符串的比較要用compareTo() 或者 compareToIgnoreCase()。s1.compareTo(s2),返回值大于0則,則前者大;等于0,一般大;小于0,后者大。比較的依據(jù)是字符串中各個(gè)字符的Unicode值。

          6、toString()方法。
          Java的任何對(duì)象都有toString()方法,是從Object對(duì)象繼承而來的。它的作用就是讓對(duì)象在輸出時(shí)看起來更有意義,而不是奇怪的對(duì)象的內(nèi)存地址。對(duì)測試也是很有幫助的。

          7、String對(duì)象是不變的!可以變化的是String對(duì)象的引用。
          String name = "ray";
          name.concat("long");  //字符串連接
          System.out.println(name); //輸出name,ok,還是"ray"
          name = name.concat("long");  //把字符串對(duì)象連接的結(jié)果賦給了name引用
          System.out.println(name);  //輸出name,oh!,變成了"raylong"
          上述三條語句其實(shí)產(chǎn)生了3個(gè)String對(duì)象,"ray","long","raylong"。第2條語句確實(shí)產(chǎn)生了"raylong"字符串,但是沒有指定把該字符串的引用賦給誰,因此沒有改變name引用。第3條語句根據(jù)不變性,并沒有改變"ray",JVM創(chuàng)建了一個(gè)新的對(duì)象,把"ray","long"的連接賦給了name引用,因此引用變了,但是原對(duì)象沒變。

          8、String的不變性的機(jī)制顯然會(huì)在String常量內(nèi)有大量的冗余。如:"1" + "2" + "3" +......+ "n" 產(chǎn)生了n+(n+1)個(gè)String對(duì)象!因此Java為了更有效地使用內(nèi)存,JVM留出一塊特殊的內(nèi)存區(qū)域,被稱為“String常量池”。對(duì)String多么照顧啊!當(dāng)編譯器遇見String常量的時(shí)候,它檢查該池內(nèi)是否已經(jīng)存在相同的String常量。如果找到,就把新常量的引用指向現(xiàn)有的String,不創(chuàng)建任何新的String常量對(duì)象。

          那么就可能出現(xiàn)多個(gè)引用指向同一個(gè)String常量,會(huì)不會(huì)有別名的危險(xiǎn)呢?No problem!String對(duì)象的不變性可以保證不會(huì)出現(xiàn)別名問題!這是String對(duì)象與普通對(duì)象的一點(diǎn)區(qū)別。

          乍看起來這是底層的機(jī)制,對(duì)我們編程沒什么影響。而且這種機(jī)制會(huì)大幅度提高String的效率,實(shí)際上卻不是這樣。為連接n個(gè)字符串使用字符串連接操作時(shí),要消耗的時(shí)間是n的平方級(jí)!因?yàn)槊績蓚€(gè)字符串連接,它們的內(nèi)容都要被復(fù)制。因此在處理大量的字符串連接時(shí),而且要求性能時(shí),我們不要用String,StringBuffer是更好的選擇。

          8、StringBuffer類。StringBuffer類是可變的,不會(huì)在字符串常量池中,而是在堆中,不會(huì)留下一大堆無用的對(duì)象。而且它可將字符串緩沖區(qū)安全地用于多個(gè)線程。每個(gè)StringBuffer對(duì)象都有一定的容量。只要StringBuffer對(duì)象所包含的字符序列的長度沒有超出此容量,就無需分配新的內(nèi)部緩沖區(qū)數(shù)組。如果內(nèi)部緩沖區(qū)溢出,則此容量自動(dòng)增大。這個(gè)固定的容量是16個(gè)字符。我給這種算法起個(gè)名字叫“添飯算法”。先給你一滿碗飯,不夠了再給你一滿碗飯。
          例子:
              StringBuffer sb = new StringBuffer();    //初始容量為 16 個(gè)字符
              sb.append("1234");    //這是4個(gè)字符,那么16個(gè)字符的容量就足夠了,沒有溢出
              System.out.println(sb.length());    //輸出字符串長度是4
              System.out.println(sb.capacity());    //輸出該字符串緩沖區(qū)的容量是16

              sb.append("12345678901234567");        //這是17個(gè)字符,16個(gè)字符的容量不夠了,擴(kuò)容為17+16個(gè)字符的容量
              System.out.println(sb.length());    //輸出字符串長度是17
              System.out.println(sb.capacity());    //輸出該字符串緩沖區(qū)的容量是34

              sb.append("890").reverse().insert(10,"-");    
              System.out.println(sb);        //輸出0987654321-09876543214321

          字符串的長度和字符緩沖區(qū)的容量是兩個(gè)概念,注意區(qū)別。
          還有串聯(lián)的方式看起來是不是很酷!用返回值連接起來可以實(shí)現(xiàn)這種簡潔和優(yōu)雅。

          10、StringBuilder類。 從J2SE 5.0 提供了StringBuilder類,它和StringBuffer類是孿生兄弟,很像。它存在的價(jià)值在于:對(duì)字符串操作的效率更高。不足的是線程安全無法保證,不保證同步。那么兩者性能到底差多少呢?很多!
          請(qǐng)參閱:http://book.csdn.net/bookfiles/135/1001354628.shtml
          實(shí)踐:
          單個(gè)線程的時(shí)候使用StringBuilder類,以提高效率,而且它的API和StringBuffer兼容,不需要額外的學(xué)習(xí)成本,物美價(jià)廉。多線程時(shí)使用StringBuffer,以保證安全。

          11、字符串的比較。
          下面這條可能會(huì)讓你暈,所以你可以選擇看或者不看。它不會(huì)對(duì)你的職業(yè)生涯造成任何影響。而且謹(jǐn)記一條,比較字符串要用equals()就ok了!一旦用了“==”就會(huì)出現(xiàn)很怪異的現(xiàn)象。之所以把這部分放在最后,是想節(jié)省大家的時(shí)間,因?yàn)檫@條又臭又長。推薦三種人:一、沒事閑著型。二、想深入地理解Java的字符串,即使明明知道學(xué)了也沒用。三、和我一樣愛好研究“茴”字有幾種寫法。

          還是那句老話,String太特殊了,以至于某些規(guī)則對(duì)String不起作用。個(gè)人感覺這種特殊性并不好。看例子:
          例子A:
              String str1 = "java";
              String str2 = "java";
              System.out.print(str1==str2);
          地球上有點(diǎn)Java基礎(chǔ)的人都知道會(huì)輸出false,因?yàn)?=比較的是引用,equals比較的是內(nèi)容。不是我忽悠大家,你們可以在自己的機(jī)子上運(yùn)行一下,結(jié)果是true!原因很簡單,String對(duì)象被放進(jìn)常量池里了,再次出現(xiàn)“java”字符串的時(shí)候,JVM很興奮地把str2的引用也指向了“java”對(duì)象,它認(rèn)為自己節(jié)省了內(nèi)存開銷。不難理解吧 呵呵
          例子B:
              String str1 = new String("java");
              String str2 = new String("java");
              System.out.print(str1==str2);
          看過上例的都學(xué)聰明了,這次肯定會(huì)輸出true!很不幸,JVM并沒有這么做,結(jié)果是false。原因很簡單,例子A中那種聲明的方式確實(shí)是在String常量池創(chuàng)建“java”對(duì)象,但是一旦看到new關(guān)鍵字,JVM會(huì)在堆中為String分配空間。兩者聲明方式貌合神離,這也是我把“如何創(chuàng)建字符串對(duì)象”放到后面來講的原因。大家要沉住氣,還有一個(gè)例子。
          例子C:
              String str1 = "java";
              String str2 = "blog";
              String s = str1+str2;
              System.out.print(s=="javablog");
          再看這個(gè)例子,很多同志不敢妄言是true還是false了吧。愛玩腦筋急轉(zhuǎn)彎的人會(huì)說是false吧……恭喜你,你會(huì)搶答了!把那個(gè)“吧”字去掉你就完全正確。原因很簡單,JVM確實(shí)會(huì)對(duì)型如String str1 = "java"; 的String對(duì)象放在字符串常量池里,但是它是在編譯時(shí)刻那么做的,而String s = str1+str2; 是在運(yùn)行時(shí)刻才能知道(我們當(dāng)然一眼就看穿了,可是Java必須在運(yùn)行時(shí)才知道的,人腦和電腦的結(jié)構(gòu)不同),也就是說str1+str2是在堆里創(chuàng)建的,s引用當(dāng)然不可能指向字符串常量池里的對(duì)象。沒崩潰的人繼續(xù)看例子D。
          例子D:
              String s1 = "java";
              String s2 = new String("java");
              System.out.print(s1.intern()==s2.intern());
          intern()是什么東東?反正結(jié)果是true。如果沒用過這個(gè)方法,而且訓(xùn)練有素的程序員會(huì)去看JDK文檔了。簡單點(diǎn)說就是用intern()方法就可以用“==”比較字符串的內(nèi)容了。在我看到intern()方法到底有什么用之前,我認(rèn)為它太多余了。其實(shí)我寫的這一條也很多余,intern()方法還存在諸多的問題,如效率、實(shí)現(xiàn)上的不統(tǒng)一……
          例子E:
              String str1 = "java";
              String str2 = new String("java");
              System.out.print(str1.equals(str2));
          無論在常量池還是堆中的對(duì)象,用equals()方法比較的就是內(nèi)容,就這么簡單!看完此條的人一定很后悔,但是在開始我勸你別看了……

          后記:用彪哥的話說“有意思嗎?”,確實(shí)沒勁。在寫這段的時(shí)候我也是思量再三,感覺自己像孔乙己炫耀“茴”字有幾種寫法。我查了一下茴 ,回,囘,囬,還有一種是“口”字里面有個(gè)“目”字,后面這四個(gè)都加上草字頭……
          posted @ 2009-06-30 15:34 Eric Gu 閱讀(220) | 評(píng)論 (0)編輯 收藏

          快速入門

          (1)模板 + 數(shù)據(jù)模型 = 輸出

          FreeMarker基于設(shè)計(jì)者和程序員是具有不同專業(yè)技能的不同個(gè)體的觀念他們是分工勞動(dòng)的:
          設(shè)計(jì)者專注于表示——?jiǎng)?chuàng)建HTML文件、圖片、Web頁面的其它可視化方面;
          程序員創(chuàng)建系統(tǒng),生成設(shè)計(jì)頁面要顯示的數(shù)據(jù)。
          經(jīng)常會(huì)遇到的問題是:在Web頁面(或其它類型的文檔)中顯示的信息在設(shè)計(jì)頁面時(shí)是無效的,是基于動(dòng)態(tài)數(shù)據(jù)的。在這里,你可以在HTML(或其它要輸出的文本)中加入一些特定指令,F(xiàn)reeMarker會(huì)在輸出頁面給最終用戶時(shí),用適當(dāng)?shù)臄?shù)據(jù)替代這些代碼。


          先來解釋一下freemaker的基本語法了,
          <# ... > 中存放所有freemaker的內(nèi)容,之外的內(nèi)容全部原樣輸出。
          <@ ... /> 是函數(shù)調(diào)用
          兩個(gè)定界符內(nèi)的內(nèi)容中,第一個(gè)符號(hào)表示指令或者函數(shù)名,其后的跟隨參數(shù)。freemaker提供的控制包括如下:
          <#if condition><#elseif condition><#else> 條件判斷
          <#list hash_or_seq as var> 遍歷hash表或者collection(freemaker稱作sequence)的成員
          <#macro name param1 param2 ... ><#nested param> 宏,無返回參數(shù)
          <#function name param1 param2><#return val>函數(shù),有返回參數(shù)
          var?member_function(...) 用函數(shù)對(duì)var進(jìn)行轉(zhuǎn)換,freemaker稱為build-ins。實(shí)際內(nèi)部實(shí)現(xiàn)類似member_function(var, ...)
          stringA[M .. N] 取子字符串,類似substring(stringA, M, N)
          {key:value, key2:value2 ...} 直接定義一個(gè)hash表
          [item0, item1, item2 ...] 直接定義一個(gè)序列
          hash0[key0] 存取hash表中key對(duì)應(yīng)的元素
          seq0[5] 存取序列指定下標(biāo)的元素
          <@function1 param0 param1 ... /> 調(diào)用函數(shù)function1
          <@macro0 param0 param1 ; nest_param0 nest_param1 ...> nest_body <
          /@macro> 調(diào)用宏,并處理宏的嵌套
          <#assign var = value > 定義變量并初始化
          <#local var = value> 在 macro 或者 function 中定義局部變量并初始化
          <#global var = value > 定義全局變量并初始化
          ${var} 輸出并替換為表達(dá)式的值
          <#visit xmlnode> 調(diào)用macro匹配xmlnode本身及其子節(jié)點(diǎn)
          <#recurse xmlnode> 調(diào)用macro匹配xmlnode的子節(jié)點(diǎn)


          下面是一個(gè)例子:

          <html>
          <head>
          <title>Welcome!</title>
          </head>
          <body>
          <h1>Welcome ${user}!</h1>
          <p>Our latest product:
          <a href="${latestProduct.url}">${latestProduct.name}</a>!
          </body>
          </html>
          這個(gè)例子是在簡單的HTML中加入了一些由${…}包圍的特定代碼,這些特定代碼是FreeMarker的指令,而包含F(xiàn)reeMarker的指令的文件就稱為模板(Template)。
          至于user、latestProduct.url和latestProduct.name來自于數(shù)據(jù)模型(data model)。
          數(shù)據(jù)模型由程序員編程來創(chuàng)建,向模板提供變化的信息,這些信息來自于數(shù)據(jù)庫、文件,甚至于在程序中直接生成。
          模板設(shè)計(jì)者不關(guān)心數(shù)據(jù)從那兒來,只知道使用已經(jīng)建立的數(shù)據(jù)模型。

          下面是一個(gè)可能的數(shù)據(jù)模型:

          (root)
          |
          +- user = "Big Joe"
          |
          +- latestProduct
          |
          +- url = "products/greenmouse.html"
          |
          +- name = "green mouse"
          數(shù)據(jù)模型類似于計(jì)算機(jī)的文件系統(tǒng),latestProduct可以看作是目錄。

          2、數(shù)據(jù)模型

          (1)基礎(chǔ)

          在快速入門中介紹了在模板中使用的三種基本對(duì)象類型:scalars、hashes 和sequences,其實(shí)還可以有其它更多的能力:

          • scalars:存儲(chǔ)單值
          • hashes:充當(dāng)其它對(duì)象的容器,每個(gè)都關(guān)聯(lián)一個(gè)唯一的查詢名字
          • sequences:充當(dāng)其它對(duì)象的容器,按次序訪問
          • 方法:通過傳遞的參數(shù)進(jìn)行計(jì)算,以新對(duì)象返回結(jié)果
          • 用戶自定義FTL標(biāo)記:宏和變換器

          通常每個(gè)變量只具有上述的一種能力,但一個(gè)變量可以具有多個(gè)上述能力,如下面的例子:

          (root)
          |
          +- mouse = "Yerri"
          |
          +- age = 12
          |
          +- color = "brown">
          mouse既是scalars又是hashes,將上面的數(shù)據(jù)模型合并到下面的模板:
          ${mouse}       <#-- use mouse as scalar -->
          ${mouse.age} <#-- use mouse as hash -->
          ${mouse.color} <#-- use mouse as hash -->
          輸出結(jié)果是:
          Yerri
          12
          brown

          (2)Scalar變量

          Scalar變量存儲(chǔ)單值,可以是:

          • 字符串:簡單文本,在模板中使用引號(hào)(單引號(hào)或雙引號(hào))括起
          • 數(shù)字:在模板中直接使用數(shù)字值
          • 日期:存儲(chǔ)日期/時(shí)間相關(guān)的數(shù)據(jù),可以是日期、時(shí)間或日期-時(shí)間(Timestamp);通常情況,日期值由程序員加到數(shù)據(jù)模型中,設(shè)計(jì)者只需要顯示它們
          • 布爾值:true或false,通常在<#if …>標(biāo)記中使用

          (3)hashes 、sequences和集合

          有些變量不包含任何可顯示的內(nèi)容,而是作為容器包含其它變量,者有兩種類型:

          • hashes:具有一個(gè)唯一的查詢名字和它包含的每個(gè)變量相關(guān)聯(lián)
          • sequences:使用數(shù)字和它包含的每個(gè)變量相關(guān)聯(lián),索引值從0開始

          集合變量通常類似sequences,除非無法訪問它的大小和不能使用索引來獲得它的子變量;集合可以看作只能由<#list …>指令使用的受限sequences

          (4)方法

          方法變量通常是基于給出的參數(shù)計(jì)算值。

          下面的例子假設(shè)程序員已經(jīng)將方法變量avg放到數(shù)據(jù)模型中,用來計(jì)算數(shù)字平均值:

          The average of 3 and 5 is: ${avg(3, 5)}
          The average of 6 and 10 and 20 is: ${avg(6, 10, 20)}
          The average of the price of python and elephant is:
          ${avg(animals.python.price, animals.elephant.price)}

          (5)宏和變換器

          宏和變換器變量是用戶自定義指令(自定義FTL標(biāo)記),會(huì)在后面講述這些高級(jí)特性

          (6)節(jié)點(diǎn)

          節(jié)點(diǎn)變量表示為樹型結(jié)構(gòu)中的一個(gè)節(jié)點(diǎn),通常在XML處理中使用,會(huì)在后面的專門章節(jié)中講

          3、模板

          (1)整體結(jié)構(gòu)

          模板使用FTL(FreeMarker模板語言)編寫,是下面各部分的一個(gè)組合:

          • 文本:直接輸出
          • Interpolation:由${和},或#{和}來限定,計(jì)算值替代輸出
          • FTL標(biāo)記:FreeMarker指令,和HTML標(biāo)記類似,名字前加#予以區(qū)分,不會(huì)輸出
          • 注釋:由<#--和-->限定,不會(huì)輸出

          下面是以一個(gè)具體模板例子:

          <html>
          <head>
          <title>Welcome!</title>
          </head>
          <body>
          <#-- Greet the user with his/her name -->
          <h1>Welcome ${user}!</h1>
          <p>We have these animals:
          <ul>
          <#list animals as being>
          <li>${being.name} for ${being.price} Euros
          </#list>
          </ul>
          </body>
          </html>

          注意事項(xiàng):

          • FTL區(qū)分大小寫,所以list是正確的FTL指令,而List不是;${name}和${NAME}是不同的
          • Interpolation只能在文本中使用
          • FTL標(biāo)記不能位于另一個(gè)FTL標(biāo)記內(nèi)部,例如:
          <#if <#include 'foo'>='bar'>...</if>
          • 注釋可以位于FTL標(biāo)記和Interpolation內(nèi)部,如下面的例子:
          <h1>Welcome ${user <#-- The name of user -->}!</h1>
          <p>We have these animals:
          <ul>
          <#list <#-- some comment... --> animals as <#-- again... --> being>
          ...
          • 余的空白字符會(huì)在模板輸出時(shí)移除

          (2)指令

          在FreeMarker中,使用FTL標(biāo)記引用指令。有三種FTL標(biāo)記,這和HTML標(biāo)記是類似的:

          • 開始標(biāo)記:<#directivename parameters>
          • 結(jié)束標(biāo)記:</#directivename>
          • 空內(nèi)容指令標(biāo)記:<#directivename parameters/>

          有兩種類型的指令:預(yù)定義指令和用戶定義指令。

          用戶定義指令要使用@替換#,如<@mydirective>...</@mydirective>(會(huì)在后面講述)。

          FTL標(biāo)記不能夠交叉,而應(yīng)該正確的嵌套,如下面的代碼是錯(cuò)誤的:

          <ul>
          <#list animals as being>
          <li>${being.name} for ${being.price} Euros
          <#if use = "Big Joe">
          (except for you)
          </#list>
          </#if> <#-- WRONG! -->
          </ul>
          如果使用不存在的指令,F(xiàn)reeMarker不會(huì)使用模板輸出,而是產(chǎn)生一個(gè)錯(cuò)誤消息。

          FreeMarker會(huì)忽略FTL標(biāo)記中的空白字符,如下面的例子:

          <#list
          animals as
          being
          >
          ${being.name} for ${being.price} Euros
          </#list >
          但是,<、</和指令之間不允許有空白字符。

          (3)表達(dá)式

          直接指定值

          • 字符串
          使用單引號(hào)或雙引號(hào)限定

          如果包含特殊字符需要轉(zhuǎn)義,如下面的例子:

          ${"It's \"quoted\" and
          this is a backslash: \\"}

          ${'It\'s "quoted" and
          this is a backslash: \\'}
          輸出結(jié)果是:
          It's "quoted" and
          this is a backslash: \

          It's "quoted" and
          this is a backslash: \
          下面是支持的轉(zhuǎn)義序列:
          轉(zhuǎn)義序列 含義
          \" 雙引號(hào)(u0022)
          \' 單引號(hào)(u0027)

          反斜杠(u005C)
          \n 換行(u000A)
          \r Return (u000D)
          \t Tab (u0009)
          \b Backspace (u0008)
          \f Form feed (u000C)
          \l <
          \g >
          \a &
          \{ {
          \xCode 4位16進(jìn)制Unicode代碼

          有一類特殊的字符串稱為raw字符串,被認(rèn)為是純文本,其中的\和{等不具有特殊含義,該類字符串在引號(hào)前面加r,下面是一個(gè)例子:

          ${r"${foo}"}

          ${r"C:\foo\bar"}
          輸出的結(jié)果是:
          ${foo}

          C:\foo\bar
          • 數(shù)字

          直接輸入,不需要引號(hào)

          精度數(shù)字使用“.”分隔,不能使用分組符號(hào)

          目前版本不支持科學(xué)計(jì)數(shù)法,所以“1E3”是錯(cuò)誤的

          不能省略小數(shù)點(diǎn)前面的0,所以“.5”是錯(cuò)誤的

          數(shù)字8、+8、08和8.00都是相同的

          • 布爾值

          true和false,不使用引號(hào)

          • 序列

          由逗號(hào)分隔的子變量列表,由方括號(hào)限定,下面是一個(gè)例子:

          <#list ["winter", "spring", "summer", "autumn"] as x>
          ${x}
          </#list>
          輸出的結(jié)果是:
          winter
          spring
          summer
          autumn
          列表的項(xiàng)目是表達(dá)式,所以可以有下面的例子:
          [2 + 2, [1, 2, 3, 4], "whatnot"]
          可以使用數(shù)字范圍定義數(shù)字序列,例如2..5等同于[2, 3, 4, 5],但是更有效率,注意數(shù)字范圍沒有方括號(hào)

          可以定義反遞增的數(shù)字范圍,如5..2

          • 散列(hash)
          由逗號(hào)分隔的鍵/值列表,由大括號(hào)限定,鍵和值之間用冒號(hào)分隔,下面是一個(gè)例子:
          {"name":"green mouse", "price":150}
          鍵和值都是表達(dá)式,但是鍵必須是字符串

          獲取變量

          • 頂層變量: ${variable},變量名只能是字母、數(shù)字、下劃線、$、@和#的組合,且不能以數(shù)字開頭
          • 從散列中獲取數(shù)據(jù)

          可以使用點(diǎn)語法或方括號(hào)語法,假設(shè)有下面的數(shù)據(jù)模型:

          (root)
          |
          +- book
          | |
          | +- title = "Breeding green mouses"
          | |
          | +- author
          | |
          | +- name = "Julia Smith"
          | |
          | +- info = "Biologist, 1923-1985, Canada"
          |
          +- test = "title"
          下面都是等價(jià)的:
          book.author.name
          book["author"].name
          book.author.["name"]
          book["author"]["name"]
          使用點(diǎn)語法,變量名字有頂層變量一樣的限制,但方括號(hào)語法沒有該限制,因?yàn)槊质侨我獗磉_(dá)式的結(jié)果
          • 從序列獲得數(shù)據(jù):和散列的方括號(hào)語法語法一樣,只是方括號(hào)中的表達(dá)式值必須是數(shù)字;注意:第一個(gè)項(xiàng)目的索引是0

          序列片斷:使用[startIndex..endIndex]語法,從序列中獲得序列片斷(也是序列);startIndex和endIndex是結(jié)果為數(shù)字的表達(dá)式

          • 特殊變量:FreeMarker內(nèi)定義變量,使用.variablename語法訪問

          字符串操作

          • Interpolation(或連接操作)

          可以使用${..}(或#{..})在文本部分插入表達(dá)式的值,例如:

          ${"Hello ${user}!"}

          ${"${user}${user}${user}${user}"}
          可以使用+操作符獲得同樣的結(jié)果
          ${"Hello " + user + "!"}

          ${user + user + user + user}
          ${..}只能用于文本部分,下面的代碼是錯(cuò)誤的:
          <#if ${isBig}>Wow!</#if>

          <#if "${isBig}">Wow!</#if>
          應(yīng)該寫成:
          <#if isBig>Wow!</#if>
          • 子串

          例子(假設(shè)user的值為“Big Joe”):

          ${user[0]}${user[4]}

          ${user[1..4]}
          結(jié)果是(注意第一個(gè)字符的索引是0):
          BJ

          ig J
          序列操作
          • 連接操作:和字符串一樣,使用+,下面是一個(gè)例子:
          <#list ["Joe", "Fred"] + ["Julia", "Kate"] as user>
          - ${user}
          </#list>
          輸出結(jié)果是:
          - Joe
          - Fred
          - Julia
          - Kate
          散列操作
          • 連接操作:和字符串一樣,使用+,如果具有相同的key,右邊的值替代左邊的值,例如:
          <#assign ages = {"Joe":23, "Fred":25} + {"Joe":30, "Julia":18}>
          - Joe is ${ages.Joe}
          - Fred is ${ages.Fred}
          - Julia is ${ages.Julia}
          輸出結(jié)果是:
          - Joe is 30
          - Fred is 25
          - Julia is 18
          算術(shù)運(yùn)算
          • +、-、×、/、%,下面是一個(gè)例子:
          ${x * x - 100}
          ${x / 2}
          ${12 % 10}
          輸出結(jié)果是(假設(shè)x為5):
          -75
          2.5
          2
          操作符兩邊必須是數(shù)字,因此下面的代碼是錯(cuò)誤的:
          ${3 * "5"} <#-- WRONG! -->  
          使用+操作符時(shí),如果一邊是數(shù)字,一邊是字符串,就會(huì)自動(dòng)將數(shù)字轉(zhuǎn)換為字符串,例如:
          ${3 + "5"}  
          輸出結(jié)果是:
          35
          使用內(nèi)建的int(后面講述)獲得整數(shù)部分,例如:
          ${(x/2)?int}
          ${1.1?int}
          ${1.999?int}
          ${-1.1?int}
          ${-1.999?int}
          輸出結(jié)果是(假設(shè)x為5):
          2
          1
          1
          -1
          -1
          • 比較操作符

          使用=(或==,完全相等)測試兩個(gè)值是否相等,使用!= 測試兩個(gè)值是否不相等

          =和!=兩邊必須是相同類型的值,否則會(huì)產(chǎn)生錯(cuò)誤,例如<#if 1 = "1">會(huì)引起錯(cuò)誤

          Freemarker是精確比較,所以對(duì)"x"、"x "和"X"是不相等的

          對(duì)數(shù)字和日期可以使用<、<=、>和>=,但不能用于字符串

          由于Freemarker會(huì)將>解釋成FTL標(biāo)記的結(jié)束字符,所以對(duì)于>和>=可以使用括號(hào)來避免這種情況,例如<#if (x > y)>

          另一種替代的方法是,使用lt、lte、gt和gte來替代<、<=、>和>=

          • 邏輯操作符

          &&(and)、||(or)、!(not),只能用于布爾值,否則會(huì)產(chǎn)生錯(cuò)誤

          例子:

          <#if x < 12 && color = "green">
          We have less than 12 things, and they are green.
          </#if>
          <#if !hot> <#-- here hot must be a boolean -->
          It's not hot.
          </#if>
          • 內(nèi)建函數(shù)

          內(nèi)建函數(shù)的用法類似訪問散列的子變量,只是使用“?”替代“.”,下面列出常用的一些函數(shù)

            • 字符串使用的:

          html:對(duì)字符串進(jìn)行HTML編碼

          cap_first:使字符串第一個(gè)字母大寫

          lower_case:將字符串轉(zhuǎn)換成小寫

          upper_case:將字符串轉(zhuǎn)換成大寫

          trim:去掉字符串前后的空白字符

            • 序列使用的:

          size:獲得序列中元素的數(shù)目

            • 數(shù)字使用的:

          int:取得數(shù)字的整數(shù)部分(如-1.9?int的結(jié)果是-1)

          例子(假設(shè)test保存字符串"Tom & Jerry"):

          ${test?html}
          ${test?upper_case?html}
          輸出結(jié)果是:
          Tom &amp; Jerry
          TOM &amp; JERRY
          • 操作符優(yōu)先順序
          操作符組 操作符
          后綴 [subvarName] [subStringRange] . (methodParams)
          一元 +expr、-expr、!
          內(nèi)建 ?
          乘法 *、 / 、%
          加法 +、-
          關(guān)系 <、>、<=、>=(lt、lte、gt、gte)
          相等 ==(=)、!=
          邏輯and &&
          邏輯or 雙豎線
          數(shù)字范圍 ..

          (4)Interpolation

          Interpolation有兩種類型:

          1. 通用Interpolation:${expr}
          1. 數(shù)字Interpolation:#{expr}或#{expr; format}

          注意:Interpolation只能用于文本部分

          • 通用Interpolation

          插入字符串值:直接輸出表達(dá)式結(jié)果

          插入數(shù)字值:根據(jù)缺省格式(由#setting指令設(shè)置)將表達(dá)式結(jié)果轉(zhuǎn)換成文本輸出;可以使用內(nèi)建函數(shù)string格式化單個(gè)Interpolation,下面是一個(gè)例子:

          <#setting number_format="currency"/>
          <#assign answer=42/>
          ${answer}
          ${answer?string} <#-- the same as ${answer} -->
          ${answer?string.number}
          ${answer?string.currency}
          ${answer?string.percent}
          輸出結(jié)果是:
          $42.00
          $42.00
          42
          $42.00
          4,200%
          插入日期值:根據(jù)缺省格式(由#setting指令設(shè)置)將表達(dá)式結(jié)果轉(zhuǎn)換成文本輸出;可以使用內(nèi)建函數(shù)string格式化單個(gè)Interpolation,下面是一個(gè)使用格式模式的例子:
          ${lastUpdated?string("yyyy-MM-dd HH:mm:ss zzzz")}
          ${lastUpdated?string("EEE, MMM d, ''yy")}
          ${lastUpdated?string("EEEE, MMMM dd, yyyy, hh:mm:ss a '('zzz')'")}
          輸出的結(jié)果類似下面的格式:
          2003-04-08 21:24:44 Pacific Daylight Time
          Tue, Apr 8, '03
          Tuesday, April 08, 2003, 09:24:44 PM (PDT)
          插入布爾值:根據(jù)缺省格式(由#setting指令設(shè)置)將表達(dá)式結(jié)果轉(zhuǎn)換成文本輸出;可以使用內(nèi)建函數(shù)string格式化單個(gè)Interpolation,下面是一個(gè)例子:
          <#assign foo=true/>
          ${foo?string("yes", "no")}
          輸出結(jié)果是:
          yes
          • 數(shù)字Interpolation的#{expr; format}形式可以用來格式化數(shù)字,format可以是:

          mX:小數(shù)部分最小X位

          MX:小數(shù)部分最大X位

          例子:

          <#-- If the language is US English the output is: -->
          <#assign x=2.582/>
          <#assign y=4/>
          #{x; M2} <#-- 2.58 -->
          #{y; M2} <#-- 4 -->
          #{x; m1} <#-- 2.6 -->
          #{y; m1} <#-- 4.0 -->
          #{x; m1M2} <#-- 2.58 -->
          #{y; m1M2} <#-- 4.0 -->

          4、雜項(xiàng)

          (1)用戶定義指令

          宏和變換器變量是兩種不同類型的用戶定義指令,它們之間的區(qū)別是宏是在模板中使用macro指令定義,而變換器是在模板外由程序定義,這里只介紹宏

          • 基本用法

          宏是和某個(gè)變量關(guān)聯(lián)的模板片斷,以便在模板中通過用戶定義指令使用該變量,下面是一個(gè)例子:

          <#macro greet>
          <font size="+2">Hello Joe!</font>
          </#macro>
          作為用戶定義指令使用宏變量時(shí),使用@替代FTL標(biāo)記中的#
          <@greet></@greet>
          如果沒有體內(nèi)容,也可以使用:
          <@greet/>
          • 參數(shù)

          在macro指令中可以在宏變量之后定義參數(shù),如:

          <#macro greet person>
          <font size="+2">Hello ${person}!</font>
          </#macro>
          可以這樣使用這個(gè)宏變量:
          <@greet person="Fred"/> and <@greet person="Batman"/> 
          輸出結(jié)果是:
            <font size="+2">Hello Fred!</font>

          and <font size="+2">Hello Batman!</font>

          宏的參數(shù)是FTL表達(dá)式,所以下面的代碼具有不同的意思:

          <@greet person=Fred/>
          這意味著將Fred變量的值傳給person參數(shù),該值不僅是字符串,還可以是其它類型,甚至是復(fù)雜的表達(dá)式

          可以有多參數(shù),下面是一個(gè)例子:

          <#macro greet person color>
          <font size="+2" color="${color}">Hello ${person}!</font>
          </#macro>
          可以這樣使用該宏變量:
          <@greet person="Fred" color="black"/> 
          其中參數(shù)的次序是無關(guān)的,因此下面是等價(jià)的:
          <@greet color="black" person="Fred"/>
          只能使用在macro指令中定義的參數(shù),并且對(duì)所有參數(shù)賦值,所以下面的代碼是錯(cuò)誤的:
          <@greet person="Fred" color="black" background="green"/>
          <@greet person="Fred"/>
          可以在定義參數(shù)時(shí)指定缺省值,如:
          <#macro greet person color="black">
          <font size="+2" color="${color}">Hello ${person}!</font>
          </#macro>
          這樣<@greet person="Fred"/>就正確了

          宏的參數(shù)是局部變量,只能在宏定義中有效

          • 嵌套內(nèi)容

          用戶定義指令可以有嵌套內(nèi)容,使用<#nested>指令執(zhí)行指令開始和結(jié)束標(biāo)記之間的模板片斷

          例子:

          <#macro border>
          <table border=4 cellspacing=0 cellpadding=4><tr><td>
          <#nested>
          </tr></td></table>
          </#macro>
          這樣使用該宏變量:
          <@border>The bordered text</@border>
          輸出結(jié)果:
            <table border=4 cellspacing=0 cellpadding=4><tr><td>
          The bordered text
          </tr></td></table>

          <#nested>指令可以被多次調(diào)用,例如:

          <#macro do_thrice>
          <#nested>
          <#nested>
          <#nested>
          </#macro>
          <@do_thrice>
          Anything.
          </@do_thrice>
          輸出結(jié)果:
            Anything.
          Anything.
          Anything.
          嵌套內(nèi)容可以是有效的FTL,下面是一個(gè)有些復(fù)雜的例子: <@border> <ul> <@do_thrice> <li><@greet person="Joe"/> </@do_thrice> </ul> </@border> }}} 輸出結(jié)果:
            <table border=4 cellspacing=0 cellpadding=4><tr><td>
          <ul>
          <li><font size="+2">Hello Joe!</font>
          <li><font size="+2">Hello Joe!</font>
          <li><font size="+2">Hello Joe!</font>
          </ul>
          </tr></td></table>
          宏定義中的局部變量對(duì)嵌套內(nèi)容是不可見的,例如:
          <#macro repeat count>
          <#local y = "test">
          <#list 1..count as x>
          ${y} ${count}/${x}: <#nested>
          </#list>
          </#macro>
          <@repeat count=3>${y?default("?")} ${x?default("?")} ${count?default("?")}</@repeat>
          輸出結(jié)果:
              test 3/1: ? ? ?
          test 3/2: ? ? ?
          test 3/3: ? ? ?
          • 在宏定義中使用循環(huán)變量

          用戶定義指令可以有循環(huán)變量,通常用于重復(fù)嵌套內(nèi)容,基本用法是:作為nested指令的參數(shù)傳遞循環(huán)變量的實(shí)際值,而在調(diào)用用戶定義指令時(shí),在<@…>開始標(biāo)記的參數(shù)后面指定循環(huán)變量的名字

          例子:

          <#macro repeat count>
          <#list 1..count as x>
          <#nested x, x/2, x==count>
          </#list>
          </#macro>
          <@repeat count=4 ; c, halfc, last>
          ${c}. ${halfc}<#if last> Last!</#if>
          </@repeat>
          輸出結(jié)果:
            1. 0.5
          2. 1
          3. 1.5
          4. 2 Last!

          指定的循環(huán)變量的數(shù)目和用戶定義指令開始標(biāo)記指定的不同不會(huì)有問題

          調(diào)用時(shí)少指定循環(huán)變量,則多指定的值不可見

          調(diào)用時(shí)多指定循環(huán)變量,多余的循環(huán)變量不會(huì)被創(chuàng)建

          (2)在模板中定義變量

          在模板中定義的變量有三種類型:

          • plain變量:可以在模板的任何地方訪問,包括使用include指令插入的模板,使用assign指令創(chuàng)建和替換
          • 局部變量:在宏定義體中有效,使用local指令創(chuàng)建和替換
          • 循環(huán)變量:只能存在于指令的嵌套內(nèi)容,由指令(如list)自動(dòng)創(chuàng)建

          宏的參數(shù)是局部變量,而不是循環(huán)變量;局部變量隱藏(而不是覆蓋)同名的plain變量;循環(huán)變量隱藏同名的局部變量和plain變量,下面是一個(gè)例子:

          <#assign x = "plain">
          1. ${x} <#-- we see the plain var. here -->
          <@test/>
          6. ${x} <#-- the value of plain var. was not changed -->
          <#list ["loop"] as x>
          7. ${x} <#-- now the loop var. hides the plain var. -->
          <#assign x = "plain2"> <#-- replace the plain var, hiding does not mater here -->
          8. ${x} <#-- it still hides the plain var. -->
          </#list>
          9. ${x} <#-- the new value of plain var. -->
          <#macro test>
          2. ${x} <#-- we still see the plain var. here -->
          <#local x = "local">
          3. ${x} <#-- now the local var. hides it -->
          <#list ["loop"] as x>
          4. ${x} <#-- now the loop var. hides the local var. -->
          </#list>
          5. ${x} <#-- now we see the local var. again -->
          </#macro>
          輸出結(jié)果:
          1. plain
          2. plain
          3. local
          4. loop
          5. local
          6. plain
          7. loop
          8. loop
          9. plain2

          內(nèi)部循環(huán)變量隱藏同名的外部循環(huán)變量,如:

          <#list ["loop 1"] as x>
          ${x}
          <#list ["loop 2"] as x>
          ${x}
          <#list ["loop 3"] as x>
          ${x}
          </#list>
          ${x}
          </#list>
          ${x}
          </#list>
          輸出結(jié)果:
            loop 1
          loop 2
          loop 3
          loop 2
          loop 1
          模板中的變量會(huì)隱藏(而不是覆蓋)數(shù)據(jù)模型中同名變量,如果需要訪問數(shù)據(jù)模型中的同名變量,使用特殊變量global,下面的例子假設(shè)數(shù)據(jù)模型中的user的值是Big Joe:
          <#assign user = "Joe Hider">
          ${user} <#-- prints: Joe Hider -->
          ${.globals.user} <#-- prints: Big Joe -->

          (3)名字空間

          通常情況,只使用一個(gè)名字空間,稱為主名字空間

          為了創(chuàng)建可重用的宏、變換器或其它變量的集合(通常稱庫),必須使用多名字空間,其目的是防止同名沖突

          • 創(chuàng)建庫

          下面是一個(gè)創(chuàng)建庫的例子(假設(shè)保存在lib/my_test.ftl中):

          <#macro copyright date>
          <p>Copyright (C) ${date} Julia Smith. All rights reserved.
          <br>Email: ${mail}</p>
          </#macro>
          <#assign mail = "jsmith@acme.com">
          使用import指令導(dǎo)入庫到模板中,F(xiàn)reemarker會(huì)為導(dǎo)入的庫創(chuàng)建新的名字空間,并可以通過import指令中指定的散列變量訪問庫中的變量:
          <#import "/lib/my_test.ftl" as my>
          <#assign mail="fred@acme.com">
          <@my.copyright date="1999-2002"/>
          ${my.mail}
          ${mail}
          輸出結(jié)果:
            <p>Copyright (C) 1999-2002 Julia Smith. All rights reserved.
          <br>Email: jsmith@acme.com</p>
          jsmith@acme.com
          fred@acme.com
          可以看到例子中使用的兩個(gè)同名變量并沒有沖突,因?yàn)樗鼈兾挥诓煌拿挚臻g

          可以使用assign指令在導(dǎo)入的名字空間中創(chuàng)建或替代變量,下面是一個(gè)例子:

          <#import "/lib/my_test.ftl" as my>
          ${my.mail}
          <#assign mail="jsmith@other.com" in my>
          ${my.mail}
          輸出結(jié)果:
          jsmith@acme.com
          jsmith@other.com
          數(shù)據(jù)模型中的變量任何地方都可見,也包括不同的名字空間,下面是修改的庫:
          <#macro copyright date>
          <p>Copyright (C) ${date} ${user}. All rights reserved.</p>
          </#macro>
          <#assign mail = "${user}@acme.com">
          假設(shè)數(shù)據(jù)模型中的user變量的值是Fred,則下面的代碼:
          <#import "/lib/my_test.ftl" as my>
          <@my.copyright date="1999-2002"/>
          ${my.mail}
          輸出結(jié)果:
            <p>Copyright (C) 1999-2002 Fred. All rights reserved.</p>
          Fred@acme.com


          補(bǔ)充(靜態(tài)方法的調(diào)用):


          方法1:
          ##定義配置文件 freemarkerstatic.properties
          _Validator=com.longyou.util.Validator
          _Functions=com.longyou.util.Functions
          _EscapeUtils=com.longyou.util.EscapeUtils
          /調(diào)用代碼
          ${_Functions.toUpperCase("Hello")}<br>
          ${_EscapeUtils.escape("狼的原野")}

          方法2:
          ${stack.findValue("@package.ClassName@method")}


          補(bǔ)充:常用語法

          EG.一個(gè)對(duì)象BOOK

          1.輸出 ${book.name}

          空值判斷:${book.name?if_exists },

          ${book.name?default(‘xxx’)}//默認(rèn)值xxx

          ${ book.name!"xxx"}//默認(rèn)值xxx

          日期格式:${book.date?string('yyyy-MM-dd')}

          數(shù)字格式:${book?string.number}--20

          ${book?string.currency}--<#-- $20.00 -->

          ${book?string.percent}—<#-- 20% -->

          插入布爾值:

          <#assign foo=ture />

          ${foo?string("yes","no")} <#-- yes -->

           

           

           

           

          2.邏輯判斷

          a:

          <#if condition>...

          <#elseif condition2>...

          <#elseif condition3>......

          <#else>...

          其中空值判斷可以寫成<#if book.name?? >

           

          b:

          <#switch value>

          <#case refValue1>

          ...

          <#break>

          <#case refValue2>

          ...

          <#break>

          ...

          <#case refValueN>

          ...

          <#break>

          <#default>

          ...

           

          3.循環(huán)讀取

          <#list sequence as item>

          ...

          空值判斷<#if bookList?size = 0>

          e.g.

          <#list employees as e>

          ${e_index}. ${e.name}

          輸出:

          1. Readonly

          2. Robbin


          freemarker中Map的使用

          <#list testMap?keys as testKey>
                 < option value="${testKey}" >
                        ${testMap[testKey]}
               </option>
          <
          /#list>

          freemarker的Eclipse插件
          • If you use Eclipse 2.x:
            1. Open the Window menu, then Open Perspective -> Install/Update
            2. Click with the right mouse button on the Feature Updates view, then select New -> Site Bookmark
            3. In the displayed dialog box, type "FreeMarker" for Name and "http://www.freemarker.org/eclipse/update" for URL. Leave the "Bookmark type" radio buttons on "Eclipse update site".
            4. Click Finish
            5. Open the tree node under the newly created update site named "FreeMarker", select the "FreeMarker X.Y.Z" feature, and install it using the Install now button in the preview pane.
          • If you use Eclipse 3.x:
            1. Help -> Software updates -> Find and install....
            2. Choose "Search for new features to install".
            3. Click Add Update Site..., and type "FreeMarker" for Name and "http://www.freemarker.org/eclipse/update" for URL.
            4. Check the box of the "FreeMarker" feature.
            5. "Next"-s until it is installed...
          posted @ 2009-06-18 09:19 Eric Gu 閱讀(471) | 評(píng)論 (0)編輯 收藏

          導(dǎo)航

          <2009年6月>
          31123456
          78910111213
          14151617181920
          21222324252627
          2829301234
          567891011

          統(tǒng)計(jì)

          常用鏈接

          留言簿(1)

          隨筆分類

          隨筆檔案

          搜索

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 商南县| 祁东县| 乌什县| 通许县| 遵化市| 寿宁县| 五莲县| 黎城县| 灵璧县| 临泽县| 巴里| 土默特右旗| 昌黎县| 南乐县| 肇东市| 阳原县| 宁城县| 广安市| 明水县| 道真| 嘉祥县| 沁水县| 蓬溪县| 偏关县| 鲜城| 云阳县| 伊春市| 松滋市| 湾仔区| 平江县| 漳浦县| 开阳县| 达尔| 卓资县| 大丰市| 乐平市| 湘阴县| 赤壁市| 东安县| 文登市| 盘山县|