<!--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-->在項目中使用Spring的注解,關于spring的注解,由兩種注解方式,
基于注釋(Annotation)的配置有越來越流行的趨勢,Spring 2.5 順應這種趨勢,提供了完全基于注釋配置 Bean、裝配 Bean 的功能,您可以使用基于注釋的 Spring IoC 替換原來基于 XML 的配置。本文通過實例詳細講述了 Spring 2.5 基于注釋 IoC 功能的使用。
注釋配置相對于 XML 配置具有很多的優勢:
- 它可以充分利用 Java 的反射機制獲取類結構信息,這些信息可以有效減少配置的工作。如使用 JPA 注釋配置 ORM 映射時,我們就不需要指定 PO 的屬性名、類型等信息,如果關系表字段和 PO 屬性名、類型都一致,您甚至無需編寫任務屬性映射信息——因為這些信息都可以通過 Java 反射機制獲取。
- 注釋和 Java 代碼位于一個文件中,而 XML 配置采用獨立的配置文件,大多數配置信息在程序開發完成后都不會調整,如果配置信息和 Java 代碼放在一起,有助于增強程序的內聚性。而采用獨立的 XML 配置文件,程序員在編寫一個功能時,往往需要在程序文件和配置文件中不停切換,這種思維上的不連貫會降低開發效率。
因此在很多情況下,注釋配置比 XML 配置更受歡迎,注釋配置有進一步流行的趨勢。Spring 2.5 的一大增強就是引入了很多注釋類,現在您已經可以使用注釋配置完成大部分 XML 配置的功能。在這篇文章里,我們將向您講述使用注釋進行 Bean 定義和依賴注入的內容。
Spring 2.5 引入了 @Autowired
注釋,它可以對類成員變量、方法及構造函數進行標注,完成自動裝配的工作。
Spring 通過一個 BeanPostProcessor
對 @Autowired
進行解析,所以要讓 @Autowired
起作用必須事先在 Spring 容器中聲明 AutowiredAnnotationBeanPostProcessor
Bean。
在Spring中配置如下:
<!-- 該 BeanPostProcessor 將自動起作用,對標注 @Autowired 的 Bean 進行自動注入 -->
<bean class="org.springframework.beans.factory.annotation.
AutowiredAnnotationBeanPostProcessor"/>
當 Spring 容器啟動時,AutowiredAnnotationBeanPostProcessor
將掃描 Spring 容器中所有 Bean,當發現 Bean 中擁有 @Autowired
注釋時就找到和其匹配(默認按類型匹配)的 Bean,并注入到對應的地方中去。
按照上面的配置,Spring 將直接采用 Java 反射機制對 Boss 中的 car
和 office
這兩個私有成員變量進行自動注入。所以對成員變量使用 @Autowired
后,您大可將它們的 setter 方法(setCar()
和 setOffice()
)從 Boss 中刪除。
當然,您也可以通過 @Autowired
對方法或構造函數進行標注,
在默認情況下使用 @Autowired
注釋進行自動注入時,Spring 容器中匹配的候選 Bean 數目必須有且僅有一個。當找不到一個匹配的 Bean 時,Spring 容器將拋出 BeanCreationException
異常,并指出必須至少擁有一個匹配的 Bean。
當不能確定 Spring 容器中一定擁有某個類的 Bean 時,可以在需要自動注入該類 Bean 的地方可以使用 @Autowired(required = false)
,這等于告訴 Spring:在找不到匹配 Bean 時也不報錯。
一般情況下,使用 @Autowired
的地方都是需要注入 Bean 的,使用了自動注入而又允許不注入的情況一般僅會在開發期或測試期碰到(如為了快速啟動 Spring 容器,僅引入一些模塊的 Spring 配置文件),所以 @Autowired(required = false)
會很少用到。
和找不到一個類型匹配 Bean 相反的一個錯誤是:如果 Spring 容器中擁有多個候選 Bean,Spring 容器在啟動時也會拋出 BeanCreationException
異常。
Spring 允許我們通過 @Qualifier
注釋指定注入 Bean 的名稱,這樣歧義就消除了,可以通過下面的方法解決異常:
清單 13. 使用 @Qualifier 注釋指定注入 Bean 的名稱
@Autowired public void setOffice(@Qualifier("office")Office office) { this.office = office; } |
@Qualifier("office")
中的 office
是 Bean 的名稱,所以 @Autowired
和 @Qualifier
結合使用時,自動注入的策略就從 byType 轉變成 byName 了。@Autowired
可以對成員變量、方法以及構造函數進行注釋,而 @Qualifier
的標注對象是成員變量、方法入參、構造函數入參。正是由于注釋對象的不同,所以 Spring 不將 @Autowired
和 @Qualifier
統一成一個注釋類。下面是對成員變量和構造函數入參進行注釋的代碼:
對成員變量進行注釋:
對成員變量使用 @Qualifier 注釋
public class Boss { @Autowired private Car car; @Autowired @Qualifier("office") private Office office; … } |
對構造函數入參進行注釋:
清單 15. 對構造函數變量使用 @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
結合使用,是對 @Autowired
有益的補充。一般來講,@Qualifier
對方法簽名中入參進行注釋會降低代碼的可讀性,而對成員變量注釋則相對好一些。
Spring 不但支持自己定義的 @Autowired
的注釋,還支持幾個由 JSR-250 規范定義的注釋,它們分別是 @Resource
、@PostConstruct
以及 @PreDestroy
。
@Resource
的作用相當于 @Autowired
,只不過 @Autowired
按 byType 自動注入,面 @Resource
默認按 byName 自動注入罷了。@Resource
有兩個屬性是比較重要的,分別是 name 和 type,Spring 將 @Resource
注釋的 name 屬性解析為 Bean 的名字,而 type 屬性則解析為 Bean 的類型。所以如果使用 name 屬性,則使用 byName 的自動注入策略,而使用 type 屬性時則使用 byType 自動注入策略。如果既不指定 name 也不指定 type 屬性,這時將通過反射機制使用 byName 自動注入策略。
Resource 注釋類位于 Spring 發布包的 lib/j2ee/common-annotations.jar 類包中,因此在使用之前必須將其加入到項目的類庫中。來看一個使用 @Resource
的例子:
清單 16. 使用 @Resource 注釋的 Boss.java
package com.baobaotao; import javax.annotation.Resource; public class Boss { // 自動注入類型為 Car 的 Bean @Resource private Car car; // 自動注入 bean 名稱為 office 的 Bean @Resource(name = "office") private Office office; } |
一般情況下,我們無需使用類似于 @Resource(type=Car.class)
的注釋方式,因為 Bean 的類型信息可以通過 Java 反射從代碼中獲取。
要讓 JSR-250 的注釋生效,除了在 Bean 類中標注這些注釋外,還需要在 Spring 容器中注冊一個負責處理這些注釋的 BeanPostProcessor
:
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/> |
CommonAnnotationBeanPostProcessor
實現了 BeanPostProcessor
接口,它負責掃描使用了 JSR-250 注釋的 Bean,并對它們進行相應的操作。
Spring 容器中的 Bean 是有生命周期的,Spring 允許在 Bean 在初始化完成后以及 Bean 銷毀前執行特定的操作,您既可以通過實現 InitializingBean/DisposableBean 接口來定制初始化之后 / 銷毀之前的操作方法,也可以通過 <bean> 元素的 init-method/destroy-method 屬性指定初始化之后 / 銷毀之前調用的操作方法。關于 Spring 的生命周期,筆者在《精通 Spring 2.x—企業應用開發精解》第 3 章進行了詳細的描述,有興趣的讀者可以查閱。
JSR-250 為初始化之后/銷毀之前方法的指定定義了兩個注釋類,分別是 @PostConstruct 和 @PreDestroy,這兩個注釋只能應用于方法上。標注了 @PostConstruct 注釋的方法將在類實例化后調用,而標注了 @PreDestroy 的方法將在類銷毀之前調用。
清單 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"); } … } |
您只需要在方法前標注 @PostConstruct
或 @PreDestroy
,這些方法就會在 Bean 初始化后或銷毀之前被 Spring 容器執行了。
我們知道,不管是通過實現 InitializingBean
/DisposableBean
接口,還是通過 <bean> 元素的 init-method/destroy-method
屬性進行配置,都只能為 Bean 指定一個初始化 / 銷毀的方法。但是使用 @PostConstruct
和 @PreDestroy
注釋卻可以指定多個初始化 / 銷毀方法,那些被標注 @PostConstruct
或 @PreDestroy
注釋的方法都會在初始化 / 銷毀時被執行。
通過以下的測試代碼,您將可以看到 Bean 的初始化 / 銷毀方法是如何被執行的:
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();// 關閉 Spring 容器,以觸發 Bean 銷毀方法的執行 } } |
這時,您將看到標注了 @PostConstruct
的 postConstruct1()
方法將在 Spring 容器啟動時,創建 Boss
Bean 的時候被觸發執行,而標注了 @PreDestroy
注釋的 preDestroy1()
方法將在 Spring 容器關閉前銷毀 Boss
Bean 的時候被觸發執行。
![]() ![]() |
![]()
|
使用 <context:annotation-config/> 簡化配置
Spring 2.1 添加了一個新的 context 的 Schema 命名空間,該命名空間對注釋驅動、屬性文件引入、加載期織入等功能提供了便捷的配置。我們知道注釋本身是不會做任何事情的,它僅提供元數據信息。要使元數據信息真正起作用,必須讓負責處理這些元數據的處理器工作起來。
而我們前面所介紹的 AutowiredAnnotationBeanPostProcessor
和 CommonAnnotationBeanPostProcessor
就是處理這些注釋元數據的處理器。但是直接在 Spring 配置文件中定義這些 Bean 顯得比較笨拙。Spring 為我們提供了一種方便的注冊這些 BeanPostProcessor
的方式,這就是 <context:annotation-config/>。請看下面的配置:
<?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 容器注冊 AutowiredAnnotationBeanPostProcessor
、CommonAnnotationBeanPostProcessor
、PersistenceAnnotationBeanPostProcessor
以及 equiredAnnotationBeanPostProcessor
這 4 個 BeanPostProcessor。
在配置文件中使用 context 命名空間之前,必須在 <beans> 元素中聲明 context 命名空間。
![]() ![]() |
![]()
|
雖然我們可以通過 @Autowired
或 @Resource
在 Bean 類中使用自動注入功能,但是 Bean 還是在 XML 文件中通過 <bean> 進行定義 —— 也就是說,在 XML 配置文件中定義 Bean,通過 @Autowired
或 @Resource
為 Bean 的成員變量、方法入參或構造函數入參提供自動注入的功能。能否也通過注釋定義 Bean,從 XML 配置文件中完全移除 Bean 定義的配置呢?答案是肯定的,我們通過 Spring 2.5 提供的 @Component
注釋就可以達到這個目標了。
下面,我們完全使用注釋定義 Bean 并完成 Bean 之間裝配:
清單 20. 使用 @Component 注釋的 Car.java
package com.baobaotao; import org.springframework.stereotype.Component; @Component public class Car { … } |
僅需要在類定義處,使用 @Component
注釋就可以將一個類定義了 Spring 容器中的 Bean。下面的代碼將 Office
定義為一個 Bean:
清單 21. 使用 @Component 注釋的 Office.java
package com.baobaotao; import org.springframework.stereotype.Component; @Component public class Office { private String officeNo = "001"; … } |
這樣,我們就可以在 Boss 類中通過 @Autowired
注入前面定義的 Car
和 Office 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
有一個可選的入參,用于指定 Bean 的名稱,在 Boss 中,我們就將 Bean 名稱定義為“boss
”。一般情況下,Bean 都是 singleton 的,需要注入 Bean 的地方僅需要通過 byType 策略就可以自動注入了,所以大可不必指定 Bean 的名稱。
在使用 @Component
注釋后,Spring 容器必須啟用類掃描機制以啟用注釋驅動 Bean 定義和注釋驅動 Bean 自動注入的策略。Spring 2.5 對 context 命名空間進行了擴展,提供了這一功能,請看下面的配置:
<?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 的配置內容已經被移除,僅需要添加一行 <context:component-scan/> 配置就解決所有問題了——Spring XML 配置文件得到了極致的簡化(當然配置元數據還是需要的,只不過以注釋形式存在罷了)。<context:component-scan/> 的 base-package 屬性指定了需要掃描的類包,類包及其遞歸子包中所有的類都會被處理。
<context:component-scan/> 還允許定義過濾器將基包下的某些類納入或排除。Spring 支持以下 4 種類型的過濾方式,通過下表說明:
過濾器類型 | 說明 |
---|---|
注釋 | 假如 com.baobaotao.SomeAnnotation 是一個注釋類,我們可以將使用該注釋的類過濾出來。 |
類名指定 | 通過全限定類名進行過濾,如您可以指定將 com.baobaotao.Boss 納入掃描,而將 com.baobaotao.Car 排除在外。 |
正則表達式 | 通過正則表達式定義過濾的類,如下所示: com\.baobaotao\.Default.* |
AspectJ 表達式 | 通過 AspectJ 表達式定義過濾的類,如下所示: com. baobaotao..*Service+ |
下面是一個簡單的例子:
<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/> 配置項不但啟用了對類包進行掃描以實施注釋驅動 Bean 定義的功能,同時還啟用了注釋驅動自動注入的功能(即還隱式地在內部注冊了 AutowiredAnnotationBeanPostProcessor
和 CommonAnnotationBeanPostProcessor
),因此當使用 <context:component-scan/> 后,就可以將 <context:annotation-config/> 移除了。
默認情況下通過 @Component
定義的 Bean 都是 singleton 的,如果需要使用其它作用范圍的 Bean,可以通過 @Scope
注釋來達到目標,如以下代碼所示:
清單 24. 通過 @Scope 指定 Bean 的作用范圍
package com.baobaotao; import org.springframework.context.annotation.Scope; … @Scope("prototype") @Component("boss") public class Boss { … } |
這樣,當從 Spring 容器中獲取 boss
Bean 時,每次返回的都是新的實例了。
![]() ![]() |
![]()
|
Spring 2.5 中除了提供 @Component
注釋外,還定義了幾個擁有特殊語義的注釋,它們分別是:@Repository
、@Service
和 @Controller
。在目前的 Spring 版本中,這 3 個注釋和 @Component
是等效的,但是從注釋類的命名上,很容易看出這 3 個注釋分別和持久層、業務層和控制層(Web 層)相對應。雖然目前這 3 個注釋和 @Component
相比沒有什么新意,但 Spring 將在以后的版本中為它們添加特殊的功能。所以,如果 Web 應用程序采用了經典的三層分層結構的話,最好在持久層、業務層和控制層分別采用 @Repository
、@Service
和 @Controller
對分層中的類進行注釋,而用 @Component
對那些比較中立的類進行注釋。
![]() ![]() |
![]()
|
是否有了這些 IOC 注釋,我們就可以完全摒除原來 XML 配置的方式呢?答案是否定的。有以下幾點原因:
- 注釋配置不一定在先天上優于 XML 配置。如果 Bean 的依賴關系是固定的,(如 Service 使用了哪幾個 DAO 類),這種配置信息不會在部署時發生調整,那么注釋配置優于 XML 配置;反之如果這種依賴關系會在部署時發生調整,XML 配置顯然又優于注釋配置,因為注釋是對 Java 源代碼的調整,您需要重新改寫源代碼并重新編譯才可以實施調整。
- 如果 Bean 不是自己編寫的類(如
JdbcTemplate
、SessionFactoryBean
等),注釋配置將無法實施,此時 XML 配置是唯一可用的方式。 - 注釋配置往往是類級別的,而 XML 配置則可以表現得更加靈活。比如相比于
@Transaction
事務注釋,使用 aop/tx 命名空間的事務配置更加靈活和簡單。
所以在實現應用中,我們往往需要同時使用注釋配置和 XML 配置,對于類級別且不會發生變動的配置可以優先考慮注釋配置;而對于那些第三方類以及容易發生調整的配置則應優先考慮使用 XML 配置。Spring 會在具體實施 Bean 創建和 Bean 注入之前將這兩種配置方式的元信息融合在一起。
![]() ![]() |
![]()
|
Spring 在 2.1 以后對注釋配置提供了強力的支持,注釋配置功能成為 Spring 2.5 的最大的亮點之一。合理地使用 Spring 2.5 的注釋配置,可以有效減少配置的工作量,提高程序的內聚性。但是這并不意味著傳統 XML 配置將走向消亡,在第三方類 Bean 的配置,以及那些諸如數據源、緩存池、持久層操作模板類、事務管理等內容的配置上,XML 配置依然擁有不可替代的地位。
什么叫控制反轉呢?套用好萊塢的一句名言就是:你呆著別動,到時我會找你。
什么意思呢?就好比一個皇帝和太監
有一天皇帝想寵幸某個美女,于是跟太監說,今夜我要寵幸美女
皇帝往往不會告訴太監,今晚幾點會回宮,會回哪張龍床,他只會告訴太監他要哪位美女
其它一切都交由太監去安排,到了晚上皇帝回宮時,自然會有美女出現在皇帝的龍床上
這就是控制反轉,而把美女送到皇帝的寢宮里面去就是注射
太監就是是框架里面的注射控制器類BeanFactory,負責找到美女并送到龍床上去
整個后宮可以看成是Spring框架,美女就是Spring控制下的JavaBean
而傳統的模式就是一個饑渴男去找小姐出臺
找領班,幫助給介紹一個云云,于是領班就開始給他張羅
介紹一個合適的給他,完事后,再把小姐還給領班,下次再來
這個過程中,領班就是查詢上下文Context,領班的一個職能就是給客戶找到他們所要的小姐
這就是lookup()方法,領班手中的小姐名錄就是JNDI//Java Naming and Directory Interface
小姐就是EJB,饑渴男是客戶端,青樓是EJB容器
看到區別了么?
饑渴男去找小姐出臺很麻煩,不僅得找,用完后還得把小姐給還回去
而皇帝爽翻了,什么都不用管,交給太監去處理,控制權轉移到太監手中去了而不是皇帝,
必要時候由太監給注射進去就可以了
*
* @author liuguangyi
* @content ejb3注解的API定義在javax.persistence.*包里面。
*
* 注釋說明:
* @Entity —— 將一個類聲明為一個實體bean(即一個持久化POJO類)
* @Id —— 注解聲明了該實體bean的標識屬性(對應表中的主鍵)。
* @Table —— 注解聲明了該實體bean映射指定的表(table),目錄(catalog)和schema的名字
* @Column —— 注解聲明了屬性到列的映射。該注解有如下的屬性
* name 可選,列名(默認值是屬性名)
* unique 可選,是否在該列上設置唯一約束(默認值false)
* nullable 可選,是否設置該列的值可以為空(默認值false)
* insertable 可選,該列是否作為生成的insert語句中的一個列(默認值true)
* updatable 可選,該列是否作為生成的update語句中的一個列(默認值true)
* columnDefinition 可選,為這個特定列覆蓋sql ddl片段(這可能導致無法在不同數據庫間移植)
* table 可選,定義對應的表(默認為主表)
* length 可選,列長度(默認值255)
* precision 可選,列十進制精度(decimal precision)(默認值0)
* scale 可選,如果列十進制數值范圍(decimal scale)可用,在此設置(默認值0)
* @GeneratedValue —— 注解聲明了主鍵的生成策略。該注解有如下屬性
* strategy 指定生成的策略(JPA定義的),這是一個GenerationType。默認是GenerationType. AUTO
* GenerationType.AUTO 主鍵由程序控制
* GenerationType.TABLE 使用一個特定的數據庫表格來保存主鍵
* GenerationType.IDENTITY 主鍵由數據庫自動生成(主要是自動增長類型)
* GenerationType.SEQUENCE 根據底層數據庫的序列來生成主鍵,條件是數據庫支持序列。(這個值要與generator一起使用)
* generator 指定生成主鍵使用的生成器(可能是orcale中的序列)。
* @SequenceGenerator —— 注解聲明了一個數據庫序列。該注解有如下屬性
* name 表示該表主鍵生成策略名稱,它被引用在@GeneratedValue中設置的“gernerator”值中
* sequenceName 表示生成策略用到的數據庫序列名稱。
* initialValue 表示主鍵初始值,默認為0.
* allocationSize 每次主鍵值增加的大小,例如設置成1,則表示每次創建新記錄后自動加1,默認為50.
* @GenericGenerator —— 注解聲明了一個hibernate的主鍵生成策略。支持十三種策略。該注解有如下屬性
* name 指定生成器名稱
* strategy 指定具體生成器的類名(指定生成策略)。
* parameters 得到strategy指定的具體生成器所用到的參數。
* 其十三種策略(strategy屬性的值)如下:
* 1.native 對于orcale采用Sequence方式,對于MySQL和SQL Server采用identity(處境主鍵生成機制),
* native就是將主鍵的生成工作將由數據庫完成,hibernate不管(很常用)
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "native")
* 2.uuid 采用128位的uuid算法生成主鍵,uuid被編碼為一個32位16進制數字的字符串。占用空間大(字符串類型)。
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "uuid")
* 3.hilo 要在數據庫中建立一張額外的表,默認表名為hibernate_unque_key,默認字段為integer類型,名稱是next_hi(比較少用)
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "hilo")
* 4.assigned 在插入數據的時候主鍵由程序處理(很常用),這是<generator>元素沒有指定時的默認生成策略。等同于JPA中的AUTO。
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "assigned")
* 5.identity 使用SQL Server和MySQL的自增字段,這個方法不能放到Oracle中,Oracle不支持自增字段,要設定sequence(MySQL和SQL Server中很常用)。等同于JPA中的IDENTITY
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "identity")
* 6.select 使用觸發器生成主鍵(主要用于早期的數據庫主鍵生成機制,少用)
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "select")
* 7.sequence 調用謹慎數據庫的序列來生成主鍵,要設定序列名,不然hibernate無法找到。
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "sequence",
* parameters = { @Parameter(name = "sequence", value = "seq_payablemoney") })
* 8.seqhilo 通過hilo算法實現,但是主鍵歷史保存在Sequence中,適用于支持Sequence的數據庫,如Orcale(比較少用)
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "seqhilo",
* parameters = { @Parameter(name = "max_lo", value = "5") })
* 9.increnment 插入數據的時候hibernate會給主鍵添加一個自增的主鍵,但是一個hibernate實例就維護一個計數器,所以在多個實例運行的時候不能使用這個方法。
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "increnment")
* 10.foreign 使用另一個相關的對象的主鍵。通常和<on
* 例:@Id
* @GeneratedValue(generator = "idGenerator")
* @GenericGenerator(name = "idGenerator", strategy = "foreign",
* parameters = { @Parameter(name = "property", value = "info") })
* Integer id;
* @On
* EmployeeInfo info;
* 11.guid 采用數據庫底層的guid算法機制,對應MySQL的uuid()函數,SQL Server的newid()函數,ORCALE的rawtohex(sys_guid())函數等
* 例:@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策略的擴展,采用立即檢索策略來獲取sequence值,需要JDBC3.0和JDK4以上(含1.4)版本
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "sequence-identity",
* parameters = { @Parameter(name = "sequence", value = "seq_payablemoney") })
*
* @On
* 方法一
* 主表: ?@On
* @PrimaryKeyJoinColumn
* public 從表類 get從表類(){return 從表對象}
* 從表:沒有主表類。
* 注意:這種方法要求主表與從表的主鍵值想對應。
* 方法二
* 主表:?@On
* @JoinColumn(name="主表外鍵") //這里指定的是數據庫中的外鍵字段。
* public 從表類 get從表類(){return 從表類}
* 從表:@On
* public 主表類 get主表類(){return 主表對象}
* 注意:@JoinColumn是可選的。默認值是從表變量名+"_"+從表的主鍵(注意,這里加的是主鍵。而不是主鍵對應的變量)。
* 方法三
* 主表:@On
* @JoinTable( name="關聯表名",
* joinColumns = @JoinColumn(name="主表外鍵"),
* inverseJoinColumns = @JoinColumns(name="從表外鍵")
* )
* 從表:@On
* public 主表類 get主表類(){return 主表對象}
* @ManyToOne 設置多對一關聯
* 方法一
* @ManyToOne(cascade={CasCadeType.PERSIST,CascadeType.MERGE})
* @JoinColumn(name="外鍵")
* public 主表類 get主表類(){return 主表對象}
* 方法二
* @ManyToOne(cascade={CascadeType.PERSIST,CascadeType.MERGE})
* @JoinTable(name="關聯表名",
* joinColumns = @JoinColumn(name="主表外鍵"),
* inverseJoinColumns = @JoinColumns(name="從表外鍵")
* )
* @On
* 方法一 使用這種配置,在為“一端”添加“多端”時,不會修改“多端”的外鍵。在“一端”加載時,不會得到“多端”。如果使用延遲加載,在讀“多端”列表時會出異常,立即加載在得到多端時,是一個空集合(集合元素為0)。
* “一端”配置
* @On
* public List<“多端”類> get“多端”列表(){return “多端”列表}
* “多端”配置參考@ManyToOne.
* 方法二
* “一端”配置
* @On
* @MapKey(name="“多端”做為Key的屬性")
* public Map<“多端”做為Key的屬性的類,主表類> get“多端”列表(){return “多端”列表}
* “多端”配置參考@ManyToOne.
* 方法三 使用這種配置,在為“一端”添加“多端”時,可以修改“多端”的外鍵。
* “一端”配置
* @On
* @JoinColumn(name="“多端”外鍵")
* public List<“多端”類> get“多端”列表(){return “多端”列表}
* “多端”配置參考@ManyToOne.
*
*
*/
1、重寫只能出現在繼承關系之中。當一個類繼承它的父類方法時,都有機會重寫該父類的方法。一個特例是父類的方法被標識為final。重寫的主要優點是能夠定義某個子類型特有的行為。
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、對于從父類繼承來的抽象方法,要么在子類用重寫的方式設計該方法,要么把子類也標識為抽象的。所以抽象方法可以說是必須要被重寫的方法。
3、重寫的意義。
重寫方法可以實現多態,用父類的引用來操縱子類對象,但是在實際運行中對象將運行其自己特有的方法。
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(){
}
}
一個原則是:使用了什么引用,編譯器就會只調用引用類所擁有的方法。如果調用子類特有的方法,如上例的h.buck(); 編譯器會抱怨的。也就是說,編譯器只看引用類型,而不是對象類型。
4、重寫方法的規則。
若想實現一個合格重寫方法,而不是重載,那么必須同時滿足下面的要求!
A、重寫規則之一:重寫方法不能比被重寫方法限制有更嚴格的訪問級別。
(但是可以更廣泛,比如父類方法是包訪問權限,子類的重寫方法是public訪問權限。)
比如:Object類有個toString()方法,開始重寫這個方法的時候我們總容易忘記public修飾符,編譯器當然不會放過任何教訓我們的機會。出錯的原因就是:沒有加任何訪問修飾符的方法具有包訪問權限,包訪問權限比public當然要嚴格了,所以編譯器會報錯的。
B、重寫規則之二:參數列表必須與被重寫方法的相同。
重寫有個孿生的弟弟叫重載,也就是后面要出場的。如果子類方法的參數與父類對應的方法不同,那么就是你認錯人了,那是重載,不是重寫。
C、重寫規則之三:返回類型必須與被重寫方法的返回類型相同。
父類方法A:void eat(){} 子類方法B:int eat(){} 兩者雖然參數相同,可是返回類型不同,所以不是重寫。
父類方法A:int eat(){} 子類方法B:long eat(){} 返回類型雖然兼容父類,但是不同就是不同,所以不是重寫。
D、重寫規則之四:重寫方法不能拋出新的異常或者比被重寫方法聲明的檢查異常更廣的檢查異常。但是可以拋出更少,更有限或者不拋出異常。
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();
}
}
這個例子中,父類拋出了檢查異常Exception,子類拋出的IOException是Exception的子類,也即是比被重寫的方法拋出了更有限的異常,這是可以的。如果反過來,父類拋出IOException,子類拋出更為寬泛的Exception,那么不會通過編譯的。
注意:這種限制只是針對檢查異常,至于運行時異常RuntimeException及其子類不再這個限制之中。
E、重寫規則之五:不能重寫被標識為final的方法。
F、重寫規則之六:如果一個方法不能被繼承,則不能重寫它。
比較典型的就是父類的private方法。下例會產生一個有趣的現象。
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.");
}
}
這段代碼是能通過編譯的。表面上看來違反了第六條規則,但實際上那是一點巧合。Animal類的eat()方法不能被繼承,因此Horse類中的eat()方法是一個全新的方法,不是重寫也不是重載,只是一個只屬于Horse類的全新的方法!這點讓很多人迷惑了,但是也不是那么難以理解。
main()方法如果是這樣:
Animal h = new Horse();
//Horse h = new Horse();
h.eat();
編譯器會報錯,為什么呢?Horse類的eat()方法是public的?。摽梢哉{用??!請牢記,多態只看父類引用的方法,而不看子類對象的方法!
二、方法的重載。
重載是有好的,它不要求你在調用一個方法之前轉換數據類型,它會自動地尋找匹配的方法。方法的重載是在編譯時刻就決定調用哪個方法了,和重寫不同。最最常用的地方就是構造器的重載。
1、基本數據類型參數的重載。
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);
}
}
輸出結果:
method:byte
method:int
method:int
method:float
method:double
method:float
可以看出:首先要尋找的是數據類型正好匹配方法。如果找不到,那么就提升為表達能力更強的數據類型,如上例沒有正好容納long的整數類型,那么就轉換為float類型的。如果通過提升也不能找到合適的兼容類型,那么編譯器就會報錯。反正是不會自動轉換為較小的數據類型的,必須自己強制轉換,自己來承擔轉變后果。
char類型比較特殊,如果找不到正好匹配的類型,它會轉化為int而不是short,雖然char是16位的。
2、重載方法的規則。
A、被重載的方法必須改變參數列表。
參數必須不同,這是最重要的!不同有兩個方面,參數的個數,參數的類型,參數的順序。
B、被重載的方法與返回類型無關。
也就是說,不能通過返回類型來區分重載方法。
C、被重載的方法可以改變訪問修飾符。
沒有重寫方法那樣嚴格的限制。
D、被重載的方法可以聲明新的或者更廣的檢查異常。
沒有重寫方法那樣嚴格的限制。
E、方法能夠在一個類中或者在一個子類中被重載。
3、帶對象引用參數的方法重載。
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);
}
}
輸出結果是:
Animal is called.
Horse is called.
Animal is called.
前兩個輸出沒有任何問題。第三個方法為什么不是輸出“Horse is called.”呢?還是那句老話,要看引用類型而不是對象類型,方法重載是在編譯時刻就決定的了,引用類型決定了調用哪個版本的重載方法。
4、重載和重寫方法區別的小結。
如果能徹底弄明白下面的例子,說明你對重載和重寫非常了解了,可以結束這節的復習了。
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");
}
}
四個輸出分別是什么?被注釋的兩條語句為什么不能通過編譯?
第一條:a.eat(); 普通的方法調用,沒有多態,沒什么技術含量。調用了Animal類的eat()方法,輸出:Animal is eating.
第二條:h.eat(); 普通的方法調用,也沒什么技術含量。調用了Horse類的eat()方法,輸出:Horse is eating.
第三條:h.eat("apple"); 重載。Horse類的兩個eat()方法重載。調用了Horse類的eat(String food)方法,輸出:Horse is eating apple
第四條:ah.eat(); 多態。前面有例子了,不難理解。輸出:Horse is eating.
第五條:a.eat("apple"); 低級的錯誤,Animal類中沒有eat(String food)方法。因此不能通過編譯。
第六條:ah.eat("apple"); 關鍵點就在這里。解決的方法還是那句老話,不能看對象類型,要看引用類型。Animal類中沒有eat(String food)方法。因此不能通過編譯。
小結一下:多態不決定調用哪個重載版本;多態只有在決定哪個重寫版本時才起作用。
重載對應編譯時,重寫對應運行時。夠簡潔的了吧!
三、構造方法。
構造方法是一種特殊的方法,沒有構造方法就不能創建一個新對象。實際上,不僅要調用對象實際類型的構造方法,還要調用其父類的構造方法,向上追溯,直到Object類。構造方法不必顯式地調用,當使用new關鍵字時,相應的構造方法會自動被調用。
1、構造方法的規則。
A、構造方法能使用任何訪問修飾符。包括private,事實上java類庫有很多都是這樣的,設計者不希望使用者創建該類的對象。
B、構造方法的名稱必須與類名相同。這樣使得構造方法與眾不同,如果我們遵守sun的編碼規范,似乎只有構造方法的首字母是大寫的。
C、構造方法不能有返回類型。
反過來說,有返回類型的不是構造方法
public class Test {
int Test(){
return 1;
}
}
這個方法是什么東西?一個冒充李逵的李鬼而已,int Test()和其他任何普通方法沒什么兩樣,就是普通的方法!只不過看起來很惡心,類似惡心的東西在考試卷子里比較多。
D、如果不在類中創建自己的構造方法,編譯器會自動生成默認的不帶參數的構造函數。
這點很容易驗證!寫一個這樣簡單的類,編譯。
class Test {
}
對生成的Test.class文件反編譯:javap Test,可以看到:
D:"JavaCode"bin>javap Test
Compiled from "Test.java"
class Test extends java.lang.Object{
Test();
}
看到編譯器自動添加的默認構造函數了吧!
E、如果只創建了帶參數的構造方法,那么編譯器不會自動添加無參的構造方法的!
F、在每個構造方法中,如果使用了重載構造函數this()方法,或者父類的構造方法super()方法,那么this()方法或者super()方法必須放在第一行。而且這兩個方法只能選擇一個,因此它們之間沒有順序問題。
G、除了編譯器生成的構造方法,而且沒有顯式地調用super()方法,那么編譯器會插入一個super()無參調用。
H、抽象類有構造方法。
四、靜態方法的重載與重寫(覆蓋)。
1、靜態方法是不能被覆蓋的。可以分兩種情況討論:
A、子類的非靜態方法“覆蓋”父類的靜態方法。
這種情況下,是不能通過編譯的。
static void print(){
System.out.println ("in father method");
}
}
class Child extends Father{
void print(){
System.out.println ("in child method");
}
}
static方法表示該方法不關聯具體的類的對象,可以通過類名直接調用,也就是編譯的前期就綁定了,不存在后期動態綁定,也就是不能實現多態。子類的非靜態方法是與具體的對象綁定的,兩者有著不同的含義。
B、子類的靜態方法“覆蓋”父類靜態方法。
這個覆蓋依然是帶引號的。事實上把上面那個例子Child類的print方法前面加上static修飾符,確實能通過編譯!但是不要以為這就是多態!多態的特點是動態綁定,看下面的例子:
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();
}
}
輸出結果是:in father method
從這個結果可以看出,并沒有實現多態。
但是這種形式很迷惑人,貌似多態,實際編程中千萬不要這樣搞,會把大家搞懵的!
它不符合覆蓋表現出來的特性,不應該算是覆蓋!
總而言之,靜態方法不能被覆蓋。
2、靜態方法可以和非靜態方法一樣被重載。
這樣的例子太多了,我不想寫例程了??纯磈ava類庫中很多這樣的例子。
如java.util.Arrays類的一堆重載的binarySearch方法。
在這里提一下是因為查資料時看到這樣的話“sun的SL275課程說,靜態方法只能控制靜態變量(他們本身沒有),靜態方法不能被重載和覆蓋……”
大家不要相信啊!可以重載的。而且靜態與非靜態方法可以重載。
從重載的機制很容易就理解了,重載是在編譯時刻就決定的了,非靜態方法都可以,靜態方法怎么可能不會呢?
1、String中的每個字符都是一個16位的Unicode字符,用Unicode很容易表達豐富的國際化字符集,比如很好的中文支持。甚至Java的標識符都可以用漢字,但是沒人會用吧(只在一本清華的《Java2實用教程》看過)。
2、判斷空字符串。根據需要自己選擇某個或者它們的組合
if ( s == null ) //從引用的角度
if ( s.length() == 0 ) //從長度判別
if ( s.trim().length () == 0 ) //是否有多個空白字符
trim()方法的作用是是移除前導和尾部的Unicode值小于'"u0020'的字符,并返回“修剪”好的字符串。這種方法很常用,比如需要用戶輸入用戶名,用戶不小心加了前導或者尾部空格,一個好的程序應該知道用戶不是故意的,即使是故意的也應該智能點地處理。
判斷空串是很常用的操作,但是Java類庫直到1.6才提供了isEmpty()方法。當且僅當 length() 為 0 時返回 true。
3、未初始化、空串""與null。它們是不同的概念。對未初始化的對象操作會被編譯器擋在門外;null是一個特殊的初始化值,是一個不指向任何對象的引用,對引用為null的對象操作會在運行時拋出異常NullPointerException;而空串是長度為0的字符串,和別的字符串的唯一區別就是長度為0。
例子:
public class StringTest{
static String s1;
public static void main(String[] args) {
String s2;
String s3 = "";
System.out.print(s1.isEmpty()); //運行時異常
System.out.print(s2.isEmpty()); //編譯出錯
System.out.print(s3.isEmpty()); //ok!輸出true
}
}
4、String類的方法很多,在編寫相關代碼的時候看看JDK文檔時有好處的,要不然花了大量時間實現一個已經存在的方法是很不值得的,因為編寫、測試、維護自己的代碼使項目的成本增加,利潤減少,嚴重的話會導致開不出工資……
5、字符串的比較。
Java不允許自定義操作符重載,因此字符串的比較要用compareTo() 或者 compareToIgnoreCase()。s1.compareTo(s2),返回值大于0則,則前者大;等于0,一般大;小于0,后者大。比較的依據是字符串中各個字符的Unicode值。
6、toString()方法。
Java的任何對象都有toString()方法,是從Object對象繼承而來的。它的作用就是讓對象在輸出時看起來更有意義,而不是奇怪的對象的內存地址。對測試也是很有幫助的。
7、String對象是不變的!可以變化的是String對象的引用。
String name = "ray";
name.concat("long"); //字符串連接
System.out.println(name); //輸出name,ok,還是"ray"
name = name.concat("long"); //把字符串對象連接的結果賦給了name引用
System.out.println(name); //輸出name,oh!,變成了"raylong"
上述三條語句其實產生了3個String對象,"ray","long","raylong"。第2條語句確實產生了"raylong"字符串,但是沒有指定把該字符串的引用賦給誰,因此沒有改變name引用。第3條語句根據不變性,并沒有改變"ray",JVM創建了一個新的對象,把"ray","long"的連接賦給了name引用,因此引用變了,但是原對象沒變。
8、String的不變性的機制顯然會在String常量內有大量的冗余。如:"1" + "2" + "3" +......+ "n" 產生了n+(n+1)個String對象!因此Java為了更有效地使用內存,JVM留出一塊特殊的內存區域,被稱為“String常量池”。對String多么照顧?。‘斁幾g器遇見String常量的時候,它檢查該池內是否已經存在相同的String常量。如果找到,就把新常量的引用指向現有的String,不創建任何新的String常量對象。
那么就可能出現多個引用指向同一個String常量,會不會有別名的危險呢?No problem!String對象的不變性可以保證不會出現別名問題!這是String對象與普通對象的一點區別。
乍看起來這是底層的機制,對我們編程沒什么影響。而且這種機制會大幅度提高String的效率,實際上卻不是這樣。為連接n個字符串使用字符串連接操作時,要消耗的時間是n的平方級!因為每兩個字符串連接,它們的內容都要被復制。因此在處理大量的字符串連接時,而且要求性能時,我們不要用String,StringBuffer是更好的選擇。
8、StringBuffer類。StringBuffer類是可變的,不會在字符串常量池中,而是在堆中,不會留下一大堆無用的對象。而且它可將字符串緩沖區安全地用于多個線程。每個StringBuffer對象都有一定的容量。只要StringBuffer對象所包含的字符序列的長度沒有超出此容量,就無需分配新的內部緩沖區數組。如果內部緩沖區溢出,則此容量自動增大。這個固定的容量是16個字符。我給這種算法起個名字叫“添飯算法”。先給你一滿碗飯,不夠了再給你一滿碗飯。
例子:
StringBuffer sb = new StringBuffer(); //初始容量為 16 個字符
sb.append("1234"); //這是4個字符,那么16個字符的容量就足夠了,沒有溢出
System.out.println(sb.length()); //輸出字符串長度是4
System.out.println(sb.capacity()); //輸出該字符串緩沖區的容量是16
sb.append("12345678901234567"); //這是17個字符,16個字符的容量不夠了,擴容為17+16個字符的容量
System.out.println(sb.length()); //輸出字符串長度是17
System.out.println(sb.capacity()); //輸出該字符串緩沖區的容量是34
sb.append("890").reverse().insert(10,"-");
System.out.println(sb); //輸出0987654321-09876543214321
字符串的長度和字符緩沖區的容量是兩個概念,注意區別。
還有串聯的方式看起來是不是很酷!用返回值連接起來可以實現這種簡潔和優雅。
10、StringBuilder類。 從J2SE 5.0 提供了StringBuilder類,它和StringBuffer類是孿生兄弟,很像。它存在的價值在于:對字符串操作的效率更高。不足的是線程安全無法保證,不保證同步。那么兩者性能到底差多少呢?很多!
請參閱:http://book.csdn.net/bookfiles/135/1001354628.shtml
實踐:
單個線程的時候使用StringBuilder類,以提高效率,而且它的API和StringBuffer兼容,不需要額外的學習成本,物美價廉。多線程時使用StringBuffer,以保證安全。
11、字符串的比較。
下面這條可能會讓你暈,所以你可以選擇看或者不看。它不會對你的職業生涯造成任何影響。而且謹記一條,比較字符串要用equals()就ok了!一旦用了“==”就會出現很怪異的現象。之所以把這部分放在最后,是想節省大家的時間,因為這條又臭又長。推薦三種人:一、沒事閑著型。二、想深入地理解Java的字符串,即使明明知道學了也沒用。三、和我一樣愛好研究“茴”字有幾種寫法。
還是那句老話,String太特殊了,以至于某些規則對String不起作用。個人感覺這種特殊性并不好??蠢樱?br /> 例子A:
String str1 = "java";
String str2 = "java";
System.out.print(str1==str2);
地球上有點Java基礎的人都知道會輸出false,因為==比較的是引用,equals比較的是內容。不是我忽悠大家,你們可以在自己的機子上運行一下,結果是true!原因很簡單,String對象被放進常量池里了,再次出現“java”字符串的時候,JVM很興奮地把str2的引用也指向了“java”對象,它認為自己節省了內存開銷。不難理解吧 呵呵
例子B:
String str1 = new String("java");
String str2 = new String("java");
System.out.print(str1==str2);
看過上例的都學聰明了,這次肯定會輸出true!很不幸,JVM并沒有這么做,結果是false。原因很簡單,例子A中那種聲明的方式確實是在String常量池創建“java”對象,但是一旦看到new關鍵字,JVM會在堆中為String分配空間。兩者聲明方式貌合神離,這也是我把“如何創建字符串對象”放到后面來講的原因。大家要沉住氣,還有一個例子。
例子C:
String str1 = "java";
String str2 = "blog";
String s = str1+str2;
System.out.print(s=="javablog");
再看這個例子,很多同志不敢妄言是true還是false了吧。愛玩腦筋急轉彎的人會說是false吧……恭喜你,你會搶答了!把那個“吧”字去掉你就完全正確。原因很簡單,JVM確實會對型如String str1 = "java"; 的String對象放在字符串常量池里,但是它是在編譯時刻那么做的,而String s = str1+str2; 是在運行時刻才能知道(我們當然一眼就看穿了,可是Java必須在運行時才知道的,人腦和電腦的結構不同),也就是說str1+str2是在堆里創建的,s引用當然不可能指向字符串常量池里的對象。沒崩潰的人繼續看例子D。
例子D:
String s1 = "java";
String s2 = new String("java");
System.out.print(s1.intern()==s2.intern());
intern()是什么東東?反正結果是true。如果沒用過這個方法,而且訓練有素的程序員會去看JDK文檔了。簡單點說就是用intern()方法就可以用“==”比較字符串的內容了。在我看到intern()方法到底有什么用之前,我認為它太多余了。其實我寫的這一條也很多余,intern()方法還存在諸多的問題,如效率、實現上的不統一……
例子E:
String str1 = "java";
String str2 = new String("java");
System.out.print(str1.equals(str2));
無論在常量池還是堆中的對象,用equals()方法比較的就是內容,就這么簡單!看完此條的人一定很后悔,但是在開始我勸你別看了……
后記:用彪哥的話說“有意思嗎?”,確實沒勁。在寫這段的時候我也是思量再三,感覺自己像孔乙己炫耀“茴”字有幾種寫法。我查了一下茴 ,回,囘,囬,還有一種是“口”字里面有個“目”字,后面這四個都加上草字頭……
快速入門
(1)模板 + 數據模型 = 輸出
FreeMarker基于設計者和程序員是具有不同專業技能的不同個體的觀念他們是分工勞動的:
設計者專注于表示——創建HTML文件、圖片、Web頁面的其它可視化方面;
程序員創建系統,生成設計頁面要顯示的數據。
經常會遇到的問題是:在Web頁面(或其它類型的文檔)中顯示的信息在設計頁面時是無效的,是基于動態數據的。在這里,你可以在HTML(或其它要輸出的文本)中加入一些特定指令,FreeMarker會在輸出頁面給最終用戶時,用適當的數據替代這些代碼。
先來解釋一下freemaker的基本語法了,
<# ... > 中存放所有freemaker的內容,之外的內容全部原樣輸出。
<@ ... /> 是函數調用
兩個定界符內的內容中,第一個符號表示指令或者函數名,其后的跟隨參數。freemaker提供的控制包括如下:
<#if condition><#elseif condition><#else> 條件判斷
<#list hash_or_seq as var> 遍歷hash表或者collection(freemaker稱作sequence)的成員
<#macro name param1 param2 ... ><#nested param> 宏,無返回參數
<#function name param1 param2><#return val>函數,有返回參數
var?member_function(...) 用函數對var進行轉換,freemaker稱為build-ins。實際內部實現類似member_function(var, ...)
stringA[M .. N] 取子字符串,類似substring(stringA, M, N)
{key:value, key2:value2 ...} 直接定義一個hash表
[item0, item1, item2 ...] 直接定義一個序列
hash0[key0] 存取hash表中key對應的元素
seq0[5] 存取序列指定下標的元素
<@function1 param0 param1 ... /> 調用函數function1
<@macro0 param0 param1 ; nest_param0 nest_param1 ...> nest_body </@macro> 調用宏,并處理宏的嵌套
<#assign var = value > 定義變量并初始化
<#local var = value> 在 macro 或者 function 中定義局部變量并初始化
<#global var = value > 定義全局變量并初始化
${var} 輸出并替換為表達式的值
<#visit xmlnode> 調用macro匹配xmlnode本身及其子節點
<#recurse xmlnode> 調用macro匹配xmlnode的子節點
下面是一個例子:
<html>這個例子是在簡單的HTML中加入了一些由${…}包圍的特定代碼,這些特定代碼是FreeMarker的指令,而包含FreeMarker的指令的文件就稱為模板(Template)。
<head>
<title>Welcome!</title>
</head>
<body>
<h1>Welcome ${user}!</h1>
<p>Our latest product:
<a href="${latestProduct.url}">${latestProduct.name}</a>!
</body>
</html>
至于user、latestProduct.url和latestProduct.name來自于數據模型(data model)。
數據模型由程序員編程來創建,向模板提供變化的信息,這些信息來自于數據庫、文件,甚至于在程序中直接生成。
模板設計者不關心數據從那兒來,只知道使用已經建立的數據模型。
下面是一個可能的數據模型:
(root)數據模型類似于計算機的文件系統,latestProduct可以看作是目錄。
|
+- user = "Big Joe"
|
+- latestProduct
|
+- url = "products/greenmouse.html"
|
+- name = "green mouse"
2、數據模型
(1)基礎
在快速入門中介紹了在模板中使用的三種基本對象類型:scalars、hashes 和sequences,其實還可以有其它更多的能力:
- scalars:存儲單值
- hashes:充當其它對象的容器,每個都關聯一個唯一的查詢名字
- sequences:充當其它對象的容器,按次序訪問
- 方法:通過傳遞的參數進行計算,以新對象返回結果
- 用戶自定義FTL標記:宏和變換器
通常每個變量只具有上述的一種能力,但一個變量可以具有多個上述能力,如下面的例子:
(root)mouse既是scalars又是hashes,將上面的數據模型合并到下面的模板:
|
+- mouse = "Yerri"
|
+- age = 12
|
+- color = "brown">
${mouse} <#-- use mouse as scalar -->輸出結果是:
${mouse.age} <#-- use mouse as hash -->
${mouse.color} <#-- use mouse as hash -->
Yerri
12
brown
(2)Scalar變量
Scalar變量存儲單值,可以是:
- 字符串:簡單文本,在模板中使用引號(單引號或雙引號)括起
- 數字:在模板中直接使用數字值
- 日期:存儲日期/時間相關的數據,可以是日期、時間或日期-時間(Timestamp);通常情況,日期值由程序員加到數據模型中,設計者只需要顯示它們
- 布爾值:true或false,通常在<#if …>標記中使用
(3)hashes 、sequences和集合
有些變量不包含任何可顯示的內容,而是作為容器包含其它變量,者有兩種類型:
- hashes:具有一個唯一的查詢名字和它包含的每個變量相關聯
- sequences:使用數字和它包含的每個變量相關聯,索引值從0開始
集合變量通常類似sequences,除非無法訪問它的大小和不能使用索引來獲得它的子變量;集合可以看作只能由<#list …>指令使用的受限sequences
(4)方法
方法變量通常是基于給出的參數計算值。
下面的例子假設程序員已經將方法變量avg放到數據模型中,用來計算數字平均值:
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標記),會在后面講述這些高級特性
(6)節點
節點變量表示為樹型結構中的一個節點,通常在XML處理中使用,會在后面的專門章節中講
3、模板
(1)整體結構
模板使用FTL(FreeMarker模板語言)編寫,是下面各部分的一個組合:
- 文本:直接輸出
- Interpolation:由${和},或#{和}來限定,計算值替代輸出
- FTL標記:FreeMarker指令,和HTML標記類似,名字前加#予以區分,不會輸出
- 注釋:由<#--和-->限定,不會輸出
下面是以一個具體模板例子:
<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>
注意事項:
- FTL區分大小寫,所以list是正確的FTL指令,而List不是;${name}和${NAME}是不同的
- Interpolation只能在文本中使用
- FTL標記不能位于另一個FTL標記內部,例如:
<#if <#include 'foo'>='bar'>...</if>
- 注釋可以位于FTL標記和Interpolation內部,如下面的例子:
<h1>Welcome ${user <#-- The name of user -->}!</h1>
<p>We have these animals:
<ul>
<#list <#-- some comment... --> animals as <#-- again... --> being>
...
- 余的空白字符會在模板輸出時移除
(2)指令
在FreeMarker中,使用FTL標記引用指令。有三種FTL標記,這和HTML標記是類似的:
- 開始標記:<#directivename parameters>
- 結束標記:</#directivename>
- 空內容指令標記:<#directivename parameters/>
有兩種類型的指令:預定義指令和用戶定義指令。
用戶定義指令要使用@替換#,如<@mydirective>...</@mydirective>(會在后面講述)。
FTL標記不能夠交叉,而應該正確的嵌套,如下面的代碼是錯誤的:
<ul>如果使用不存在的指令,FreeMarker不會使用模板輸出,而是產生一個錯誤消息。
<#list animals as being>
<li>${being.name} for ${being.price} Euros
<#if use = "Big Joe">
(except for you)
</#list>
</#if> <#-- WRONG! -->
</ul>
FreeMarker會忽略FTL標記中的空白字符,如下面的例子:
<#list但是,<、</和指令之間不允許有空白字符。
animals as
being
>
${being.name} for ${being.price} Euros
</#list >
(3)表達式
直接指定值
- 字符串
如果包含特殊字符需要轉義,如下面的例子:
${"It's \"quoted\" and輸出結果是:
this is a backslash: \\"}
${'It\'s "quoted" and
this is a backslash: \\'}
It's "quoted" and下面是支持的轉義序列:
this is a backslash: \
It's "quoted" and
this is a backslash: \
轉義序列 | 含義 |
---|---|
\" | 雙引號(u0022) |
\' | 單引號(u0027) |
反斜杠(u005C) | |
\n | 換行(u000A) |
\r | Return (u000D) |
\t | Tab (u0009) |
\b | Backspace (u0008) |
\f | Form feed (u000C) |
\l | < |
\g | > |
\a | & |
\{ | { |
\xCode | 4位16進制Unicode代碼 |
有一類特殊的字符串稱為raw字符串,被認為是純文本,其中的\和{等不具有特殊含義,該類字符串在引號前面加r,下面是一個例子:
${r"${foo}"}輸出的結果是:
${r"C:\foo\bar"}
${foo}
C:\foo\bar
- 數字
直接輸入,不需要引號
精度數字使用“.”分隔,不能使用分組符號
目前版本不支持科學計數法,所以“1E3”是錯誤的
不能省略小數點前面的0,所以“.5”是錯誤的
數字8、+8、08和8.00都是相同的
- 布爾值
true和false,不使用引號
- 序列
由逗號分隔的子變量列表,由方括號限定,下面是一個例子:
<#list ["winter", "spring", "summer", "autumn"] as x>輸出的結果是:
${x}
</#list>
winter列表的項目是表達式,所以可以有下面的例子:
spring
summer
autumn
[2 + 2, [1, 2, 3, 4], "whatnot"]可以使用數字范圍定義數字序列,例如2..5等同于[2, 3, 4, 5],但是更有效率,注意數字范圍沒有方括號
可以定義反遞增的數字范圍,如5..2
- 散列(hash)
{"name":"green mouse", "price":150}鍵和值都是表達式,但是鍵必須是字符串
獲取變量
- 頂層變量: ${variable},變量名只能是字母、數字、下劃線、$、@和#的組合,且不能以數字開頭
- 從散列中獲取數據
可以使用點語法或方括號語法,假設有下面的數據模型:
(root)下面都是等價的:
|
+- book
| |
| +- title = "Breeding green mouses"
| |
| +- author
| |
| +- name = "Julia Smith"
| |
| +- info = "Biologist, 1923-1985, Canada"
|
+- test = "title"
book.author.name使用點語法,變量名字有頂層變量一樣的限制,但方括號語法沒有該限制,因為名字是任意表達式的結果
book["author"].name
book.author.["name"]
book["author"]["name"]
- 從序列獲得數據:和散列的方括號語法語法一樣,只是方括號中的表達式值必須是數字;注意:第一個項目的索引是0
序列片斷:使用[startIndex..endIndex]語法,從序列中獲得序列片斷(也是序列);startIndex和endIndex是結果為數字的表達式
- 特殊變量:FreeMarker內定義變量,使用.variablename語法訪問
字符串操作
- Interpolation(或連接操作)
可以使用${..}(或#{..})在文本部分插入表達式的值,例如:
${"Hello ${user}!"}可以使用+操作符獲得同樣的結果
${"${user}${user}${user}${user}"}
${"Hello " + user + "!"}${..}只能用于文本部分,下面的代碼是錯誤的:
${user + user + user + user}
<#if ${isBig}>Wow!</#if>應該寫成:
<#if "${isBig}">Wow!</#if>
<#if isBig>Wow!</#if>
- 子串
例子(假設user的值為“Big Joe”):
${user[0]}${user[4]}結果是(注意第一個字符的索引是0):
${user[1..4]}
BJ序列操作
ig J
- 連接操作:和字符串一樣,使用+,下面是一個例子:
<#list ["Joe", "Fred"] + ["Julia", "Kate"] as user>輸出結果是:
- ${user}
</#list>
- 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}
- Joe is 30算術運算
- Fred is 25
- Julia is 18
- +、-、×、/、%,下面是一個例子:
${x * x - 100}輸出結果是(假設x為5):
${x / 2}
${12 % 10}
-75操作符兩邊必須是數字,因此下面的代碼是錯誤的:
2.5
2
${3 * "5"} <#-- WRONG! -->使用+操作符時,如果一邊是數字,一邊是字符串,就會自動將數字轉換為字符串,例如:
${3 + "5"}輸出結果是:
35使用內建的int(后面講述)獲得整數部分,例如:
${(x/2)?int}輸出結果是(假設x為5):
${1.1?int}
${1.999?int}
${-1.1?int}
${-1.999?int}
2
1
1
-1
-1
- 比較操作符
使用=(或==,完全相等)測試兩個值是否相等,使用!= 測試兩個值是否不相等
=和!=兩邊必須是相同類型的值,否則會產生錯誤,例如<#if 1 = "1">會引起錯誤
Freemarker是精確比較,所以對"x"、"x "和"X"是不相等的
對數字和日期可以使用<、<=、>和>=,但不能用于字符串
由于Freemarker會將>解釋成FTL標記的結束字符,所以對于>和>=可以使用括號來避免這種情況,例如<#if (x > y)>
另一種替代的方法是,使用lt、lte、gt和gte來替代<、<=、>和>=
- 邏輯操作符
&&(and)、||(or)、!(not),只能用于布爾值,否則會產生錯誤
例子:
<#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>
- 內建函數
內建函數的用法類似訪問散列的子變量,只是使用“?”替代“.”,下面列出常用的一些函數
-
- 字符串使用的:
html:對字符串進行HTML編碼
cap_first:使字符串第一個字母大寫
lower_case:將字符串轉換成小寫
upper_case:將字符串轉換成大寫
trim:去掉字符串前后的空白字符
-
- 序列使用的:
size:獲得序列中元素的數目
-
- 數字使用的:
int:取得數字的整數部分(如-1.9?int的結果是-1)
例子(假設test保存字符串"Tom & Jerry"):
${test?html}輸出結果是:
${test?upper_case?html}
Tom & Jerry
TOM & JERRY
- 操作符優先順序
操作符組 | 操作符 |
---|---|
后綴 | [subvarName] [subStringRange] . (methodParams) |
一元 | +expr、-expr、! |
內建 | ? |
乘法 | *、 / 、% |
加法 | +、- |
關系 | <、>、<=、>=(lt、lte、gt、gte) |
相等 | ==(=)、!= |
邏輯and | && |
邏輯or | 雙豎線 |
數字范圍 | .. |
(4)Interpolation
Interpolation有兩種類型:
- 通用Interpolation:${expr}
- 數字Interpolation:#{expr}或#{expr; format}
注意:Interpolation只能用于文本部分
- 通用Interpolation
插入字符串值:直接輸出表達式結果
插入數字值:根據缺省格式(由#setting指令設置)將表達式結果轉換成文本輸出;可以使用內建函數string格式化單個Interpolation,下面是一個例子:
<#setting number_format="currency"/>輸出結果是:
<#assign answer=42/>
${answer}
${answer?string} <#-- the same as ${answer} -->
${answer?string.number}
${answer?string.currency}
${answer?string.percent}
$42.00插入日期值:根據缺省格式(由#setting指令設置)將表達式結果轉換成文本輸出;可以使用內建函數string格式化單個Interpolation,下面是一個使用格式模式的例子:
$42.00
42
$42.00
4,200%
${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')'")}
2003-04-08 21:24:44 Pacific Daylight Time插入布爾值:根據缺省格式(由#setting指令設置)將表達式結果轉換成文本輸出;可以使用內建函數string格式化單個Interpolation,下面是一個例子:
Tue, Apr 8, '03
Tuesday, April 08, 2003, 09:24:44 PM (PDT)
<#assign foo=true/>輸出結果是:
${foo?string("yes", "no")}
yes
- 數字Interpolation的#{expr; format}形式可以用來格式化數字,format可以是:
mX:小數部分最小X位
MX:小數部分最大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、雜項
(1)用戶定義指令
宏和變換器變量是兩種不同類型的用戶定義指令,它們之間的區別是宏是在模板中使用macro指令定義,而變換器是在模板外由程序定義,這里只介紹宏
- 基本用法
宏是和某個變量關聯的模板片斷,以便在模板中通過用戶定義指令使用該變量,下面是一個例子:
<#macro greet>作為用戶定義指令使用宏變量時,使用@替代FTL標記中的#
<font size="+2">Hello Joe!</font>
</#macro>
<@greet></@greet>如果沒有體內容,也可以使用:
<@greet/>
- 參數
在macro指令中可以在宏變量之后定義參數,如:
<#macro greet person>可以這樣使用這個宏變量:
<font size="+2">Hello ${person}!</font>
</#macro>
<@greet person="Fred"/> and <@greet person="Batman"/>輸出結果是:
<font size="+2">Hello Fred!</font>
and <font size="+2">Hello Batman!</font>
宏的參數是FTL表達式,所以下面的代碼具有不同的意思:
<@greet person=Fred/>這意味著將Fred變量的值傳給person參數,該值不僅是字符串,還可以是其它類型,甚至是復雜的表達式
可以有多參數,下面是一個例子:
<#macro greet person color>可以這樣使用該宏變量:
<font size="+2" color="${color}">Hello ${person}!</font>
</#macro>
<@greet person="Fred" color="black"/>其中參數的次序是無關的,因此下面是等價的:
<@greet color="black" person="Fred"/>只能使用在macro指令中定義的參數,并且對所有參數賦值,所以下面的代碼是錯誤的:
<@greet person="Fred" color="black" background="green"/>可以在定義參數時指定缺省值,如:
<@greet person="Fred"/>
<#macro greet person color="black">這樣<@greet person="Fred"/>就正確了
<font size="+2" color="${color}">Hello ${person}!</font>
</#macro>
宏的參數是局部變量,只能在宏定義中有效
- 嵌套內容
用戶定義指令可以有嵌套內容,使用<#nested>指令執行指令開始和結束標記之間的模板片斷
例子:
<#macro border>這樣使用該宏變量:
<table border=4 cellspacing=0 cellpadding=4><tr><td>
<#nested>
</tr></td></table>
</#macro>
<@border>The bordered text</@border>輸出結果:
<table border=4 cellspacing=0 cellpadding=4><tr><td>
The bordered text
</tr></td></table>
<#nested>指令可以被多次調用,例如:
<#macro do_thrice>輸出結果:
<#nested>
<#nested>
<#nested>
</#macro>
<@do_thrice>
Anything.
</@do_thrice>
Anything.嵌套內容可以是有效的FTL,下面是一個有些復雜的例子: <@border> <ul> <@do_thrice> <li><@greet person="Joe"/> </@do_thrice> </ul> </@border> }}} 輸出結果:
Anything.
Anything.
<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>
<#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>
test 3/1: ? ? ?
test 3/2: ? ? ?
test 3/3: ? ? ?
- 在宏定義中使用循環變量
用戶定義指令可以有循環變量,通常用于重復嵌套內容,基本用法是:作為nested指令的參數傳遞循環變量的實際值,而在調用用戶定義指令時,在<@…>開始標記的參數后面指定循環變量的名字
例子:
<#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>
1. 0.5
2. 1
3. 1.5
4. 2 Last!
指定的循環變量的數目和用戶定義指令開始標記指定的不同不會有問題
調用時少指定循環變量,則多指定的值不可見
調用時多指定循環變量,多余的循環變量不會被創建
(2)在模板中定義變量
在模板中定義的變量有三種類型:
- plain變量:可以在模板的任何地方訪問,包括使用include指令插入的模板,使用assign指令創建和替換
- 局部變量:在宏定義體中有效,使用local指令創建和替換
- 循環變量:只能存在于指令的嵌套內容,由指令(如list)自動創建
宏的參數是局部變量,而不是循環變量;局部變量隱藏(而不是覆蓋)同名的plain變量;循環變量隱藏同名的局部變量和plain變量,下面是一個例子:
<#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>
1. plain
2. plain
3. local
4. loop
5. local
6. plain
7. loop
8. loop
9. plain2
內部循環變量隱藏同名的外部循環變量,如:
<#list ["loop 1"] as x>輸出結果:
${x}
<#list ["loop 2"] as x>
${x}
<#list ["loop 3"] as x>
${x}
</#list>
${x}
</#list>
${x}
</#list>
loop 1模板中的變量會隱藏(而不是覆蓋)數據模型中同名變量,如果需要訪問數據模型中的同名變量,使用特殊變量global,下面的例子假設數據模型中的user的值是Big Joe:
loop 2
loop 3
loop 2
loop 1
<#assign user = "Joe Hider">
${user} <#-- prints: Joe Hider -->
${.globals.user} <#-- prints: Big Joe -->
(3)名字空間
通常情況,只使用一個名字空間,稱為主名字空間
為了創建可重用的宏、變換器或其它變量的集合(通常稱庫),必須使用多名字空間,其目的是防止同名沖突
- 創建庫
下面是一個創建庫的例子(假設保存在lib/my_test.ftl中):
<#macro copyright date>使用import指令導入庫到模板中,Freemarker會為導入的庫創建新的名字空間,并可以通過import指令中指定的散列變量訪問庫中的變量:
<p>Copyright (C) ${date} Julia Smith. All rights reserved.
<br>Email: ${mail}</p>
</#macro>
<#assign mail = "jsmith@acme.com">
<#import "/lib/my_test.ftl" as my>輸出結果:
<#assign mail="fred@acme.com">
<@my.copyright date="1999-2002"/>
${my.mail}
${mail}
<p>Copyright (C) 1999-2002 Julia Smith. All rights reserved.可以看到例子中使用的兩個同名變量并沒有沖突,因為它們位于不同的名字空間
<br>Email: jsmith@acme.com</p>
jsmith@acme.com
fred@acme.com
可以使用assign指令在導入的名字空間中創建或替代變量,下面是一個例子:
<#import "/lib/my_test.ftl" as my>輸出結果:
${my.mail}
<#assign mail="jsmith@other.com" in my>
${my.mail}
jsmith@acme.com數據模型中的變量任何地方都可見,也包括不同的名字空間,下面是修改的庫:
jsmith@other.com
<#macro copyright date>假設數據模型中的user變量的值是Fred,則下面的代碼:
<p>Copyright (C) ${date} ${user}. All rights reserved.</p>
</#macro>
<#assign mail = "${user}@acme.com">
<#import "/lib/my_test.ftl" as my>輸出結果:
<@my.copyright date="1999-2002"/>
${my.mail}
<p>Copyright (C) 1999-2002 Fred. All rights reserved.</p>
Fred@acme.com
補充(靜態方法的調用):
方法1:
##定義配置文件 freemarkerstatic.properties
_Validator=com.longyou.util.Validator
_Functions=com.longyou.util.Functions
_EscapeUtils=com.longyou.util.EscapeUtils
/調用代碼
${_Functions.toUpperCase("Hello")}<br>
${_EscapeUtils.escape("狼的原野")}
方法2:
${stack.findValue("@package.ClassName@method")}
補充:常用語法
EG.一個對象BOOK
1.輸出 ${book.name}
空值判斷:${book.name?if_exists },
${book.name?default(‘xxx’)}//默認值xxx
${ book.name!"xxx"}//默認值xxx
日期格式:${book.date?string('yyyy-MM-dd')}
數字格式:${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.循環讀取
<#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}" >
</#list>
freemarker的Eclipse插件
- If you use Eclipse 2.x:
- Open the Window menu, then Open Perspective -> Install/Update
- Click with the right mouse button on the Feature Updates view, then select New -> Site Bookmark
- 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".
- Click Finish
- 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:
- Help -> Software updates -> Find and install....
- Choose "Search for new features to install".
- Click Add Update Site..., and type "FreeMarker" for Name and "http://www.freemarker.org/eclipse/update" for URL.
- Check the box of the "FreeMarker" feature.
- "Next"-s until it is installed...