天天看點

Spring實戰1:Spring初探主要内容

spring的使命——簡化java開發 spring容器 spring的整體架構 spring的新發展

現在的java程式員趕上了好時候。在将近20年的曆史中,java的發展曆經沉浮。盡管有很多為人诟病的産品,例如applets、ejb、java data object(jdo)和數不清的日志架構,java還是發展為一個龐大且豐富的開發平台,很多企業級應用都是基于jvm平台建構。spring是jvm開發平台中的一顆明珠。

spring最開始出現的目的是替代企業級開發架構ejb,相比ejb,spring提供更輕量和更易用的程式設計模型。spring的重要特點是非侵入式增強pojo(plain old java object)的能力。

在後續的發展過程中,ejb也效仿spring的做法提供了簡單的以pojo為中心的程式設計模型,現在的ejb架構也擁有依賴注入(di)和面向切面程式設計(aop)能力,可以論證是受spring成功的影響。

盡管j2ee一直在追趕spring的發展,但是spring本身也沒有停止進步。現在,spring在一些j2ee剛剛涉入或者完全沒有涉入的領域飛速發展:移動開發、社交api整合、nosql資料庫、雲計算和大資料。就目前來看,spring的未來一片光明。

重要的事情再強調一遍:現在的java程式員趕上了好時候。

這篇文章會從一個比較高的層次探索spring,介紹spring架構解決了哪些主要問題。

spring是一種開源架構,由rod johnson發明,并在其著作《expert one-on-one:j2ee設計與開發》。spring的初衷是降低企業級開發的複雜性,并試圖通過pojo對象實作之前ejb這類重型架構才能實作的功能。spring不僅僅對服務端開發有用,任何java應用都可受益于spring的簡潔、易測試和低耦合等特性。

spring架構中使用beans或javabeans來表示應用程式中的元件,但這并不意味着該元件必須嚴格滿足java bean的規範。

spring做了很多事情,但是歸根到底是一些基本的思路,而所有這些思路最終都導向spring的使命:簡化java開發。

spring通過下列四種政策來簡化java開發:

基于pojo的輕量級、最小侵入式開發;

通過依賴注入和面向接口程式設計實作松耦合;

通過面向切面程式設計和慣例實作聲明式程式設計;

幾乎spring的每條特性都可以追溯到這四條政策之一,接下來分别對這四條政策進行闡述,并給出具體的代碼說明spring如何簡化java開發。

如果你做java開發足夠久,你應該遇到過很多會束縛程式員能力的開發架構,這些架構要求程式員繼承架構提供的類或者實作它提供的接口,例如ejb架構中的session beans,另外,在ejb之前的很多架構中也有類似的侵入式程式設計模型,如struts、webwork、tapestry等等。

spring盡量避免讓自己的api污染你的應用代碼。spring幾乎不會強制要求開發人員實作某個spring提供的接口或者繼承某個spring提供的類,在spring應用中的java類看起來和普通類一樣,不過,spring現在經常使用注解來修飾java類,但是這個類還是一個pojo。

舉個代碼例子說明,看如下的helloworldbean

可以看出,這就是一個簡單的java類-pojo,沒有什麼特殊的标志表明它是一個spring元件。spring這種非侵入式程式設計模型使得這個類在spring和非spring架構下具備相同的功能。

盡管形式非常簡單,pojo的能力值卻可能非常高,例如spring可以通過依賴注入編織這些pojos來激發pojo的能力。

依賴注入聽起來比較吓人,貌似一種非常複雜的程式設計技術或者設計模式。實際上依賴注入并不複雜,通過在工程中應用依賴注入技術,可以得到更簡單、更容易了解和測試的代碼。

除了hello-world級别的程式,稍微複雜一點的java應用都需要多個類配合實作功能。一般而言,每個類自己負責擷取它要合作的類對象的引用,這會導緻代碼高度耦合且難以測試。

首先看如下代碼:

可以看出,damselrescuingknight在它的構造函數中建立了自己的quest執行個體——rescuedamselquest執行個體,這使得damselrescuingknight與rescuedamselquest緊密耦合,如果需要刺殺damsel,則這個刀可以使用,但是如果需要刺殺恐龍,則這個刀就派不上用場了。

更糟的是,給damselrescuingknight寫單元測試很不友善,在這個測試中,你必須确認:當調用knight的emarkonquest函數時,quest的embark函數也正确調用,但這并不容易。

耦合是一頭雙頭怪:一方面,緊耦合的代碼難以測試、難以複用并且難以了解,并且經常陷入“修複一個bug但引入一個新的bug”的開發怪圈中;另一方面,應用程式必須存在适當的耦合,否則該應用無法完成任何功能。總之,耦合是必要的,但是應該控制元件之間的耦合程度。

通過使用依賴注入(di)技術,對象之間的依賴關系由spring架構提供的容器進行管理,而不需要某個對象主動建立自己需要的引用,如下圖所示:

Spring實戰1:Spring初探主要内容

依賴注入的作用

再看一個braveknight類的例子:

該對象不再局限于一種quest執行個體,在構造過程中利用構造函數的參數傳入quest執行個體,這種類型的依賴注入稱為構造注入。

還有一點需要注意,使用接口定義quest執行個體,這就是面向接口程式設計,使得braveknight不再局限于某種特定的quest實作,這就是di帶來的最大的好處——松耦合。

在上述例子代碼可以看出,spring相當于将依賴注入的位置從braveknight類中剝離出來,那麼具體的依賴注入代碼如何寫呢?開發人員如何規定給braveknight注入哪個quest實作,例如slaydragonquest?

在spirng架構中,最通用的方法是通過寫xml配置檔案來定義元件之間的依賴關系,如下所示:

在這個xml配置檔案中分别定義了braveknight和slaydragonquest兩個bean:在braveknightbean的定義中,通過構造器函數傳入一個slaydragonquest的引用;在slaydragonquest的定義中,通過spel語言将system.out傳入它的構造函數。

spring 3.0引入了javaconfig,這種寫法比xml檔案的好處是具備類型安全檢查,例如,上面xml配置檔案可以這麼寫:

不論是基于xml的配置還是基于java檔案的配置,都由spring架構負責管理beans之間的依賴關系。

在spring應用中,由application context負責加載beans,并将這些beans根據配置檔案編織在一起。spring架構提供了幾種application context的實作,如果使用xml格式的配置檔案,則使用classpathxmlapplicationcontext;如果使用java檔案形式的配置檔案,則使用annotationconfigapplicationcontext。

上述代碼中,根據knightconfig.java檔案建立spring應用上下文,可以把該應用上下文看成對象工廠,來擷取idknight的bean。

依賴注入(di)實作了子產品之間的松耦合,而利用面向切面程式設計(aop)可以将涉及整個應用的基礎功能(安全、日志)放在一個可複用的子產品中。

aop是一種在軟體系統中實作關注點分離的技術。軟體系統由幾個子產品構成,每個子產品負責一種功能,不過在系統中有些需求需要涉及到所有的子產品,例如日志、事務管理和安全等。如果将這些需求相關的代碼都分散在各個子產品中,一方面是不友善維護、另一方面是與原來每個子產品的業務邏輯代碼混淆在一起,不符合單一職責原則。

實作系統級别處理的代碼分散在多個子子產品中,這意味着如果要修改這些處理代碼,則要在每個子產品中都進行修改。即使将這些代碼封裝到一個子產品中,在沒給個子子產品中隻保留對方法的調用,這些方法調用還是在各個子產品中重複出現。

業務邏輯代碼與非核心功能的代碼混淆在一起。例如,一個添加address book的方法應該隻關心如何添加address book,而不應該關心該操作是否安全或者是否能夠實作事務處理。

下面這張圖可以展現這種複雜性,左邊的業務邏輯子產品與右邊的系統服務子產品溝通太過密切,每個業務子產品需要自己負責調用這些系統服務子產品。

Spring實戰1:Spring初探主要内容

業務邏輯子產品與系統服務子產品過度互動

aop可以子產品化這些系統服務,然後利用聲明式程式設計定義該子產品需要應用到那些業務邏輯子產品上。這使得業務子產品更簡潔,更專注于處理業務邏輯,簡而言之,切面(aspects)確定pojo仍然是普通的java類。

可以将切面想象為覆寫在一些業務子產品上的毯子,如下圖所示。在系統中有一些子產品負責核心的業務邏輯,利用aop可以為所有這些子產品增加額外的功能,而且核心業務子產品無需知道切面子產品的存在。

Spring實戰1:Spring初探主要内容

切面就像毯子一樣覆寫在幾個核心業務子產品之上

繼續上面的例子,如果需要一個人記錄braveknight的所作所為,下面代碼是該日志服務:

然後在xml檔案中定義minstrel對應的切面:

在這個配置檔案中增加了aop配置名字空間。首先定義minstrel的bean,然後利用<aop:config>标簽定義aop相關的配置;然後在<aop:aspect>節點中引用minstrel,定義方面;aspect負責将pointcut和要執行的函數(before、after或者around)連接配接在一起。

spring架構中的一些子子產品也是基于aop實作的,例如負責事務處理和負責安全的子產品。

在程式設計過程中有沒有感覺經常需要寫重複無用的代碼才能實作簡單的功能,最經典的例子是jdbc的使用,這些代碼就是樣闆式代碼(boilerplate code)。

以jdbc的使用舉個例子,這種原始的寫法你一定見過:

可以看到,上面這麼一坨代碼中隻有少數是真正用于查詢資料(業務邏輯)的。除了jdbc的接口,其他jms、jndi以及rest服務的用戶端api等也有類似的情況出現。

spring試圖通過模闆來消除重複代碼,這裡所用的是模闆設計模式。對于jdbc接口,spring提供了jdbctemplate模闆來消除上面那個代碼片段中的樣闆式代碼,例子代碼如下:

你沒有看錯,就是利用回調函數實作的,有興趣的讀者可以深入研究下jdbctemplate的源碼實作。

我們上面已經示範了spring簡化java開發的四種政策:面向pojo開發、依賴注入(di)、面向切面程式設計和模闆工具。在舉例的過程中,我們稍微提到一點如何使用xml配置檔案定義bean和aop相關的對象,但是這些配置檔案的加載原理是怎樣的?這就需要研究下spring的容器,spring中所定義的bean都由spring容器管理。

基于spring架構建構的應用中的對象,都由spring容器(container)管理,如下圖所示。spring容器負責建立對象、編織對象和配置對象,負責對象的整個生命周期。

Spring實戰1:Spring初探主要内容

spring容器的作用

容器是spring架構的核心,通過依賴注入(di)管理構成spring應用的元件。正是因為有容器管理各個元件之間的協作關系,使得每個spring元件都很好了解、便于複用和單元測試。

spring容器有多種實作,可以分為兩類:

bean factories(由org.springframework.beans.factory.beanfactory接口定義)是最簡單的容器,隻提供基本的依賴注入功能;

application context(由org.springframework.context.applicationcontext接口定義)在bean factory的基礎上提供application-framework架構服務,例如可以從properties檔案中解析配置資訊、可以對外公布application events。

spring提供了多種application context,可列舉如下:

annotationconfigapplicationcontext——從java配置檔案中加載應用上下文;

annotationconfigwebapplicationcontext——從java配置檔案中加載spring web應用上下文;

classpathxmlapplicationcontext——從classpath(resources目錄)下加載xml格式的應用上下文定義檔案;

filesystemxmlapplicationcontext——從指定檔案系統目錄下加載xml格式的應用上下文定義檔案;

xmlwebapplicationcontext——從classpath(resources目錄)下加載xml格式的spring web應用上下文。

通過應用上下文執行個體,可以通過getbean()方法獲得對應的bean。

在傳統的java應用中,一個對象的生命周期非常簡單:通過new建立一個對象,然後該對象就可以使用,當這個對象不再使用時,由java垃圾回收機制進行處理和回收。

在spring應用中,bean的生命周期的控制更加精細。spring提供了很多節點供開發人員定制某個bean的建立過程,掌握這些節點如何使用非常重要。spring中bean的生命周期如下圖所示:

Spring實戰1:Spring初探主要内容

bean的生命周期

可以看出,bean factory負責bean建立的最初四步,然後移交給應用上下文做後續建立過程:

spring初始化bean

spring将值和其他bean的引用注入(inject)到目前bean的對應屬性中;

如果bean實作了beannameaware接口,spring會傳入bean的id來調用setbeanname方法;

如果bean實作了beanfactoryaware接口,spring傳入bean factory的引用來調用setbeanfactory方法;

如果bean實作了applicationcontextaware接口,spring将傳入應用上下文的引用來調用setapplicationcontext方法;

如果bean實作了beanpostprocessor接口,則spring調用postprocessbeforeinitialization方法,這個方法在初始化和屬性注入之後調用,在任何初始化代碼之前調用;

如果bean實作了initializingbean接口,則需要調用該接口的afterpropertiesset方法;如果在bean定義的時候設定了init-method屬性,則需要調用該屬性指定的初始化方法;

如果bean實作了beanpostprocessor接口,則spring調用postprocessafterinitialization方法

在這個時候bean就可以用于在應用上下文中使用了,當上下文退出時bean也會被銷毀;

如果bean實作了disposablebean接口,spring會調用destroy()方法;如果在bean定義的時候設定了destroy-method, 則此時需要調用指定的方法。

本節主要總結了如何啟動spring容器,以及spring應用中bean的生命周期。

除了spring的核心子產品,spring還提供了其他的工具元件,這些元件擴充了spring的功能,例如webservice、rest、mobile和nosql,形成了豐富的開發生态。

spring 4.0you 20個獨立的子產品,每個包含三個檔案:二進制庫、源檔案和文檔,完整的庫清單如下圖所示:

Spring實戰1:Spring初探主要内容

spring 4.0包含20個子產品

按照功能劃分,這些子產品可以分成六組,如下圖所示:

Spring實戰1:Spring初探主要内容

spring架構的六組子產品

這些子產品幾乎可以滿足所有企業級應用開發的需求,但是開發人員并不需要完全使用spring的這些子產品,可以自由選擇符合項目需求的第三方子產品——spring為一些第三方子產品提供了互動接口。

spring架構的核心子產品,其他所有子產品都基于該子產品建構。spring容器負責管理spring應用中bean的建立、配置和管理。在這子產品中有spring bean factory,該接口提供了最基本的依賴注入(di)功能;基于bean factory,該子產品提供了集中spring應用上下文的實作,可以供開發人員選擇。

除了bean factory和application context,該子產品還支援其他企業級服務,例如email、jndi access、ejb integration和scheduling。

spring架構通過aop子產品提供面向切面程式設計的能力。通過aop子產品,一些系統層面的需求(事務、安全)可以與它們真正要作用到的子產品互相解耦合。

spring的jdbc和data-access object子產品将資料庫操作的一些樣闆式代碼封裝起來,免去了開發人員的很多工作量。這個子產品還對資料庫層的異常進行了封裝,并向上提供含義更豐富的異常資訊。

spring并未實作自己的orm架構,但是它提供了跟其他幾個orm架構整合的能力,例如hibernate、mybatis、java persistence ap等等,而且這些orm架構都支援使用spring提供的事務管理子產品。

spring提供了自己的 web開發架構——spring mvc,除此之外,這個子產品還提供遠端調用支援:remote method invocation(rmi)、hessian、burlap和jax-ws。

不常使用

可以與常用的junit、mockito、spock等測試架構整合使用。

如果隻是學習spring的核心子產品,将會錯過不少spring社群提供的經典項目,下面介紹的這些項目使得spring幾乎可以覆寫整個java開發(ps:帶*的項目值得每位spring使用者仔細學習)。

基于spring mvc架構拓展,利用該架構可以建構流式web應用。

雖然核心的spring 架構提供了将spring bean 以聲明的方式釋出為web service,但是這些服務基于一個具有争議性的架構(拙劣的契約置後模型)之上而建構的。這些服務的契約由bean 的接口來決定。 spring web service 提供了契約優先的web service模型,服務的實作都是為了滿足服務的契約而編寫的。

許多企業級應用都需要與其他應用進行互動。spring integration 提供了幾種通用的應用內建模式的spring 聲明式風格的實作。

當我們需要對資料進行大量操作時,沒有任何技術可以比批處理更能勝任此場景的。如果需要開發一個批處理應用,你可以借助于spring 強大的面向pojo 的程式設計模型來使用spring batch 來實作。

spring data用于簡化資料庫相關的開發工作。盡管多年以來關系型資料庫都是企業級應用開發的主流,但是随着移動網際網路的發展,對nosql這類菲關系型資料庫的需求也越來越強。

無論你選擇nosql還是關系型資料庫,spring datat都能提供簡潔的程式設計模型,例如非常友善的repository機制,可以為開發人員自動建立具體的sql實作。

社交網絡是網際網路冉冉升起的一顆新星,越來越多的應用正在融入社交網絡網站,例如facebook 或者twitter。如果對此感興趣,你可以了解下spring social,spring 的一個社交網絡擴充子產品。

移動應用是另一個引人矚目的軟體開發領域。智能手機和平闆裝置已成為許多使用者首選的用戶端。spring mobile 是spring 新的擴充子產品用于支援移動web 應用開發。

與spring mobile 相關的是spring android 項目。這個新項目旨在通過spring 架構為開發基于android 裝置的本地應用提供某些簡單的支援。最初,這個項目提供了spring 的resttemplate 版本(請檢視第11 章了解resttemplete)可以用于android 應用。

spring boot是spring社群中發展速度最快的架構之一,它旨在簡化spring的使用,解決spring開發時遇到的“配置地獄”問題。

主要總結下spring社群的趨勢:

注重注解,能用注解解決的盡量用注解,盡量少寫xml配置檔案;

spring boot已經是spring社群中增長最迅速的架構,前三名是:spring framework,spring boot和spring security

支援java 8,通過java8的lambda表達式,使得一些回調接口更易使用和閱讀。

與groovy開發平滑支援,groovy是jvm上的python語言,在spring項目中可以寫單元測試;

支援jsr-310:data和time api,為開發人員提供豐富的接口操作java.util.date或者java.util.clendar。