Spring Boot筆記十:IOC控制反轉


IOC控制反轉和DI依賴注入

以前一直聽說控制反轉和依賴注入,一直不知道是什么,不理解。現在懂了

舉個例子,我一個人想要穿衣服,用代碼實現怎么做呢?

衣服類 衣服=new 衣服類();
人類 人=new 人類();
人.set衣服=衣服();

大概就是這樣子,衣服有一個類,人有一個類,人這個類里面有一個穿衣服的方法set衣服,所以,為了實現一個人穿衣服的功能,我寫了3個東西

  1. 創建衣服類
  2. 創建人類
  3. 人類依賴的衣服對象,我手動的設置賦值了(賦值就是注入)

這個不難理解吧,那么控制反轉IOC到底是干嘛的呢?就是不需要我們手動的去創建類了,也不需要我們手動的給類的依賴進行賦值了也就是注入,用了IOC,代碼就會變成這樣

人類 人=IOC容器.get人類(); 

沒了...完事!就是這么簡單,你可能會問,衣服類不需要創建?不需要,IOC容器知道人類依賴衣服類,自己創建完成了,你可能會問,人類里面還得set衣服呢,不需要去賦值注入?不需要,IOC容器自動幫你注入了

img

IOC容器這么好用?我要用!!

這就是控制反轉IOC,其本質就是幫你創建一些對象,幫你去注入依賴的對象。

那么依賴注入DI是啥呢?其實就是IOC,這倆是一樣的。只不過IOC這個名字聽起來側重於控制反轉,就是容器幫你創建對象,DI這個名字聽起來側重於依賴注入,容器幫你注入依賴的對象。

IOC和DI就仿佛於許嵩和Vae一樣,不同的名字,一樣的人。

IOC實現Hello World

上面搞懂了概念,接下來用代碼去實現一下IOC

使用maven導入包

有3個jar包是需要的,如下

  <!--https://mvnrepository.com/artifact/org.springframework/spring-beans-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.1.4.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.1.4.RELEASE</version>
        </dependency>
         <!--https://mvnrepository.com/artifact/commons-logging/commons-logging-->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>

新建IOC配置文件

在resources目錄下新建一個IOC的配置文件applicationContext.xml,等下需要通過這個配置文件去創建IOC容器

<?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="helloWorld" class="com.vae.springboot.bean.HelloWorld">
        <property name="userName" value="許嵩"></property>
    </bean>



</beans>
注意:property標簽里面給屬性賦值的時候,name一定是字段名,這個字段一定要有set屬性,否則無效

新建一個類HelloWorld

內容就寫一個輸出方法

package com.vae.springboot.bean;

public class HelloWorld {

    private String userName;
    public void  setUserName(String userName){
        this.userName=userName;
    }

    public void sayHello(){
        System.out.println("Hello World "+userName);
    }

}

新建HelloWorld的測試類

這個測試類里面,我們會使用兩種方式來輸出sayHello方法,一種是普通的方式,一種是IOC方式

package com.vae.springboot.bean;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class HelloWorldTest {

    //正常的,我們怎么去調用sayHello方法
    @Test
    public void sayHelloTest(){
        HelloWorld helloWorld=new HelloWorld();
        helloWorld.setUserName("許嵩");
        helloWorld.sayHello();
        //這就是最普遍的做法吧,自己創建對象,自己注入依賴值,啥都是自己干
    }

    //來一個IOC的例子
    @Test
    public void sayHelloIOC(){
        HelloWorld helloWorld=null;
        //--------------------IOC開始了-------------------
        //1.從classpath路徑去尋找配置文件,加載我們的配置
        Resource resources= new ClassPathResource("applicationContext.xml");
        //2.加載配置文件之后,創建IOC容器
        BeanFactory factory=new XmlBeanFactory(resources);
        //3.從Spring IOC容器中獲取指定名稱的對象
        helloWorld= (HelloWorld) factory.getBean("helloWorld");
        //--------------------IOC結束了---------------------
        helloWorld.sayHello();
    }

}

直接執行sayHelloTest方法,sayHelloIOC方法,結果是一樣的,一個是普通的自己去創建對象,去注入依賴,一種是IOC容器幫你創建和注入。可能有人會說,為什么IOC的代碼還多......看着IOC也沒有簡單啊,代碼還多......別慌,往下看,到時候絕對驚喜。(現在就想知道的看下面的@Autowired)

先講解一下上面的代碼

BeanFactory:表示Spring IOC容器,專門生產bean對象的工廠,負責配置,創建和管理bean

bean:被Spring IOC容器管理的對象都是bean,可以理解為Spring下皆為bean

Spring IOC容器怎么知道哪些是管理的對象?

  1. xml配置文件
  2. 注解
  3. Java代碼

其中xml配置文件的方式我們已經講了,這也是最簡單的方式了,后面的兩種方式暫留到時候補在這里

暫留地...

IOC容器是怎么去管理對象的呢?到底是怎么幫助我們去創建對象的,到底是怎么幫助我們去自動注入的?我們在測試類里面去模擬一下IOC的工作原理,使用到的技術有兩個,反射和內省

