天天看點

談談SpringFramework與IoC依賴查找談談SpringFramework與IoC依賴查找

談談SpringFramework與IoC依賴查找

生活不會按照你想要的方式進行,它會給你一段時間,讓你孤獨又迷惘,等你度過低潮,那些獨處的時光必定能照亮你的路。走得最急的,都是最美的風景;傷得最深的,也總是那些最真的感情。收拾起心情,繼續向前走,就會發現:錯過花,你将收獲雨,錯過雨,你會遇到彩虹。

1. 面試題

先說下該篇文章可延伸出的面試題.

1. 談談SpringFramework / 說說你了解的SpringFramework

SpringFramework 是一個開源的、松耦合的、分層的、可配置的一站式企業級 Java 開發架構,它的核心是 IOC 與 AOP ,它可以更容易的建構出企業級 Java 應用,并且它可以根據應用開發的元件需要,整合對應的技術。

松耦合的: 為了描述IOC和AOP, 可能會延伸出IOC松耦合相關内容

可配置: 給後面的SpringBoot(約定大于配置)做鋪墊

IOC 與 AOP: Inverse of Control 控制反轉、Aspect Oriented Programming 面向切面程式設計

2. 為何使用SpringFramework

可通過如下幾點進行描述:

  1. IOC 實作了元件之間的解耦
  2. AOP 切面程式設計将應用業務做統一或特定的功能增強, 可實作應用業務與增強邏輯的解耦
  3. 容器管理應用中使用的Bean、托管Bean的生命周期、事件與監聽的驅動機制
  4. Web、事務控制、測試、與其他技術的整合

3. SpringFramework包含哪些子產品?

  • beans、core、context、expression 【核心包】
  • aop 【切面程式設計】
  • jdbc 【整合 jdbc 】
  • orm 【整合 ORM 架構】
  • tx 【事務控制】
  • web 【 Web 層技術】
  • test 【整合測試】

4. 依賴查找與依賴注入的對比

作用目标 實作方式
依賴查找(DL) 通常是類成員 使用上下文(容器)主動擷取
依賴注入(DI) 可以是方法體内也可以是方法體外 借助上下文被動的接收

5. BeanFactory與ApplicationContext的對比

BeanFactory 接口提供了一個抽象的配置和對象的管理機制,

ApplicationContext 是 BeanFactory 的子接口,它簡化了與 AOP 的整合、消息機制、事件機制,以及對 Web 環境的擴充( WebApplicationContext 等)

ApplicationContext

