<!--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-->在項(xiàng)目中使用Spring的注解,關(guān)于spring的注解,由兩種注解方式,
基于注釋(Annotation)的配置有越來(lái)越流行的趨勢(shì),Spring 2.5 順應(yīng)這種趨勢(shì),提供了完全基于注釋配置 Bean、裝配 Bean 的功能,您可以使用基于注釋的 Spring IoC 替換原來(lái)基于 XML 的配置。本文通過(guò)實(shí)例詳細(xì)講述了 Spring 2.5 基于注釋 IoC 功能的使用。
注釋配置相對(duì)于 XML 配置具有很多的優(yōu)勢(shì):
- 它可以充分利用 Java 的反射機(jī)制獲取類結(jié)構(gòu)信息,這些信息可以有效減少配置的工作。如使用 JPA 注釋配置 ORM 映射時(shí),我們就不需要指定 PO 的屬性名、類型等信息,如果關(guān)系表字段和 PO 屬性名、類型都一致,您甚至無(wú)需編寫(xiě)任務(wù)屬性映射信息——因?yàn)檫@些信息都可以通過(guò) Java 反射機(jī)制獲取。
- 注釋和 Java 代碼位于一個(gè)文件中,而 XML 配置采用獨(dú)立的配置文件,大多數(shù)配置信息在程序開(kāi)發(fā)完成后都不會(huì)調(diào)整,如果配置信息和 Java 代碼放在一起,有助于增強(qiáng)程序的內(nèi)聚性。而采用獨(dú)立的 XML 配置文件,程序員在編寫(xiě)一個(gè)功能時(shí),往往需要在程序文件和配置文件中不停切換,這種思維上的不連貫會(huì)降低開(kāi)發(fā)效率。
因此在很多情況下,注釋配置比 XML 配置更受歡迎,注釋配置有進(jìn)一步流行的趨勢(shì)。Spring 2.5 的一大增強(qiáng)就是引入了很多注釋類,現(xiàn)在您已經(jīng)可以使用注釋配置完成大部分 XML 配置的功能。在這篇文章里,我們將向您講述使用注釋進(jìn)行 Bean 定義和依賴注入的內(nèi)容。
Spring 2.5 引入了 @Autowired
注釋,它可以對(duì)類成員變量、方法及構(gòu)造函數(shù)進(jìn)行標(biāo)注,完成自動(dòng)裝配的工作。
Spring 通過(guò)一個(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 中的 car
和 office
這兩個(gè)私有成員變量進(jìn)行自動(dòng)注入。所以對(duì)成員變量使用 @Autowired
后,您大可將它們的 setter 方法(setCar()
和 setOffice()
)從 Boss 中刪除。
當(dāng)然,您也可以通過(guò) @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ì)在開(kāi)發(fā)期或測(cè)試期碰到(如為了快速啟動(dòng) Spring 容器,僅引入一些模塊的 Spring 配置文件),所以 @Autowired(required = false)
會(huì)很少用到。
和找不到一個(gè)類型匹配 Bean 相反的一個(gè)錯(cuò)誤是:如果 Spring 容器中擁有多個(gè)候選 Bean,Spring 容器在啟動(dòng)時(shí)也會(huì)拋出 BeanCreationException
異常。
Spring 允許我們通過(guò) @Qualifier
注釋指定注入 Bean 的名稱,這樣歧義就消除了,可以通過(guò)下面的方法解決異常:
清單 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ǔ)充。一般來(lái)講,@Qualifier
對(duì)方法簽名中入?yún)⑦M(jìn)行注釋會(huì)降低代碼的可讀性,而對(duì)成員變量注釋則相對(duì)好一些。
Spring 不但支持自己定義的 @Autowired
的注釋,還支持幾個(gè)由 JSR-250 規(guī)范定義的注釋,它們分別是 @Resource
、@PostConstruct
以及 @PreDestroy
。
@Resource
的作用相當(dāng)于 @Autowired
,只不過(guò) @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í)將通過(guò)反射機(jī)制使用 byName 自動(dòng)注入策略。
Resource 注釋類位于 Spring 發(fā)布包的 lib/j2ee/common-annotations.jar 類包中,因此在使用之前必須將其加入到項(xiàng)目的類庫(kù)中。來(lái)看一個(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; } |
一般情況下,我們無(wú)需使用類似于 @Resource(type=Car.class)
的注釋方式,因?yàn)?Bean 的類型信息可以通過(guò) 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)的操作。
Spring 容器中的 Bean 是有生命周期的,Spring 允許在 Bean 在初始化完成后以及 Bean 銷毀前執(zhí)行特定的操作,您既可以通過(guò)實(shí)現(xiàn) InitializingBean/DisposableBean 接口來(lái)定制初始化之后 / 銷毀之前的操作方法,也可以通過(guò) <bean> 元素的 init-method/destroy-method 屬性指定初始化之后 / 銷毀之前調(diào)用的操作方法。關(guān)于 Spring 的生命周期,筆者在《精通 Spring 2.x—企業(yè)應(yīng)用開(kāi)發(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í)行了。
我們知道,不管是通過(guò)實(shí)現(xiàn) InitializingBean
/DisposableBean
接口,還是通過(guò) <bean> 元素的 init-method/destroy-method
屬性進(jìn)行配置,都只能為 Bean 指定一個(gè)初始化 / 銷毀的方法。但是使用 @PostConstruct
和 @PreDestroy
注釋卻可以指定多個(gè)初始化 / 銷毀方法,那些被標(biāo)注 @PostConstruct
或 @PreDestroy
注釋的方法都會(huì)在初始化 / 銷毀時(shí)被執(zhí)行。
通過(guò)以下的測(cè)試代碼,您將可以看到 Bean 的初始化 / 銷毀方法是如何被執(zhí)行的:
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)注了 @PostConstruct
的 postConstruct1()
方法將在 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/> 簡(jiǎn)化配置
Spring 2.1 添加了一個(gè)新的 context 的 Schema 命名空間,該命名空間對(duì)注釋驅(qū)動(dòng)、屬性文件引入、加載期織入等功能提供了便捷的配置。我們知道注釋本身是不會(huì)做任何事情的,它僅提供元數(shù)據(jù)信息。要使元數(shù)據(jù)信息真正起作用,必須讓負(fù)責(zé)處理這些元數(shù)據(jù)的處理器工作起來(lái)。
而我們前面所介紹的 AutowiredAnnotationBeanPostProcessor
和 CommonAnnotationBeanPostProcessor
就是處理這些注釋元數(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è) AutowiredAnnotationBeanPostProcessor
、CommonAnnotationBeanPostProcessor
、PersistenceAnnotationBeanPostProcessor
以及 equiredAnnotationBeanPostProcessor
這 4 個(gè) BeanPostProcessor。
在配置文件中使用 context 命名空間之前,必須在 <beans> 元素中聲明 context 命名空間。
![]() ![]() |
![]()
|
雖然我們可以通過(guò) @Autowired
或 @Resource
在 Bean 類中使用自動(dòng)注入功能,但是 Bean 還是在 XML 文件中通過(guò) <bean> 進(jìn)行定義 —— 也就是說(shuō),在 XML 配置文件中定義 Bean,通過(guò) @Autowired
或 @Resource
為 Bean 的成員變量、方法入?yún)⒒驑?gòu)造函數(shù)入?yún)⑻峁┳詣?dòng)注入的功能。能否也通過(guò)注釋定義 Bean,從 XML 配置文件中完全移除 Bean 定義的配置呢?答案是肯定的,我們通過(guò) 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 類中通過(guò) @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
有一個(gè)可選的入?yún)ⅲ糜谥付?Bean 的名稱,在 Boss 中,我們就將 Bean 名稱定義為“boss
”。一般情況下,Bean 都是 singleton 的,需要注入 Bean 的地方僅需要通過(guò) 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)看下面的配置:
<?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> |
這里,所有通過(guò) <bean> 元素定義 Bean 的配置內(nèi)容已經(jīng)被移除,僅需要添加一行 <context:component-scan/> 配置就解決所有問(wèn)題了——Spring XML 配置文件得到了極致的簡(jiǎn)化(當(dāng)然配置元數(shù)據(jù)還是需要的,只不過(guò)以注釋形式存在罷了)。<context:component-scan/> 的 base-package 屬性指定了需要掃描的類包,類包及其遞歸子包中所有的類都會(huì)被處理。
<context:component-scan/> 還允許定義過(guò)濾器將基包下的某些類納入或排除。Spring 支持以下 4 種類型的過(guò)濾方式,通過(guò)下表說(shuō)明:
過(guò)濾器類型 | 說(shuō)明 |
---|---|
注釋 | 假如 com.baobaotao.SomeAnnotation 是一個(gè)注釋類,我們可以將使用該注釋的類過(guò)濾出來(lái)。 |
類名指定 | 通過(guò)全限定類名進(jìn)行過(guò)濾,如您可以指定將 com.baobaotao.Boss 納入掃描,而將 com.baobaotao.Car 排除在外。 |
正則表達(dá)式 | 通過(guò)正則表達(dá)式定義過(guò)濾的類,如下所示: com\.baobaotao\.Default.* |
AspectJ 表達(dá)式 | 通過(guò) AspectJ 表達(dá)式定義過(guò)濾的類,如下所示: com. baobaotao..*Service+ |
下面是一個(gè)簡(jiǎn)單的例子:
<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è)了 AutowiredAnnotationBeanPostProcessor
和 CommonAnnotationBeanPostProcessor
),因此當(dāng)使用 <context:component-scan/> 后,就可以將 <context:annotation-config/> 移除了。
默認(rèn)情況下通過(guò) @Component
定義的 Bean 都是 singleton 的,如果需要使用其它作用范圍的 Bean,可以通過(guò) @Scope
注釋來(lái)達(dá)到目標(biāo),如以下代碼所示:
清單 24. 通過(guò) @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è)擁有特殊語(yǔ)義的注釋,它們分別是:@Repository
、@Service
和 @Controller
。在目前的 Spring 版本中,這 3 個(gè)注釋和 @Component
是等效的,但是從注釋類的命名上,很容易看出這 3 個(gè)注釋分別和持久層、業(yè)務(wù)層和控制層(Web 層)相對(duì)應(yīng)。雖然目前這 3 個(gè)注釋和 @Component
相比沒(méi)有什么新意,但 Spring 將在以后的版本中為它們添加特殊的功能。所以,如果 Web 應(yīng)用程序采用了經(jīng)典的三層分層結(jié)構(gòu)的話,最好在持久層、業(yè)務(wù)層和控制層分別采用 @Repository
、@Service
和 @Controller
對(duì)分層中的類進(jìn)行注釋,而用 @Component
對(duì)那些比較中立的類進(jìn)行注釋。
![]() ![]() |
![]()
|
是否有了這些 IOC 注釋,我們就可以完全摒除原來(lái) 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)整,您需要重新改寫(xiě)源代碼并重新編譯才可以實(shí)施調(diào)整。
- 如果 Bean 不是自己編寫(xiě)的類(如
JdbcTemplate
、SessionFactoryBean
等),注釋配置將無(wú)法實(shí)施,此時(shí) XML 配置是唯一可用的方式。 - 注釋配置往往是類級(jí)別的,而 XML 配置則可以表現(xiàn)得更加靈活。比如相比于
@Transaction
事務(wù)注釋,使用 aop/tx 命名空間的事務(wù)配置更加靈活和簡(jiǎn)單。
所以在實(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 注入之前將這兩種配置方式的元信息融合在一起。
![]() ![]() |
![]()
|
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 配置依然擁有不可替代的地位。
什么叫控制反轉(zhuǎn)呢?套用好萊塢的一句名言就是:你呆著別動(dòng),到時(shí)我會(huì)找你。
什么意思呢?就好比一個(gè)皇帝和太監(jiān)
有一天皇帝想寵幸某個(gè)美女,于是跟太監(jiān)說(shuō),今夜我要寵幸美女
皇帝往往不會(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)班就開(kāi)始給他張羅
介紹一個(gè)合適的給他,完事后,再把小姐還給領(lǐng)班,下次再來(lái)
這個(gè)過(guò)程中,領(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)去就可以了
*
* @author liuguangyi
* @content ejb3注解的API定義在javax.persistence.*包里面。
*
* 注釋說(shuō)明:
* @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語(yǔ)句中的一個(gè)列(默認(rèn)值true)
* updatable 可選,該列是否作為生成的update語(yǔ)句中的一個(gè)列(默認(rèn)值true)
* columnDefinition 可選,為這個(gè)特定列覆蓋sql ddl片段(這可能導(dǎo)致無(wú)法在不同數(shù)據(jù)庫(kù)間移植)
* table 可選,定義對(duì)應(yīng)的表(默認(rèn)為主表)
* length 可選,列長(zhǎng)度(默認(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ù)庫(kù)表格來(lái)保存主鍵
* GenerationType.IDENTITY 主鍵由數(shù)據(jù)庫(kù)自動(dòng)生成(主要是自動(dòng)增長(zhǎng)類型)
* GenerationType.SEQUENCE 根據(jù)底層數(shù)據(jù)庫(kù)的序列來(lái)生成主鍵,條件是數(shù)據(jù)庫(kù)支持序列。(這個(gè)值要與generator一起使用)
* generator 指定生成主鍵使用的生成器(可能是orcale中的序列)。
* @SequenceGenerator —— 注解聲明了一個(gè)數(shù)據(jù)庫(kù)序列。該注解有如下屬性
* name 表示該表主鍵生成策略名稱,它被引用在@GeneratedValue中設(shè)置的“gernerator”值中
* sequenceName 表示生成策略用到的數(shù)據(jù)庫(kù)序列名稱。
* 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ù)庫(kù)完成,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ù)庫(kù)中建立一張額外的表,默認(rèn)表名為hibernate_unque_key,默認(rèn)字段為integer類型,名稱是next_hi(比較少用)
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "hilo")
* 4.assigned 在插入數(shù)據(jù)的時(shí)候主鍵由程序處理(很常用),這是<generator>元素沒(méi)有指定時(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ù)庫(kù)主鍵生成機(jī)制,少用)
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "select")
* 7.sequence 調(diào)用謹(jǐn)慎數(shù)據(jù)庫(kù)的序列來(lái)生成主鍵,要設(shè)定序列名,不然hibernate無(wú)法找到。
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "sequence",
* parameters = { @Parameter(name = "sequence", value = "seq_payablemoney") })
* 8.seqhilo 通過(guò)hilo算法實(shí)現(xiàn),但是主鍵歷史保存在Sequence中,適用于支持Sequence的數(shù)據(jù)庫(kù),如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ì)象的主鍵。通常和<on
* 例:@Id
* @GeneratedValue(generator = "idGenerator")
* @GenericGenerator(name = "idGenerator", strategy = "foreign",
* parameters = { @Parameter(name = "property", value = "info") })
* Integer id;
* @On
* EmployeeInfo info;
* 11.guid 采用數(shù)據(jù)庫(kù)底層的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ò)展,采用立即檢索策略來(lái)獲取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 從表對(duì)象}
* 從表:沒(méi)有主表類。
* 注意:這種方法要求主表與從表的主鍵值想對(duì)應(yīng)。
* 方法二
* 主表:?@On
* @JoinColumn(name="主表外鍵") //這里指定的是數(shù)據(jù)庫(kù)中的外鍵字段。
* public 從表類 get從表類(){return 從表類}
* 從表:@On
* public 主表類 get主表類(){return 主表對(duì)象}
* 注意:@JoinColumn是可選的。默認(rèn)值是從表變量名+"_"+從表的主鍵(注意,這里加的是主鍵。而不是主鍵對(duì)應(yīng)的變量)。
* 方法三
* 主表:@On
* @JoinTable( name="關(guān)聯(lián)表名",
* joinColumns = @JoinColumn(name="主表外鍵"),
* inverseJoinColumns = @JoinColumns(name="從表外鍵")
* )
* 從表:@On
* 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="從表外鍵")
* )
* @On
* 方法一 使用這種配置,在為“一端”添加“多端”時(shí),不會(huì)修改“多端”的外鍵。在“一端”加載時(shí),不會(huì)得到“多端”。如果使用延遲加載,在讀“多端”列表時(shí)會(huì)出異常,立即加載在得到多端時(shí),是一個(gè)空集合(集合元素為0)。
* “一端”配置
* @On
* public List<“多端”類> get“多端”列表(){return “多端”列表}
* “多端”配置參考@ManyToOne.
* 方法二
* “一端”配置
* @On
* @MapKey(name="“多端”做為Key的屬性")
* public Map<“多端”做為Key的屬性的類,主表類> get“多端”列表(){return “多端”列表}
* “多端”配置參考@ManyToOne.
* 方法三 使用這種配置,在為“一端”添加“多端”時(shí),可以修改“多端”的外鍵。
* “一端”配置
* @On
* @JoinColumn(name="“多端”外鍵")
* public List<“多端”類> get“多端”列表(){return “多端”列表}
* “多端”配置參考@ManyToOne.
*
*
*/
1、重寫(xiě)只能出現(xiàn)在繼承關(guān)系之中。當(dāng)一個(gè)類繼承它的父類方法時(shí),都有機(jī)會(huì)重寫(xiě)該父類的方法。一個(gè)特例是父類的方法被標(biāo)識(shí)為final。重寫(xiě)的主要優(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ì)于從父類繼承來(lái)的抽象方法,要么在子類用重寫(xiě)的方式設(shè)計(jì)該方法,要么把子類也標(biāo)識(shí)為抽象的。所以抽象方法可以說(shuō)是必須要被重寫(xiě)的方法。
3、重寫(xiě)的意義。
重寫(xiě)方法可以實(shí)現(xiàn)多態(tài),用父類的引用來(lá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ì)抱怨的。也就是說(shuō),編譯器只看引用類型,而不是對(duì)象類型。
4、重寫(xiě)方法的規(guī)則。
若想實(shí)現(xiàn)一個(gè)合格重寫(xiě)方法,而不是重載,那么必須同時(shí)滿足下面的要求!
A、重寫(xiě)規(guī)則之一:重寫(xiě)方法不能比被重寫(xiě)方法限制有更嚴(yán)格的訪問(wèn)級(jí)別。
(但是可以更廣泛,比如父類方法是包訪問(wèn)權(quán)限,子類的重寫(xiě)方法是public訪問(wèn)權(quán)限。)
比如:Object類有個(gè)toString()方法,開(kāi)始重寫(xiě)這個(gè)方法的時(shí)候我們總?cè)菀淄沺ublic修飾符,編譯器當(dāng)然不會(huì)放過(guò)任何教訓(xùn)我們的機(jī)會(huì)。出錯(cuò)的原因就是:沒(méi)有加任何訪問(wèn)修飾符的方法具有包訪問(wèn)權(quán)限,包訪問(wèn)權(quán)限比public當(dāng)然要嚴(yán)格了,所以編譯器會(huì)報(bào)錯(cuò)的。
B、重寫(xiě)規(guī)則之二:參數(shù)列表必須與被重寫(xiě)方法的相同。
重寫(xiě)有個(gè)孿生的弟弟叫重載,也就是后面要出場(chǎng)的。如果子類方法的參數(shù)與父類對(duì)應(yīng)的方法不同,那么就是你認(rèn)錯(cuò)人了,那是重載,不是重寫(xiě)。
C、重寫(xiě)規(guī)則之三:返回類型必須與被重寫(xiě)方法的返回類型相同。
父類方法A:void eat(){} 子類方法B:int eat(){} 兩者雖然參數(shù)相同,可是返回類型不同,所以不是重寫(xiě)。
父類方法A:int eat(){} 子類方法B:long eat(){} 返回類型雖然兼容父類,但是不同就是不同,所以不是重寫(xiě)。
D、重寫(xiě)規(guī)則之四:重寫(xiě)方法不能拋出新的異常或者比被重寫(xiě)方法聲明的檢查異常更廣的檢查異常。但是可以拋出更少,更有限或者不拋出異常。
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的子類,也即是比被重寫(xiě)的方法拋出了更有限的異常,這是可以的。如果反過(guò)來(lái),父類拋出IOException,子類拋出更為寬泛的Exception,那么不會(huì)通過(guò)編譯的。
注意:這種限制只是針對(duì)檢查異常,至于運(yùn)行時(shí)異常RuntimeException及其子類不再這個(gè)限制之中。
E、重寫(xiě)規(guī)則之五:不能重寫(xiě)被標(biāo)識(shí)為final的方法。
F、重寫(xiě)規(guī)則之六:如果一個(gè)方法不能被繼承,則不能重寫(xiě)它。
比較典型的就是父類的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ò)編譯的。表面上看來(lái)違反了第六條規(guī)則,但實(shí)際上那是一點(diǎn)巧合。Animal類的eat()方法不能被繼承,因此Horse類中的eat()方法是一個(gè)全新的方法,不是重寫(xiě)也不是重載,只是一個(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è)方法了,和重寫(xiě)不同。最最常用的地方就是構(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ù)類型,如上例沒(méi)有正好容納long的整數(shù)類型,那么就轉(zhuǎn)換為float類型的。如果通過(guò)提升也不能找到合適的兼容類型,那么編譯器就會(huì)報(bào)錯(cuò)。反正是不會(huì)自動(dòng)轉(zhuǎn)換為較小的數(shù)據(jù)類型的,必須自己強(qiáng)制轉(zhuǎn)換,自己來(lái)承擔(dān)轉(zhuǎn)變后果。
char類型比較特殊,如果找不到正好匹配的類型,它會(huì)轉(zhuǎn)化為int而不是short,雖然char是16位的。
2、重載方法的規(guī)則。
A、被重載的方法必須改變參數(shù)列表。
參數(shù)必須不同,這是最重要的!不同有兩個(gè)方面,參數(shù)的個(gè)數(shù),參數(shù)的類型,參數(shù)的順序。
B、被重載的方法與返回類型無(wú)關(guān)。
也就是說(shuō),不能通過(guò)返回類型來(lái)區(qū)分重載方法。
C、被重載的方法可以改變?cè)L問(wèn)修飾符。
沒(méi)有重寫(xiě)方法那樣嚴(yán)格的限制。
D、被重載的方法可以聲明新的或者更廣的檢查異常。
沒(méi)有重寫(xiě)方法那樣嚴(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è)輸出沒(méi)有任何問(wèn)題。第三個(gè)方法為什么不是輸出“Horse is called.”呢?還是那句老話,要看引用類型而不是對(duì)象類型,方法重載是在編譯時(shí)刻就決定的了,引用類型決定了調(diào)用哪個(gè)版本的重載方法。
4、重載和重寫(xiě)方法區(qū)別的小結(jié)。
如果能徹底弄明白下面的例子,說(shuō)明你對(duì)重載和重寫(xiě)非常了解了,可以結(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è)輸出分別是什么?被注釋的兩條語(yǔ)句為什么不能通過(guò)編譯?
第一條:a.eat(); 普通的方法調(diào)用,沒(méi)有多態(tài),沒(méi)什么技術(shù)含量。調(diào)用了Animal類的eat()方法,輸出:Animal is eating.
第二條:h.eat(); 普通的方法調(diào)用,也沒(méi)什么技術(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類中沒(méi)有eat(String food)方法。因此不能通過(guò)編譯。
第六條:ah.eat("apple"); 關(guān)鍵點(diǎn)就在這里。解決的方法還是那句老話,不能看對(duì)象類型,要看引用類型。Animal類中沒(méi)有eat(String food)方法。因此不能通過(guò)編譯。
小結(jié)一下:多態(tài)不決定調(diào)用哪個(gè)重載版本;多態(tài)只有在決定哪個(gè)重寫(xiě)版本時(shí)才起作用。
重載對(duì)應(yīng)編譯時(shí),重寫(xiě)對(duì)應(yīng)運(yùn)行時(shí)。夠簡(jiǎn)潔的了吧!
三、構(gòu)造方法。
構(gòu)造方法是一種特殊的方法,沒(méi)有構(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)造方法能使用任何訪問(wèn)修飾符。包括private,事實(shí)上java類庫(kù)有很多都是這樣的,設(shè)計(jì)者不希望使用者創(chuàng)建該類的對(duì)象。
B、構(gòu)造方法的名稱必須與類名相同。這樣使得構(gòu)造方法與眾不同,如果我們遵守sun的編碼規(guī)范,似乎只有構(gòu)造方法的首字母是大寫(xiě)的。
C、構(gòu)造方法不能有返回類型。
反過(guò)來(lái)說(shuō),有返回類型的不是構(gòu)造方法
public class Test {
int Test(){
return 1;
}
}
這個(gè)方法是什么東西?一個(gè)冒充李逵的李鬼而已,int Test()和其他任何普通方法沒(méi)什么兩樣,就是普通的方法!只不過(guò)看起來(lái)很惡心,類似惡心的東西在考試卷子里比較多。
D、如果不在類中創(chuàng)建自己的構(gòu)造方法,編譯器會(huì)自動(dòng)生成默認(rèn)的不帶參數(shù)的構(gòu)造函數(shù)。
這點(diǎn)很容易驗(yàn)證!寫(xiě)一個(gè)這樣簡(jiǎn)單的類,編譯。
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)添加無(wú)參的構(gòu)造方法的!
F、在每個(gè)構(gòu)造方法中,如果使用了重載構(gòu)造函數(shù)this()方法,或者父類的構(gòu)造方法super()方法,那么this()方法或者super()方法必須放在第一行。而且這兩個(gè)方法只能選擇一個(gè),因此它們之間沒(méi)有順序問(wèn)題。
G、除了編譯器生成的構(gòu)造方法,而且沒(méi)有顯式地調(diào)用super()方法,那么編譯器會(huì)插入一個(gè)super()無(wú)參調(diào)用。
H、抽象類有構(gòu)造方法。
四、靜態(tài)方法的重載與重寫(xiě)(覆蓋)。
1、靜態(tài)方法是不能被覆蓋的。可以分兩種情況討論:
A、子類的非靜態(tài)方法“覆蓋”父類的靜態(tài)方法。
這種情況下,是不能通過(guò)編譯的。
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ì)象,可以通過(guò)類名直接調(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í)能通過(guò)編譯!但是不要以為這就是多態(tài)!多態(tài)的特點(diǎn)是動(dòng)態(tài)綁定,看下面的例子:
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é)果可以看出,并沒(méi)有實(shí)現(xiàn)多態(tài)。
但是這種形式很迷惑人,貌似多態(tài),實(shí)際編程中千萬(wàn)不要這樣搞,會(huì)把大家搞懵的!
它不符合覆蓋表現(xiàn)出來(lái)的特性,不應(yīng)該算是覆蓋!
總而言之,靜態(tài)方法不能被覆蓋。
2、靜態(tài)方法可以和非靜態(tài)方法一樣被重載。
這樣的例子太多了,我不想寫(xiě)例程了。看看java類庫(kù)中很多這樣的例子。
如java.util.Arrays類的一堆重載的binarySearch方法。
在這里提一下是因?yàn)椴橘Y料時(shí)看到這樣的話“sun的SL275課程說(shuō),靜態(tài)方法只能控制靜態(tài)變量(他們本身沒(méi)有),靜態(tài)方法不能被重載和覆蓋……”
大家不要相信啊!可以重載的。而且靜態(tài)與非靜態(tài)方法可以重載。
從重載的機(jī)制很容易就理解了,重載是在編譯時(shí)刻就決定的了,非靜態(tài)方法都可以,靜態(tài)方法怎么可能不會(huì)呢?
1、String中的每個(gè)字符都是一個(gè)16位的Unicode字符,用Unicode很容易表達(dá)豐富的國(guó)際化字符集,比如很好的中文支持。甚至Java的標(biāo)識(shí)符都可以用漢字,但是沒(méi)人會(huì)用吧(只在一本清華的《Java2實(shí)用教程》看過(guò))。
2、判斷空字符串。根據(jù)需要自己選擇某個(gè)或者它們的組合
if ( s == null ) //從引用的角度
if ( s.length() == 0 ) //從長(zhǎng)度判別
if ( s.trim().length () == 0 ) //是否有多個(gè)空白字符
trim()方法的作用是是移除前導(dǎo)和尾部的Unicode值小于'"u0020'的字符,并返回“修剪”好的字符串。這種方法很常用,比如需要用戶輸入用戶名,用戶不小心加了前導(dǎo)或者尾部空格,一個(gè)好的程序應(yīng)該知道用戶不是故意的,即使是故意的也應(yīng)該智能點(diǎn)地處理。
判斷空串是很常用的操作,但是Java類庫(kù)直到1.6才提供了isEmpty()方法。當(dāng)且僅當(dāng) length() 為 0 時(shí)返回 true。
3、未初始化、空串""與null。它們是不同的概念。對(duì)未初始化的對(duì)象操作會(huì)被編譯器擋在門(mén)外;null是一個(gè)特殊的初始化值,是一個(gè)不指向任何對(duì)象的引用,對(duì)引用為null的對(duì)象操作會(huì)在運(yùn)行時(shí)拋出異常NullPointerException;而空串是長(zhǎng)度為0的字符串,和別的字符串的唯一區(qū)別就是長(zhǎng)度為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類的方法很多,在編寫(xiě)相關(guān)代碼的時(shí)候看看JDK文檔時(shí)有好處的,要不然花了大量時(shí)間實(shí)現(xiàn)一個(gè)已經(jīng)存在的方法是很不值得的,因?yàn)榫帉?xiě)、測(cè)試、維護(hù)自己的代碼使項(xiàng)目的成本增加,利潤(rùn)減少,嚴(yán)重的話會(huì)導(dǎo)致開(kāi)不出工資……
5、字符串的比較。
Java不允許自定義操作符重載,因此字符串的比較要用compareTo() 或者 compareToIgnoreCase()。s1.compareTo(s2),返回值大于0則,則前者大;等于0,一般大;小于0,后者大。比較的依據(jù)是字符串中各個(gè)字符的Unicode值。
6、toString()方法。
Java的任何對(duì)象都有toString()方法,是從Object對(duì)象繼承而來(lái)的。它的作用就是讓對(duì)象在輸出時(shí)看起來(lái)更有意義,而不是奇怪的對(duì)象的內(nèi)存地址。對(duì)測(cè)試也是很有幫助的。
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"
上述三條語(yǔ)句其實(shí)產(chǎn)生了3個(gè)String對(duì)象,"ray","long","raylong"。第2條語(yǔ)句確實(shí)產(chǎn)生了"raylong"字符串,但是沒(méi)有指定把該字符串的引用賦給誰(shuí),因此沒(méi)有改變name引用。第3條語(yǔ)句根據(jù)不變性,并沒(méi)有改變"ray",JVM創(chuàng)建了一個(gè)新的對(duì)象,把"ray","long"的連接賦給了name引用,因此引用變了,但是原對(duì)象沒(méi)變。
8、String的不變性的機(jī)制顯然會(huì)在String常量?jī)?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)編譯器遇見(jiàn)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)別名問(wèn)題!這是String對(duì)象與普通對(duì)象的一點(diǎn)區(qū)別。
乍看起來(lái)這是底層的機(jī)制,對(duì)我們編程沒(méi)什么影響。而且這種機(jī)制會(huì)大幅度提高String的效率,實(shí)際上卻不是這樣。為連接n個(gè)字符串使用字符串連接操作時(shí),要消耗的時(shí)間是n的平方級(jí)!因?yàn)槊績(jī)蓚€(gè)字符串連接,它們的內(nèi)容都要被復(fù)制。因此在處理大量的字符串連接時(shí),而且要求性能時(shí),我們不要用String,StringBuffer是更好的選擇。
8、StringBuffer類。StringBuffer類是可變的,不會(huì)在字符串常量池中,而是在堆中,不會(huì)留下一大堆無(wú)用的對(duì)象。而且它可將字符串緩沖區(qū)安全地用于多個(gè)線程。每個(gè)StringBuffer對(duì)象都有一定的容量。只要StringBuffer對(duì)象所包含的字符序列的長(zhǎng)度沒(méi)有超出此容量,就無(wú)需分配新的內(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è)字符的容量就足夠了,沒(méi)有溢出
System.out.println(sb.length()); //輸出字符串長(zhǎng)度是4
System.out.println(sb.capacity()); //輸出該字符串緩沖區(qū)的容量是16
sb.append("12345678901234567"); //這是17個(gè)字符,16個(gè)字符的容量不夠了,擴(kuò)容為17+16個(gè)字符的容量
System.out.println(sb.length()); //輸出字符串長(zhǎng)度是17
System.out.println(sb.capacity()); //輸出該字符串緩沖區(qū)的容量是34
sb.append("890").reverse().insert(10,"-");
System.out.println(sb); //輸出0987654321-09876543214321
字符串的長(zhǎng)度和字符緩沖區(qū)的容量是兩個(gè)概念,注意區(qū)別。
還有串聯(lián)的方式看起來(lái)是不是很酷!用返回值連接起來(lái)可以實(shí)現(xiàn)這種簡(jiǎn)潔和優(yōu)雅。
10、StringBuilder類。 從J2SE 5.0 提供了StringBuilder類,它和StringBuffer類是孿生兄弟,很像。它存在的價(jià)值在于:對(duì)字符串操作的效率更高。不足的是線程安全無(wú)法保證,不保證同步。那么兩者性能到底差多少呢?很多!
請(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)檫@條又臭又長(zhǎng)。推薦三種人:一、沒(méi)事閑著型。二、想深入地理解Java的字符串,即使明明知道學(xué)了也沒(méi)用。三、和我一樣愛(ài)好研究“茴”字有幾種寫(xiě)法。
還是那句老話,String太特殊了,以至于某些規(guī)則對(duì)String不起作用。個(gè)人感覺(jué)這種特殊性并不好。看例子:
例子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!原因很簡(jiǎn)單,String對(duì)象被放進(jìn)常量池里了,再次出現(xiàn)“java”字符串的時(shí)候,JVM很興奮地把str2的引用也指向了“java”對(duì)象,它認(rèn)為自己節(jié)省了內(nèi)存開(kāi)銷。不難理解吧 呵呵
例子B:
String str1 = new String("java");
String str2 = new String("java");
System.out.print(str1==str2);
看過(guò)上例的都學(xué)聰明了,這次肯定會(huì)輸出true!很不幸,JVM并沒(méi)有這么做,結(jié)果是false。原因很簡(jiǎn)單,例子A中那種聲明的方式確實(shí)是在String常量池創(chuàng)建“java”對(duì)象,但是一旦看到new關(guān)鍵字,JVM會(huì)在堆中為String分配空間。兩者聲明方式貌合神離,這也是我把“如何創(chuàng)建字符串對(duì)象”放到后面來(lái)講的原因。大家要沉住氣,還有一個(gè)例子。
例子C:
String str1 = "java";
String str2 = "blog";
String s = str1+str2;
System.out.print(s=="javablog");
再看這個(gè)例子,很多同志不敢妄言是true還是false了吧。愛(ài)玩腦筋急轉(zhuǎn)彎的人會(huì)說(shuō)是false吧……恭喜你,你會(huì)搶答了!把那個(gè)“吧”字去掉你就完全正確。原因很簡(jiǎn)單,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)不同),也就是說(shuō)str1+str2是在堆里創(chuàng)建的,s引用當(dāng)然不可能指向字符串常量池里的對(duì)象。沒(méi)崩潰的人繼續(xù)看例子D。
例子D:
String s1 = "java";
String s2 = new String("java");
System.out.print(s1.intern()==s2.intern());
intern()是什么東東?反正結(jié)果是true。如果沒(méi)用過(guò)這個(gè)方法,而且訓(xùn)練有素的程序員會(huì)去看JDK文檔了。簡(jiǎn)單點(diǎn)說(shuō)就是用intern()方法就可以用“==”比較字符串的內(nèi)容了。在我看到intern()方法到底有什么用之前,我認(rèn)為它太多余了。其實(shí)我寫(xiě)的這一條也很多余,intern()方法還存在諸多的問(wèn)題,如效率、實(shí)現(xiàn)上的不統(tǒng)一……
例子E:
String str1 = "java";
String str2 = new String("java");
System.out.print(str1.equals(str2));
無(wú)論在常量池還是堆中的對(duì)象,用equals()方法比較的就是內(nèi)容,就這么簡(jiǎn)單!看完此條的人一定很后悔,但是在開(kāi)始我勸你別看了……
后記:用彪哥的話說(shuō)“有意思嗎?”,確實(shí)沒(méi)勁。在寫(xiě)這段的時(shí)候我也是思量再三,感覺(jué)自己像孔乙己炫耀“茴”字有幾種寫(xiě)法。我查了一下茴 ,回,囘,囬,還有一種是“口”字里面有個(gè)“目”字,后面這四個(gè)都加上草字頭……
快速入門(mén)
(1)模板 + 數(shù)據(jù)模型 = 輸出
FreeMarker基于設(shè)計(jì)者和程序員是具有不同專業(yè)技能的不同個(gè)體的觀念他們是分工勞動(dòng)的:
設(shè)計(jì)者專注于表示——?jiǎng)?chuàng)建HTML文件、圖片、Web頁(yè)面的其它可視化方面;
程序員創(chuàng)建系統(tǒng),生成設(shè)計(jì)頁(yè)面要顯示的數(shù)據(jù)。
經(jīng)常會(huì)遇到的問(wèn)題是:在Web頁(yè)面(或其它類型的文檔)中顯示的信息在設(shè)計(jì)頁(yè)面時(shí)是無(wú)效的,是基于動(dòng)態(tài)數(shù)據(jù)的。在這里,你可以在HTML(或其它要輸出的文本)中加入一些特定指令,F(xiàn)reeMarker會(huì)在輸出頁(yè)面給最終用戶時(shí),用適當(dāng)?shù)臄?shù)據(jù)替代這些代碼。
先來(lái)解釋一下freemaker的基本語(yǔ)法了,
<# ... > 中存放所有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> 宏,無(wú)返回參數(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>這個(gè)例子是在簡(jiǎn)單的HTML中加入了一些由${…}包圍的特定代碼,這些特定代碼是FreeMarker的指令,而包含F(xiàn)reeMarker的指令的文件就稱為模板(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來(lái)自于數(shù)據(jù)模型(data model)。
數(shù)據(jù)模型由程序員編程來(lái)創(chuàng)建,向模板提供變化的信息,這些信息來(lái)自于數(shù)據(jù)庫(kù)、文件,甚至于在程序中直接生成。
模板設(shè)計(jì)者不關(guān)心數(shù)據(jù)從那兒來(lái),只知道使用已經(jīng)建立的數(shù)據(jù)模型。
下面是一個(gè)可能的數(shù)據(jù)模型:
(root)數(shù)據(jù)模型類似于計(jì)算機(jī)的文件系統(tǒng),latestProduct可以看作是目錄。
|
+- user = "Big Joe"
|
+- latestProduct
|
+- url = "products/greenmouse.html"
|
+- name = "green mouse"
2、數(shù)據(jù)模型
(1)基礎(chǔ)
在快速入門(mén)中介紹了在模板中使用的三種基本對(duì)象類型:scalars、hashes 和sequences,其實(shí)還可以有其它更多的能力:
- scalars:存儲(chǔ)單值
- hashes:充當(dāng)其它對(duì)象的容器,每個(gè)都關(guān)聯(lián)一個(gè)唯一的查詢名字
- sequences:充當(dāng)其它對(duì)象的容器,按次序訪問(wèn)
- 方法:通過(guò)傳遞的參數(shù)進(jìn)行計(jì)算,以新對(duì)象返回結(jié)果
- 用戶自定義FTL標(biāo)記:宏和變換器
通常每個(gè)變量只具有上述的一種能力,但一個(gè)變量可以具有多個(gè)上述能力,如下面的例子:
(root)mouse既是scalars又是hashes,將上面的數(shù)據(jù)模型合并到下面的模板:
|
+- mouse = "Yerri"
|
+- age = 12
|
+- color = "brown">
${mouse} <#-- use mouse as scalar -->輸出結(jié)果是:
${mouse.age} <#-- use mouse as hash -->
${mouse.color} <#-- use mouse as hash -->
Yerri
12
brown
(2)Scalar變量
Scalar變量存儲(chǔ)單值,可以是:
- 字符串:簡(jiǎn)單文本,在模板中使用引號(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開(kāi)始
集合變量通常類似sequences,除非無(wú)法訪問(wèn)它的大小和不能使用索引來(lái)獲得它的子變量;集合可以看作只能由<#list …>指令使用的受限sequences
(4)方法
方法變量通常是基于給出的參數(shù)計(jì)算值。
下面的例子假設(shè)程序員已經(jīng)將方法變量avg放到數(shù)據(jù)模型中,用來(lái)計(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)變量表示為樹(shù)型結(jié)構(gòu)中的一個(gè)節(jié)點(diǎn),通常在XML處理中使用,會(huì)在后面的專門(mén)章節(jié)中講
3、模板
(1)整體結(jié)構(gòu)
模板使用FTL(FreeMarker模板語(yǔ)言)編寫(xiě),是下面各部分的一個(gè)組合:
- 文本:直接輸出
- Interpolation:由${和},或#{和}來(lái)限定,計(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ū)分大小寫(xiě),所以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)記是類似的:
- 開(kāi)始標(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>如果使用不存在的指令,F(xiàn)reeMarker不會(huì)使用模板輸出,而是產(chǎn)生一個(gè)錯(cuò)誤消息。
<#list animals as being>
<li>${being.name} for ${being.price} Euros
<#if use = "Big Joe">
(except for you)
</#list>
</#if> <#-- WRONG! -->
</ul>
FreeMarker會(huì)忽略FTL標(biāo)記中的空白字符,如下面的例子:
<#list但是,<、</和指令之間不允許有空白字符。
animals as
being
>
${being.name} for ${being.price} Euros
</#list >
(3)表達(dá)式
直接指定值
- 字符串
如果包含特殊字符需要轉(zhuǎn)義,如下面的例子:
${"It's \"quoted\" and輸出結(jié)果是:
this is a backslash: \\"}
${'It\'s "quoted" and
this is a backslash: \\'}
It's "quoted" and下面是支持的轉(zhuǎn)義序列:
this is a backslash: \
It's "quoted" and
this is a backslash: \
轉(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}"}輸出的結(jié)果是:
${r"C:\foo\bar"}
${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>輸出的結(jié)果是:
${x}
</#list>
winter列表的項(xiàng)目是表達(dá)式,所以可以有下面的例子:
spring
summer
autumn
[2 + 2, [1, 2, 3, 4], "whatnot"]可以使用數(shù)字范圍定義數(shù)字序列,例如2..5等同于[2, 3, 4, 5],但是更有效率,注意數(shù)字范圍沒(méi)有方括號(hào)
可以定義反遞增的數(shù)字范圍,如5..2
- 散列(hash)
{"name":"green mouse", "price":150}鍵和值都是表達(dá)式,但是鍵必須是字符串
獲取變量
- 頂層變量: ${variable},變量名只能是字母、數(shù)字、下劃線、$、@和#的組合,且不能以數(shù)字開(kāi)頭
- 從散列中獲取數(shù)據(jù)
可以使用點(diǎn)語(yǔ)法或方括號(hào)語(yǔ)法,假設(shè)有下面的數(shù)據(jù)模型:
(root)下面都是等價(jià)的:
|
+- book
| |
| +- title = "Breeding green mouses"
| |
| +- author
| |
| +- name = "Julia Smith"
| |
| +- info = "Biologist, 1923-1985, Canada"
|
+- test = "title"
book.author.name使用點(diǎn)語(yǔ)法,變量名字有頂層變量一樣的限制,但方括號(hào)語(yǔ)法沒(méi)有該限制,因?yàn)槊质侨我獗磉_(dá)式的結(jié)果
book["author"].name
book.author.["name"]
book["author"]["name"]
- 從序列獲得數(shù)據(jù):和散列的方括號(hào)語(yǔ)法語(yǔ)法一樣,只是方括號(hào)中的表達(dá)式值必須是數(shù)字;注意:第一個(gè)項(xiàng)目的索引是0
序列片斷:使用[startIndex..endIndex]語(yǔ)法,從序列中獲得序列片斷(也是序列);startIndex和endIndex是結(jié)果為數(shù)字的表達(dá)式
- 特殊變量:FreeMarker內(nèi)定義變量,使用.variablename語(yǔ)法訪問(wèn)
字符串操作
- Interpolation(或連接操作)
可以使用${..}(或#{..})在文本部分插入表達(dá)式的值,例如:
${"Hello ${user}!"}可以使用+操作符獲得同樣的結(jié)果
${"${user}${user}${user}${user}"}
${"Hello " + user + "!"}${..}只能用于文本部分,下面的代碼是錯(cuò)誤的:
${user + user + user + user}
<#if ${isBig}>Wow!</#if>應(yīng)該寫(xiě)成:
<#if "${isBig}">Wow!</#if>
<#if isBig>Wow!</#if>
- 子串
例子(假設(shè)user的值為“Big Joe”):
${user[0]}${user[4]}結(jié)果是(注意第一個(gè)字符的索引是0):
${user[1..4]}
BJ序列操作
ig J
- 連接操作:和字符串一樣,使用+,下面是一個(gè)例子:
<#list ["Joe", "Fred"] + ["Julia", "Kate"] as user>輸出結(jié)果是:
- ${user}
</#list>
- Joe散列操作
- Fred
- Julia
- Kate
- 連接操作:和字符串一樣,使用+,如果具有相同的key,右邊的值替代左邊的值,例如:
<#assign ages = {"Joe":23, "Fred":25} + {"Joe":30, "Julia":18}>輸出結(jié)果是:
- Joe is ${ages.Joe}
- Fred is ${ages.Fred}
- Julia is ${ages.Julia}
- Joe is 30算術(shù)運(yùn)算
- Fred is 25
- Julia is 18
- +、-、×、/、%,下面是一個(gè)例子:
${x * x - 100}輸出結(jié)果是(假設(shè)x為5):
${x / 2}
${12 % 10}
-75操作符兩邊必須是數(shù)字,因此下面的代碼是錯(cuò)誤的:
2.5
2
${3 * "5"} <#-- WRONG! -->使用+操作符時(shí),如果一邊是數(shù)字,一邊是字符串,就會(huì)自動(dòng)將數(shù)字轉(zhuǎn)換為字符串,例如:
${3 + "5"}輸出結(jié)果是:
35使用內(nèi)建的int(后面講述)獲得整數(shù)部分,例如:
${(x/2)?int}輸出結(jié)果是(假設(shè)x為5):
${1.1?int}
${1.999?int}
${-1.1?int}
${-1.999?int}
2
1
1
-1
-1
- 比較操作符
使用=(或==,完全相等)測(cè)試兩個(gè)值是否相等,使用!= 測(cè)試兩個(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)來(lái)避免這種情況,例如<#if (x > y)>
另一種替代的方法是,使用lt、lte、gt和gte來(lái)替代<、<=、>和>=
- 邏輯操作符
&&(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ù)的用法類似訪問(wèn)散列的子變量,只是使用“?”替代“.”,下面列出常用的一些函數(shù)
-
- 字符串使用的:
html:對(duì)字符串進(jìn)行HTML編碼
cap_first:使字符串第一個(gè)字母大寫(xiě)
lower_case:將字符串轉(zhuǎn)換成小寫(xiě)
upper_case:將字符串轉(zhuǎn)換成大寫(xiě)
trim:去掉字符串前后的空白字符
-
- 序列使用的:
size:獲得序列中元素的數(shù)目
-
- 數(shù)字使用的:
int:取得數(shù)字的整數(shù)部分(如-1.9?int的結(jié)果是-1)
例子(假設(shè)test保存字符串"Tom & Jerry"):
${test?html}輸出結(jié)果是:
${test?upper_case?html}
Tom & Jerry
TOM & JERRY
- 操作符優(yōu)先順序
操作符組 | 操作符 |
---|---|
后綴 | [subvarName] [subStringRange] . (methodParams) |
一元 | +expr、-expr、! |
內(nèi)建 | ? |
乘法 | *、 / 、% |
加法 | +、- |
關(guān)系 | <、>、<=、>=(lt、lte、gt、gte) |
相等 | ==(=)、!= |
邏輯and | && |
邏輯or | 雙豎線 |
數(shù)字范圍 | .. |
(4)Interpolation
Interpolation有兩種類型:
- 通用Interpolation:${expr}
- 數(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"/>輸出結(jié)果是:
<#assign answer=42/>
${answer}
${answer?string} <#-- the same as ${answer} -->
${answer?string.number}
${answer?string.currency}
${answer?string.percent}
$42.00插入日期值:根據(jù)缺省格式(由#setting指令設(shè)置)將表達(dá)式結(jié)果轉(zhuǎn)換成文本輸出;可以使用內(nèi)建函數(shù)string格式化單個(gè)Interpolation,下面是一個(gè)使用格式模式的例子:
$42.00
42
$42.00
4,200%
${lastUpdated?string("yyyy-MM-dd HH:mm:ss zzzz")}輸出的結(jié)果類似下面的格式:
${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插入布爾值:根據(jù)缺省格式(由#setting指令設(shè)置)將表達(dá)式結(jié)果轉(zhuǎn)換成文本輸出;可以使用內(nèi)建函數(shù)string格式化單個(gè)Interpolation,下面是一個(gè)例子:
Tue, Apr 8, '03
Tuesday, April 08, 2003, 09:24:44 PM (PDT)
<#assign foo=true/>輸出結(jié)果是:
${foo?string("yes", "no")}
yes
- 數(shù)字Interpolation的#{expr; format}形式可以用來(lái)格式化數(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)的模板片斷,以便在模板中通過(guò)用戶定義指令使用該變量,下面是一個(gè)例子:
<#macro greet>作為用戶定義指令使用宏變量時(shí),使用@替代FTL標(biāo)記中的#
<font size="+2">Hello Joe!</font>
</#macro>
<@greet></@greet>如果沒(méi)有體內(nèi)容,也可以使用:
<@greet/>
- 參數(shù)
在macro指令中可以在宏變量之后定義參數(shù),如:
<#macro greet person>可以這樣使用這個(gè)宏變量:
<font size="+2">Hello ${person}!</font>
</#macro>
<@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ù)的次序是無(wú)關(guān)的,因此下面是等價(jià)的:
<@greet color="black" person="Fred"/>只能使用在macro指令中定義的參數(shù),并且對(duì)所有參數(shù)賦值,所以下面的代碼是錯(cuò)誤的:
<@greet person="Fred" color="black" background="green"/>可以在定義參數(shù)時(shí)指定缺省值,如:
<@greet person="Fred"/>
<#macro greet person color="black">這樣<@greet person="Fred"/>就正確了
<font size="+2" color="${color}">Hello ${person}!</font>
</#macro>
宏的參數(shù)是局部變量,只能在宏定義中有效
- 嵌套內(nèi)容
用戶定義指令可以有嵌套內(nèi)容,使用<#nested>指令執(zhí)行指令開(kāi)始和結(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>輸出結(jié)果:
<#nested>
<#nested>
<#nested>
</#macro>
<@do_thrice>
Anything.
</@do_thrice>
Anything.嵌套內(nèi)容可以是有效的FTL,下面是一個(gè)有些復(fù)雜的例子: <@border> <ul> <@do_thrice> <li><@greet person="Joe"/> </@do_thrice> </ul> </@border> }}} 輸出結(jié)果:
Anything.
Anything.
<table border=4 cellspacing=0 cellpadding=4><tr><td>宏定義中的局部變量對(duì)嵌套內(nèi)容是不可見(jiàn)的,例如:
<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>輸出結(jié)果:
<#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: ? ? ?
- 在宏定義中使用循環(huán)變量
用戶定義指令可以有循環(huán)變量,通常用于重復(fù)嵌套內(nèi)容,基本用法是:作為nested指令的參數(shù)傳遞循環(huán)變量的實(shí)際值,而在調(diào)用用戶定義指令時(shí),在<@…>開(kāi)始標(biāo)記的參數(shù)后面指定循環(huán)變量的名字
例子:
<#macro repeat count>輸出結(jié)果:
<#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!
指定的循環(huán)變量的數(shù)目和用戶定義指令開(kāi)始標(biāo)記指定的不同不會(huì)有問(wèn)題
調(diào)用時(shí)少指定循環(huán)變量,則多指定的值不可見(jiàn)
調(diào)用時(shí)多指定循環(huán)變量,多余的循環(huán)變量不會(huì)被創(chuàng)建
(2)在模板中定義變量
在模板中定義的變量有三種類型:
- plain變量:可以在模板的任何地方訪問(wèn),包括使用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">輸出結(jié)果:
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
內(nèi)部循環(huán)變量隱藏同名的外部循環(huán)變量,如:
<#list ["loop 1"] as x>輸出結(jié)果:
${x}
<#list ["loop 2"] as x>
${x}
<#list ["loop 3"] as x>
${x}
</#list>
${x}
</#list>
${x}
</#list>
loop 1模板中的變量會(huì)隱藏(而不是覆蓋)數(shù)據(jù)模型中同名變量,如果需要訪問(wèn)數(shù)據(jù)模型中的同名變量,使用特殊變量global,下面的例子假設(shè)數(shù)據(jù)模型中的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)名字空間
通常情況,只使用一個(gè)名字空間,稱為主名字空間
為了創(chuàng)建可重用的宏、變換器或其它變量的集合(通常稱庫(kù)),必須使用多名字空間,其目的是防止同名沖突
- 創(chuàng)建庫(kù)
下面是一個(gè)創(chuàng)建庫(kù)的例子(假設(shè)保存在lib/my_test.ftl中):
<#macro copyright date>使用import指令導(dǎo)入庫(kù)到模板中,F(xiàn)reemarker會(huì)為導(dǎo)入的庫(kù)創(chuàng)建新的名字空間,并可以通過(guò)import指令中指定的散列變量訪問(wèn)庫(kù)中的變量:
<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>輸出結(jié)果:
<#assign mail="fred@acme.com">
<@my.copyright date="1999-2002"/>
${my.mail}
${mail}
<p>Copyright (C) 1999-2002 Julia Smith. All rights reserved.可以看到例子中使用的兩個(gè)同名變量并沒(méi)有沖突,因?yàn)樗鼈兾挥诓煌拿挚臻g
<br>Email: jsmith@acme.com</p>
jsmith@acme.com
fred@acme.com
可以使用assign指令在導(dǎo)入的名字空間中創(chuàng)建或替代變量,下面是一個(gè)例子:
<#import "/lib/my_test.ftl" as my>輸出結(jié)果:
${my.mail}
<#assign mail="jsmith@other.com" in my>
${my.mail}
jsmith@acme.com數(shù)據(jù)模型中的變量任何地方都可見(jiàn),也包括不同的名字空間,下面是修改的庫(kù):
jsmith@other.com
<#macro copyright date>假設(shè)數(shù)據(jù)模型中的user變量的值是Fred,則下面的代碼:
<p>Copyright (C) ${date} ${user}. All rights reserved.</p>
</#macro>
<#assign mail = "${user}@acme.com">
<#import "/lib/my_test.ftl" as my>輸出結(jié)果:
<@my.copyright date="1999-2002"/>
${my.mail}
<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ǔ)充:常用語(yǔ)法
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>...
其中空值判斷可以寫(xiě)成<#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}" >
</#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...
Alt+回車 導(dǎo)入包,自動(dòng)修正
Ctrl+N 查找類
Ctrl+Shift+N 查找文件
Ctrl+Alt+L 格式化代碼
Ctrl+Alt+O 優(yōu)化導(dǎo)入的類和包
Alt+Insert 生成代碼(如get,set方法,構(gòu)造函數(shù)等)
Ctrl+E或者Alt+Shift+C 最近更改的代碼
Ctrl+R 替換文本
Ctrl+F 查找文本
Ctrl+Shift+Space 自動(dòng)補(bǔ)全代碼
Ctrl+空格 代碼提示
Ctrl+Alt+Space 類名或接口名提示
Ctrl+P 方法參數(shù)提示
Ctrl+Shift+Alt+N 查找類中的方法或變量
Alt+Shift+C 對(duì)比最近修改的代碼
Shift+F6 重構(gòu)-重命名
Ctrl+Shift+先上鍵
Ctrl+X 刪除行
Ctrl+D 復(fù)制行
Ctrl+/ 或 Ctrl+Shift+/ 注釋(// 或者/*...*/ )
Ctrl+J 自動(dòng)代碼
Ctrl+E 最近打開(kāi)的文件
Ctrl+H 顯示類結(jié)構(gòu)圖
Ctrl+Q 顯示注釋文檔
Alt+F1 查找代碼所在位置
Alt+1 快速打開(kāi)或隱藏工程面板
Ctrl+Alt+ left/right 返回至上次瀏覽的位置
Alt+ left/right 切換代碼視圖
Alt+ Up/Down 在方法間快速移動(dòng)定位
Ctrl+Shift+Up/Down 代碼向上/下移動(dòng)。
F2 或Shift+F2 高亮錯(cuò)誤或警告快速定位
代碼標(biāo)簽輸入完成后,按Tab,生成代碼。
選中文本,按Ctrl+Shift+F7 ,高亮顯示所有該文本,按Esc高亮消失。
Ctrl+W 選中代碼,連續(xù)按會(huì)有其他效果
選中文本,按Alt+F3 ,逐個(gè)往下查找相同文本,并高亮顯示。
Ctrl+Up/Down 光標(biāo)跳轉(zhuǎn)到第一行或最后一行下
Ctrl+B 快速打開(kāi)光標(biāo)處的類或方法



…
/”)把所選塊包圍起來(lái)。要反注釋一個(gè)代碼塊就在塊中任何一個(gè)地方按Ctrl-Shift-/即可。















研究了很久新出的 Spring 2.5, 總算大致明白了如何用標(biāo)注定義 Bean, 但是如何定義和注入類型為 java.lang.String 的 bean 仍然未解決, 希望得到高人幫助.
總的來(lái)看 Java EE 5 的標(biāo)注開(kāi)發(fā)方式開(kāi)來(lái)是得到了大家的認(rèn)可了.
@Service 相當(dāng)于定義 bean, 自動(dòng)根據(jù) bean 的類名生成一個(gè)首字母小寫(xiě)的 bean
@Autowired 則是自動(dòng)注入依賴的類, 它會(huì)在類路徑中找成員對(duì)應(yīng)的類/接口的實(shí)現(xiàn)類, 如果找到多個(gè), 需要用 @Qualifier("chineseMan") 來(lái)指定對(duì)應(yīng)的 bean 的 ID.
一定程度上大大簡(jiǎn)化了代碼的編寫(xiě), 例如一對(duì)一的 bean 映射現(xiàn)在完全不需要寫(xiě)任何額外的 bean 定義了.
下面是代碼的運(yùn)行結(jié)果:
man.sayHello()=你好
SimpleMan said: Hi
org.example.EnglishMan@12bcd4b said: Hello
代碼:
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/> <context:component-scan base-package="org.example"/> </beans>
測(cè)試類:
import org.example.IMan;
import org.example.SimpleMan;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTest {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
SimpleMan dao = (SimpleMan) ctx.getBean("simpleMan");
System.out.println(dao.hello());
IMan man = (IMan) ctx.getBean("usMan");
System.out.println(man.sayHello());
}
}
自動(dòng)探測(cè)和注入bean的類:
package org.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class SimpleMan {
// 自動(dòng)注入名稱為 Man 的 Bean
@Autowired(required = false)
@Qualifier("chineseMan")
//@Qualifier("usMan")
private IMan man;
/**
* @return the man
*/
public IMan getMan() {
return man;
}
/**
* @param man the man to set
*/
public void setMan(IMan man) {
this.man = man;
}
public String hello() {
System.out.println("man.sayHello()=" + man.sayHello());
return "SimpleMan said: Hi";
}
}
一個(gè)接口和兩個(gè)實(shí)現(xiàn)類:
package org.example;
/**
* 抽象的人接口.
* @author BeanSoft
* @version 1.0
*/
public interface IMan {
/**
* 打招呼的抽象定義.
* @return 招呼的內(nèi)容字符串
*/
public String sayHello();
}
package org.example;
import org.springframework.stereotype.Service;
/**
* 中國(guó)人的實(shí)現(xiàn).
* @author BeanSoft
*/
@Service
public class ChineseMan implements IMan {
public String sayHello() {
return "你好";
}
}
package org.example;
import org.springframework.stereotype.Service;
/**
* @author BeanSoft
*
*/
@Service("usMan")
// 這里定義了一個(gè) id 為 usMan 的 Bean, 標(biāo)注里面的屬性是 bean 的 id
public class EnglishMan implements IMan {
public String sayHello() {
return this + " said: Hello!";
}
}
IoC與DI
首先想說(shuō)說(shuō)IoC(Inversion of Control,控制倒轉(zhuǎn))。這是spring的核心,貫穿始終。所謂IoC,對(duì)于spring框架來(lái)說(shuō),就是由spring來(lái)負(fù)責(zé)控制對(duì)象的生命周期和對(duì)象間的關(guān)系。這是什么意思呢,舉個(gè)簡(jiǎn)單的例子,我們是如何找女朋友的?常見(jiàn)的情況是,我們到處去看哪里有長(zhǎng)得漂亮身材又好的mm,然后打聽(tīng)她們的興趣愛(ài)好、qq號(hào)、電話號(hào)、ip號(hào)、iq號(hào)………,想辦法認(rèn)識(shí)她們,投其所好送其所要,然后嘿嘿……這個(gè)過(guò)程是復(fù)雜深?yuàn)W的,我們必須自己設(shè)計(jì)和面對(duì)每個(gè)環(huán)節(jié)。傳統(tǒng)的程序開(kāi)發(fā)也是如此,在一個(gè)對(duì)象中,如果要使用另外的對(duì)象,就必須得到它(自己new一個(gè),或者從JNDI中查詢一個(gè)),使用完之后還要將對(duì)象銷毀(比如Connection等),對(duì)象始終會(huì)和其他的接口或類藕合起來(lái)。
那么IoC是如何做的呢?有點(diǎn)像通過(guò)婚介找女朋友,在我和女朋友之間引入了一個(gè)第三者:婚姻介紹所。婚介管理了很多男男女女的資料,我可以向婚介提出一個(gè)列表,告訴它我想找個(gè)什么樣的女朋友,比如長(zhǎng)得像李嘉欣,身材像林熙雷,唱歌像周杰倫,速度像卡洛斯,技術(shù)像齊達(dá)內(nèi)之類的,然后婚介就會(huì)按照我們的要求,提供一個(gè)mm,我們只需要去和她談戀愛(ài)、結(jié)婚就行了。簡(jiǎn)單明了,如果婚介給我們的人選不符合要求,我們就會(huì)拋出異常。整個(gè)過(guò)程不再由我自己控制,而是有婚介這樣一個(gè)類似容器的機(jī)構(gòu)來(lái)控制。Spring所倡導(dǎo)的開(kāi)發(fā)方式就是如此,所有的類都會(huì)在spring容器中登記,告訴spring你是個(gè)什么東西,你需要什么東西,然后spring會(huì)在系統(tǒng)運(yùn)行到適當(dāng)?shù)臅r(shí)候,把你要的東西主動(dòng)給你,同時(shí)也把你交給其他需要你的東西。所有的類的創(chuàng)建、銷毀都由 spring來(lái)控制,也就是說(shuō)控制對(duì)象生存周期的不再是引用它的對(duì)象,而是spring。對(duì)于某個(gè)具體的對(duì)象而言,以前是它控制其他對(duì)象,現(xiàn)在是所有對(duì)象都被spring控制,所以這叫控制反轉(zhuǎn)。如果你還不明白的話,我決定放棄。
IoC的一個(gè)重點(diǎn)是在系統(tǒng)運(yùn)行中,動(dòng)態(tài)的向某個(gè)對(duì)象提供它所需要的其他對(duì)象。這一點(diǎn)是通過(guò)DI(Dependency Injection,依賴注入)來(lái)實(shí)現(xiàn)的。比如對(duì)象A需要操作數(shù)據(jù)庫(kù),以前我們總是要在A中自己編寫(xiě)代碼來(lái)獲得一個(gè)Connection對(duì)象,有了 spring我們就只需要告訴spring,A中需要一個(gè)Connection,至于這個(gè)Connection怎么構(gòu)造,何時(shí)構(gòu)造,A不需要知道。在系統(tǒng)運(yùn)行時(shí),spring會(huì)在適當(dāng)?shù)臅r(shí)候制造一個(gè)Connection,然后像打針一樣,注射到A當(dāng)中,這樣就完成了對(duì)各個(gè)對(duì)象之間關(guān)系的控制。A需要依賴 Connection才能正常運(yùn)行,而這個(gè)Connection是由spring注入到A中的,依賴注入的名字就這么來(lái)的。那么DI是如何實(shí)現(xiàn)的呢? Java 1.3之后一個(gè)重要特征是反射(reflection),它允許程序在運(yùn)行的時(shí)候動(dòng)態(tài)的生成對(duì)象、執(zhí)行對(duì)象的方法、改變對(duì)象的屬性,spring就是通過(guò)反射來(lái)實(shí)現(xiàn)注入的。關(guān)于反射的相關(guān)資料請(qǐng)查閱java doc。
我想通過(guò)Bromon的介紹,大家對(duì)IoC和DI都有了比較生動(dòng)的理解了。再來(lái)看看《expert one-on-one J2EE Development without EJB中文版》是怎么解釋這兩個(gè)概念的。書(shū)上是這么說(shuō)的:
IoC是一個(gè)很大的概念,可以用不同的方式來(lái)實(shí)現(xiàn)。主要的實(shí)現(xiàn)形式有兩種:
依賴查找:容器提供回調(diào)接口和上下文環(huán)境給組件。EJB和Apache Avalon都是使用這種方式。
依賴注入:組件不做定位查詢,只是提供普通的Java方法讓容器去決定依賴關(guān)系。容器全權(quán)負(fù)責(zé)組件的裝配,它會(huì)把符合依賴關(guān)系的對(duì)象通過(guò)JavaBean屬性或者構(gòu)造子傳遞給需要的對(duì)象。通過(guò)JavaBean屬性注射依賴關(guān)系的做法稱為設(shè)值方法注入(Setter Injection);將依賴關(guān)系作為構(gòu)造子參數(shù)傳入的做法稱為構(gòu)造子注入(Constructor Injection)。
附圖說(shuō)明:
到這里,大家應(yīng)該對(duì)IoC與DI都有了初步的認(rèn)識(shí)了。其實(shí)就Spring來(lái)說(shuō),就是JavaBean由Spring來(lái)管理組裝,表面上看就少了幾個(gè)new字,其實(shí)就是為了降低耦合度,這也是我們做軟件的目標(biāo)之一。
至于Spring是怎樣實(shí)現(xiàn)IoC的,《expert one-on-one J2EE Development without EJB中文版》第七章“Spring框架介紹”很詳細(xì)的列舉了多種方法。說(shuō)實(shí)在,一下子看這么多,我真有點(diǎn)糊涂了。我還是自己寫(xiě)個(gè)Demo熟悉一下大名鼎鼎的Spring吧。
首先得下載Spring。Spring網(wǎng)上有兩種Spring 包一種是spring-framework-1.2.6-with-dependencies.zip,另一種是spring-framework-1.2.6.zip。當(dāng)然最好是下載spring-framework-1.2.6-with-dependencies.zip形式的,因?yàn)槔锩姘烁嗟臇|東。spring-framework-1.2.6-with-dependencies.zip的下載地址是:http://prdownloads.sourceforge.net/springframework/spring-framework-1.2.6-with-dependencies.zip。
下載下來(lái),解壓后,你會(huì)發(fā)現(xiàn)里面有很多jar文件。因?yàn)閯倓偨佑|Spring,因此我只需要spring-core.jar(spring-framework-1.2.6"dist),將其導(dǎo)入eclipse的構(gòu)建路徑中。另外,log日志是需要的,這也是為了養(yǎng)成良好的編程習(xí)慣。將log4j-1.2.9.jar(spring-framework-1.2.6"lib"log4j)和commons-logging.jar(spring-framework-1.2.6"lib"jakarta-commons)導(dǎo)入到構(gòu)建路徑中。
準(zhǔn)備就緒,開(kāi)始寫(xiě)Demo了。
我的工程的結(jié)構(gòu)是:

<!--[if !supportLists]-->1、<!--[endif]-->log4j.properties代碼:
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%c{1} - %m%n
如何使用Log4j,請(qǐng)看我的另一篇轉(zhuǎn)貼的文章:如何使用Log4J。
<!--[if !supportLists]-->2、<!--[endif]-->HelloBean的代碼:
public class HelloBean {
private String helloworld="Hello!World!";
public String getHelloworld() {
return helloworld;
}
public void setHelloworld(String helloworld) {
this.helloworld = helloworld;
}
}
這是一個(gè)簡(jiǎn)單的JavaBean,有個(gè)String類型的helloworld屬性,初始值是"Hello!World!"。
<!--[if !supportLists]-->3、<!--[endif]-->Bean.xml代碼:
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="helloBean" class="com.HelloBean">
<property name="helloworld">
<value>Hello!Rick</value>
</property>
</bean>
</beans>
Spirng重點(diǎn)之一就是配置文件,上面是個(gè)相當(dāng)簡(jiǎn)單的配置文件,我想大家都應(yīng)該看得懂。最后就是寫(xiě)應(yīng)用程序了。
4、<!--[endif]-->Test的代碼:
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
public class Test {
public static void main(String[] args) {
//實(shí)例化JavaBean,主要是為了比較new對(duì)象和依賴注入兩者的區(qū)別
HelloBean hellobean=new HelloBean();
System.out.println(hellobean.getHelloworld());
//通過(guò)Spring訪問(wèn)JavaBean組件
Resource resource=new ClassPathResource("com/bean.xml");
BeanFactory factory=new XmlBeanFactory(resource);
hellobean=(HelloBean)factory.getBean("helloBean");
System.out.println(hellobean.getHelloworld());
}
}
Spring的依賴注入相對(duì)復(fù)雜一點(diǎn),主要是明白調(diào)用別的Bean,不是通過(guò)實(shí)例化對(duì)象來(lái)調(diào)用,而是告訴Spring,我需要什么Bean,然后Spring再向你的Bean里面注入你所需要的Bean對(duì)象。
接下來(lái)說(shuō)說(shuō)代碼實(shí)現(xiàn),我只是在剛剛的例子上再添加一點(diǎn)東東。
首先要增加一個(gè)HelloImp的接口,這是問(wèn)什么呢,那你得問(wèn)Spring,它定的規(guī)矩:JavaBean的實(shí)現(xiàn)要有兩個(gè)部分,一個(gè)接口,一個(gè)默認(rèn)實(shí)現(xiàn)。你不照做就不行。
HelloImp代碼:
public interface HelloImp {
public void getName();
}
實(shí)現(xiàn)HelloImp的Hello代碼:
public class Hello implements HelloImp {
public void getName(){
System.out.println("Jack");
}
}
接著就是在HelloBean中調(diào)用Hello了。Spring的不同之處也在這體現(xiàn)出來(lái)。
public class HelloBean {
private String helloworld="Hello!World!";
private HelloImp hello; //注意這個(gè)私有對(duì)象是借口
public String getHelloworld() {
return helloworld;
}
public void setHelloworld(String helloworld) {
this.helloworld = helloworld;
}
public void setHello(HelloImp hello) {
this.hello = hello;
}
public void get(){
this.hello.getName();
}
}
配置文件也需要增加一點(diǎn)東西:
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!—注意引用的類是具體的類Hello-->
<bean id="myHello" class="com.Hello">
</bean>
<bean id="helloBean" class="com.HelloBean">
<property name="helloworld">
<value>Hello!Rick</value>
</property>
<property name="hello">
<ref bean="myHello"></ref>
</property>
</bean>
</beans>
注意字體加粗的部分。
最后在Test中添加一句hellobean.get();就可以看到結(jié)果了。
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
public class Test {
public static void main(String[] args) {
HelloBean hellobean=new HelloBean();
System.out.println(hellobean.getHelloworld());
Resource resource=new ClassPathResource("com/bean.xml");
BeanFactory factory=new XmlBeanFactory(resource);
hellobean=(HelloBean)factory.getBean("helloBean");
System.out.println(hellobean.getHelloworld());
hellobean.get();
}
}
到這,Spring的IoC和DI總算有了一定的認(rèn)識(shí),也算是敲開(kāi)了Spring的大門(mén)了。