@Test
    public void testIOC() throws Exception {
        String className="com.vae.springboot.study.bean.HelloWorld";
        HelloWorld helloWorld=null;
        //--------------------模擬IOC開始了-------------------
        //1.使用反射創建對象
        Class clzz=Class.forName(className);
        Constructor con=clzz.getConstructor();
        con.setAccessible(true);//設置構造器可訪問性為true
        Object obj=con.newInstance();

        //2.使用內省機制獲取所有的屬性名稱
        BeanInfo beanInfo=Introspector.getBeanInfo(clzz,Object.class);
        PropertyDescriptor[] pds=beanInfo.getPropertyDescriptors();

        for (PropertyDescriptor pd : pds) {
            String propertyName=pd.getName();
            if ("userName".equals(propertyName)) {
                pd.getWriteMethod().invoke(obj,"蜀雲泉");
            }
        }
        helloWorld=(HelloWorld)obj;
        //--------------------模擬IOC結束了---------------------
        helloWorld.sayHello();
    }

執行測試方法,輸出結果正是

Hello World 蜀雲泉

這不就是和IOC的效果一樣嘛,所以我們可以說,IOC的本質就是反射+內省機制

IOC容器getBean方法的三種簽名

我們上面的IOC容器有一行是加載Bean的,以便於獲取指定的對象,如下:

helloWorld= (HelloWorld) factory.getBean("helloWorld");

這里的helloWorld正是我們xml文件里面的bean的id,這個就叫做簽名,有三種方式:

  1. 根據Bean對象在容器中的id來獲取

    這個其實就是我們使用的方式,我們可以嘗試着把xml文件中bean的id復制一個出來,當有兩個id為helloWorld時,就會報錯。所以我們Bean的id一定要是唯一的

    <?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="helloWorld" class="com.vae.springboot.bean.HelloWorld">
            <property name="userName" value="許嵩"></property>
        </bean>
        <bean id="helloWorld" class="com.vae.springboot.bean.HelloWorld">
            <property name="userName" value="許嵩"></property>
        </bean>
    
    </beans>
    
  2. 根據類型獲取Bean

    這種方式的代碼是這樣的

     helloWorld= factory.getBean(HelloWord.class);
    

    根據類的類型獲取Bean,連強轉都不需要了,當然,這種方式也是有問題的,xml里面再賦值一下,兩個bean的id不一樣,class一樣的時候,還是會報錯,報類找到的Bean不是唯一的

    <?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="helloWorld" class="com.vae.springboot.bean.HelloWorld">
            <property name="userName" value="許嵩"></property>
        </bean>
        <bean id="helloWorld2" class="com.vae.springboot.bean.HelloWorld">
            <property name="userName" value="許嵩"></property>
        </bean>
    
    
    </beans>
    
  3. 根據id+類型來獲取Bean

    這個就牛逼了,上面的1和2的綜合體啊,媽媽再也不怕我獲取Bean對象的時候報錯了

    helloWorld= factory.getBean("helloWorld",HelloWord.class);
    

getBean的三種簽名,我們以后就使用第三種。

xml配置文件的import導入

我們的applicationContext.xml里面寫的是Bean,一個項目里面那么多需要控制反轉的,難道我們寫上千個Bean?這樣都寫在一個xml文件里,太大,也太亂,所以我們可以分開寫每個包里面寫個自己的xml,然后applicationContext.xml直接import導入就可以了

    <!--導入其他的配置文件-->
    <import resource="classpath:com.vae.springboot.bean.HelloWorld.xml"></import>

大概就是這樣,直接在拆分的xml文件上單擊右鍵,Copy Reference就可以了,import默認是從classpath下面找的,所以我們加上一個classpath:,加不加都一樣,默認就是這個,所以還是加上吧

@Autowired

牛逼的地方來了啊,我們上面寫過了好幾行的IOC代碼,抱怨了IOC這么牛逼還要寫好幾行代碼,現在@Autowired,來了

@Autowired:表示自動按照類型去Spring容器中找到對應的Bean對象,然后自動注入

再來貼一下我們上面寫的IOC代碼吧

 @Test
    public void sayHelloIOC(){
        HelloWorld helloWorld=null;
        //--------------------IOC開始了-------------------
        //1.從classpath路徑去尋找配置文件,加載我們的配置
        Resource resources= new ClassPathResource("applicationContext.xml");
        //2.加載配置文件之后,創建IOC容器
        BeanFactory factory=new XmlBeanFactory(resources);
        //3.從Spring IOC容器中獲取指定名稱的對象
        helloWorld= (HelloWorld) factory.getBean("helloWorld");
        //--------------------IOC結束了---------------------
        helloWorld.sayHello();
    }

嘖嘖,慘不忍睹,這樣寫,好麻煩....來看看注解的方式

@ContextConfiguration("classpath:applicationContext.xml")

先在測試類頭上加個這個,意思是找到我們的Spring容器,classpath就是resource目錄

然后寫測試方法

    @Autowired
    private HelloWorld helloWorld;
    @Test
    public void sayHelloIOCNB(){
        helloWorld.sayHello();
    }

