1. 程式人生 > >Spring IOC 學習總結

Spring IOC 學習總結

class文件 .net 全局 監聽 mave object pri 想想 forname

1 什麽是IOC、DI

  IoC—Inversion of Control,即“控制反轉”,不是什麽技術,而是一種設計思想。在Java開發中,Ioc意味著將你設計好的對象交給容器控制,而不是傳統的在你的對象內部直接控制。

傳統Java SE程序設計,我們直接在對象內部通過new進行創建對象,是程序主動去創建依賴對象;而IoC是有專門一個容器來創建這些對象,即由Ioc容器來控制對象的創建。

  IoC 不是一種技術,只是一種思想,一個重要的面向對象編程的法則,它能指導我們如何設計出松耦合、更優良的程序。傳統應用程序都是由我們在類內部主動創建依賴對象,從而導致類與類之間高耦合,難於測試;有了IoC容器後,把創建和查找依賴對象的控制權交給了容器,由容器進行註入組合對象,所以對象與對象之間是 松散耦合,這樣也方便測試,利於功能復用,更重要的是使得程序的整個體系結構變得非常靈活。

  其實IoC對編程帶來的最大改變不是從代碼上,而是從思想上,發生了“主從換位”的變化。應用程序原本是老大,要獲取什麽資源都是主動出擊,但是在IoC/DI思想中,應用程序就變成被動的了,被動的等待IoC容器來創建並註入它所需要的資源了。

  DI—Dependency Injection,即“依賴註入”:組件之間依賴關系由容器在運行期決定,形象的說,即由容器動態的將某個依賴關系註入到組件之中。依賴註入的目的並非為軟件系統帶來更多功能,而是為了提升組件重用的頻率,並為系統搭建一個靈活、可擴展的平臺。通過依賴註入機制,我們只需要通過簡單的配置,而無需任何代碼就可指定目標需要的資源,完成自身的業務邏輯,而不需要關心具體的資源來自何處,由誰實現。

  理解DI的關鍵是:“誰依賴誰,為什麽需要依賴,誰註入誰,註入了什麽”,那我們來深入分析一下:

  ●誰依賴於誰:當然是應用程序依賴於IoC容器;

  ●為什麽需要依賴:應用程序需要IoC容器來提供對象需要的外部資源;

  ●誰註入誰:很明顯是IoC容器註入應用程序某個對象,應用程序依賴的對象;

  ●註入了什麽:就是註入某個對象所需要的外部資源(包括對象、資源、常量數據)。

  IoC和DI有什麽關系呢?其實它們是同一個概念的不同角度描述,由於控制反轉概念比較含糊(可能只是理解為容器控制對象這一個層面,很難讓人想到誰來維護對象關系),所以2004年大師級人物Martin Fowler又給出了一個新的名字:“依賴註入”,相對IoC 而言,“依賴註入”明確描述了“被註入對象依賴IoC容器配置依賴對象”。

  IoC技術是Spring的核心,對於spring框架來說,就是由spring來負責控制對象的生命周期和對象間的關系。所有的類都會在spring容器中登記,告訴spring你是個什麽東西,你需要什麽東西,然後spring會在系統運行到適當的時候,把你要的東西主動給你,同時也把你交給其他需要你的東西。所有的類的創建、銷毀都由 spring來控制,也就是說控制對象生存周期的不再是引用它的對象,而是spring。對於某個具體的對象而言,以前是它控制其他對象,現在是所有對象都被spring控制,所以這叫控制反轉。IoC的一個重點是在系統運行中,動態的向某個對象提供它所需要的其他對象。這一點是通過DI(Dependency Injection,依賴註入)來實現的。

  所以控制反轉IoC(Inversion of Control)是說創建對象的控制權進行轉移,以前創建對象的主動權和創建時機是由自己把控的,而現在這種權力轉移到第三方,比如轉移交給了IoC容器,它就是一個專門用來創建對象的工廠,你要什麽對象,它就給你什麽對象,有了 IoC容器,依賴關系就變了,原先的依賴關系就沒了,它們都依賴IoC容器了,通過IoC容器來建立它們之間的關系。

  註意,一般將BeanFactory稱為IoC容器,而稱ApplicationContext和WebApplicationContext為應用上下文或Web應用上下文。在本篇博客中,把WebApplicationContext和ApplicationContext統稱為Spring容器。

2 IoC相關的Java基礎知識

