高手組合 Scala整合Spring框架
Scala近期正式發(fā)布了2.8版本,這門優(yōu)秀的編程語言將簡(jiǎn)潔、清晰的語法與面向?qū)ο蠛秃瘮?shù)式編程范式無縫融合起來,同時(shí)又完全兼容于Java,這樣Scala就能使用Java開發(fā)者所熟知的Java API和眾多的框架了。在這種情況下,我們可以通過Scala改進(jìn)并簡(jiǎn)化現(xiàn)有的Java框架。此外,Scala的學(xué)習(xí)門檻也非常低,因?yàn)槲覀兛梢暂p松將其集成到“眾所周知的Java世界中”。
51CTO推薦專題:Scala編程語言
本文將介紹如何通過Scala整合當(dāng)今世界最為流行的框架之一Spring。Spring不僅支持如依賴注入和面向方面的編程等高效的編程范式,還提供了大量的膠水代碼與Hibernate、Toplink等框架以及JEE環(huán)境交互,后者更是可以保證Scala能平滑地融入到企業(yè)當(dāng)中,毫無疑問,這是Spring的成功所在。
為了清楚地闡釋Scala與Spring的整合原理,本文將使用一個(gè)簡(jiǎn)單的示例應(yīng)用。這個(gè)應(yīng)用會(huì)使用到Scala、Spring和Hibernate/JPA,其領(lǐng)域模型如下圖所示:
該領(lǐng)域模型展示了一個(gè)簡(jiǎn)化的社交網(wǎng)絡(luò)應(yīng)用:人與人之間可以彼此鏈接起來。
第一步
后面的講解都將基于該領(lǐng)域模型。首先介紹如何實(shí)現(xiàn)一個(gè)泛型DAO,并通過Hibernate/JPA使用Scala為Person實(shí)體實(shí)現(xiàn)一個(gè)具體的DAO,該DAO的名字為PersonDao,里面封裝了CRUD操作。如下所示:
- val p1 = new Person(“Rod Johnson”)
- val p2 = dao.findByName(“Martin Odersky”)
- p1.link(p2)
- personDao.save(p1)
第二步
接下來介紹如何將Person實(shí)體轉(zhuǎn)換為一個(gè)“內(nèi)容豐富”的領(lǐng)域?qū)ο?,在調(diào)用link方法時(shí),該對(duì)象內(nèi)部會(huì)使用NotificationService執(zhí)行額外的邏輯,這個(gè)服務(wù)會(huì)“神奇地”按需注入到對(duì)象中。下圖展示了這一切:
- val p1 = Person(“Martin Odersky”) //the omission of the ‘new’ keyword is intentional
- val p2 = dao.findByName(“Rod Johnson”)
- p1.link(p2) //magic happens here
- personDao.save(p1)
第三步
最后,本文將介紹Spring是如何從Scala的高級(jí)概念:特征(traits)中受益的。特征可以將內(nèi)容豐富的Person領(lǐng)域?qū)ο筠D(zhuǎn)換為羽翼豐滿的OO類,這個(gè)類能夠?qū)崿F(xiàn)所有的職責(zé),包括CRUD操作。如下所示:
- Person(“Martin Odersky”).save
第一步:使用Scala、Spring和Hibernate/JPA實(shí)現(xiàn)DAO
需求
毫無疑問,DAO在設(shè)計(jì)上應(yīng)該有一個(gè)泛型DAO和一個(gè)針對(duì)Person實(shí)體的具體DAO。泛型DAO中應(yīng)該包含基本的CRUD方法,如save、remove、findById和findAll等。由于是泛型,因此它處理的是類型而不是具體的實(shí)體實(shí)現(xiàn)??偟膩碚f,這個(gè)泛型DAO具有如下的接口定義:
- trait GenericDao[T] {
- def findAll():List[T]
- def save(entity:T):T
- def remove(entity:T):Unit
- def findById(id:Serializable):T
- }
Person實(shí)體類的具體DAO應(yīng)該增加一個(gè)特定于Person實(shí)體的finder方法:
- trait PersonDao extends GenericDao[Person] {
- def findByName(name:String):List[Person]
- //more finders here…
- }
我們需要考慮如下具體的實(shí)現(xiàn)細(xì)節(jié)以便利用上Scala提供的眾多富有成效的特性:
◆關(guān)于集合:雖然底層的JPA實(shí)現(xiàn)并不知道所謂的Scala集合,但DAO接口返回的卻是Scala集合類型(scala.List)而不是Java集合。因?yàn)镾cala集合要比Java集合強(qiáng)大的多,因此DAO方法的調(diào)用者非常希望方法能夠返回Scala集合。這樣,我們需要將JPA返回的Java集合平滑地轉(zhuǎn)換為Scala集合。
◆關(guān)于回調(diào):Spring用于粘合JPA、JMS等框架的大多數(shù)膠水代碼都是基于模板模式,比如JpaTemplate、JmsTemplate等。雖然這些模板通過一些便捷的方法在一定程度上隱藏了底層框架的復(fù)雜性,但很多時(shí)候我們還是不可避免地要直接訪問底層的實(shí)現(xiàn)類,如EntityManager、JmsSession等。在這種情況下,Spring通過JpaCallback等回調(diào)類來實(shí)現(xiàn)我們的愿望。回調(diào)方法doIn…(..)唯一的參數(shù)就是指向?qū)崿F(xiàn)類的引用,比如EntityManager。下面的示例闡述了這種編程模型:
- jpaTemplate.execute(new JpaCallback() {
- public Object doInJpa(EntityManager em) throws PersistenceException {
- //… do something with the EntityManager
- return null;
- }
- });
上面的代碼有兩點(diǎn)值得我們注意:首先,匿名內(nèi)部回調(diào)類的實(shí)例化需要大量的樣板代碼。其次,還有一個(gè)限制:匿名內(nèi)部類JpaCallback之外的所有參數(shù)都必須是final的。如果從Scala的視角來看待這種回調(diào)模式,我們發(fā)現(xiàn)里面充斥的全都是某個(gè)“函數(shù)”的繁瑣實(shí)現(xiàn)。我們真正想要的只是能夠直接訪問EntityManager而已,并不需要匿名內(nèi)部類,而且還得實(shí)現(xiàn)里面的doInJpa(…)方法,這有點(diǎn)太小題大作了。換句話說,我們只需要下面這一行足矣:
- jpaTemplate.execute((em:EntityManager) => em.createQuery(…)// etc. );
問題在于如何通過優(yōu)雅的方式實(shí)現(xiàn)這個(gè)功能。
◆關(guān)于getter和setter:使用了Spring bean的類至少要有一個(gè)setter方法,該方法對(duì)應(yīng)于特定bean的名稱。毫無疑問,這些setter是框架所需的樣板代碼,如果不使用構(gòu)造器注入也能避免這一點(diǎn)豈不美哉?
實(shí)現(xiàn)
如果用Scala實(shí)現(xiàn)泛型與Person DAO,那么上面提到的一切問題都將迎刃而解,請(qǐng)看:
- object GenericJpaDaoSupport {
- implicit def jpaCallbackWrapper[T](func:(EntityManager) => T) = {
- new JpaCallback {
- def doInJpa(session:EntityManager ) = func(session).asInstanceOf[Object]}
- }
- }
- import Scala.collection.jcl.Conversions._
- class GenericJpaDaoSupport[T](val entityClass:Class[T]) extends JpaDaoSupport with GenericDao[T] {
- def findAll():List[T] = {
- getJpaTemplate().find("from " + entityClass.getName).toList.asInstanceOf[List[T]]
- }
- def save(entity:T) :T = {
- getJpaTemplate().persist(entity)
- entity
- }
- def remove(entity:T) = {
- getJpaTemplate().remove(entity);
- }
- def findById(id:Serializable):T = {
- getJpaTemplate().find(entityClass, id).asInstanceOf[T];
- }
- }
- class JpaPersonDao extends GenericJpaDaoSupport(classOf[Person]) with PersonDao {
- def findByName(name:String) = { getJpaTemplate().executeFind( (em:EntityManager) => {
- val query = em.createQuery("SELECT p FROM Person p WHERE p.name like :name");
- query.setParameter("name", "%" + name + "%");
- query.getResultList();
- }).asInstanceOf[List[Person]].toList
- }
- }
使用:
- class PersonDaoTestCase extends AbstractTransactionalDataSourceSpringContextTests {
- @BeanProperty var personDao:PersonDao = null
- override def getConfigLocations() = Array("ctx-jpa.xml", "ctx-datasource.xml")
- def testSavePerson {
- expect(0)(personDao.findAll().size)
- personDao.save(new Person("Rod Johnson"))
- val persons = personDao.findAll()
- expect(1)( persons size)
- assert(persons.exists(_.name ==”Rod Johnson”))
- }
- }
接下來解釋上面的代碼是如何解決之前遇到的那些問題的:
關(guān)于集合
Scala 2.7.x提供了一個(gè)方便的Java集合到Scala集合的轉(zhuǎn)換類,這是通過隱式轉(zhuǎn)換實(shí)現(xiàn)的。上面的示例將一個(gè)Java list轉(zhuǎn)換為Scala list,如下代碼所示:
導(dǎo)入Scala.collection.jcl.Conversions類的所有方法:
- import Scala.collection.jcl.Conversions._
這個(gè)類提供了隱式的轉(zhuǎn)換方法將Java集合轉(zhuǎn)換為對(duì)應(yīng)的Scala集合“包裝器”。對(duì)于java.util.List來說,Scala會(huì)創(chuàng)建一個(gè)Scala.collection.jcl.BufferWrapper。
調(diào)用BufferWrapper的toList()方法返回Scala.List集合的一個(gè)實(shí)例。
下面的代碼闡述了這個(gè)轉(zhuǎn)換過程:
- def findAll() : List[T] = {
- getJpaTemplate().find("from " + entityClass.getName).toList.asInstanceOf[List[T]]
- }
總是手工調(diào)用“toList”方法來轉(zhuǎn)換集合有些麻煩。幸好,Scala 2.8(在本文撰寫之際尚未發(fā)布最終版)將會(huì)解決這個(gè)瑕疵,它可以通過scala.collection.JavaConversions類將Java轉(zhuǎn)換為Scala,整個(gè)過程完全透明。
關(guān)于回調(diào)
可以通過隱式轉(zhuǎn)換將Spring回調(diào)輕松轉(zhuǎn)換為Scala函數(shù),如GenericJpaDaoSupport對(duì)象中所示:
- implicit def jpaCallbackWrapper[T](func:(EntityManager) => T) = {
- new JpaCallback {
- def doInJpa(session:EntityManager ) = func(session).asInstanceOf[Object]}
- }
借助于這個(gè)轉(zhuǎn)換,我們可以通過一個(gè)函數(shù)來調(diào)用JpaTemplate的execute方法而無需匿名內(nèi)部類JPACallback了,這樣就能直接與感興趣的對(duì)象打交道了:
- jpaTemplate.execute((em:EntityManager) => em.createQuery(…)// etc. );
這么做消除了另一處樣板代碼。
關(guān)于getter和setter
默認(rèn)情況下,Scala編譯器并不會(huì)生成符合JavaBean約定的getter和setter方法。然而,可以通過在實(shí)例變量上使用Scala注解來生成JavaBean風(fēng)格的getter和setter方法。下面的示例取自上文的PersonDaoTestCase:
- import reflect._
- @BeanProperty var personDao:PersonDao = _
@BeanProperty注解告訴Scala編譯器生成setPersonDao(…)和getPersonDao()方法,而這正是Spring進(jìn)行依賴注入所需的。這個(gè)簡(jiǎn)單的想法能為每個(gè)實(shí)例變量省掉3~6行的setter與getter方法代碼。
第二步:按需進(jìn)行依賴注入的富領(lǐng)域?qū)ο?/strong>
到目前為止,我們精簡(jiǎn)了DAO模式的實(shí)現(xiàn),該實(shí)現(xiàn)只能持久化實(shí)體的狀態(tài)。實(shí)體本身并沒有什么,它只維護(hù)了一個(gè)狀態(tài)而已。對(duì)于領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)的擁躉來說,這種簡(jiǎn)單的實(shí)體并不足以應(yīng)對(duì)復(fù)雜領(lǐng)域的挑戰(zhàn)。一個(gè)實(shí)體若想成為富領(lǐng)域?qū)ο蟛粌H要包含狀態(tài),還得能調(diào)用業(yè)務(wù)服務(wù)。為了達(dá)成這一目標(biāo),需要一種透明的機(jī)制將服務(wù)注入到領(lǐng)域?qū)ο笾校还軐?duì)象在何處實(shí)例化都該如此。
Scala與Spring的整合可以在運(yùn)行期輕松將服務(wù)透明地注入到各種對(duì)象中。后面將會(huì)提到,這種機(jī)制的技術(shù)基礎(chǔ)是DDD,可以用一種優(yōu)雅的方式將實(shí)體提升為富領(lǐng)域?qū)ο蟆?/p>
需求
為了說清楚何謂按需的依賴注入,我們?yōu)檫@個(gè)示例應(yīng)用加一個(gè)新需求:在調(diào)用Person實(shí)體的link方法時(shí),它不僅會(huì)鏈接相應(yīng)的Person,還會(huì)調(diào)用NotificationService以通知鏈接的雙方。下面的代碼闡述了這個(gè)新需求:
- class Person
- { @BeanProperty var notificationService:NotificationService = _ def link(relation:Person) =
- { relations.add(relation) notificationService.nofity(PersonLinkageNotification(this, relation))
- } //other code omitted for readability
- }
毫無疑問,在實(shí)例化完P(guān)erson實(shí)體或從數(shù)據(jù)庫中取出Person實(shí)體后就應(yīng)該可以使用NotificationService了,無需手工設(shè)置。
使用Spring實(shí)現(xiàn)自動(dòng)裝配
我們使用Spring的自動(dòng)裝配來實(shí)現(xiàn)這個(gè)功能,這是通過Java單例類RichDomainObjectFactory達(dá)成的:
- public class RichDomainObjectFactory implements BeanFactoryAware
- {
- pritic RichDomainObjectFactory singleton = new
- RichDomainObjectFactory();
- public static RichDomainObjectFactory autoWireFactory()
- {
- return singleton;
- }
- public void autowire(Object instance)
- {
- factory.autowireBeanProperties(instance)
- }
- public void setBeanFactory(BeanFactory factory) throws BeansException {
- this.factory = (AutowireCapableBeanFactory) factory;
- }
- }
通過將RichDomainObjectFactory聲明為Spring bean,Spring容器確保在容器初始化完畢后就設(shè)定好了AutowireCapableBeanFactory:
- <bean class="org.jsi.di.spring.RichDomainObjectFactory" factory-method="autoWireFactory"/>
這里并沒有讓Spring容器創(chuàng)建自己的RichDomainObjectFactory實(shí)例,而是在bean定義中使用了factory-method屬性,它會(huì)強(qiáng)制Spring使用autoWireFactory()方法返回的引用,該引用是單例的。這樣會(huì)將AutowireCapableBeanFactory注入到單例的RichDomainObjectFactory中。由于可以在同一個(gè)類裝載器范圍內(nèi)訪問單例對(duì)象,這樣該范圍內(nèi)的所有類都可以使用RichDomainObjectFactory了,它能以一種非侵入、松耦合的方式使用Spring的自動(dòng)裝配特性。毋庸置疑,Scala代碼也可以訪問到RichDomainObjectFactory單例并使用其自動(dòng)裝配功能。
在設(shè)定完這個(gè)自動(dòng)裝配工廠后,接下來需要在代碼/框架中定義鉤子(hook)了。總的來說需要在兩個(gè)地方定義:
◆ORM層,它負(fù)責(zé)從數(shù)據(jù)庫中加載實(shí)體
◆需要“手工”創(chuàng)建新實(shí)體的代碼中
自動(dòng)裝配ORM層中的領(lǐng)域?qū)ο?/strong>
由于文中的示例代碼使用了JPA/Hibernate,因此在實(shí)體加載后需要將這些框架所提供的設(shè)備掛載到RichDomainObjectFactory中。JPA/Hibernate提供了一個(gè)攔截器API,這樣可以攔截和定制實(shí)體加載等事件。為了自動(dòng)裝配剛加載的實(shí)體,需要使用如下的攔截器實(shí)現(xiàn):
- class DependencyInjectionInterceptor extends EmptyInterceptor {
- override def onLoad(instance:Object, id:Serializable, propertieValues:Array[Object],propertyNames:Array[String], propertyTypes:Array[Type]) = {
- RichDomainObjectFactory.autoWireFactory.autowire(instance)
- false
- }
- }
該攔截器需要做的唯一一件事就是將加載的實(shí)體傳遞給RichDomainObjectFactory的autowire方法。對(duì)于該示例應(yīng)用來說,onLoad方法的實(shí)現(xiàn)保證了每次從數(shù)據(jù)庫中加載Person實(shí)體后都將NotificationService注入其中。
此外,還需要通過hibernate.ejb.interceptor屬性將攔截器注冊(cè)到JPA的持久性上下文中:
- <persistence-unit name="ScalaSpringIntegration" transaction-type="RESOURCE_LOCAL">
- <provider>org.hibernate.ejb.HibernatePersistence</provider>
- <property name="hibernate.ejb.interceptor" value="org.jsi.domain.jpa.DependencyInjectionInterceptor" />
- </properties>
- <!-- more properties here-->
- </persistence-unit>
DependencyInjectionInterceptor非常強(qiáng)大,每次從數(shù)據(jù)庫中加載實(shí)體后它都能將在Spring中配置的服務(wù)注入其中。那如果我們?cè)趹?yīng)用代碼而非JAP等框架中實(shí)例化實(shí)體時(shí)又該怎么辦呢?
自動(dòng)裝配“手工”實(shí)例化的領(lǐng)域?qū)ο?/p>
要想自動(dòng)裝配應(yīng)用代碼中實(shí)例化的實(shí)體,最簡(jiǎn)單也是最笨的辦法就是通過RichDomainObjectFactory的方式顯式進(jìn)行自動(dòng)裝配。由于這個(gè)辦法將RichDomainObjectFactory類與實(shí)體創(chuàng)建代碼緊耦合起來,因此并不推薦使用。幸好,Scala提供了“組件對(duì)象”的概念,它擔(dān)負(fù)起工廠的職責(zé),可以靈活實(shí)現(xiàn)構(gòu)造邏輯。
對(duì)于該示例應(yīng)用,我們采用如下方式實(shí)現(xiàn)Person對(duì)象以便“自動(dòng)”提供自動(dòng)裝配功能:
- import org.jsi.di.spring.RichDomainObjectFactory._
- object Person {
- def apply(name:String) = {
- autoWireFactory.autowire(new Person(name))
- }
- }
import聲明會(huì)導(dǎo)入RichDomainObjectFactory的所有靜態(tài)方法,其中的autoWireFactory()方法會(huì)處理RichDomainObjectFactory單例對(duì)象。
Scala對(duì)象另一個(gè)便利的構(gòu)造手段就是apply()方法,其規(guī)則是擁有apply方法的任何對(duì)象在調(diào)用時(shí)可以省略掉.apply()。這樣,Scala會(huì)將對(duì)Person()的調(diào)用轉(zhuǎn)給Person.apply(),因此可以將自動(dòng)裝配代碼放到apply()方法中。
這樣,無需使用“new”關(guān)鍵字就可以調(diào)用Person()了,它會(huì)返回一個(gè)新的實(shí)體,返回前所有必要的服務(wù)都已經(jīng)注入進(jìn)去了,該實(shí)體也成為一個(gè)“富”DDD實(shí)體了。
現(xiàn)在我們可以使用富領(lǐng)域?qū)ο罅?,它是可持久化的,也能在需要時(shí)調(diào)用其中的服務(wù):
- trait JpaPersistable[T] extends JpaDaoSupport {
- def getEntity:T;
- def findAll():List[T] = {
- getJpaTemplate().find("from " + getEntityClass.getName).toList.asInstanceOf[List[T]]
- }
- def save():T = {
- getJpaTemplate().persist(getEntity)
- getEntity
- }
- def remove() = {
- getJpaTemplate().remove(getEntity);
- }
- def findById(id:Serializable):T = {
- getJpaTemplate().find(getEntityClass, id).asInstanceOf[T];
- }
- //…more code omitted for readability
- }
在繼續(xù)之前,我們需要解釋一下為何要用Java而不是Scala來實(shí)現(xiàn)RichDomainObjectFactory,原因是由Scala處理static的方式造成的。Scala故意沒有提供static關(guān)鍵字,因?yàn)閟tatic與復(fù)合的OO/函數(shù)式范式有沖突。Scala語言所提供的唯一一個(gè)靜態(tài)特性就是對(duì)象,其在Java中的等價(jià)物就是單例。由于Scala缺少static方法,因此Spring沒法通過上文介紹的factory-method屬性獲得RichDomainObjectFactory這樣的工廠對(duì)象。這樣,我們就沒法將Spring的AutowireCapableBeanFactory直接注入到Person對(duì)象中了。因此,這里使用Java而非Scala來利用Spring的自動(dòng)裝配功能,它能徹底填充static鴻溝。
第三步:使用Scala traits打造功能完善的領(lǐng)域?qū)ο?/strong>
到目前為止一切尚好,此外,Scala還為OO純粹主義者提供了更多特性。使用DAO持久化實(shí)體與純粹的OO理念有些許沖突。從廣泛使用的DAO/Repository模式的角度來說,DAO只負(fù)責(zé)執(zhí)行持久化操作,而實(shí)體則只維護(hù)其狀態(tài)。但純粹的OO對(duì)象不僅有狀態(tài),還要有行為。
上文介紹的實(shí)體是擁有服務(wù)的,這些服務(wù)封裝了一些行為性職責(zé),但持久化部分并不在其中。為什么不把所有的行為性和狀態(tài)性職責(zé)都賦給實(shí)體呢,就像OO純粹主義者所倡導(dǎo)的那樣,讓實(shí)體自己負(fù)責(zé)持久化操作。事實(shí)上,這是習(xí)慣問題。但使用Java很難以優(yōu)雅的方式讓實(shí)體自己去實(shí)現(xiàn)持久化操作。這種設(shè)計(jì)嚴(yán)重依賴于繼承,因?yàn)槌志没椒ㄒ诟割愔袑?shí)現(xiàn)。這種方式相當(dāng)麻煩,也缺少靈活性。Java從概念上就缺少一個(gè)良好設(shè)計(jì)的根基,沒法很好地實(shí)現(xiàn)這種邏輯。但Scala則不同,因?yàn)镾cala有traits。
所謂trait就是可以包含實(shí)現(xiàn)的接口。它類似于C++中多繼承的概念,但卻沒有眾所周知的diamond syndrome副作用。通過將DAO代碼封裝到trait中,該DAO trait所提供的所有持久化方法可自動(dòng)為所有實(shí)現(xiàn)類所用。這種方式完美地詮釋了DRY(Don’t Repeat Yourself)準(zhǔn)則,因?yàn)槌志没壿嬛粚?shí)現(xiàn)一次,在需要的時(shí)候可以多次混合到領(lǐng)域類中。
對(duì)于該示例應(yīng)用來說,其DAO trait如下代碼所示:
- trait JpaPersistable[T] extends JpaDaoSupport {
- def getEntity:T;
- def findAll():List[T] = {
- getJpaTemplate().find("from " + getEntityClass.getName).toList.asInstanceOf[List[T]]
- }
- def save():T = {
- getJpaTemplate().persist(getEntity)
- getEntity
- }
- def remove() = {
- getJpaTemplate().remove(getEntity);
- }
- def findById(id:Serializable):T = {
- getJpaTemplate().find(getEntityClass, id).asInstanceOf[T];
- }
- //…more code omitted for readability
- }
作為一個(gè)傳統(tǒng)的DAO,該trait繼承了Spring的JpaDaoSupport,但它并沒有提供save、update和delete方法(這些方法需要接收一個(gè)實(shí)體作為參數(shù))轉(zhuǎn)而定義了一個(gè)抽象方法getEntity,需要持久化功能的領(lǐng)域?qū)ο蟮脤?shí)現(xiàn)這個(gè)方法。JpaPersistable trait在內(nèi)部實(shí)現(xiàn)中使用getEntity來保存、更新和刪除特定的實(shí)體,如下代碼片段所示。
- trait JpaPersistable[T] extends JpaDaoSupport {
- def getEntity:T
- def remove() = {
- getJpaTemplate().remove(getEntity);
- }
- //…more code omitted for readability
- )
實(shí)現(xiàn)該trait的領(lǐng)域?qū)ο笾恍鑼?shí)現(xiàn)getEntity方法即可,該方法的實(shí)現(xiàn)僅僅是返回一個(gè)自身引用:
- class Person extends JpaPersistable[Person] with java.io.Serializable {
- def getEntity = this
- //…more code omitted for readability
- }
這就是全部了。所有需要持久化行為的領(lǐng)域?qū)ο笾恍鑼?shí)現(xiàn)JpaPersistable trait即可。最后我們得到的是一個(gè)包含了狀態(tài)和行為功能完善的領(lǐng)域?qū)ο?,完全符合純粹的OO編程的理念:
- Person(“Martin Odersky”).save
無論你是否為純粹的OO理念的擁護(hù)者,這個(gè)示例都闡釋了Scala(尤其是traits概念)是如何輕松實(shí)現(xiàn)純粹的OO設(shè)計(jì)的。
結(jié)論
本文示例介紹了Scala與Spring是如何實(shí)現(xiàn)互補(bǔ)的。Scala簡(jiǎn)明、強(qiáng)大的范式(比如函數(shù)與特征)再結(jié)合Spring的依賴注入、AOP和Java AP為我們I提供了更廣闊的空間,相對(duì)于Java代碼來說,Scala的實(shí)現(xiàn)更具表現(xiàn)力、代碼量也更少。
如果具有Spring和Java基礎(chǔ),Scala的學(xué)習(xí)曲線非常低,因?yàn)槲覀冎恍枰獙W(xué)習(xí)一門新語言就行,無需再學(xué)大量的API了。
Scala和Spring所提供的眾多功能使得這一組合成為企業(yè)采用Scala的最佳選擇??傊覀兡芤詷O低的代價(jià)遷移到更加強(qiáng)大的編程范式上來。
關(guān)于作者
Urs Peter是Xebia的高級(jí)咨詢師,專注于企業(yè)級(jí)Java和敏捷開發(fā)。它有9年的IT從業(yè)經(jīng)歷。在整個(gè)IT職業(yè)生涯中,他擔(dān)任過不同角色,從開發(fā)者、軟件架構(gòu)師到Scrum Master。目前,他在下一代的荷蘭鐵路信息系統(tǒng)項(xiàng)目中擔(dān)任Scrum Master,該項(xiàng)目部分使用Scala實(shí)現(xiàn)。他還是Xebia的一名Scala布道師和荷蘭Scala用戶組的活躍分子。
文中所用源代碼
感興趣的讀者可以使用git:git clone git://github.com/upeter/Scala-Spring-Integration.git在http://github.com/upeter/Scala-Spring-Integration上下載完整的源代碼并使用maven構(gòu)建。
查看英文原文:Scala & Spring: Combine the best of both worlds
【編輯推薦】
- 編程思想碰撞 Scala不是改良的Java
- Scala 2.8最終發(fā)布 全新功能值得期待
- Scala vs F#:函數(shù)式編程特性大比拼(一)
- Scala vs F#:函數(shù)式編程特性大比拼(二)
- 用Java在各種框架下編譯Scala項(xiàng)目