Java開發學習(四)----bean的三種例項化方式

|舊市拾荒| 發表於 2022-06-12
Java

一、環境準備

準備開發環境

  • 建立一個Maven專案

  • pom.xml新增依賴

  • resources下新增spring的配置檔案applicationContext.xml

    最終專案的結構如下:

  Java開發學習(四)----bean的三種例項化方式

二、構造方法例項化

在上述的環境下,我們來研究下Spring中的第一種bean的建立方式構造方法例項化:

步驟1:準備需要被建立的類

準備一個BookDao和BookDaoImpl類

public interface BookDao {
    public void save();
}

public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
    }

}
步驟2:將類配置到Spring容器
<?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.xsd">

	<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
</beans>
步驟3:編寫執行程式
public class AppForInstanceBook {
    public static void main(String[] args) {
        ApplicationContext ctx = new 
            ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();

    }
}
步驟4:類中提供建構函式測試

在BookDaoImpl類中新增一個無參建構函式,並列印一句話,方便觀察結果。

public class BookDaoImpl implements BookDao {
    public BookDaoImpl() {
        System.out.println("book dao constructor is running ....");
    }
    public void save() {
        System.out.println("book dao save ...");
    }

}

執行程式,如果控制檯有列印建構函式中的輸出,說明Spring容器在建立物件的時候也走的是建構函式

Java開發學習(四)----bean的三種例項化方式

步驟5:將建構函式改成private測試
public class BookDaoImpl implements BookDao {
    private BookDaoImpl() {
        System.out.println("book dao constructor is running ....");
    }
    public void save() {
        System.out.println("book dao save ...");
    }

}

執行程式,能執行成功,說明內部走的依然是建構函式,能訪問到類中的私有構造方法,顯而易見Spring底層用的是反射

Java開發學習(四)----bean的三種例項化方式

步驟6:建構函式中新增一個引數測試
public class BookDaoImpl implements BookDao {
    private BookDaoImpl(int i) {
        System.out.println("book dao constructor is running ....");
    }
    public void save() {
        System.out.println("book dao save ...");
    }

}

執行程式,程式會報錯,說明Spring底層使用的是類的無參構造方法。

Java開發學習(四)----bean的三種例項化方式

三、分析Spring的錯誤資訊

接下來,我們主要研究下Spring的報錯資訊

  • 錯誤資訊從下往上依次檢視,因為上面的錯誤大都是對下面錯誤的一個包裝,最核心錯誤是在最下面

  • Caused by: java.lang.NoSuchMethodException: com.itheima.dao.impl.BookDaoImpl.<init>()

    • Caused by 翻譯為引起,即出現錯誤的原因

    • java.lang.NoSuchMethodException:丟擲的異常為沒有這樣的方法異常

    • com.itheima.dao.impl.BookDaoImpl.<init>():哪個類的哪個方法沒有被找到導致的異常,<init>()指定是類的構造方法,即該類的無參構造方法

如果最後一行錯誤獲取不到錯誤資訊,接下來檢視第二層:

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.itheima.dao.impl.BookDaoImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.itheima.dao.impl.BookDaoImpl.<init>()

  • nested:巢狀的意思,後面的異常內容和最底層的異常是一致的

  • Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.itheima.dao.impl.BookDaoImpl]: No default constructor found;

    • Caused by: 引發

    • BeanInstantiationException:翻譯為bean例項化異常

    • No default constructor found:沒有一個預設的建構函式被發現

看到這其實錯誤已經比較明顯,給大家個練習,把倒數第三層的錯誤分析下吧:

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'bookDao' defined in class path resource [applicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.itheima.dao.impl.BookDaoImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.itheima.dao.impl.BookDaoImpl.<init>()。

建立bean異常,錯誤建立bean:例項化bean失敗

因為每一個類預設都會提供一個無參建構函式,所以其實真正在使用這種方式的時候,我們什麼也不需要做。這也是我們以後比較常用的一種方式。

四、靜態工廠例項化

接下來研究Spring中的第二種bean的建立方式靜態工廠例項化:

4.1 工廠方式建立bean

在講這種方式之前,我們需要先回顧一個知識點是使用工廠來建立物件的方式:

(1)準備一個OrderDao和OrderDaoImpl類

public interface OrderDao {
    public void save();
}

public class OrderDaoImpl implements OrderDao {
    public void save() {
        System.out.println("order dao save ...");
    }
}

(2)建立一個工廠類OrderDaoFactory並提供一個==靜態方法==

//靜態工廠建立物件
public class OrderDaoFactory {
    public static OrderDao getOrderDao(){
        return new OrderDaoImpl();
    }
}

(3)編寫AppForInstanceOrder執行類,在類中通過工廠獲取物件

public class AppForInstanceOrder {
    public static void main(String[] args) {
        //通過靜態工廠建立物件
        OrderDao orderDao = OrderDaoFactory.getOrderDao();
        orderDao.save();
    }
}

(4)執行後,可以檢視到結果

Java開發學習(四)----bean的三種例項化方式

如果程式碼中物件是通過上面的這種方式來建立的,如何將其交給Spring來管理呢?

4.2 靜態工廠例項化

這就要用到Spring中的靜態工廠例項化的知識了,具體實現步驟為:

(1)在spring的配置檔案application.properties中新增以下內容:

<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>

class:工廠類的類全名

factory-mehod:具體工廠類中建立物件的方法名

對應關係如下圖:

Java開發學習(四)----bean的三種例項化方式

(2)在AppForInstanceOrder執行類,使用從IOC容器中獲取bean的方法進行執行測試

public class AppForInstanceOrder {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");

        orderDao.save();

    }
}

(3)執行後,可以檢視到結果

Java開發學習(四)----bean的三種例項化方式

看到這,可能有人會問了,你這種方式在工廠類中不也是直接new物件的,和我自己直接new沒什麼太大的區別,而且靜態工廠的方式反而更復雜,這種方式的意義是什麼?

主要的原因是:

  • 在工廠的靜態方法中,我們除了new物件還可以做其他的一些業務操作,這些操作必不可少,如:

public class OrderDaoFactory {
    public static OrderDao getOrderDao(){
        System.out.println("factory setup....");//模擬必要的業務操作
        return new OrderDaoImpl();
    }
}

之前new物件的方式就無法新增其他的業務內容,重新執行,檢視結果:

Java開發學習(四)----bean的三種例項化方式

介紹完靜態工廠例項化後,這種方式一般是用來相容早期的一些老系統,所以瞭解為主

五、例項工廠與FactoryBean

接下來繼續來研究Spring的第三種bean的建立方式例項工廠例項化:

5.1 環境準備

(1)準備一個UserDao和UserDaoImpl類

public interface UserDao {
    public void save();
}

public class UserDaoImpl implements UserDao {

    public void save() {
        System.out.println("user dao save ...");
    }
}

(2)建立一個工廠類OrderDaoFactory並提供一個普通方法,注意此處和靜態工廠的工廠類不一樣的地方是方法不是靜態方法

public class UserDaoFactory {
    public UserDao getUserDao(){
        return new UserDaoImpl();
    }
}

(3)編寫AppForInstanceUser執行類,在類中通過工廠獲取物件

public class AppForInstanceUser {
    public static void main(String[] args) {
        //建立例項工廠物件
        UserDaoFactory userDaoFactory = new UserDaoFactory();
        //通過例項工廠物件建立物件
        UserDao userDao = userDaoFactory.getUserDao();
        userDao.save();
}

(4)執行後,可以檢視到結果

Java開發學習(四)----bean的三種例項化方式

對於上面這種例項工廠的方式如何交給Spring管理呢?

5.2 例項工廠例項化

具體實現步驟為:

(1)在spring的配置檔案中新增以下內容:

<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>

例項化工廠執行的順序是:

  • 建立例項化工廠物件,對應的是第一行配置

  • 呼叫物件中的方法來建立bean,對應的是第二行配置

    • factory-bean:工廠的例項物件

    • factory-method:工廠物件中的具體建立物件的方法名,對應關係如下:

      Java開發學習(四)----bean的三種例項化方式

factory-mehod:具體工廠類中建立物件的方法名

(2)在AppForInstanceUser執行類,使用從IOC容器中獲取bean的方法進行執行測試

public class AppForInstanceUser {
    public static void main(String[] args) {
        ApplicationContext ctx = new 
            ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = (UserDao) ctx.getBean("userDao");
        userDao.save();
    }
}

(3)執行後,可以檢視到結果

Java開發學習(四)----bean的三種例項化方式

例項工廠例項化的方式就已經介紹完了,配置的過程還是比較複雜,所以Spring為了簡化這種配置方式就提供了一種叫FactoryBean的方式來簡化開發。

5.3 FactoryBean的使用

具體的使用步驟為:

(1)建立一個UserDaoFactoryBean的類,實現FactoryBean介面,重寫介面的方法

public class UserDaoFactoryBean implements FactoryBean<UserDao> {
    //代替原始例項工廠中建立物件的方法
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }
    //返回所建立類的Class物件
    public Class<?> getObjectType() {
        return UserDao.class;
    }
}

(2)在Spring的配置檔案中進行配置

(3)AppForInstanceUser執行類不用做任何修改,直接執行

Java開發學習(四)----bean的三種例項化方式

這種方式在Spring去整合其他框架的時候會被用到。

檢視原始碼會發現,FactoryBean介面其實會有三個方法,分別是:

T getObject() throws Exception;

Class<?> getObjectType();

default boolean isSingleton() {
		return true;
}

方法一:getObject(),被重寫後,在方法中進行物件的建立並返回

方法二:getObjectType(),被重寫後,主要返回的是被建立類的Class物件

方法三:沒有被重寫,因為它已經給了預設值,從方法名中可以看出其作用是設定物件是否為單例,預設true,從意思上來看,我們猜想預設應該是單例,如何來驗證呢?

思路很簡單,就是從容器中獲取該物件的多個值,列印到控制檯,檢視是否為同一個物件。

public class AppForInstanceUser {
    public static void main(String[] args) {
        ApplicationContext ctx = new 
            ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao1 = (UserDao) ctx.getBean("userDao");
        UserDao userDao2 = (UserDao) ctx.getBean("userDao");
        System.out.println(userDao1);
        System.out.println(userDao2);
    }
}

列印結果,如下:

Java開發學習(四)----bean的三種例項化方式

通過驗證,會發現預設是單例,那如果想改成單例具體如何實現?

只需要將isSingleton()方法進行重寫,修改返回為false,即可

//FactoryBean建立物件
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
    //代替原始例項工廠中建立物件的方法
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }

    public Class<?> getObjectType() {
        return UserDao.class;
    }

    public boolean isSingleton() {
        return false;
    }
}

重新執行AppForInstanceUser,檢視結果

Java開發學習(四)----bean的三種例項化方式

從結果中可以看出現在已經是非單例了,但是一般情況下我們都會採用單例,也就是採用預設即可。所以isSingleton()方法一般不需要進行重寫。

六、bean例項化小結

(1)bean是如何建立的呢

(2)Spring的IOC例項化物件的三種方式分別是:

  • 構造方法(常用)

  • 靜態工廠(瞭解)

  • 例項工廠(瞭解)

    • FactoryBean(實用)

這些方式中,重點掌握構造方法FactoryBean即可。

需要注意的一點是,構造方法在類中預設會提供,但是如果重寫了構造方法,預設的就會消失,在使用的過程中需要注意,如果需要重寫構造方法,最好把預設的構造方法也重寫下。