根據Spring中的核心IoC深度理解設計模式(一)
學習Spring系列,永遠離不開的就是IoC控制反轉和AOP面向切面程式設計,並且在其中充滿了設計模式的魅力.
之前面試也被問到過,簡單的理解過程是不夠的,敗在了理解原始碼上面,為了今後的學習,想用原始碼去理解一下到底什麼IoC和AOP。
首先是IoC,所謂控制反轉,就是把原先我們程式碼裡面需要實現的物件建立、依賴的程式碼,反轉給容器來幫忙實現。那麼必然的我們需要建立一個容器,同時需要一種描述來讓容器知道需要建立的物件與物件的關係。這個描述最具體表現就是我們可配置的檔案。即由IoC容器幫物件找相應的依賴物件並注入,而不是由物件主動去找。
引入IoC的目的是(1)脫開、降低類之間的耦合;(2)倡導面向介面程式設計、實施依賴倒換原則; (3)提高系統可插入、可測試、可修改等特性。
在Sping IoC的體系結構中BeanFactory作為最頂層的一個介面類,它定義了IoC容器的基本功能規範。並且為了區分在 Spring 內部在操作過程中物件的傳遞和轉化過程中,對物件的資料訪問做限制,使用了多層介面
ListableBeanFactory 介面表示這些 Bean 是可列表的. HierarchicalBeanFactory 表示的是這些 Bean 是有繼承關係的,也就是每個Bean 有可能有父 Bean。
AutowireCapableBeanFactory 介面定義 Bean 的自動裝配規則。
預設實現類是 DefaultListableBeanFactory,他實現了所有的介面.
Bean的建立是典型的工廠模式,
因此我們借代碼來了解一下三種工廠模式
首先是簡單工廠模式,
比如說我喜歡女孩子,那抽象一個女孩子的基類或者介面,
package com.DesignModel.SimpleFactory; /*新建一個女生的抽象類*/ public abstract class Girl { /*描述女孩的樣子*/ public abstract void desc(); } class wzGirl extends Girl{ @Override public void desc() { System.out.println("我是文靜的女孩子,乖巧懂事惹人愛"); } } class xgGirl extends Girl { @Override public void desc() { System.out.println("我是性感的女孩子,膚白貌美大長腿"); } } class kaGirl extends Girl{ @Override public void desc() { System.out.println("我是可愛的女孩子,俏皮開朗更動人"); } } /*然後我們就可以開一個女子學校了,讓大家可以選擇自己喜歡的型別*/ class girlSchool{ public static final int TYPE_WZ= 1;//文靜的女孩子 public static final int TYPE_XG = 2;//性感的女孩子 public static final int TYPE_KA = 3;//可愛的女孩子 public static Girl createGirls(int type) { switch (type) { case TYPE_WZ: return new wzGirl(); case TYPE_XG: return new xgGirl(); case TYPE_KA: default: return new kaGirl(); } } public static void main(String[] args) { /*現在我要選擇一個文靜的女孩子*/ Girl girl=girlSchool.createGirls(girlSchool.TYPE_WZ); girl.desc(); } }
看girlschool就給了我們一個文靜的女孩子,是不是很直接,要什麼給什麼.
簡單工廠類的特點就是
1 它是一個具體的類,非介面 抽象類。有一個重要的create()方法,利用if或者 switch建立產品並返回。
2 create()方法通常是靜態的,所以也稱之為靜態工廠。
缺點
1 擴充套件性差(我想增加一型別的女孩子,除了新增一個女孩類,還需要修改工廠類方法)
2 不同的產品需要不同額外引數的時候 不支援。
接著看第二個,叫做工廠方法模式
這個模式提供一個用於建立物件的介面(工廠介面),讓其實現類(工廠實現類)決定例項化哪一個類(產品類),並且由該實現類建立對應類的例項。
作用是可以一定程度增加擴充套件性,若增加一個產品實現,只需要實現產品介面,修改工廠建立產品的方法
可以一定程度增加程式碼的封裝性、可讀性
在hibernate裡通過sessionFactory建立session、通過代理方式生成ws客戶端時,通過工廠構建報文中格式化資料的物件都用到了這個工廠方法模式.
即應用在使用者知道要建立這個類但不關心物件是如何建立的情況下,
用這些類舉個例子
先是有一個工廠類介面
然後是抽象類寫方法,這個MyAbstractMessage,就是組裝訊息
然後是使用者進行操作了類
第四個是午餐產品傳送訊息的方法介面
都五個是午餐工廠,製作午餐的
剩下三個是產品類
作為工廠方法模式,要點就是要提供一個產品類的介面(MyAbstractMessage)。產品類均要實現這個介面(也可以是abstract類,即抽象產品)。
提供一個工廠類的介面。工廠類均要實現這個介面(圖中LunchFactory)。
由工廠實現類(MylunchFactory)建立產品類的例項。工廠實現類應有一個方法,用來例項化產品類。
使用者操作類的程式碼
package com.DesignModel.SimpleFactory.FactoryMethod;
public class MyFactoryMethodMain {
public static void main(String[] args) {
LunchFactory lunchFactory = new MyLunchFactory();
MyLunch myLunch;
// 對於這個消費者來說,不用知道如何生產message這個產品,耦合度降低
try {
// 先來一個雞腿
myLunch = lunchFactory.createMessage("Drumstick");
myLunch.sendMesage();
// 來一個蛋糕
myLunch = lunchFactory.createMessage("Cake");
myLunch.sendMesage();
// 再來一杯茶飲
myLunch = lunchFactory.createMessage("Tea");
myLunch.sendMesage();
} catch (Exception e) {
e.printStackTrace();
}
}
}
午餐工廠實現類的程式碼
package com.DesignModel.SimpleFactory.FactoryMethod;
import java.util.HashMap;
import java.util.Map;
public class MyLunchFactory implements LunchFactory{
@Override
public MyLunch createMessage(String messageType) {
MyLunch lunch;
Map<String, Object> messageParam = new HashMap<String, Object>();
// 根據某些條件去選擇究竟建立哪一個具體的實現物件,條件可以傳入的,也可以從其它途徑獲取。
// 雞腿
if ("Drumstick".equals(messageType)) {
lunch = new MyMessageDrumstick();
messageParam.put("Drumstick", "500");
} else
// 茶飲
if ("tea".equals(messageType)) {
lunch= new MyMessageTea();
messageParam.put("RedTea", "100");
} else
// 蛋糕
if ("cake".equals(messageType)) {
lunch = new MyMessageCake();
messageParam.put("cake", "50");
} else
// 預設生產蛋糕這個產品
{
lunch= new MyMessageDrumstick();
messageParam.put("cake", "50");
}
lunch.setMessageParam(messageParam);
return lunch;
}
}
接下來是最重要的抽象工廠模式,也是工廠模式的靈魂
借用網上大佬的總結:
抽象工廠模式是工廠方法模式的升級版本,他用來建立一組相關或者相互依賴的物件。他與工廠方法模式的區別就在於,工廠方法模式針對的是一個產品等級結構;而抽象工廠模式則是針對的多個產品等級結構。在程式設計中,通常一個產品結構,表現為一個介面或者抽象類,也就是說,工廠方法模式提供的所有產品都是衍生自同一個介面或抽象類,而抽象工廠模式所提供的產品則是衍生自不同的介面或抽象類。
在抽象工廠模式中,有一個產品族的概念:所謂的產品族,是指位於不同產品等級結構中功能相關聯的產品組成的家族。抽象工廠模式所提供的一系列產品就組成一個產品族;而工廠方法提供的一系列產品稱為一個等級結構。
簡單來說,三個奶球的榴蓮冰激凌,和三個奶球的香蕉冰激凌是一個產品族。
但是三個奶球的榴蓮冰激凌和兩個奶球的榴蓮冰激凌,三個奶球的香蕉冰激凌,兩個個奶球的香蕉冰激凌就屬於兩個不同的重量級的。所以得用抽象工廠。
總的來說
使用工廠模式的目的一般就是為了解耦,當需要建立的物件是一系列相互關聯或相互依賴的產品族時,便可以使用抽象工廠模式。說的更明白一點,就是一個繼承體系中,如果存在著多個等級結構(即存在著多個抽象類),並且分屬各個等級結構中的實現類之間存在著一定的關聯或者約束,就可以使用抽象工廠模式。假如各個等級結構中的實現類之間不存在關聯或約束,則使用多個獨立的工廠來對產品進行建立,則更合適一點。
程式碼舉例
interface IProduct1 {
public void show();
}
interface IProduct2 {
public void show();
}
class Product1 implements IProduct1 {
public void show() {
System.out.println("這是1型產品");
}
}
class Product2 implements IProduct2 {
public void show() {
System.out.println("這是2型產品");
}
}
interface IFactory {
public IProduct1 createProduct1();
public IProduct2 createProduct2();
}
class Factory implements IFactory{
public IProduct1 createProduct1() {
return new Product1();
}
public IProduct2 createProduct2() {
return new Product2();
}
}
public class Client {
public static void main(String[] args){
IFactory factory = new Factory();
factory.createProduct1().show();
factory.createProduct2().show();
}
}
看完了這三種工廠模式,我們再回頭看Spring 裡面Beanfactory的程式碼就很容易明白了。
在BeanFactory裡只對IOC容器的基本行為作了定義,根本不關心你的bean是如何定義怎樣載入的。正如我們只關心工廠裡得到什麼的產品物件,至於工廠是怎麼生產這些物件的,這個基本的介面不關心。
public interface BeanFactory {
//對FactoryBean的轉義定義,因為如果使用bean的名字檢索FactoryBean得到的物件是工廠生成的物件,
//如果需要得到工廠本身,需要轉義
String FACTORY_BEAN_PREFIX = "&";
//根據bean的名字,獲取在IOC容器中得到bean例項
Object getBean(String name) throws BeansException;
//根據bean的名字和Class型別來得到bean例項,增加了型別安全驗證機制。
Object getBean(String name, Class requiredType) throws BeansException;
//提供對bean的檢索,看看是否在IOC容器有這個名字的bean
boolean containsBean(String name);
//根據bean名字得到bean例項,並同時判斷這個bean是不是單例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
//得到bean例項的Class型別
Class getType(String name) throws NoSuchBeanDefinitionException;
//得到bean的別名,如果根據別名檢索,那麼其原名也會被檢索出來
String[] getAliases(String name);
}
而要知道工廠是如何產生物件的,我們需要看具體的IOC容器實現。
接下來說一說IOC容器的初始化。主要是三步:
Resource定位(Bean的定義檔案定位)
將Resource定位好的資源載入到BeanDefinition
將BeanDefiniton註冊到容器中
關於容器主要有兩個系列,一個是比較low的IOC容器,叫做XmlBeanFactory
看一下原始碼
public class XmlBeanFactory extends DefaultListableBeanFactory{
private final XmlBeanDefinitionReader reader;
public XmlBeanFactory(Resource resource)throws BeansException{
this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory)
throws BeansException{
super(parentBeanFactory);
this.reader = new XmlBeanDefinitionReader(this);
this.reader.loadBeanDefinitions(resource);
}
}
我們發現是簡單的繼承了BeanFactory的子類DefaultListableBeanFactory類,它包含了基本Spirng IOC容器所具有的重要功能,在spring中實際上已經把它當成預設的IoC容器來使用。
另外一個是ApplicationContext
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory,
HierarchicalBeanFactory,
MessageSource,
ApplicationEventPublisher,
ResourcePatternResolver ,
ResourceLoader
是BeanFactory的增強版。更易與SpringAOP整合、訊息資源處理(國際化處理)、事件傳遞及各種不同應用層的context實現(如針對web應用的WebApplicationContext)。 簡而言之,BeanFactory提供了配製框架及基本功能,而ApplicationContext則增加了更多支援企業核心內容的功能。ApplicationContext完全由BeanFactory擴充套件而來,因而BeanFactory所具備的能力和行為也適用於ApplicationContext。
以ApplicationContext舉例來說明一下初始化的三個步驟
1.定位Resources
ApplicationContext的所有實現類都實現RecourceLoader介面,因此可以直接呼叫getResource(引數)獲取Resoure物件。不同的ApplicatonContext實現類使用getResource方法取得的資源型別不同。
2.第二步 將Resource定位好的資源載入到BeanDefinition
BeanDefinition相當於一個數據結構,這個資料結構的生成過程是根據定位的resource資源物件中的bean而來的,這些bean在Spirng IoC容器內部表示成了的BeanDefintion這樣的資料結構,IoC容器對bean的管理和依賴注入的實現都是通過操作BeanDefinition來進行的。
原始碼載入BeanDefinition的過程
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();//獲取所有定位到的resource資源位置(使用者定義)
if (configResources != null) {
reader.loadBeanDefinitions(configResources);//載入resources
}
String[] configLocations = getConfigLocations();//獲取所有本地配置檔案的位置(容器自身)
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);//載入resources
}
}
第三步,將BeanDefiniton註冊到容器中
最終Bean配置會被解析成BeanDefinition封裝到BeanDefinitionHolder類中,之後
beanFactory.registerBeanDefinition(beanName,bdHolder.getBeanDefinition()),
註冊到
DefaultListableBeanFactory.beanDefinitionMap中。之後客戶端如果要獲取Bean物件,Spring容器會根據註冊的BeanDefinition資訊進行例項化。
初始化結束之後,要進行的就是依賴注入
依賴注入的時間:
(1)使用者第一次通過getBean方法向IoC容索要Bean時,IoC容器觸發依賴注入。
(2).當用戶在Bean定義資源中為元素配置了lazy-init屬性,即讓容器在解析註冊Bean定義時進行預例項化,觸發依賴注入。
在Spring中,如果Bean定義的單態模式(Singleton),則容器在建立之前先從快取中查詢,以確保整個容器中只存在一個例項物件。如果Bean定義的是原型模式(Prototype),則容器每次都會建立一個新的例項物件。除此之外,Bean定義還可以擴充套件為指定其生命週期範圍。例如Request和Session。
Spring IoC容器是如何將屬性的值注入到Bean例項物件中去的:
(1).對於集合型別的屬性,將其屬性值解析為目標型別的集合後直接賦值給屬性。
(2).對於非集合型別的屬性,大量使用了JDK的反射和內省機制,通過屬性的getter方法(reader method)獲取指定屬性注入以前的值,同時呼叫屬性的setter方法(writer method)為屬性設定注入後的值。