2.1 反射

  IOC中最基本的技術就是“反射(Reflection)”。反射機制: 反射是java語言的一個特性,它允程序在運行時(註意不是編譯的時候)來進行自我檢查並且對內部的成員進行操作。例如它允許一個java的類獲取它所有的成員變量和方法並且顯示出來。通過這種能力可以徹底的了解自身的情況為下一步的動作做準備。Java的反射機制的實現要借助於4個類:class,Constructor,Field,Method;其中class代表的時類對象,Constructor-類的構造器對象,Field-類的屬性對象,Method-類的方法對象。通過這四個對象我們可以粗略的看到一個類的各個組 成部分。

  Java反射的作用:在Java運行時環境中,對於任意一個類,可以知道這個類有哪些屬性和方法。對於任意一個對象,可以調用它的任意一個方法。這種動態獲取類的信息以及動態調用對象的方法的功能來自於Java 語言的反射(Reflection)機制。

  Java 反射機制主要提供了以下功能,在運行時判斷任意一個對象所屬的類。在運行時構造任意一個類的對象。在運行時判斷任意一個類所具有的成員變量和方法。在運行時調用任意一個對象的方法。

  從Java內存模型的角度理解反射,首先上一張Java內存模型的神圖:

  技術分享圖片

  

  首先我們了解一下JVM,什麽是JVM,Java的虛擬機,java之所以能跨平臺就是因為這個東西,你可以理解成一個進程,程序,只不過他的作用是用來跑你的代碼的。

  假如你寫了一段代碼:Object o=new Object(); 運行了起來!

  首先JVM會啟動,你的代碼會編譯成一個.class文件,然後被類加載器加載進jvm的內存中,你的類Object加載到方法區中,創建了Object類的class對象到堆中,註意這個不是new出來的對象,而是類的類型對象,每個類只有一個class對象,作為方法區類的數據結構的接口。jvm創建對象前,會先檢查類是否加載,尋找類對應的class對象,若加載好,則為你的對象分配內存,初始化也就是代碼:new Object()。

  上面的流程就是你自己寫好的代碼扔給jvm去跑,跑完就over了,jvm關閉,你的程序也停止了。

  為什麽要講這個呢?因為要理解反射必須知道它在什麽場景下使用。大家想想上面的程序對象是自己new的,程序相當於寫死了給jvm去跑。假如一個服務器上突然遇到某個請求哦要用到某個類,哎呀但沒加載進jvm,是不是要停下來自己寫段代碼,new一下,哦啟動一下服務器,(腦殘)!

  反射是什麽呢?當我們的程序在運行時,需要動態的加載一些類這些類可能之前用不到所以不用加載到jvm,而是在運行時根據需要才加載,這樣的好處對於服務器來說不言而喻,舉個例子我們的項目底層有時是用mysql,有時用oracle,需要動態地根據實際情況加載驅動類,這個時候反射就有用了,假設 com.java.dbtest.myqlConnection,com.java.dbtest.oracleConnection這兩個類我們要用,這時候我們的程序就寫得比較動態化,通過Class tc = Class.forName("com.java.dbtest.TestConnection");通過類的全類名讓jvm在服務器中找到並加載這個類,而如果是oracle則傳入的參數就變成另一個了。這時候就可以看到反射的好處了,這個動態性就體現出java的特性了。

  反射可以實現動態創建對象和編譯,體現出很大的靈活性(特別是在J2EE的開發中它的靈活性就表現的十分明顯)。通過反射機制我們可以獲得類的各種內容,進行了反編譯。對於JAVA這種先編譯再運行的語言來說,反射機制可以使代碼更加靈活,更加容易實現面向對象。反射機制的缺點:對性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什麽並且它 滿足我們的要求。這類操作總是慢於只直接執行相同的操作。

  Spring的IOC正是反射技術最好的舞臺,IOC中大量利用反射技術,通過配置XML或者@Annotation的方式配置裝配bean到Spring容器,程序運行起來後,根據需要,Spring容器在動態的註入bean到需要的對象中。

  那麽,Spring是如何裝載XML文件以及資源文件的呢?在2.2中介紹。

2.2 資源訪問利器:Spring Resource接口

  JDK所提供的訪問資源的類(如java.net.URL、File等)並不能很好地滿足各種底層資源的訪問需求,比如缺少從類路徑或者Web容器的上下文中獲取資源的操作類。鑒於此,Spring設計了一個Resource接口,它為應用提供了更強的底層資源訪問能力。Spring的各種資源加載,都是通過這個接口來實現的

  Resource接口的主要方法有

    boolean exists():資源是否存在。

    boolean isOpen():資源是否打開。

    URL getURL():如果底層資源可以表示為URL,該方法返回對應的URL對象。

    File getFile():如果底層資源對應一個文件,該方法返回對應的File對象。

    InputStream getInputStream() throws IOException:返回資源對應的輸入流。

  為了可以在不顯示使用Resource實現類的情況下,僅通過資源地址的特殊標識就可以加載相應的資源,Spring提供了一個強大的加載資源的機制,可以通過"classpath:","file:"等資源地址前綴識別不同的資源類型,還支持ant風格的帶通配符的資源地址。

  技術分享圖片

3 BeanFactory和ApplicationContext

  什麽是POJO,什麽是Java Bean,什麽是Bean?

  1)POJO = "Plain Old Java Object",是MartinFowler等發明的一個術語,用來表示普通的Java對象,個人理解就是含有一些私有屬性,以及getter/setter/constructer方法的普通java類。

  2)Java Bean比POJO要復雜一些,需要滿足一些規範,例如:需要實現 Serializable 接口用於實現 Bean 的持久性。

  3)Bean比Java Bean更寬泛一些,所有可以被Spring容器實例化並管理的Java類都可以稱之為Bean。

  Spring通過一個配置文件描述Bean及Bean之間的依賴關系,利用Java語言的反射功能實例化Bean並建立Bean之間的依賴關系。Spring的IOC容器在完成這些底層工作的基礎上,還提供了Bean實例緩存、生命周期管理、Bean實例代理、事件發布、資源裝載等高級服務。

  BeanFactory是Spring框架最核心的接口,它提供了高級Ioc的配置機制。ApplicationContext建立在BeanFactory基礎之上,提供了更多面向應用的功能,它提供了國際化支持和框架事件體系,更易於創建實際應用。兩者都可稱為Spring容器。

  二者的用途,一般可以進行簡單的劃分:BeanFactory是Spring框架的基礎設施,面向Spring本身;ApplicationContext面向使用Spring框架的開發者,幾乎所有的應用場合都可以直接使用ApplicationContext而非底層的BeanFactory。

3.1 WebApplicationContext

  WebApplicationContext專門為Web應用準備的,它允許從相對於Web根目錄的路徑中裝在配置文件(web.xml)完成初始化工作。從WebApplicationContext中可以獲得ServletContext的引用,整個Web應用上下文對象將作為屬性放置到ServletContext中,以便Web應用環境可以訪問Spring應用上下文。

  在非Web應用的環境下,Bean只有singleton和prototype兩種作用域,在WebApplicationContext中又添加了三個新的作用域:request、session和global session。

  WebApplicationContext的初始化方式和BeanFactory、ApplicationContext有所區別,因為WebApplicationContext需要ServletContext實力,也就是說,它必須在擁有Web容器(Tomcat、Jetty等)的前提下才能完成啟動工作。可以在web.xml中配置自啟動的Servlet或定義Web容器監聽器(ServletContextListner),借助兩者中的任何一個,就可以完成啟動Spring Web應用上下文的工作。

  自啟動的Servlet啟動WebApplicationContext:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0"
         metadata-complete="true">
  <!--用maven創建的web-app需要修改servlet的版本為3.1-->
  <!--配置DispatcherServlet-->
  <servlet>
    <servlet-name>seckill-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!--
        配置SpringMVC 需要配置的文件
        spring-dao.xml,spring-service.xml,spring-web.xml
        Mybites -> spring -> springMVC
    -->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring/spring-*.xml</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>seckill-dispatcher</servlet-name>
    <!--默認匹配所有請求-->
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

  通過Web容器監聽器啟動WebApplicationContext:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:smart-context.xml</param-value>
    </context-param>
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

    <servlet>
        <servlet-name>smart</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>3</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>smart</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>
</web-app>

4 Bean作用域

  Scope,也稱作用域,在 Spring IoC 容器是指其創建的 Bean 對象相對於其他 Bean 對象的請求可見範圍。在 Spring IoC 容器中具有以下幾種作用域:基本作用域(singleton、prototype),Web 作用域(reqeust、session、globalsession),自定義作用域。

    • singleton:單例模式,在整個Spring IoC容器中,使用singleton定義的Bean將只有一個實例

    • prototype:原型模式,每次通過容器的getBean方法獲取prototype定義的Bean時,都將產生一個新的Bean實例

    • request:對於每次HTTP請求,使用request定義的Bean都將產生一個新實例,即每次HTTP請求將會產生不同的Bean實例。只有在Web應用中使用Spring時,該作用域才有效

    • session:對於每次HTTP Session,使用session定義的Bean豆漿產生一個新實例。同樣只有在Web應用中使用Spring時,該作用域才有效

    • globalsession:每個全局的HTTP Session,使用session定義的Bean都將產生一個新實例。典型情況下,僅在使用portlet context的時候有效。同樣只有在Web應用中使用Spring時,該作用域才有效

  其中比較常用的是singleton和prototype兩種作用域。對於singleton作用域的Bean,每次請求該Bean都將獲得相同的實例。容器負責跟蹤Bean實例的狀態,負責維護Bean實例的生命周期行為;如果一個Bean被設置成prototype作用域,程序每次請求該id的Bean,Spring都會新建一個Bean實例,然後返回給程序。在這種情況下,Spring容器僅僅使用new 關鍵字創建Bean實例,一旦創建成功,容器不在跟蹤實例,也不會維護Bean實例的狀態。

  如果不指定Bean的作用域,Spring默認使用singleton作用域。Java在創建Java實例時,需要進行內存申請;銷毀實例時,需要完成垃圾回收,這些工作都會導致系統開銷的增加。因此,prototype作用域Bean的創建、銷毀代價比較大。而singleton作用域的Bean實例一旦創建成功,可以重復使用。因此,除非必要,否則盡量避免將Bean被設置成prototype作用域。

