如今的Java程序員遇上了好時候。在將近20年的歷史中,Java的發展歷經沉浮。儘管有不少爲人詬病的產品,例如applets、EJB、Java Data Object(JDO)和數不清的日誌框架,Java仍是發展爲一個龐大且豐富的開發平臺,不少企業級應用都是基於JVM平臺構建。Spring是JVM開發平臺中的一顆明珠。java
Spring最開始出現的目的是替代企業級開發框架EJB,相比EJB,Spring提供更輕量和更易用的編程模型。Spring的重要特色是非侵入式加強POJO(plain old java object)的能力。android
在後續的發展過程當中,EJB也效仿Spring的作法提供了簡單的以POJO爲中心的編程模型,如今的EJB框架也擁有依賴注入(DI)和麪向切面編程(AOP)能力,能夠論證是受Spring成功的影響。程序員
儘管J2EE一直在追趕Spring的發展,可是Spring自己也沒有中止進步。如今,Spring在一些J2EE剛剛涉入或者徹底沒有涉入的領域飛速發展:移動開發、社交API整合、NoSQL數據庫、雲計算和大數據。就目前來看,Spring的將來一片光明。web
重要的事情再強調一遍:如今的Java程序員遇上了好時候。spring
這篇文章會從一個比較高的層次探索Spring,介紹Spring框架解決了哪些主要問題。數據庫
1.1 簡化Java開發express
Spring是一種開源框架,由Rod Johnson發明,並在其著做《Expert One-on-One:J2EE設計與開發》。Spring的初衷是下降企業級開發的複雜性,並試圖經過POJO對象實現以前EJB這類重型框架才能實現的功能。Spring不只僅對服務端開發有用,任何Java應用均可受益於Spring的簡潔、易測試和低耦合等特性。編程
Spring框架中使用beans或JavaBeans來表示應用程序中的組件,但這並不意味着該組件必須嚴格知足Java Bean的規範。設計模式
Spring作了不少事情,可是歸根究竟是一些基本的思路,而全部這些思路最終都導向Spring的使命:簡化Java開發。安全
Spring經過下列四種策略來簡化Java開發:
幾乎Spring的每條特性均可以追溯到這四條策略之一,接下來分別對這四條策略進行闡述,並給出具體的代碼說明Spring如何簡化Java開發。
1.1.1 激發POJO的能力
若是你作Java開發足夠久,你應該遇到過不少會束縛程序員能力的開發框架,這些框架要求程序員繼承框架提供的類或者實現它提供的接口,例如EJB框架中的session beans,另外,在EJB以前的不少框架中也有相似的侵入式編程模型,如Struts、WebWork、Tapestry等等。
Spring儘可能避免讓本身的API污染你的應用代碼。Spring幾乎不會強制要求開發人員實現某個Spring提供的接口或者繼承某個Spring提供的類,在Spring應用中的Java類看起來和普通類同樣,不過,Spring如今常用註解來修飾Java類,可是這個類仍是一個POJO。
舉個代碼例子說明,看以下的HelloWorldBean
package com.spring.sample; public class HelloWorldBean { public String sayHello() { return "Hello World"; } }
能夠看出,這就是一個簡單的Java類-POJO,沒有什麼特殊的標誌代表它是一個Spring組件。Spring這種非侵入式編程模型使得這個類在Spring和非Spring框架下具有相同的功能。
儘管形式很是簡單,POJO的能力值卻可能很是高,例如Spring能夠經過依賴注入編織這些POJOs來激發POJO的能力。
1.1.2 依賴注入
依賴注入聽起來比較嚇人,貌似一種很是複雜的編程技術或者設計模式。實際上依賴注入並不複雜,經過在工程中應用依賴注入技術,能夠獲得更簡單、更容易理解和測試的代碼。
How DI works
除了Hello-world級別的程序,稍微複雜一點的Java應用都須要多個類配合實現功能。通常而言,每一個類本身負責獲取它要合做的類對象的引用,這會致使代碼高度耦合且難以測試。
首先看以下代碼:
package com.spring.sample.knights; public class DamselRescuingKnight implements Knight { private RescueDamselQuest quest; public DamselRescuingKnight() { this.quest = new RescueDamselQuest(); //與RescueDamselQuest緊耦合 } public void embarkOnQuest() { quest.emark(); } }
能夠看出,DamselRescuingKnight在它的構造函數中建立了本身的Quest實例——RescueDamselQuest實例,這使得DamselRescuingKnight與RescueDamselQuest緊密耦合,若是須要刺殺Damsel,則這個刀可使用,可是若是須要刺殺恐龍,則這個刀就派不上用場了。
更糟的是,給DamselRescuingKnight寫單元測試很不方便,在這個測試中,你必須確認:當調用knight的emarkOnQuest函數時,quest的embark函數也正確調用,但這並不容易。
耦合是一頭雙頭怪:一方面,緊耦合的代碼難以測試、難以複用而且難以理解,而且常常陷入「修復一個bug但引入一個新的bug」的開發怪圈中;另外一方面,應用程序必須存在適當的耦合,不然該應用沒法完成任何功能。總之,耦合是必要的,可是應該控制組件之間的耦合程度。
經過使用依賴注入(DI)技術,對象之間的依賴關係由Spring框架提供的容器進行管理,而不須要某個對象主動建立本身須要的引用,以下圖所示:
依賴注入的做用
再看一個BraveKnight類的例子:
package com.spring.sample.knights; public class BraveKnight implements Knight { private Quest quest; public BraveKnight(Quest quest) { // Quest實例被注入 this.quest = quest; } public void embarkOnQuest() { quest.emark(); } }
該對象再也不侷限於一種quest實例,在構造過程當中利用構造函數的參數傳入quest實例,這種類型的依賴注入稱爲構造注入。
還有一點須要注意,使用接口定義quest實例,這就是面向接口編程,使得BraveKnight再也不侷限於某種特定的Quest實現,這就是DI帶來的最大的好處——鬆耦合。
若是想學習Java工程化、高性能及分佈式、深刻淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友能夠加個人Java高級交流:787707172,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們。
實現依賴注入
在上述例子代碼能夠看出,Spring至關於將依賴注入的位置從BraveKnight類中剝離出來,那麼具體的依賴注入代碼如何寫呢?開發人員如何規定給BraveKnight注入哪一個Quest實現,例如SlayDragonQuest?
package com.spring.sample.knights; import java.io.PrintStream; public class SlayDragonQuest implements Quest { private PrintStream stream; public SlayDragonQuest(PrintStream stream) { this.stream = stream; } public void emark() { stream.println("Embarking on quest to slay the dragon!"); } }
在Spirng框架中,最通用的方法是經過寫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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="knight" class="com.spring.sample.knights.BraveKnight"> <constructor-arg ref="quest" /> </bean> <bean id="quest" class="com.spring.sample.knights.SlayDragonQuest"> <constructor-arg value="#{T(System).out}" /> </bean> </beans>
在這個xml配置文件中分別定義了BraveKnight和SlayDragonQuest兩個bean:在BraveKnightbean的定義中,經過構造器函數傳入一個SlayDragonQuest的引用;在SlayDragonQuest的定義中,經過SpEL語言將System.out傳入它的構造函數。
Spring 3.0引入了JavaConfig,這種寫法比xml文件的好處是具有類型安全檢查,例如,上面XML配置文件能夠這麼寫:
package com.spring.sample.knights.config; import com.spring.sample.knights.BraveKnight; import com.spring.sample.knights.Knight; import com.spring.sample.knights.Quest; import com.spring.sample.knights.SlayDragonQuest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class KnightConfig { @Bean public Knight knight() { return new BraveKnight(quest()); } @Bean public Quest quest() { return new SlayDragonQuest(System.out); } }
不管是基於XML的配置仍是基於Java文件的配置,都由Spring框架負責管理beans之間的依賴關係。
啓動依賴注入
在Spring應用中,由application context負責加載beans,並將這些beans根據配置文件編織在一塊兒。Spring框架提供了幾種application context的實現,若是使用XML格式的配置文件,則使用ClassPathXmlApplicationContext;若是使用Java文件形式的配置文件,則使用AnnotationConfigApplicationContext。
package com.spring.sample.knights; import com.spring.sample.knights.config.KnightConfig; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class KnightMain { public static void main(String[] args) { // ClassPathXmlApplicationContext context = // new ClassPathXmlApplicationContext("classpath:/knight.xml"); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(KnightConfig.class); Knight knight = context.getBean(Knight.class); knight.embarkOnQuest(); context.close(); } }
上述代碼中,根據KnightConfig.java文件建立Spring應用上下文,能夠把該應用上下文當作對象工廠,來獲取idknight的bean。
若是你想了解更多關於DI的知識,能夠查看Dhanji R. Prasanna's Dependency Injectionhttps://www.manning.com/books/dependency-injection一書。
1.1.3 切面編程
依賴注入(DI)實現了模塊之間的鬆耦合,而利用面向切面編程(AOP)能夠將涉及整個應用的基礎功能(安全、日誌)放在一個可複用的模塊中。
AOP是一種在軟件系統中實現關注點分離的技術。軟件系統由幾個模塊構成,每一個模塊負責一種功能,不過在系統中有些需求須要涉及到全部的模塊,例如日誌、事務管理和安全等。若是將這些需求相關的代碼都分散在各個模塊中,一方面是不方便維護、另外一方面是與原來每一個模塊的業務邏輯代碼混淆在一塊兒,不符合單一職責原則。
下面這張圖能夠體現這種複雜性,左邊的業務邏輯模塊與右邊的系統服務模塊溝通太過密切,每一個業務模塊須要本身負責調用這些系統服務模塊。
業務邏輯模塊與系統服務模塊過分交互
AOP能夠模塊化這些系統服務,而後利用聲明式編程定義該模塊須要應用到那些業務邏輯模塊上。這使得業務模塊更簡潔,更專一於處理業務邏輯,簡而言之,切面(aspects)確保POJO仍然是普通的Java類。
能夠將切面想象爲覆蓋在一些業務模塊上的毯子,以下圖所示。在系統中有一些模塊負責核心的業務邏輯,利用AOP能夠爲全部這些模塊增長額外的功能,並且核心業務模塊無需知道切面模塊的存在。
切面就像毯子同樣覆蓋在幾個核心業務模塊之上
AOP實踐
繼續上面的例子,若是須要一我的記錄BraveKnight的所做所爲,下面代碼是該日誌服務:
package com.spring.sample.knights; import java.io.PrintStream; public class Minstrel { private PrintStream stream; public Minstrel(PrintStream stream) { this.stream = stream; } public void singBeforeQuest() { stream.println("Fa la la, the knight is so brave!"); } public void singAfterQuest() { stream.println("Tee hee hee, the brave knight did embark on a quest!"); } }
而後在XML文件中定義Minstrel對應的切面:
<?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="knight" class="com.spring.sample.knights.BraveKnight"> <constructor-arg ref="quest" /> </bean> <bean id="quest" class="com.spring.sample.knights.SlayDragonQuest"> <constructor-arg value="#{T(System).out}" /> </bean> <bean id="minstrel" class="com.spring.sample.knights.Minstrel"> <constructor-arg value="#{T(System).out}" /> </bean> <aop:config> <aop:aspect ref="minstrel"> <aop:pointcut id="embark" expression="execution(* *.embarkOnQuest(..))"/> <aop:before method="singBeforeQuest" pointcut-ref="embark" /> <aop:after method="singAfterQuest" pointcut-ref="embark" /> </aop:aspect> </aop:config> </beans>
在這個配置文件中增長了aop配置名字空間。首先定義Minstrel的bean,而後利用<aop:config>標籤訂義aop相關的配置;而後在<aop:aspect>節點中引用minstrel,定義方面;aspect負責將pointcut和要執行的函數(before、after或者around)鏈接在一塊兒。
還有一種更先進的寫法,利用註解和Java配置文件,能夠參考aop docs
Spring框架中的一些子模塊也是基於AOP實現的,例如負責事務處理和負責安全的模塊。
1.1.4 使用模板消除重複代碼
在編程過程當中有沒有感受常常須要寫重複無用的代碼才能實現簡單的功能,最經典的例子是JDBC的使用,這些代碼就是樣板式代碼(boilerplate code)。
以JDBC的使用舉個例子,這種原始的寫法你必定見過:
public Employee getEmployeeById(long id) { Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; try { conn = dataSource.getConnection(); stmt = conn.prepareStatement("select id, name from employee where id=?"); stmt.setLong(1, id); rs = stmt.executeQuery(); Employee employee = null; if (rs.next()) { employee = new Employee(); employee.setId(rs.getLong("id")); employee.setName(rs.getString("name")); } return employee; } catch (SQLException e) { } finally { if (rs != null) { try { rs.close(); } catch (SQLException e) { } } if (stmt != null) { try { stmt.close(); } catch (SQLException e) { } } if (conn != null) { try { conn.close(); } catch (SQLException e) { } } } return null; }
能夠看到,上面這麼一坨代碼中只有少數是真正用於查詢數據(業務邏輯)的。除了JDBC的接口,其餘JMS、JNDI以及REST服務的客戶端API等也有相似的狀況出現。
Spring試圖經過模板來消除重複代碼,這裏所用的是模板設計模式。對於JDBC接口,Spring提供了JdbcTemplate模板來消除上面那個代碼片斷中的樣板式代碼,例子代碼以下:
public Employee getEmployeeById(long id) { return jdbcTemplate.queryForObject( "select id, name from employee where id=?", new RowMapper<Employee>() { public Employee mapRow(ResultSet resultSet, int i) throws SQLException { Employee employee = new Employee(); employee.setId(resultSet.getLong("id")); employee.setName(resultSet.getString("name")); return employee; } }); }
你沒有看錯,就是利用回調函數實現的,有興趣的讀者能夠深刻研究下JdbcTemplate的源碼實現。
咱們上面已經演示了Spring簡化Java開發的四種策略:面向POJO開發、依賴注入(DI)、面向切面編程和模板工具。在舉例的過程當中,咱們稍微提到一點如何使用XML配置文件定義bean和AOP相關的對象,可是這些配置文件的加載原理是怎樣的?這就須要研究下Spring的容器,Spring中所定義的bean都由Spring容器管理。
1.2 使用容器管理beans
基於Spring框架構建的應用中的對象,都由Spring容器(container)管理,以下圖所示。Spring容器負責建立對象、編織對象和配置對象,負責對象的整個生命週期。
Spring容器的做用
容器是Spring框架的核心,經過依賴注入(DI)管理構成Spring應用的組件。正是由於有容器管理各個組件之間的協做關係,使得每一個Spring組件都很好理解、便於複用和單元測試。
Spring容器有多種實現,能夠分爲兩類:
1.2.1 應用上下文(application context)
Spring提供了多種application context,可列舉以下:
經過應用上下文實例,能夠經過getBean()方法得到對應的bean。
1.2.2 bean的生命週期
在傳統的Java應用中,一個對象的生命週期很是簡單:經過new建立一個對象,而後該對象就可使用,當這個對象再也不使用時,由Java垃圾回收機制進行處理和回收。
在Spring應用中,bean的生命週期的控制更加精細。Spring提供了不少節點供開發人員定製某個bean的建立過程,掌握這些節點如何使用很是重要。Spring中bean的生命週期以下圖所示:
bean的生命週期
能夠看出,bean factory負責bean建立的最初四步,而後移交給應用上下文作後續建立過程:
本節主要總結了如何啓動Spring容器,以及Spring應用中bean的生命週期。
1.3 Spring總體架構
除了Spring的核心模塊,Spring還提供了其餘的工具組件,這些組件擴展了Spring的功能,例如webservice、REST、mobile和NOSQL,造成了豐富的開發生態。
1.3.1 Spring模塊
Spring 4.0you 20個獨立的模塊,每一個包含三個文件:二進制庫、源文件和文檔,完整的庫列表以下圖所示:
Spring 4.0包含20個模塊
按照功能劃分,這些模塊能夠分紅六組,以下圖所示:
Spring框架的六組模塊
這些模塊幾乎能夠知足全部企業級應用開發的需求,可是開發人員並不須要徹底使用Spring的這些模塊,能夠自由選擇符合項目需求的第三方模塊——Spring爲一些第三方模塊提供了交互接口。
CORE SPRING CONTAINER
Spring框架的核心模塊,其餘全部模塊都基於該模塊構建。Spring容器負責管理Spring應用中bean的建立、配置和管理。在這模塊中有Spring bean factory,該接口提供了最基本的依賴注入(DI)功能;基於bean factory,該模塊提供了集中Spring應用上下文的實現,能夠供開發人員選擇。
除了bean factory和application context,該模塊還支持其餘企業級服務,例如email、JNDI access、EJB integration和scheduling。
SPRING's AOP MODULE
Spring框架經過AOP模塊提供面向切面編程的能力。經過AOP模塊,一些系統層面的需求(事務、安全)能夠與它們真正要做用到的模塊相互解耦合。
DATA ACCESS AND INTEGRATION
Spring的JDBC和data-access object模塊將數據庫操做的一些樣板式代碼封裝起來,免去了開發人員的不少工做量。這個模塊還對數據庫層的異常進行了封裝,並向上提供含義更豐富的異常信息。
Spring並未實現本身的ORM框架,可是它提供了跟其餘幾個ORM框架整合的能力,例如Hibernate、Mybatis、Java Persistence AP等等,並且這些ORM框架都支持使用Spring提供的事務管理模塊。
WEB AND REMOTING
Spring提供了本身的 WEB開發框架——Spring MVC,除此以外,這個模塊還提供遠程調用支持:Remote Method Invocation(RMI)、Hessian、Burlap和JAX-WS。
INSTRUMENTATION
不常使用
TESTING
若是想學習Java工程化、高性能及分佈式、深刻淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友能夠加個人Java高級交流:787707172,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們。
能夠與經常使用的JUNIT、Mockito、Spock等測試框架整合使用。
1.3.2 Spring portfolio
若是隻是學習Spring的核心模塊,將會錯過很多Spring社區提供的經典項目,下面介紹的這些項目使得Spring幾乎能夠覆蓋整個Java開發(PS:帶*的項目值得每位Spring用戶仔細學習)。
SPRING WEB FLOW
基於Spring MVC框架拓展,利用該框架能夠構建流式web應用。
SPRING WEB SERVICE
雖然核心的Spring 框架提供了將Spring Bean 以聲明的方式發佈爲Web Service,可是這些服務基於一個具備爭議性的架構(拙劣的契約置後模型)之上而構建的。這些服務的契約由Bean 的接口來決定。 Spring Web Service 提供了契約優先的Web Service模型,服務的實現都是爲了知足服務的契約而編寫的。
SPRING SECURITY(*)
安全對於許多應用都是一個很是關鍵的切面。利用Spring AOP,Spring Security爲Spring 應用提供了聲明式的安全機制。咱們將在第9 章講解如何爲應用添加SpringSecurity。你能夠在主頁http://static.springsource.org/spring-security/site 得到關於SpringSecurity 更多的信息。
SPRING INTEGRATION
許多企業級應用都須要與其餘應用進行交互。Spring Integration 提供了幾種通用的應用集成模式的Spring 聲明式風格的實現。
咱們不會在本書覆蓋Spring Integration 內容,可是若是你想了解更多關於SpringIntegration 的信息, 我推薦Mark Fisher、Jonas Partner、Marius Bogoevici 和IweinFuld 編寫的《Spring Integration in Action》;或者還能夠訪問Spring Integration 的主頁http://www.springsource.org/spring-integration。
SPRING BATCH
當咱們須要對數據進行大量操做時,沒有任何技術能夠比批處理更能勝任此場景的。若是須要開發一個批處理應用,你能夠藉助於Spring 強大的面向POJO 的編程模型來使用Spring Batch 來實現。
Spring Batch 超出了本書的範疇,可是你能夠閱讀Thierry Templier 和Arnaud Cogoluègnes編寫的《Spring Batch in Action》,或者訪問Spring Batch 的主頁http://static.springsource.org/spring-batch。
SPRING DATA(*)
Spring Data用於簡化數據庫相關的開發工做。儘管多年以來關係型數據庫都是企業級應用開發的主流,可是隨着移動互聯網的發展,對NoSQL這類菲關係型數據庫的需求也愈來愈強。
不管你選擇NoSQL仍是關係型數據庫,Spring Datat都能提供簡潔的編程模型,例如很是方便的repository機制,能夠爲開發人員自動建立具體的SQL實現。
SPRING SOCIAL
社交網絡是互聯網冉冉升起的一顆新星,愈來愈多的應用正在融入社交網絡網站,例如Facebook 或者Twitter。若是對此感興趣,你能夠了解下Spring Social,Spring 的一個社交網絡擴展模塊。
Spring Social 相對還比較新穎,我並無計劃將它放入本書,可是你能夠訪問http://www.springsource.org/spring-social 瞭解Spring Social 更多的相關信息。
SPRING MOBILE
移動應用是另外一個引人矚目的軟件開發領域。智能手機和平板設備已成爲許多用戶首選的客戶端。Spring Mobile 是Spring 新的擴展模塊用於支持移動Web 應用開發。
與Spring Mobile 相關的是Spring Android 項目。這個新項目旨在經過Spring 框架爲開發基於Android 設備的本地應用提供某些簡單的支持。最初,這個項目提供了Spring 的RestTemplate 版本(請查看第11 章瞭解RestTemplete)能夠用於Android 應用。
再次聲明,這兩個項目已超出了本書的範圍,可是若是你對這兩個項目感興趣,能夠訪問http://www.springsource.org/spring-mobile 和http://www.springsource.org/spring-android 瞭解更多相關的信息。
SPRING BOOT(*)
Spring Boot是Spring社區中發展速度最快的框架之一,它旨在簡化Spring的使用,解決Spring開發時遇到的「配置地獄」問題。
Spring Boot經過大量使用自動配置技術,能夠取消大量的XML配置文件,同時該框架提出了starter的概念,用於簡化pom文件。
1.4 Spring的新特色
主要總結下Spring社區的趨勢:
歡迎工做一到八年的Java工程師朋友們加入Java高級交流:787707172
本羣提供免費的學習指導 架構資料 以及免費的解答
不懂得問題均可以在本羣提出來 以後還會有直播平臺和講師直接交流噢