主要擴充了以下功能:

  • AOP 的支援(

    AnnotationAwareAspectJAutoProxyCreator

    作用于 Bean 的初始化之後 )
  • 配置元資訊(

    BeanDefinition

    Environment

    、注解等 )
  • 資源管理(

    Resource

    抽象 )
  • 事件驅動機制(

    ApplicationEvent

    ApplicationListener

  • 消息與國際化(

    LocaleResolver

  • Environment

    抽象( SpringFramework 3.1 以後)

2. SpringFramework發展史

在Spring技術之前,J2EE興起,當時的J2EE學習成本極高,開發速度慢,開發出來的程式性能消耗也高,已經跟不上當時應用程式的需要。

在2002 年,Rod Johnson寫了一本書名為《Expert One-on-One J2EE design and development》 ,書中對當時現有的 J2EE 應用的架構和EJB架構存在的臃腫、低效等問題提出了質疑,并且積極尋找和探索解決方案。

基于普通Java類和依賴注入的思想提出了更為簡單的解決方案,這便是Spring架構核心思想的萌芽

過了 2 年,2004 年 SpringFramework 1.0.0 橫空出世,随後 Rod Johnson 又寫了一本書**《Expert one-on-one J2EE Development without EJB》**,當時在 J2EE 開發界引起了巨大轟動,這本書中直接告訴開發者完全可以不使用 EJB 開發 J2EE 應用,而是可以換用一種更輕量級、更簡單的架構來代替,那就是 SpringFramework 。

那時在開發界是種種的質疑,大概是這樣的,納尼? 質疑IBM諸多大佬的設計精華,這個是什麼人?為何如此嚣張? 而後 還是被一些開發者嘗試使用了,使用後發現确實要比EJB好用,不那麼臃腫,性能也有所改善,提供的一些特性也優于EJB,于是就慢慢轉投SpringFramework

下面展示下SpringFramework重要版本的更新時間及主要特性

SpringFramework版本 對應jdk版本 重要特性
SpringFramework 1.x jdk 1.3 基于 xml 的配置
SpringFramework 2.x jdk 1.4 改良 xml 檔案、初步支援注解式配置
SpringFramework 3.x Java 5 注解式配置、JavaConfig 程式設計式配置、Environment 抽象
SpringFramework 4.x Java 6 SpringBoot 1.x、核心容器增強、條件裝配、WebMvc 基于 Servlet3.0
SpringFramework 5.x Java 8 SpringBoot 2.x、響應式程式設計、SpringWebFlux、支援 Kotlin

3. IOC依賴查找

基礎架構搭建

  1. 建立Maven子產品,這裡以

    ioc-learning

    為例
  2. 引入依賴
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>
           
  1. 建立配置檔案

    ioc-learning-dl.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
           https://www.springframework.org/schema/beans/spring-beans.xsd">
   
   </beans>
           
  1. 聲明普通類

    Person.java

public class Person {
}
           
  1. ioc-learning-dl.xml

    配置檔案加入

    Persion

    的聲明
<bean id="person" class="com.huodd.bean.Person"></bean>
           
  1. 建立啟動類
public class DlApplication {
    public static void main(String[] args) {
        // 讀取配置檔案  使用接口 BeanFactory 接收 
        BeanFactory factory = new ClassPathXmlApplicationContext("dl/ioc-learning-dl.xml");
        // 通過配置檔案中聲明的 id 進行對象的擷取
        Person person = (Person) factory.getBean("person");
        System.out.println(person);
    }
}
           
  1. 運作列印
[email protected]
           

成功列印出

Person

的全限定類名 + 記憶體位址,證明編寫成功。

3.1 byName 名稱查找

上述基礎架構中的步驟6

核心代碼

Person person = (Person) factory.getBean("person");
           

3.2 byType 類型查找

1. 普通類

  1. 修改配置檔案

    ioc-learning-dl.xml

    person

    的聲明中

    id

    屬性去掉
<bean class="com.huodd.bean.Person"></bean>
           
  1. 修改啟動類
public static void main(String[] args) {
        BeanFactory factory = new ClassPathXmlApplicationContext("dl/ioc-learning-dl.xml");
//        Person person = (Person) factory.getBean("person");
        Person person = factory.getBean(Person.class);
        System.out.println(person);
    }
           

getBean

方法參數中直接傳入

Class

類型 傳回值也無需再進行強轉

  1. 運作

    main

    方法 列印如下
[email protected]
           

2. 接口

  1. 建立接口

    demoDao

    以及 實作類

    DemoDaoImpl

public interface DemoDao {
    List<String> findAll();
}

public class DemoDaoImpl implements DemoDao{
    @Override
    public List<String> findAll() {
        return Arrays.asList("user1", "user2", "user3");
    }
}

           
  1. 修改配置檔案

    ioc-learning-dl.xml

    加入

    DemoDaoImpl

    的聲明
<bean class="com.huodd.dao.DemoDaoImpl"></bean>
           
  1. 修改啟動類
public static void main(String[] args) {
        BeanFactory factory = new ClassPathXmlApplicationContext("dl/ioc-learning-dl.xml");
        DemoDao demoDao = factory.getBean(DemoDaoImpl.class);
        System.out.println(demoDao);
        System.out.println(demoDao.findAll());
    }
           
  1. 運作

    main

    方法 列印結果如下
[email protected]
[user1, user2, user3]
           

由此可見

DemoDaoImpl

注入成功 并且

BeanFactory

可以根據接口類型找到對應的實作類

3.3 進階查找

ofType 根據類型查找多個

如果一個接口有多個實作類,如何一次性的把所有的實作類都取出來呢? 前面用到的

getBean

方法顯然無法滿足 需使用到

ofType

方法

  1. 繼上面的代碼 建立2個

    DemoDao

    的實作類 如下
public class DemoMysqlDaoImpl implements DemoDao {
    @Override
    public List<String> findAll() {
        return Arrays.asList("mysql_user1", "mysql_user2", "mysql_user3");
    }
}
public class DemoOracleDaoImpl implements DemoDao {
    @Override
    public List<String> findAll() {
        return Arrays.asList("oracle_user1", "oracle_user2", "oracle_user3");
    }
}
           
  1. 修改配置檔案

    ioc-learning-dl.xml

    加入建立的兩個實作類的聲明
<bean class="com.huodd.dao.impl.DemoMysqlDaoImpl"></bean>
 <bean class="com.huodd.dao.impl.DemoOracleDaoImpl"></bean>
           
  1. 修改啟動類
public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("dl/ioc-learning-dl.xml");
        Map<String, DemoDao> beans = ctx.getBeansOfType(DemoDao.class);
        beans.forEach((beanName, bean) -> {
            System.out.println(beanName + " : " + bean.toString());
        });

    }
           

運作

main

方法 列印結果如下

com.huodd.dao.impl.DemoMysqlDaoImpl#0 : [mysql_user1, mysql_user2, mysql_user3]
com.huodd.dao.impl.DemoOracleDaoImpl#0 : [oracle_user1, oracle_user2, oracle_user3]
           

細心的小夥伴可能會發現 為何這裡讀取配置檔案的傳回值使用的是

ApplicationContext

而不使用

BeanFactory

ApplicationContext

也是一個接口,通過IDEA的

diagram

檢視類的繼承鍊,可以看到該接口繼承了

BeanFactory

官方文章中有這樣的解釋:

org.springframework.beans

org.springframework.context

包是 SpringFramework 的 IOC 容器的基礎。

BeanFactory

接口提供了一種進階配置機制,能夠管理任何類型的對象。

ApplicationContext

BeanFactory

的子接口。它增加了:

  • 與 SpringFramework 的 AOP 功能輕松內建
  • 消息資源處理(用于國際化)
  • 事件釋出
  • 應用層特定的上下文,例如 Web 應用程式中使用的

    WebApplicationContext

如此說來

ApplicationContext

包含了 **

BeanFactory

的所有功能,**并且還擴充了更多的特性

其實對于我們目前的最主要原因是

BeanFactory

中木有

getBeansOfType()

這個方法~~~

withAnnotation 根據注解查找

IOC 容器還可以根據類上标注的注解來查找對應的 Bean

  1. 建立一個注解類
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface animal {
}
           
  1. 建立幾個bean對象
@Animal
public class Dog {
}

@Animal
public class Cat {
}

public class Xiaoming {
}
           

其中隻有

Xiaoming

這個類沒有添加

@Animal

注解

  1. 修改XML配置檔案,添加如下三個聲明
<bean id="dog" class="com.huodd.bean.Dog"></bean>
<bean id="cat" class="com.huodd.bean.Cat"></bean>
<bean id="xiaoming" class="com.huodd.bean.Xiaoming"></bean>
           
  1. 修改啟動類
public class DlApplication {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("dl/ioc-learning-dl.xml");
        Map<String, Object> beans = ctx.getBeansWithAnnotation(Animal.class);
        beans.forEach((beanName, bean) -> {
            System.out.println(beanName + " : " + bean);
        });
    }
}
           
  1. 運作

    main

    方法 列印結果如下