5 IoC Bean的4種配置方式比較

類別基於XML配置基於註解配置基於Java類配置基於Groovy DSL配置
Bean定義 在XML文件中通過<bean>元素定義Bean,如:<bean class="com.xgj.UserDao"/> 在Bean實現類處通過標註@Component或衍型類@Repository、@Service及@Controller定義Bean 在標註了@Configuration的Java類中,通過在類方法上標註@Bean定義一個Bean。方法必須提供Bean的實例化邏輯 在Groovy 文件中通過DSL定義Bean的名稱 ,如 userDao(UserDao)
Bean名稱 通過<bean>的id或name屬性定義,如:<bean id="userDao" class="com.xgj.UserDao"/> 默認名稱為:com.xgj.userDao#0 通過註解的value屬性定義,如@Component(“userDao”)。默認名稱為小寫字母打頭的類名(不帶包名):userDao 通過@Bean的name屬性定義,如@Bean(“userDao”),默認名稱為方法名 通過GroovyDSL定義Bean的名稱
Bean註入 通過<property>子元素或通過p命名空間的動態屬性,如p:userDao-ref=”userDao”進行註入 通過在成員變量或方法入參處標註@Autowired,按類型匹配自動註入。還可以配合使用@Qualifier按名稱匹配方式註入 比較靈活,可以通過在方法處通過@Autowired方法入參綁定Bean,然後在方法中通過代碼進行註入,還可以通過調用配置類的@Bean方法進行註入 比較靈活,可以在方法出通過ref()方法進行註入,如ref(“logDao”)
Bean生命過程方法 通過<bean>的init-method和destory-method屬性指定Bean實現類的方法名。最多只能指定一個初始化方法和一個銷毀方法。 通過在目標方法上標註@PostConstruct和@PreDestroy註解指定初始化或銷毀方法,可以定義任意多個方法 通過@Bean的initMethod或destoryMethod指定一個初始化或銷毀方法.對於初始化方法來說,你可以直接在方法內部通過代碼的方式靈活定義初始化邏輯 通過bean->bean,initMehtod或者bean.destoryMethod指定一個初始化或者銷毀方法
Bean作用範圍 通過<bean>的scope屬性指定,如:<bean class="com.xgj.UserDao" scope="prototype"/> 通過在類定義處標註@Scope指定,如@Scope(“prototype”) 通過在Bean方法定義處標註@Scope指定 通過bean->bean,scope=”prototype”指定
Bean延遲初始化 通過<bean>的lazy-init屬性指定,默認為default,繼承於的default-lazy-init設置,該值默認為false 通過在類定義處標註@Lazy指定,如@Lazy(true) 通過在Bean方法定義處標註@Lazy指定 通過bean->bean.lazyInit-true指定

  我在項目中一般采用“XML+註解”的配置方式,DataSource、JdbcTemplate由於無法在類中標註註解,所以通過XML配置方式比較好,命名空間:如aop、context等,只能采用基於XML的配置。Bean的實現類是當前項目開發的,可以直接在Java類中使用基於註解的配置,例如說Service層實現類可以標註@Service,Controller實現類標註@Controller,配合@Autowired就可以很好地使用基於註解的配置進行Bean的定義和註入。

6 IoC 在Spring Web中的應用

  個人理解,一個Spring Web應用中,是從web.xml開始加載的,通過WebApplicationContext中加載web.xml可以獲得ServletContext的引用,讀取到web.xml中其他Spring配置文件的路徑,再通過Spring Resource接口加載配置文件,根據註解掃描或者XML配置實例化、註入Bean到WebApplicationContext(Spring 容器),整個Spring 容器(WebApplicationContext對象)將作為屬性放置到Web容器(ServletContext)中,以便Web容器可以訪問Spring容器中的內容。

參考資料:

https://blog.csdn.net/qq_22654611/article/details/52606960/

https://www.zhihu.com/question/24304289/answer/76541818

https://www.zhihu.com/question/24304289/answer/147529485

https://blog.csdn.net/u011468990/article/details/49995865

《精通Spring 4.x 企業應用開發實戰》 陳雄華 林開雄 文建國 編著

Spring IOC 學習總結