探索Spring系列(三)揭開SpringIOC的面紗

前言

  做爲一名java開發人員,咱們接觸最先最多的框架確定就是spring了,一次次不停的使用spring框架提供的功能幫助咱們快速開發,然而其中的核心功能IOC(控制反轉)和AOP能詳細闡明的卻很少,咱們知其然,殊不知其因此然,那麼下面咱們就一塊兒探索一下spring中的IOC吧java

正篇

  • IOC概念

例:在現實生活中個人頭髮長了,影響了個人帥氣,那麼我就須要理髮,我不可能本身給本身理髮,首先我沒有理髮的工具,其次我可能把本身的頭髮剪得像狗啃的同樣,那麼影響我帥氣的外表,就沒有妹子願意跟我一塊兒約會了,這確定是我不肯意發生的。其次,我也不是王思聰,我也不願能擁有一名只爲本身服務的人員,那麼正確的作法就是去村口的理髮店找Tony老師來給我打造帥氣的髮型。咱們來分解一下剪髮的過程,我是需求提出者,頭髮變帥是要的結果,首先若是我本身爲本身理髮,不只可能達不到預期的效果,並且還費時費力,那麼我就須要一名專業的理髮師來爲我服務,達到最終的目的,這個過程咱們可能稱之爲解耦合的過程。
在Java開發中,一個功能的實現每每是由多個類和方法來共同協做完成的,在沒有IOC以前咱們建立一個類的依賴類的方式是new object(),控制權在本類手中,這些依賴關係將會使系統的複雜度提升,不利於維護和開發,這是咱們不肯意看到的,在有了IOC以後,一個類所須要的依賴類由IOC來管理,那麼咱們只要關心自己類的功能和方法便可,將控制權交給了IOC容器,這就是我理解的控制反轉。web

其中BeanFactory是IOC容器的基本實現,ApplicationContext是BeanFactory的子接口,提供更高級的特性。 而下面幾個類則是具體的實現類,能夠加載配置文件中定義的bean,管理全部加載的bean,有請求的時候分配bean。spring

  • DI(依賴注入)

建立應用對象之間的關聯關係的傳統方法(經過構造器或者查找)一般會致使結構複雜的代碼,這些代碼難以被複用也很難進行單元測試,若是狀況不嚴重的話,這些對象所作的事情只是超出了應該作的範圍,而最壞的狀況是,這些對象的彼此之間高度耦合,難以複用和測試。
在spring中,對象無需本身查找或建立與其所關聯的其餘對象。相反,容器負責把須要互相協做的對象引用賦予各個對象。例如,一個訂單管理組件須要信用卡認證組件,但它不須要本身建立信用卡認證組件。訂單管理組件只須要代表本身兩手空空,容器就會主動賦予它一個信用卡認證組件。建立應用對象之間協做關係的行爲一般稱爲裝配(wiring),這也是依賴注入(DI)的本質。(摘錄spring實戰第四版第二章)編程

  • 自動化配置Bean

1:在Java中進行顯式配置 (我的喜歡)
咱們定義接口以及其實現類springboot

package com.lly.springtest1.ioc;
/**
 * @Author lly
 * @Description 水果接口類
 * @Date 2019/1/29 10:23 AM
 * @Param  
 * @return 
 **/
public interface IFruitService {
    /**
     * @Author lly
     * @Description  顯示水果信息
     * @Date 2019/1/29 10:25 AM
     * @Param  []
     * @return
     **/
    void showFruitInfo();
}

複製代碼
package com.lly.springtest1.ioc;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * @ClassName OrangeServiceImpl
 * @Description 水果實現類-橘子
 * @Author lly
 * @Date 2019/1/29 10:17 AM
 * @Version 1.0
 **/
@Component
@Data
@Slf4j
public class OrangeServiceImpl implements IFruitService {


    @Override
    public void showFruitInfo() {
        log.info("橘子的重量是:{}kg",10);
    }
}


複製代碼

掃描組件配置類,@ComponentScan註解會默認掃描與其配置類相同的包以及這個包下面的全部的子包,查找帶有@Component註解的類,固然咱們能夠直接定@ComponentScan掃描的包bash

#單個包
@ComponentScan("包名")
@ComponentScan(basePackage="包名")
#多個包
@ComponentScan(basePackage={"包名","包名"})
複製代碼
package com.lly.springtest1.ioc;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @ClassName TestIoc
 * @Description 掃描組件配置類
 * @Author lly
 * @Date 2019/1/29 10:30 AM
 * @Version 1.0
 **/
@ComponentScan
@Configuration
public class TestIoc {

}

複製代碼

單元測試session

package com.lly.springtest1.ioc;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestIoc.class)
public class TestIocTest {
    @Autowired
    private IFruitService iFruitService;
    @Test
    public void  showInfo(){
        iFruitService.showFruitInfo();
    }


}
複製代碼

結果框架

能夠看到咱們的組件orangeEntity已經成功被spring容器管理了,成功注入到測試類中
關於 @Autowired 自動裝配的方式除了上述還有經過構造器和setter方法注入效果都是同樣的

2:隱式的bean發現機制和自動裝配 (我的喜歡)
其實這種方式咱們在 探索Spring系列(一)Spring容器和Bean的生命週期 這裏章節已經見到過了,下面貼出核心代碼ide

package com.lly.springtest1.entity;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class BeanLifeCycle {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanLifeCycle.class);
        context.close();
    }

    @Bean
    public MyBeanPostProcessor getBean() {
        return new MyBeanPostProcessor();
    }

    @Bean(initMethod = "myInit",destroyMethod = "myDestroy")
    public GirlFriendEntity getGirl() {
        GirlFriendEntity girl = new GirlFriendEntity();
        girl.setName("穎寶");
        return girl;
    }
}

複製代碼

這種配置的方式也能夠將bean歸入spring容器的管理中,下面咱們來測試一下工具

package com.lly.springtest1.entity;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = BeanLifeCycle.class)
public class BeanLifeCycleTest {


    @Autowired
    private  MyBeanPostProcessor myBeanPostProcessor;

    @Test
    public void  show(){
        Assert.assertNotNull(myBeanPostProcessor);

    }
}
複製代碼

能夠看到bean已經被容器管理了,成功注入到測試類中去了

3:xml中顯示配置(我的不喜歡這種方式,配置很繁瑣,有興趣的同窗自行了解學習)

4:總結,上述幾種裝配bean的方式,均可以實現一樣的功能,也能夠混合使用,使用哪一種方式徹底能夠按照開發者我的的習慣和喜愛來決定,可是做者目前使用的都是前兩種,消除配置式編程,更快樂,在目前比較流行的springboot開發中也是推薦前兩種

  • 高級裝配Bean

1:消除歧義性 在上文中咱們定義個一個IFruitService,orangeEntity 這個類實現了這個接口,咱們在測試類中是直接注入的,那麼我能夠想一想一下,在實際的開發中,可能存在一個接口對應多個實現類的狀況,spring在幫我自動注入的時候是怎麼幫咱們選擇的呢,下面咱們來實驗一下。
咱們再建立一個水果的實現類 香蕉

package com.lly.springtest1.ioc;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * @ClassName BananaServiceImpl
 * @Description 水果實現類-橘子
 * @Author lly
 * @Date 2019/1/29 10:17 AM
 * @Version 1.0
 **/
@Component
@Data
@Slf4j
public class BananaServiceImpl implements IFruitService {
    @Override
    public void showFruitInfo() {
        log.info("香蕉的重量是:{}kg",100);
    }
}

複製代碼

而後咱們再次啓動測試類發現

錯誤信息以下:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.lly.springtest1.ioc.IFruitService' available: expected single matching bean but found 2: bananaServiceImpl,orangeServiceImpl
複製代碼

經過查看咱們發現IFruitService這個接口有2個實現類,咱們沒有指定要使用哪一個,那麼spring是不會知道咱們將要使用哪一個的。

解決方法:
spring提供2中方式來解決這個問題
第一種:使用@Qualifier註解來指定咱們要使用的具體實現類

咱們能夠看到在咱們指定了具體實現類後測試用例順利經過。
第二種:使用@Primary註解來指定哪一個實現類做爲首選實現類,咱們在香蕉類上來加上這個註解

package com.lly.springtest1.ioc;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

/**
 * @ClassName BananaServiceImpl
 * @Description 水果實現類-橘子
 * @Author lly
 * @Date 2019/1/29 10:17 AM
 * @Version 1.0
 **/
@Component
@Data
@Slf4j
@Primary
public class BananaServiceImpl implements IFruitService {


    @Override
    public void showFruitInfo() {
        log.info("香蕉的重量是:{}kg",100);
    }
}

複製代碼

測試類改造以下

package com.lly.springtest1.ioc;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestIoc.class)
public class TestIocTest {
    @Autowired
    private IFruitService iFruitService;
    @Test
    public void  showInfo(){
        iFruitService.showFruitInfo();
    }

}
複製代碼

啓動查看結果

測試用例經過,固然這個註解在接口實現類上市互斥的,只有一個實現類上能夠加,若是超過1個實現類上使用此註解,一樣會出現咱們剛開始出現的異常信息

  • Bean的做用域

1)單例(singleton)在整個應用中,只建立一個實例;(默認狀況下,spring應用上下文中全部的bean都是單例模式)。 2)原型(prototype)每次注入或者經過spring應該上下文獲取的都是一個新的實例。 3)會話(session)在web應用中,爲每一個會話建立一個bean實例。 4)請求(request)在web應用中。爲每一個請求建立一個bean實例。

註解式指定bean做用域

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Scope(value = WebApplicationContext.SCOPE_SESSION,proxyMode = ScopedProxyMode.INTERFACES)
@Scope(value = WebApplicationContext.SCOPE_REQUEST,proxyMode = ScopedProxyMode.INTERFACES)
複製代碼

另外還可使用xml配置式,這裏不作詳解

總結

工欲善其事必先利其器,spring做爲咱們使用最頻繁的框架,熟悉其主要功能和原理是頗有必有的,否則咱們一直摸着石頭過河,下面一章咱們將要介紹spring另一個重要功能AOP(面向切面編程)

相關文章
相關標籤/搜索