1. 程式人生 > >Spring原理初探

Spring原理初探

     在學習Spring之前首先來看一張圖,通過對於這張圖的理解進入我們需要學習的Java框架Spring。

                          

    這張圖上展示了一個基於SSH,B/S結構的單體Java應用的搭建過程,首先通過瀏覽器進入到Filter攔截器,進入到Structs2,通過Service層進入到Hibernate,從而進行資料庫,經過而這整個過程都是由SpringIOC容器控制。這張圖向我們展示了一個比較基礎的JavaWeb的搭建過程。萬變不離其宗,後來使用的SSM框架,現在比較火的微服務架構,都離不開這張圖上展示的內容所提供給大家的思想。每個框架的實現宗旨都是一樣的,只不過實現的方式不一樣。其實在這個框架最初的時候應該是以JSP+Servlet比較常用,後來為了解決前後端分離的問題,降低耦合度。將這些功能結構按照MVC分層實現。個人的理解MVC為一種程式設計思想,可以很多場合使用到,從Web、後端、移動端在很多的場合下將MVC作為一種程式設計思想應該是比較恰當的,對於MVC的設計思想這裡不做過多的說明。下面我們就來學習Spring框架的深入理解,既然說到要深入理解那麼就應該先來個簡單一點的為引導進入到深處。

    先簡單的解釋一下Spring是什麼東西。首先Spring是一個開源的框架,是由Apache公司開發的,Spring是為了簡化企業級應用開發而生的(也就是上面那張圖上所展示的內容),使用Spring可以使簡單的JavaBean實現以前只有EJB(企業級JavaBean)才能實現的功能。Spring是一個IOC(DI)和AOP容器框架

    IOC:控制反轉(依賴注入)

    AOP:面向切面程式設計

    具體的說Spring的特點有以下幾點

    -輕量級:不是說Spring的程式碼少,而是Spring是非侵入式的,也就是說引入Spring框架並不會對原來的應用產生影響,也可以理解為基於Spring開發的應用中物件可以不依賴與Spring的API(應用程式介面)

    -依賴注入(DI)Dependency Injection

    -面向切面程式設計(AOP)Aspect oriented programming

    -容器 從上面圖中我們可以看出很多的框架,例如Struts2、hibernate、mybatis等框架都是可以被裝入Spring的容器中。這個容器包含並且管理應用物件的生命週期。

    -框架 Spring 實現了簡單的元件的配合組成一個複雜的應用,在Spring中可以使用XML和Java註解組合這些物件。

    -一站式 Spring為企業級應用提供了一站式的解決方案,在IOC和AOP基礎上可以整合各種的企業應用的開源的框架和優秀的第三方類庫(實際想Spring自身也是提供了SpringMVC展現層技術和SpringJDBC資料持久化技術)但是由於Spring提供了一站式的解決方案,才會有那麼的開源技術通過Spring框架整合形成比Spring原生更加優秀的解決方案     。

                               

    例如圖中Spring提供了Core Container核心容器、DataAccess/Integration資料持久化、Web等的支援。

    介紹完Spring的主要內容之後,我們就來看看Spring的開發環境的搭建,下圖中展示了Spring框架所需要的基本jar包。這裡圖中展示的是Spring4的Jar包,實際開發中可以更具實際情況選擇自己的版本。

                                                               

    開發工具由自己選擇這裡我所用的開發工具是IntelliJ IDEA 開發工具,構建的Spring工程目錄如下圖所示     

                                                          

    這裡介紹一個Spring配置檔案的位置,一個典型的Spring專案需要在建立一個或者多個Bean的配置檔案,這些配置檔案用於在SpringIOC容器裡配置Bean,Beean的配置檔案通常情況下放在classpath路徑下,當然也可以放在其它目錄下。在開中經常放置的位置就是類路徑下,如上圖中所示。

    建立一個Spring專案

  1. 匯入jar包
  2. 建立例項物件

package com.nihui.spring.bean;import java.util.ArrayList;public class World {     private String name;     public void hello(){         System.out.println("hello world " +name );     }     public String getName() {         return name;     }     public void setName(String name) {         this.name = name;     } }

  1. 配置Spring配置檔案
<?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="World" class="com.nihui.spring.bean.World">

        <property name="name" value="Spring"></property>

    </bean>

  </beans>
  1. 建立程式啟動類
package com.nihui.spring.bean;

  import org.springframework.context.ApplicationContext;

  import org.springframework.context.support.ClassPathXmlApplicationContext;

  

  public class Main {

    public static void main(String[] args) {

        //使用Spring框架之後這些操作都可以交給Spring來完成

        //1.建立Spring的IOC容器

        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        //2.從IOC容器中獲取到Bean的示例

        World helloWorld = (World) context.getBean("World");

        //3.進行方法的呼叫
helloWorld.hello();
}

}

    完成以上的操作就使用Spring建立一個簡單的Spring的專案,同樣也是Spring最簡單的使用場景。在實際開發中Spring的應用還是比較複雜的,首先最簡單的方式來學習。

        接下來介紹的是Spring配置檔案,我們都知道Spring框架在使用的時候並不是我們例子中所體現的那個樣子,而是通過配置檔案的方式將各種各樣的資源整合在一起,這個概念在之前也有提到過,所以在Spring開發中配置檔案的編寫也是比較重要的。

    IOC和DI

    首先簡單的說一下IOC(控制反轉),IOC的基本思想就是反轉資源獲取的方向,傳統的資源查詢方式要求元件向容器發起請求,然後獲取到所要請求的資源作為迴應,應用了IOC之後,則是容器主動的將資源推送給它所管理的元件,元件只需要做的是選擇一種合適的方式來接受資源,這種行為就被看做是元件被動的查詢資源。DI(依賴注入),這個是對IOC的另一種表達方式,也就是元件以一些預先定義好的方式(例如setter方法)接受來自容器的資源注入,相對於IOC而言,這種表述更直接。

                                    

    如圖,傳統情況下一個容器中有兩個類A和B,要想把A類作為B類的屬性需要先建立一個A類的物件,然後建立一個B類的物件,通過物件的setter方法將這個屬性賦給B類。而使用IOC容器,則是容器就把這個注入關係給完成了,我們只需要通過B類的getter方法獲取到物件的屬性就可以了,達到了在程式碼上的解耦合。

    在IOC之前需要解決的幾個問題

    -分離介面的實現

 需求:生成HTML或PDF格式的不同型別報表

                           

    如圖需要先定義一個ReportService類,而兩種實現方式需要實現同一個介面,而且介面和兩個類都需要繼承ReportService類。並沒有實現解耦合,元件類之間的耦合度還是很高。這個就引出了第二種方式

    -採用工廠設計模式

                                

    這裡看出,ReportService與功能類之間的耦合度降低了,但是與工廠類之間的耦合有所增加,也沒有很好的解決元件之間的耦合問題。這種情況下,需要擴充套件新功能的時候需要改動的類還是比較多的,各個功能類之間的影響還是比較大的。

    -採用控制反轉

                         

    這裡我們可以看出與工廠設計模式相比較,ReportService與功能類之間的耦合度有所降低,但是相比較之下,在實現不同的功能的時候只需要擴充套件ReportGenerator介面就可以以了。Container類直接作為一個“中間人”調和了元件之間的關係。

    這裡我們可以來了解一下Spring框架的底層實現原理,這裡我們可以看到控制反轉與工廠方法模式的最大的差異就是少了一個工廠類的整合,而是通過容器將這個依賴注入到了各個類中,不是通過工廠來獲取。其實在Spring底層實現中就是通過工廠模式實現了功能類之間的解耦合。使用工廠模式獲取到對應的配置檔案中的內容,利用反射機制獲取到配置檔案中定義的類的例項物件,然後通過工廠類返回。這樣看來Spring容器所做的事情就是將這些東西進行了一個簡單的封裝。主要使用到的原理就是Java提供的反射機制,當然了底層實現並不僅僅使用到的這個技術應該是多種技術的結合。才能誕生Spring這樣一個優秀的框架。

    那麼簡單的說完底層的原理我們就來了解一下這個配置檔案到底應該該怎麼使用。在Java中我們使用dom4j來解析XML配置檔案,當然Spring還提供了註解方式,這裡我們先介紹一下XML配置檔案對於Bean物件的配置

    Bean配置方式,通過全類名(主要使用者反射可以找到對應的類)、通過工廠方法(靜態工廠方法&例項工廠方法)、FactoryBean。在SpringIOC容器有兩種,一種是BeanFactory一種是ApplicationContext,這裡需要說明一下BeanFactory和FactoryBean並不是我寫錯了而是一個是面向開發者,一個面向Spring底層。那麼,哪一個是面向開發者,哪一個是面向Spring底層呢?在上面我們說過Spring框架的底層是通過工廠模式實現的所以說FactoryBean是為了底層提供不同的“工廠”,而BeanFactory是面向開發者,但是這個我們並不會經常的使用,這個面向開發者應該定義為Spring開發者而不是應用開發者。作為應用開發者經常使用到的就是ApplicationContext。

    依賴注入的方式有兩種一種是屬性注入,一種是構造器注入。

<bean id="World" class="com.nihui.spring.bean.World">    <property name="name" value="Spring"></property></bean>

    在XML檔案中通過Bean節點的方式,來配置Bean,而這個解析規則是由Spring規定好的。

    id:Bean名字,唯一標識,若id沒有指定Spring將自動將許可權定性為類名作為Bean的名字,id可以指定多個名字,名字之間通過逗號、分號或者空格分隔。

    Spring容器中讀入Bean配置建立一個Bean之前,必須對它進行例項化,只有在容器例項化之後才可以從IOC容器中獲取的Bean物件的例項並使用。這裡就提到了之前的內容,這裡我們對這裡兩個例項化IOC容器的方式做官方的理解

    BeanFactory:IOC容器的基本實現,也就是之前提到的Spring底層實現,可以理解為Spring的“基礎設施”,面向Spring本身;也就是之前提到的Spring的開發者,

    ApplicationContext:提供了更多的高階特性,是BeanFactory的子介面,面向Spring應用開發者,但是幾乎所有的應用場合都是直接使用ApplicationContext而不是使用底層的BeanFactory。但是無論哪一種實現方式,配置檔案都是相同的。

    這裡詳細介紹一下ApplicationContext

    ApplicationContext有兩個主要的實現類,ClassPathXmlApplicationContext:從類路徑下載入配置檔案;FileSystemXmlApplicationContext:從檔案系統中載入配置檔案。ConfigurationApplication擴充套件與ApplicationContext,新增加的兩個主要方法,refresh()和close(),讓ApplicationContext具有啟動、重新整理和關閉上下文的能力。

    ApplicationContext在初始化上下文時就例項化所有的單例Bean。

    WebApplicationContext是專門為WEB應用而準備,它允許從相對於WEB的根目錄的路徑中完成初始化工作。