天天看點

Spring學習第一章 、 第一節:Spring Ioc容器前言正文

Spring Ioc容器

  • 前言
  • 正文
    • Spring IoC 容器
      • IoC 容器
      • Spring BeanFactory 容器
      • Spring ApplicationContext 容器
    • Spring Bean
      • Spring Bean 定義
      • Spring Bean作用域
      • Spring Bean生命周期
        • 初始化方法和銷毀方法
      • Spring Bean延遲加載
      • Spring Bean後置處理器
      • 引用
    • 本文用于個人學習和分享,多處借鑒,如有不足之處請指正

前言

上一章講過有關IoC(Inversion of Control 控制反轉)的内容,我們先來看看這指的是什麼:

控制反轉(Inversion of Control,縮寫為IoC),是面向對象程式設計中的一種設計原則,可以用來減低計算機代碼之間的耦合度。其中最常見的方式叫做依賴注入(Dependency Injection,簡稱DI),還有一種方式叫“依賴查找”(Dependency Lookup)。通過控制反轉,對象在被建立的時候,由一個調控系統内所有對象的外界實體,将其所依賴的對象的引用傳遞給它。也可以說,依賴被注入到對象中。

好吧,其實我看完這段話這段話也是丈二和尚摸不着頭腦,如果非要用我的了解說的話(在spring中),控制反轉就是将執行個體化對象的任務交給了IoC容器。

juo個梨子~

一般情況下,我們會使用new的方法執行個體化一個Bean對象(實際上是POJO,Bean更好了解一些,如果想了解POJO可以參考:Bean、POJO、DAO、EJB的差別)來進行資料的傳遞等:

Student s1 = new Student();
s1.setName("張");
s1.setAge(20);
           

但是,在spring中,可能會采用XML或注解對Bean對象進行參數注入(沒錯就是注入,後面章節會提到哦)是以可以說spring是采用依賴注入的方式實作控制反轉的,可以把對象從應用中解耦出去(個人了解)。

沒錯,這一章就會圍繞控制反轉對Spring進行學習。

正文

Spring IoC 容器

IoC 容器

首先看一些概念

IoC 容器

Spring 容器是 Spring 架構的核心。容器将建立對象,把它們連接配接在一起,配置它們,并管理他們的整個生命周期從建立到銷毀。Spring 容器使用依賴注入(DI)來管理組成一個應用程式的元件。這些對象被稱為 Spring Beans.

通過閱讀配置中繼資料提供的指令,容器知道對哪些對象進行執行個體化,配置群組裝。配置中繼資料可以通過 XML,Java 注釋或 Java 代碼來表示。下圖是 Spring 如何工作的進階視圖。

Spring學習第一章 、 第一節:Spring Ioc容器前言正文

Spring IoC 容器利用 Java 的 POJO 類和配置中繼資料來生成完全配置和可執行的系統或應用程式。

IOC 容器具有依賴注入功能的容器,它可以建立對象,IOC 容器負責執行個體化、定位、配置應用程式中的對象及建立這些對象間的依賴。通常new一個執行個體,控制權由程式員控制,而"控制反轉"是指new執行個體工作不由程式員來做而是交給Spring容器來做。在Spring中BeanFactory是IOC容器的實際代表者。

序号 描述
1 Spring BeanFactory 容器。 它是最簡單的容器,給 DI 提供了基本的支援,它用 org.springframework.beans.factory.BeanFactory 接口來定義. BeanFactory 或者相關的接口,如 BeanFactoryAware,InitializingBean,DisposableBean,在 Spring 中仍然存在具有大量的與 Spring 整合的第三方架構的反向相容性的目的。 2
2 Spring ApplicationContext 容器。該容器添加了更多的企業特定的功能,例如從一個屬性檔案中解析文本資訊的能力,釋出應用程式事件給感興趣的事件監聽器的能力。該容器是由 org.springframework.context.ApplicationContext 接口定義。

ApplicationContext 容器包括 BeanFactory 容器的所有功能,是以通常建議超過 BeanFactory。BeanFactory 仍然可以用于輕量級的應用程式,如移動裝置或基于 applet 的應用程式,其中它的資料量和速度是顯著。

Spring BeanFactory 容器

