應聘阿里,字節跳動,美團必須掌握的Spring IOC與工廠模式

Spring IOC與工廠模式

PS:本文內容較爲硬核,須要對java的面向對象、反射、類加載器、泛型、properties、XML等基礎知識有較深理解。java

(一)簡單介紹

在講Spring IOC以前,有必要先來聊一下工廠模式(Factory Pattern)。工廠模式可將Java對象的調用者從被調用者的實現邏輯中分離出來。工廠模式是Java中最經常使用的設計模式之一。這種類型的設計模式屬於建立型模式,它提供了一種建立對象的最佳方式。在工廠模式中,咱們在建立對象時不會對客戶端暴露建立邏輯,而且是經過使用一個共同的接口來指向新建立的對象。web

這麼講可能有點抽象,簡單的說就是之後咱們不用本身new對象了,對象的實例化都交給工廠來完成,咱們須要對象的時候直接問工廠拿一個就行,一會咱們會來看一個例子。在這裏有一點要說明,spring IOC與工廠模式並非徹底相同的,最大的不一樣在於普通的工廠模式內部仍是使用new來建立對象,可是spring IOC是用反射來建立對象,這麼作有什麼好處呢?spring

(二)「new」 VS 「反射」

下面咱們來看一個工廠模式例子。爲了演示方便,我將全部的類和接口都寫在一塊兒:設計模式

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實現) 存放生產完的對象

(三)真正的Spring IOC

看完了我寫的「山寨」IOC,咱們再來畫個圖看一看真正的spring IOC的結構執行過程,其實與我寫的基本是一致的。


webp

咱們來看看執行過程:

1.讀取Bean配置信息放入Bean定義註冊表
2.根據Bean註冊表實例化Bean
3.將實例化以後的bean實例放入Bean緩存池(HashMap實現)
4.應用程序經過類名從Bean緩存池中取出Bean實例
看了這麼多,咱們仍是來看看具體如何使用spring IOC容器來建立對象吧。首先,就像上面個人那個山寨IOC同樣,咱們要先來編寫XML文件,XML比properties要複雜,不過好在咱們暫時還用不到那麼複雜的部分。首先先去官網的文檔裏面找一段模板抄下來:


webp

<?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容器取出對象便可。那麼這個容器在哪?如今固然是隻能咱們手動建立這個容器,否則他也不會憑空產生對吧。


webp

咱們來看一眼這個接口繼承圖,咱們只須要關注兩個,一個是裏面的一個頂級接口——BeanFactory,另外一個是最底下的ApplicationContext接口。BeanFactory是簡單容器,他實現了容器的基本功能,典型方法如 getBean、containsBean等。ApplicationContext是應用上下文,他在簡單容器的基礎上,增長上下文的特性。咱們開發時通常都是使用ApplicationContext接口,由於他的功能比BeanFactory更強大。固然,「應用上下文」這個名字可能有點奇怪,不過咱們只須要記得他就是那個spring IOC容器接口就行。接口有了,接下來就是要找實現類。

ApplicationContext有好多的實現類,咱們就挑一個最經常使用的講——ClassPathXmlApplicationContext。


webp

咱們先來看一眼他的名字: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的細節和一些其餘功能。最後放一張截圖,不清楚項目結構的能夠看一眼。

webp

相關文章
相關標籤/搜索