dog : [email protected]
cat : [email protected]
           

延遲查找

對于一些特殊場景,需要依賴容器中某些特定的bean 但是當他們不存在時如何使用預設/或者預設政策來處理邏輯呢?

這裡我們先把xml配置檔案中的

Dog

的聲明暫時删掉

這樣我們在擷取dog的時候

ctx.getBean(Dog.class)

就會報錯

NoSuchBeanDefinitionException

  1. 現有方案啟用預設政策
Dog dog;
try {
    dog = ctx.getBean(Dog.class);
} catch (NoSuchBeanDefinitionException e) {
    // 找不到Dog時手動建立
    dog = new Dog();
}
System.out.println(dog);
           

這裡我們把業務代碼寫在了catch代碼塊中,不夠優雅,性能也有待改善,而且如果後期每個bean都這樣處理,代碼量巨大

  1. 改造下 擷取之前檢查
Dog dog = ctx.containsBean("dog") ? (Dog) ctx.getBean("dog") : new Dog();
           

這裡使用到了

ApplicationContext

中的方法

containsBean

用于檢查容器中是否有指定的bean

該方法看似已經沒有問題了,但是要考慮到該方法傳遞的參數隻能傳遞bean的id 不能按照bean的類型去查找 如果bean的名字是其他的呢,工作量還是巨大的

  1. 使用延遲查找

該機制的大概思路為 當我們想要擷取一個Bean的時候,先傳回給我們一個包裝類,等到我們真正去使用的時候再去“拆包”檢查裡面到底有沒有該Bean對象

使用方法如下

ObjectProvider<Dog> dogProvider = ctx.getBeanProvider(Dog.class);
           

上面代碼可以看到 就是按照前面的思路進行處理的,傳回了一個“包裝”給我們,當我們使用的時候直接調用

getObject

方法

但如果 容器中沒有該Bean 還是會報

NoSuchBeanDefinitionException

,下面會介紹下

ObjectProvider

提供的其他方法

  • getIfAvailable()

    該方法可以在找不到Bean的時候傳回null 而不抛出異常

    可以使用如下方法實作

    Dog dog = dogProvider.getIfAvailable(Dog::new);
               
  • ifAvailable()

    該方法是在取到Bean後馬上或者間歇的使用

    代碼如下

    dogProvider.ifAvailable(dog -> System.out.println(dog)); // 或者使用方法引用
               

以上就是關于SpringFramework以及IoC的依賴查找相關内容,小夥伴可以再去頂部檢視下面試題,是否都可以了解了并且掌握了呢.

getObject`方法

但如果 容器中沒有該Bean 還是會報

NoSuchBeanDefinitionException

,下面會介紹下

ObjectProvider

提供的其他方法

  • getIfAvailable()

    該方法可以在找不到Bean的時候傳回null 而不抛出異常

    可以使用如下方法實作

    Dog dog = dogProvider.getIfAvailable(Dog::new);
               
  • ifAvailable()

    該方法是在取到Bean後馬上或者間歇的使用

    代碼如下

    dogProvider.ifAvailable(dog -> System.out.println(dog)); // 或者使用方法引用
               

以上就是關于SpringFramework以及IoC的依賴查找相關内容,小夥伴可以再去頂部檢視下面試題,是否都可以了解了并且掌握了呢.