先來一個較為官方的解釋(源自w3cSchool):

這是一個最簡單的容器,它主要的功能是為依賴注入 (DI) 提供支援,這個容器接口在 org.springframework.beans.factory.BeanFactor 中被定義。BeanFactory 和相關的接口,比如BeanFactoryAware、DisposableBean、InitializingBean,仍舊保留在 Spring 中,主要目的是向後相容已經存在的和那些 Spring 整合在一起的第三方架構。

在 Spring 中,有大量對 BeanFactory 接口的實作。其中,最常被使用的是 XmlBeanFactory 類。這個容器從一個 XML 檔案中讀取配置中繼資料,由這些中繼資料來生成一個被配置化的系統或者應用。

在資源寶貴的移動裝置或者基于 applet 的應用當中, BeanFactory 會被優先選擇。否則,一般使用的是 ApplicationContext,除非你有更好的理由選擇 BeanFactory。

額。。。依舊是說了一大堆==,無法形象的表示進行了解,我們來建立一個新的Spring應用程式來體會什麼是Spring容器。

  1. 首先在你的IDE中建立一個工程,并在src檔案夾下建立包或檔案夾,這裡我們建立名為com.hello包
  2. 将Spring的庫檔案(.jar包)導入項目中(可以直接導入也可以使用一些自動化部署工具如:Maven、Gradle等導入)
  3. 在com.hello下建立HelloWorld.java,在src下建立Main.java
package com.hello;
public class HelloWorld {
  private String message;
  //這裡需要設定set,get方法用于之後容器對對象屬性進行注入
  public void setMessage(String message){
   this.message  = message;
  }
  public void getMessage(){
   System.out.println("Message : " + message);
  }
}
           
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class Main {
  public static void main(String[] args) {
     //執行個體化XmlBeanFactory容器,會利用 ClassPathResource() API 去加載在路徑 CLASSPATH 下可用的 bean 配置檔案,初始化建立并初始化配置檔案中的對象
     XmlBeanFactory factory = new XmlBeanFactory
                            (new ClassPathResource("ioc.xml"));
     //通過配置檔案中的 bean ID 來傳回一個真正的對象,該對象最後可以用于實際的對象。一旦得到這個對象,你就可以利用這個對象來調用任何方法。
     HelloWorld obj = (HelloWorld) factory.getBean("helloWorld");
     obj.getMessage();
  }
}
           

4.在src下建立容器的配置檔案ioc.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-3.0.xsd">
   <!--
   id是用于之後ioc容器擷取,而property是指使用set方法注入依賴關系(之後詳細提及)
   -->
   <bean id="helloWorld" class="com.tutorialspoint.HelloWorld">
       <property name="message" value="Hello World!"/>
   </bean>

</beans>
           

最後運作得到的結果應該是:

message: Hello World!
           

Spring ApplicationContext 容器

一些贅述(比較官方的東西以後來看沒準有不同的體會):

Spring ApplicationContext 容器

Application Context 是 BeanFactory 的子接口,也被成為 Spring 上下文。

Application Context 是 spring 中較進階的容器。和 BeanFactory 類似,它可以加載配置檔案中定義的 bean,将所有的 bean 集中在一起,當有請求的時候配置設定 bean。 另外,它增加了企業所需要的功能,比如,從屬性檔案中解析文本資訊和将事件傳遞給所指定的監聽器。這個容器在 org.springframework.context.ApplicationContext interface 接口中定義。

ApplicationContext 包含 BeanFactory 所有的功能,一般情況下,相對于 BeanFactory,ApplicationContext 會更加優秀。當然,BeanFactory 仍可以在輕量級應用中使用,比如移動裝置或者基于 applet 的應用程式。

最常被使用的 ApplicationContext 接口實作:

  • FileSystemXmlApplicationContext:該容器從 XML 檔案中加載已被定義的 bean。在這裡,你需要提供給構造器 XML 檔案的完整路徑。
  • ClassPathXmlApplicationContext:該容器從 XML 檔案中加載已被定義的 bean。在這裡,你不需要提供 XML 檔案的完整路徑,隻需正确配置 CLASSPATH 環境變量即可,因為,容器會從 CLASSPATH 中搜尋 bean 配置檔案。
  • WebXmlApplicationContext:該容器會在一個 web 應用程式的範圍内加載在 XML 檔案中已被定義的 bean。

之前已經對BeanFactory進行了示例,在導包等步驟沒有其他差別,我們隻看在容器初始化 的Main方法上的差別:

FileSystemXmlApplicationContext

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class MainApp {
   public static void main(String[] args) {
      ApplicationContext context = new FileSystemXmlApplicationContext
            ("C:workspace/HelloSpring/src/ioc.xml");
      HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
      obj.getMessage();
   }
}
           

ClassPathXmlApplicationContext:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
package com.hello.HelloWorld;

public class MainApp {
   public static void main(String[] args) {
      ApplicationContext ac = new ClassPathXmlApplicationContext(
						"scope.xml");
	//獲得對象(預設情況下,是同一個對象)
	HelloWorld hello = ac.getBean("helloWorld",HelloWorld.class);
      hello.getMessage();
   }
}
           

發現有什麼不同嘛?再看一遍:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
   public static void main(String[] args) {
      ApplicationContext context = 
             new ClassPathXmlApplicationContext("Beans.xml");
      HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
      obj.getMessage();
   }
}
           

這些栗子應該能找到其中的規律吧,那麼最後一個就總結一下,之後會有專門的章節講WebApplicationContext。

XmlWebApplicationContext:從Web系統中的XML檔案來載入Bean定義的信
ServletContext servletContext = request.getSession().getServletContext();    
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
           

配置WebApplicationContext的兩種方法:

  1. 利用Listener接口來實作
<listener>

       <listener-class>

org.springframework.web.context.ContextLoaderListener

</listener-class>

</listener>

<context-param>

       <param-name>contextConfigLocation</param-name>

       <param-value>classpath:applicationContext</param-value>

</context-param>
           
  1. 利用Servlet接口來實作
<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>classpath:applicationContext</param-value>

</context-param>
           
<Servlet>

       <servlet-name>context</servlet-name>

       <servlet-class>

           org.springframework.web.context.ContextLoaderServlet

       </servlet-class>

</servlet>
           

Spring Bean

Spring Bean 定義

被稱作 bean 的對象是構成應用程式的支柱也是由 Spring IoC 容器管理的。bean 是一個被執行個體化,組裝,并通過 Spring IoC 容器所管理的對象。這些 bean 是由用容器提供的配置中繼資料建立的,例如,已經在先前章節看到的,在 XML 的表單中的 定義。

配置中繼資料(建立對象)

注意!接下來的配置檔案xml會以不同的名稱表現在不同章節,該章節的xml檔案名與該章節的容器啟動時加載的xml檔案名将會相同,

  1. 使用無參構造器建立對象
package ioc; 
public class B{
    public B(){
        //給類添加無參構造器,雖然預設有該構造器,這裡作為說明。
    }
}
           
<!-- 使用無參構造器建立對象-->
<beans>
<bean id="b1" class="ioc.B"/>
</beans>
           

class:這個屬性是強制性的,并且指定用來建立 bean 的 bean 類。(使用全限定名)

id:指定bean的名稱,唯一(會通過id獲得對象)。

//啟動spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//擷取對象
B b1 = (B)ac.getBean("b1");//因為傳回的是Object類,是以需要強轉
B b1 = ac.getBean("b1",B.class)//指定傳回類型,不需要強轉
           

這樣就可以進行無參構造器方式的建立對象了!

2. 使用靜态工廠方法建立對象:

<bean id="cal1" 
class="java.util.Calendar"
factory-method="getInstance">
</bean>
           

factory-mothod:調用工廠方法執行個體化對象

(前提:該方法是一個靜态方法。)

進行擷取:

ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");

Calendar cal1 = ac.getBean("cal1",B.class)
           
  1. 使用執行個體工廠方法建立對象
<bean id="time1" 
	 factory-bean="cal1"
	 factory-method="getTime"
	 ></bean>
           

哇,沒有指定類就可以啦?

原因:spring容器此時可以調用cal1對象的getTime方法建立對象

Spring Bean作用域

首先我們寫一個執行個體:(為了差別)

//啟動spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("scope.xml");
//獲得對象(預設情況下,是同一個對象)
ScopeBean s1 = ac.getBean("s1",ScopeBean.class);
ScopeBean s2 = ac.getBean("s1",ScopeBean.class);
//測試s1s2是否為同一個對象
System.out.println(s1 == s2);
           

傳回值是true!

怎麼會這樣呢?如何建立多個執行個體呢?這就要提到Spring Bean的作用域了

Bean 的作用域

當在 Spring 中定義一個 bean 時,你必須聲明該 bean 的作用域的選項。

如果為了強制 Spring 在每次需要時都産生一個新的 bean 執行個體,你應該聲明 bean 的作用域的屬性為 prototype。
如果你想讓 Spring 在每次需要時都傳回同一個bean執行個體,你應該聲明 bean 的作用域的屬性為 singleton。

此時就提到了Spring 架構支援以下五個作用域,分别為singleton、prototype、request、session和global session,5種作用域。

  1. singleton
嗯。。。你沒看錯就是單例的,要知道單例模式的對象在生産和工作出現是很頻繁的,是以spring也提供了産生單例對象的方法

現在我們先建立一個單例對象

ApplicationContext ac = new ClassPathXmlApplicationContext("scope.xml");
ScopeBean s1 = ac.getBean("s1",ScopeBean.class);
ScopeBean s2 = ac.getBean("s1",ScopeBean.class);
System.out.println(s1 == s2);
           
哈哈,現在發現了什麼嘛,spring不指定scope時,其實預設情況下建立的對象是單例的

那就意味着:如果将建立Bean時的scope屬性設定為singleton或者不進行設定,那麼容器在擷取對象時會建立同一個對象!

2. prototype

剛才提到的singleton情況,隻需要把scope屬性設定為prototype,就可以在每次從容器中調用Bean時,都傳回一個新的執行個體,即每次調用getBean()時,相當于執行newXxxBean()。

還等什麼,快去試試吧!

  • ++以下作用域隻适用于WebApplicationContext環境++
  1. request

每次HTTP請求都會建立一個新的Bean,該作用域僅适用于WebApplicationContext環境

  1. session

同一個HTTP Session共享一個Bean,不同Session使用不同的Bean,僅适用于WebApplicationContext環境

  1. global-session

一般用于Portlet應用環境,該運用域僅适用于WebApplicationContext環境

Spring Bean生命周期

Bean的生命周期可以表達為:Bean的定義——Bean的初始化——Bean的使用——Bean的銷毀
  1. 定義
  2. 初始化(配置設定資源)
  3. 使用
  4. 銷毀(釋放資源)

Spring中有初始化方法和銷毀方法來控制Spring Bean的生命周期

初始化方法和銷毀方法

package scope;
public class MessageBean {
    public MessageBean() {
		System.out.println("MessageBean()");
}
	public void init() {
		System.out.println("init()");
	}
	public void sendMsg{
	    System.out.println("sendMsg()");
	}
	public void destory() {
		System.out.println("destory()");
	}
}
           
看一個示例(模拟有上述方法)
<bean id="mb1" class="scope.MessageBean"
	init-method="init"
	destroy-method="destory"> </bean>
           

init-method和destroy-method分别在配置檔案中設定調用的初始化方法和銷毀方法,(在Bean初始化和銷毀時會自動執行初始化和銷毀方法)

調用該方法的sendMsg并進行關閉容器就可以示範效果:

//測試生命周期
//AbstractApplicationContext是ApplicationContext的子接口,ClassPathXmlApplicationContext實作了ApplicationContext,隻有AbstractApplicationContext中才有關閉容器的方法。
AbstractApplicationContext ac = new ClassPathXmlApplicationContext("scope.xml");
	MessageBean m1 = ac.getBean("mb1",MessageBean.class) ;
	m1.sendMsg();
	ac.close();
           
  1. 初始化回調
org.springframework.beans.factory.InitializingBean 接口指定一個單一的方法:
void afterPropertiesSet() throws Exception;
           

可以簡單的實作該接口進行初始化

public class ExampleBean implements InitializingBean {
   public void afterPropertiesSet() {
      // do some initialization work
   }
}
           
<bean id="exampleBean" 
      class="examples.ExampleBean"
      init-method="init"/>
           
  1. 銷毀回調
org.springframework.beans.factory.DisposableBean 接口指定一個單一的方法:
void destroy() throws Exception;
           

同理:

public class ExampleBean implements DisposableBean {
   public void destroy() {
      // do some destruction work
   }
}
           
<bean id="exampleBean"
         class="examples.ExampleBean"
         destroy-method="destroy"/>
           

大佬說的一些細節0.0:

如果你在非 web 應用程式環境中使用 Spring 的 IoC 容器;例如在豐富的用戶端桌面環境中;那麼在 JVM 中你要注冊關閉 hook。這樣做可以確定正常關閉,為了讓所有的資源都被釋放,可以在單個 beans 上調用 destroy 方法。

建議你不要使用 InitializingBean 或者 DisposableBean 的回調方法,因為 XML 配置在命名方法上提供了極大的靈活性。

Spring Bean延遲加載

直接上栗子了?:

ApplicationContext ac = new ClassPathXmlApplicationContext("scope.xml");
		//spring容器啟動會自動将單例bean建立好
		//可設定延遲加載,在bean中(懶加載)
           

為了友善示範,從作用域一節開始,xml沒有進行改變。

!!!為啥我啥都沒調顯示有對象建立了呢?

Spring在啟動時,會檢查到作用域為單例的Bean并自動執行個體化,

而延遲加載可以取消這一操作,等調用時再進行執行個體化:

<bean id="mb1" class="scope.MessageBean"
	init-method="init"
	destroy-method="destory"
	lazy-init="true">
	<!--這是個預設的singleton Bean-->
           
指定是否延遲加載,值為true時延遲加載,預設值為false。

Spring Bean後置處理器

贅述 x N (=_=)

Bean 後置處理器允許在調用初始化方法前後對 Bean 進行額外的處理。

BeanPostProcessor 接口定義回調方法,你可以實作該方法來提供自己的執行個體化邏輯,依賴解析邏輯等。你也可以在 Spring 容器通過插入一個或多個 BeanPostProcessor 的實作來完成執行個體化,配置和初始化一個bean之後實作一些自定義邏輯回調方法。

你可以配置多個 BeanPostProcessor 接口,通過設定 BeanPostProcessor 實作的 Ordered 接口提供的 order 屬性來控制這些 BeanPostProcessor 接口的執行順序。

BeanPostProcessor 可以對 bean(或對象)執行個體進行操作,這意味着 Spring IoC 容器執行個體化一個 bean 執行個體,然後 BeanPostProcessor 接口進行它們的工作。

ApplicationContext 會自動檢測由 BeanPostProcessor 接口的實作定義的 bean,注冊這些 bean 為後置處理器,然後通過在容器中建立 bean,在适當的時候調用它。

package scope;
public class MessageBean {
    private String message;
    public MessageBean() {
		this.message  = message;
}
	public void init() {
		System.out.println("init()");
	}
	public void sendMsg{
	    System.out.println("sendMsg()");
	}
	public void destory() {
		System.out.println("destory()");
	}
}
           
這是實作 BeanPostProcessor 的非常簡單的例子,它在任何 bean 的初始化的之前和之後輸入該 bean 的名稱。你可以在初始化 bean 的之前和之後實作更複雜的邏輯,因為你有兩個通路内置 bean 對象的後置處理程式的方法。
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.BeansException;
public class InitMessage implements BeanPostProcessor {
   public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
      System.out.println("BeforeInitialization : " + beanName);
      return bean;  // you can return any other object as well
   }
   public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
      System.out.println("AfterInitialization : " + beanName);
      return bean;  // you can return any other object as well
   }
}
           
AbstractApplicationContext ac = new ClassPathXmlApplicationContext("scope.xml");
	MessageBean m1 = ac.getBean("mb1",MessageBean.class) ;
	m1.sendMsg();
	ac.close();
           

示範之後就明白了!

引用

本文有自己的代碼,同時也參考了很多部落格和教程:

Spring概述:https://www.w3cschool.cn/wkspring/pesy1icl.html

Ioc:https://baike.baidu.com/item/控制反轉/1158025?fr=aladdin

本文用于個人學習和分享,多處借鑒,如有不足之處請指正

繼續閱讀