PS:本文內容較爲硬核,須要對java的面向對象、反射、類加載器、泛型、properties、XML等基礎知識有較深理解。java
在講Spring IOC以前,有必要先來聊一下工廠模式(Factory Pattern)。工廠模式可將Java對象的調用者從被調用者的實現邏輯中分離出來。工廠模式是Java中最經常使用的設計模式之一。這種類型的設計模式屬於建立型模式,它提供了一種建立對象的最佳方式。在工廠模式中,咱們在建立對象時不會對客戶端暴露建立邏輯,而且是經過使用一個共同的接口來指向新建立的對象。web
這麼講可能有點抽象,簡單的說就是之後咱們不用本身new對象了,對象的實例化都交給工廠來完成,咱們須要對象的時候直接問工廠拿一個就行,一會咱們會來看一個例子。在這裏有一點要說明,spring IOC與工廠模式並非徹底相同的,最大的不一樣在於普通的工廠模式內部仍是使用new來建立對象,可是spring IOC是用反射來建立對象,這麼作有什麼好處呢?spring
下面咱們來看一個工廠模式例子。爲了演示方便,我將全部的類和接口都寫在一塊兒:設計模式
public interface Shape { void draw();}
public class Rectangle implements Shape { @Override public void draw() { System.out.println("Inside Rectangle::draw() method."); }}
public class Square implements Shape { @Override public void draw() { System.out.println("Inside Square::draw() method."); }}
public class Circle implements Shape { @Override public void draw() { System.out.println("Inside Circle::draw() method."); }}
public class ShapeFactory { public Shape getShape(String shapeType){ if(shapeType == null){ return null; } if(shapeType.equalsIgnoreCase("CIRCLE")){ return new Circle(); } else if(shapeType.equalsIgnoreCase("RECTANGLE")){ return new Rectangle(); } else if(shapeType.equalsIgnoreCase("SQUARE")){ return new Square(); } return null; }}
咱們來看一下這段代碼幹了啥。咱們看這個ShapeFactory,裏面有個getShape方法,輸入圖形的名字咱們就能得到相應圖形的對象。這就是所謂的工廠,有了這個工廠以後,咱們想用什麼圖形的對象,直接調用getShape方法就能得到了。這樣使用這些對象的類就能夠和這些圖形類解耦。可是咱們很容易發現,如今工廠能生產三個不一樣的對象,若是咱們要加一個新的對象到工廠中,是很是麻煩的,咱們要修改代碼而後從新編譯,就比如現實中的工廠忽然想要加一條新生產線是很麻煩的同樣。因而咱們確定要尋求改進,這就孕育了spring IOC。緩存
spring IOC的思想與工廠模式基本是同樣的,只是建立對象的方式從「new」變成了反射,這就帶來了很大的靈活性。不過,如今閱讀spring源碼還爲時過早,因而我本身寫了一個簡單的例子來模擬spring IOC的基本原理。app
首先,若是咱們要用反射建立對象,全類名是必不可少的(反射不太記得的朋友請好好複習一下反射),而後咱們還須要一個類名,用來告訴工廠咱們須要哪一個對象(就像上面getShape方法傳入的參數shapeType同樣),這個名字能夠隨便取,可是不能重複。這樣咱們就有了建立對象的兩個要素,而後咱們須要一個key-value對把這兩個關聯起來。而後就造成了這樣一個模式:咱們傳入類名,工廠去查詢key-value對,找到對應的全類名,而後經過全類名利用反射建立對象,再返回給咱們。是否是很簡單呢?框架
話很少說,咱們先來建立這個key-value對,也就是所謂的配置文件,spring中用的是XML,我這裏爲了簡化就用properties吧,原理都是同樣的:ide
//文件名:bean.propertiescircle=com.demo.Circle rectangle=com.demo.Rectangle square=com.demo.Square
配置文件有了以後,咱們來寫咱們的BeanFactory。函數
//文件名:BeanFactory.javapackage com.demo;import java.io.IOException;import java.io.InputStream;import java.util.Enumeration;import java.util.HashMap;import java.util.Map;import java.util.Properties;public class BeanFactory { //配置對象(類比spring IOC容器中的Bean定義註冊表) private static final Properties props; //保存建立好的對象的容器,與類名組成key-value對(類比spring IOC容器中的Bean緩存池) private static Map<String, Object> beans; static { props = new Properties(); //經過類加載器讀入配置文件 InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"); try { //加載配置文件到配置對象 props.load(in); //初始化容器 beans = new HashMap<>(); Enumeration<Object> keys = props.keys(); //循環遍歷配置對象中的全部的類名(key) while (keys.hasMoreElements()){ String key = keys.nextElement().toString(); //經過類名拿到全類名(value) String beanPath = props.getProperty(key); //利用全類名反射建立對象 Object value = Class.forName(beanPath).getDeclaredConstructor().newInstance(); //將對象放入容器中 beans.put(key, value); } } catch (IOException e) { throw new ExceptionInInitializerError("初始化properties失敗!程序終止!"); } catch (Exception e){ e.printStackTrace(); } } public static Object getBean(String beanName){ //從容器中獲取對象 return beans.get(beanName); }}
另外三個類和一個接口依舊沿用上面那個工廠模式的例子的,本案例全部java文件都位於com.demo包下。咱們來仔細看看這個BeanFactory(我本身寫的山寨版,模仿spring IOC基本功能),首先咱們先抓住核心,核心就是裏面的Map<String, Object> beans,這個東西直接對標了spring IOC容器中的Bean緩存池,用來存放建立好的對象,用Map是爲了能夠直接經過類名取到對應的對象。而後咱們來看看這些對象是如何生產出來的:spa
Object value = Class.forName(beanPath).getDeclaredConstructor().newInstance();
很顯然,反射就在這一句上,咱們經過類的全類名來建立了對象,全類名來自於咱們的Properties對象,也就是讀取咱們的配置文件產生的對象,對標spring IOC容器中的Bean定義註冊表。如今你應該已經明白了這個配置文件的做用,他就像咱們給工廠的一張生產單,上面寫了咱們須要生產的對象。而Bean緩存池至關於工廠的倉庫,用來存儲生產完的對象,等待被取出。而咱們定義的Bean實現類(就是上面的那些Circle、Square之類的)至關於圖紙,告訴工廠這些對象是什麼樣,應該如何去生產。咱們來總結一下:
模塊 功能
Bean實現類 說明如何生產
Bean定義註冊表 說明須要生產哪些
Bean緩存池(HashMap實現) 存放生產完的對象
看完了我寫的「山寨」IOC,咱們再來畫個圖看一看真正的spring IOC的結構執行過程,其實與我寫的基本是一致的。
咱們來看看執行過程:
1.讀取Bean配置信息放入Bean定義註冊表
2.根據Bean註冊表實例化Bean
3.將實例化以後的bean實例放入Bean緩存池(HashMap實現)
4.應用程序經過類名從Bean緩存池中取出Bean實例
看了這麼多,咱們仍是來看看具體如何使用spring IOC容器來建立對象吧。首先,就像上面個人那個山寨IOC同樣,咱們要先來編寫XML文件,XML比properties要複雜,不過好在咱們暫時還用不到那麼複雜的部分。首先先去官網的文檔裏面找一段模板抄下來:
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd"></beans>
這就是spring配置文件的模板了,上面定義了一些XML文件的約束,也就是咱們在那些XML標籤裏能寫啥。咱們之後的開發都會基於這個模板。而後,就是咱們的類名和全類名組成的key-value對了,這些流程和上面都是徹底同樣的,只是寫法有所不一樣,咱們把該寫的都寫上(注意個人寫法)。
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="circle" class="com.demo.Circle"/> <bean id="square" class="com.demo.Square"/> <bean id="rectangle" class="com.demo.Rectangle"/></beans>
很顯然,這個id就至關於類名,這個class就至關於全類名,是否是和個人山寨版基本同樣?
固然,既然咱們是在使用框架,因此那些繁瑣的工做都不須要咱們去作了,也就是說咱們不須要去關心對象是如何建立如何管理的,這些都由spring幫咱們完成了。咱們要作的就是直接問spring IOC容器取出對象便可。那麼這個容器在哪?如今固然是隻能咱們手動建立這個容器,否則他也不會憑空產生對吧。
咱們來看一眼這個接口繼承圖,咱們只須要關注兩個,一個是裏面的一個頂級接口——BeanFactory,另外一個是最底下的ApplicationContext接口。BeanFactory是簡單容器,他實現了容器的基本功能,典型方法如 getBean、containsBean等。ApplicationContext是應用上下文,他在簡單容器的基礎上,增長上下文的特性。咱們開發時通常都是使用ApplicationContext接口,由於他的功能比BeanFactory更強大。固然,「應用上下文」這個名字可能有點奇怪,不過咱們只須要記得他就是那個spring IOC容器接口就行。接口有了,接下來就是要找實現類。
ApplicationContext有好多的實現類,咱們就挑一個最經常使用的講——ClassPathXmlApplicationContext。
咱們先來看一眼他的名字:ClassPathXmlApplicationContext。翻譯爲:類路徑XML應用上下文,嗯,這個名字更奇怪了。其實,他就是一個只能讀取類路徑下的XML文件做爲配置文件的應用上下文實現類。那我再舉一個例子:FileSystemXmlApplicationContext,他是幹嗎的?嗯,他是文件系統應用上下文,也就是說他能夠讀取磁盤任意位置(須要有讀權限)的XML做爲配置文件。
這樣咱們就能夠實例化咱們的IOC容器了:
package com.demo;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class Test { public static void main(String[] args) { //構造函數參數爲配置文件名稱 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml"); Shape circle = (Shape) applicationContext.getBean("circle"); circle.draw(); }}
這樣咱們就拿到了容器裏面的對象,是否是和個人山寨版基本同樣呢?讀到這裏,你應該已經理解了spring IOC的原理了,後面我會更新新的文章,分析spring IOC的細節和一些其餘功能。最後放一張截圖,不清楚項目結構的能夠看一眼。