沒了.....就這么簡單...@Autowired牛逼(破音)

IOC容器

雖然我們講過了牛逼的@Autowired,但是弱雞的還是需要講一下,ICO容器有倆

  1. BeanFactory:懶,懶得很
  2. ApplicationContext:用這個

我們在上面已經用過BeanFactory了,ApplicationContext這個其實是BeanFactory的一個子接口,我們再看看這倆的方式有啥不同

    @Test
    public void sayHelloIOC(){
        HelloWorld helloWorld=null;
        //--------------------IOC開始了-------------------
        //1.從classpath路徑去尋找配置文件,加載我們的配置
        Resource resources= new ClassPathResource("applicationContext.xml");
        //2.加載配置文件之后,創建IOC容器
        BeanFactory factory=new XmlBeanFactory(resources);
        //3.從Spring IOC容器中獲取指定名稱的對象
        System.out.println("上面的代碼沒有創建Bean,下面的代碼獲取Bean的時候才會創建Bean");
        helloWorld= (HelloWorld) factory.getBean("helloWorld");
        //--------------------IOC結束了---------------------
        helloWorld.sayHello();
    }

    @Test
    public void sayHelloIOCctx(){
        HelloWorld helloWorld=null;
        //--------------------IOC開始了-------------------
        ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
        System.out.println("上面的代碼已經創建Bean對象了,下面的獲取Bean,獲取已有的Bean");
        helloWorld= ctx.getBean("helloWorld",HelloWorld.class);
        //--------------------IOC結束了---------------------
        helloWorld.sayHello();
    }

我在打印里面寫了,這倆其實是一樣的結果,但是BeanFactory懶,你不獲取Bean,我就不創建Bean,ApplicationContext比較好,所以IOC容器,我選擇ApplicationContext

Bean的作用域

  1. singltton:單例,在IOC容器中的Bean實例,都是唯一的
  2. prototype:多例,在IOC容器中的Bean,每次都返回一個新的對象
  3. ......

作用域有好幾個,我這里只介紹兩個,一個單例,一個多例的,xml配置如下

    <bean id="helloWorld1" class="com.vae.springboot.bean.HelloWorld" scope="singleton"></bean>
    <bean id="helloWorld2" class="com.vae.springboot.bean.HelloWorld" scope="prototype"></bean>

Bean的初始化和銷毀

我新建一個類,就叫MyDataSource

package com.vae.springboot.bean;

public class MyDataSource {

    public MyDataSource(){

    }

    public void open(){
        System.out.println("初始化");
    }

    public void dowork(){
        System.out.println("工作");
    }

    public void close(){
        System.out.println("銷毀");
    }
}

然后新建一個測試類

package com.vae.springboot.bean;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration("classpath:applicationContext.xml")
public class MyDataSourceTest {

    //最普通的方式
    @Test
    public void test1(){
        MyDataSource myDataSource=new MyDataSource();
        myDataSource.open();
        myDataSource.dowork();
        myDataSource.close();
    }

    @Autowired
    private MyDataSource myDataSource;
    //IOC容器的方式
    @Test
    public void test2(){
        myDataSource.dowork();
    }


}

普通方式和IOC容器的方式,都是有初始化和銷毀的,原因是我們的xml寫了這一行

  <bean id="myDataSource" class="com.vae.springboot.bean.MyDataSource"  init-method="open" destroy-method="close"></bean>

Bean的初始化和銷毀,以后就用xml配置吧,就讓IOC來管理吧,省事

人衣看DI

我們開頭講了一個人和衣服的例子,這次,我們使用DI(IOC)再來看看,還是這3種方式

  1. xml配置方式
  2. 注解方式
  3. Java代碼方式

我們來一個一個的用代碼實現一下

xml配置方式(不推薦使用,但是還是看會)

新建一個類,Person

package com.vae.bean;

public class Person {
    private Clothes clothes;

    public void setClothes(Clothes clothes) {
        this.clothes = clothes;
    }

    @Override
    public String toString() {
        return "Person{" +
                "clothes=" + clothes +
                '}';
    }
}

新建一個類,Clothes

package com.vae.bean;

public class Clothes {
    public Clothes() {
        System.out.println("我是一件衣服,我很方");
    }
}

我們的applicationContext.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
           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="clothes" class="com.vae.bean.Clothes"></bean>
    <bean id="person" class="com.vae.bean.Person" autowire="byName"></bean>

</beans>

bean里的autowire有好幾個值,byName和byType都可以用,construct不能用,因為衣服類默認構造器沒了

最后,我們的測試類

package com.vae.bean;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration("classpath:applicationContext.xml")
public class PersonTest {

    @Autowired
    private Person person;

    @Test
    public void test(){
        System.out.println(person);
    }
}

執行一下,效果還是很棒的

如果Clothes類中還有一個類類型的變量,比如叫款式style,可以這樣

    <bean id="style" class="com.vae.springboot.study.bean.style"/>
    <bean id="helloWorld" class="com.vae.springboot.study.bean.HelloWord">
        <property name="userName" ref="style"></property>
    </bean>

使用ref可以注入類類型


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM