1. 程式人生 > 實用技巧 >【Spring】IoC控制反轉詳解

【Spring】IoC控制反轉詳解

IOC(概念和原理)

  1. 什麼是 IOC
    1. 控制反轉(Inversion of Control,縮寫為IoC),把物件建立和物件之間的呼叫過程,交給 Spring 進行管理
    2. 使用 IOC 目的:為了耦合度降低
    3. 做入門案例就是 IOC 實現
  2. IOC 底層原理
    1. xml 解析
    2. 工廠模式
    3. 反射
  3. 畫圖講解 IOC 底層原理

IOC(BeanFactory 介面)

  1. IOC 思想基於 IOC 容器完成,IOC 容器底層就是物件工廠
  2. Spring 提供 IOC 容器實現兩種方式:(兩個介面)
    1. BeanFactory:IOC 容器基本實現,是 Spring 內部的使用介面,不提供開發人員進行使用

      載入配置檔案時不會建立物件,在獲取物件(使用)才去建立物件

    2. ApplicationContext:BeanFactory 介面的子介面,提供更多更強大的功能,一般由開發人員進行使用

      載入配置檔案時候就會把在配置檔案物件進行建立

  3. ApplicationContext 介面有實現類

IOC 操作 Bean 管理(概念)

  1. 什麼是 Bean 管理
    1. Bean 管理指的是兩個操作
    2. Spring 建立物件
    3. Spirng 注入屬性
  2. Bean 管理操作有兩種方式
    1. 基於 xml 配置檔案方式實現
    2. 基於註解方式實現

IOC 操作 Bean 管理(基於 xml 方式)

  1. 基於 xml 方式建立物件

    <!--配置 User 物件建立-->
    <bean id="user" class="com.spring5.User"></bean>
    1. 在 spring 配置檔案中,使用 bean 標籤,標籤裡面新增對應屬性,就可以實現物件建立
    2. 在 bean 標籤有很多屬性,介紹常用的屬性
      • id 屬性:唯一標識
      • class 屬性:類全路徑(包類路徑)
    3. 建立物件時候,預設也是執行無引數構造方法完成物件建立
  2. 基於 xml 方式注入屬性
    1. DI:依賴注入,就是注入屬性
  3. 第一種注入方式:使用 set 方法進行注入
    1. 建立類,定義屬性和對應的 set 方法

      /**
      * 演示使用 set 方法進行注入屬性
      */
      public class Book {
          //建立屬性
          private String bname;
          private String bauthor;
          //建立屬性對應的 set 方法
          public void setBname(String bname) {
              this.bname = bname;
          }
          public void setBauthor(String bauthor) {
              this.bauthor = bauthor;
          } 
      }
    2. 在 spring 配置檔案配置物件建立,配置屬性注入

      <!--2 set 方法注入屬性-->
      <bean id="book" class="com.atguigu.spring5.Book">
          <!--使用 property 完成屬性注入
              name:類裡面屬性名稱
              value:向屬性注入的值
          -->
          <property name="bname" value="易筋經"></property>
          <property name="bauthor" value="達摩老祖"></property>
      </bean> 
  4. 第二種注入方式:使用有引數構造進行注入
    1. 建立類,定義屬性,建立屬性對應有引數構造方法

      /**
      + 使用有引數構造注入
      */
      public class Orders {
          //屬性
          private String oname;
          private String address;
          //有引數構造
          public Orders(String oname,String address) {
              this.oname = oname;
              this.address = address;
          } 
      }
    2. 在 spring 配置檔案中進行配置

      <!--3 有引數構造注入屬性-->
      <bean id="orders" class="com.atguigu.spring5.Orders">
          <constructor-arg name="oname" value="電腦"></constructor-arg>
          <constructor-arg name="address" value="China"></constructor-arg>
      </bean>
  5. p 名稱空間注入(瞭解)
    使用 p 名稱空間注入,可以簡化基於 xml 配置方式
    1. 第一步 新增 p 名稱空間在配置檔案中

      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:p="http://www.springframework.org/schema/p"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/schema.xsd">
      </beans>
    2. 第二步 進行屬性注入,在 bean 標籤裡面進行操作

      <!--2 set 方法注入屬性-->
      <bean id="book" class="com.atguigu.spring5.Book" p:bname="九陽神功" p:bauthor="無名氏"></bean>

IOC 操作 Bean 管理(xml 注入其他型別屬性)

  1. 字面量
    1. null 值

      <!--null 值-->
      <property name="address">
          <null/>
      </property>
    2. 屬性值包含特殊符號

      <!--屬性值包含特殊符號
       1 把<>進行轉義 &lt; &gt;
       2 把帶特殊符號內容寫到 CDATA
      -->
      <property name="address">
          <value><![CDATA[<<南京>>]]></value>
      </property>
  2. 注入屬性-外部 bean
    1. 建立兩個類 service 類和 dao 類
    2. 在 service 呼叫 dao 裡面的方法
    3. 在 spring 配置檔案中進行配置
    public class UserService {
        //建立 UserDao 型別屬性,生成 set 方法
        private UserDao userDao;
        public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
        }
        public void add() {
        System.out.println("service add...............");
        userDao.update();
        } 
    }
    <!--1 service 和 dao 物件建立-->
    <bean id="userService" class="com.atguigu.spring5.service.UserService">
        <!--注入 userDao 物件
            name 屬性:類裡面屬性名稱
            ref 屬性:建立 userDao 物件 bean 標籤 id 值
        -->
        <property name="userDao" ref="userDaoImpl"></property>
    </bean>
    <bean id="userDaoImpl" class="com.atguigu.spring5.dao.UserDaoImpl"></bean>
  3. 注入屬性-內部 bean
    1. 一對多關係:部門和員工
      一個部門有多個員工,一個員工屬於一個部門
      部門是一,員工是多
    2. 在實體類之間表示一對多關係,員工表示所屬部門,使用物件型別屬性進行表示

      //部門類
      public class Dept {
          private String dname;
          public void setDname(String dname) {
              this.dname = dname;
          } 
      }
      //員工類
      public class Emp {
          private String ename;
          private String gender;
          //員工屬於某一個部門,使用物件形式表示
          private Dept dept;
          public void setDept(Dept dept) {
              this.dept = dept;
          }
          public void setEname(String ename) {
              this.ename = ename;
          }
          public void setGender(String gender) {
              this.gender = gender;
          } 
      }
    3. 在 spring 配置檔案中進行配置

      <!--內部 bean-->
      <bean id="emp" class="com.atguigu.spring5.bean.Emp">
          <!--設定兩個普通屬性-->
          <property name="ename" value="lucy"></property>
          <property name="gender" value="女"></property>
          <!--設定物件型別屬性-->
          <property name="dept">
          <bean id="dept" class="com.atguigu.spring5.bean.Dept">
          <property name="dname" value="安保部"></property>
          </bean>
          </property>
      </bean>
  4. 注入屬性-級聯賦值
    1. 第一種寫法

      <!--級聯賦值-->
      <bean id="emp" class="com.atguigu.spring5.bean.Emp">
          <!--設定兩個普通屬性-->
          <property name="ename" value="lucy"></property>
          <property name="gender" value="女"></property>
          <!--級聯賦值-->
          <property name="dept" ref="dept"></property>
      </bean>
      <bean id="dept" class="com.atguigu.spring5.bean.Dept">
          <property name="dname" value="財務部"></property>
      </bean>
    2. 第二種寫法:需要用到 get 方法

      <!--級聯賦值--> 
      <bean id="emp" class="com.atguigu.spring5.bean.Emp">
          <!--設定兩個普通屬性-->
          <property name="ename" value="lucy"></property>
          <property name="gender" value="女"></property>
          <!--級聯賦值-->
          <property name="dept" ref="dept"></property>
          <property name="dept.dname" value="技術部"></property>
      </bean>
      <bean id="dept" class="com.atguigu.spring5.bean.Dept">
          <property name="dname" value="財務部"></property>
      </bean>

IOC 操作 Bean 管理(xml 注入集合屬性)

  1. 注入陣列型別屬性
  2. 注入 List 集合型別屬性
  3. 注入 Map 集合型別屬性
    1. 建立類,定義陣列、list、map、set 型別屬性,生成對應 set 方法

      public class Stu {
          //1 陣列型別屬性
          private String[] courses;
          //2 list 集合型別屬性
          private List<String> list;
          //3 map 集合型別屬性
          private Map<String,String> maps;
          //4 set 集合型別屬性
          private Set<String> sets;
          public void setSets(Set<String> sets) {
              this.sets = sets;
          }
          public void setCourses(String[] courses) {
              this.courses = courses;
          }
          public void setList(List<String> list) {
              this.list = list;
          }
          public void setMaps(Map<String, String> maps) {
              this.maps = maps;
          } 
      }
    2. 在 spring 配置檔案進行配置

      <!--1 集合型別屬性注入-->
      <bean id="stu" class="com.atguigu.spring5.collectiontype.Stu">
          <!--陣列型別屬性注入-->
          <property name="courses">
              <array>
                  <value>java 課程</value>
                  <value>資料庫課程</value>
              </array>
          </property>
          <!--list 型別屬性注入-->
          <property name="list">
              <list>
                  <value>張三</value>
                  <value>小三</value>
              </list>
          </property>
          <!--map 型別屬性注入-->
          <property name="maps">
              <map>
                  <entry key="JAVA" value="java"></entry>
                  <entry key="PHP" value="php"></entry>
              </map>
          </property>
          <!--set 型別屬性注入-->
          <property name="sets">
              <set>
                  <value>MySQL</value>
                  <value>Redis</value>
              </set>
          </property>
      </bean>
  4. 在集合裡面設定物件型別值

    <!--建立多個 course 物件-->
    <bean id="course1" class="com.atguigu.spring5.collectiontype.Course">
        <property name="cname" value="Spring5 框架"></property>
    </bean>
    <bean id="course2" class="com.atguigu.spring5.collectiontype.Course">
        <property name="cname" value="MyBatis 框架"></property>
    </bean>
    <!--注入 list 集合型別,值是物件-->
    <property name="courseList">
        <list>
            <ref bean="course1"></ref>
            <ref bean="course2"></ref>
        </list>
    </property>
    5、把集合注入部分提取出來
    1. 在 spring 配置檔案中引入名稱空間 util

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:p="http://www.springframework.org/schema/p"
             xmlns:util="http://www.springframework.org/schema/util"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
             http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
      </beans>
    2. 使用 util 標籤完成 list 集合注入提取

      <!--1 提取 list 集合型別屬性注入-->
      <util:list id="bookList">
          <value>易筋經</value>
          <value>九陰真經</value>
          <value>九陽神功</value>
      </util:list>
      <!--2 提取 list 集合型別屬性注入使用--> 
      <bean id="book" class="com.atguigu.spring5.collectiontype.Book">
          <property name="list" ref="bookList"></property>
      </bean>

IOC 操作 Bean 管理(FactoryBean)

  1. Spring 有兩種型別 bean,一種普通 bean,另外一種工廠 bean(FactoryBean)
  2. 普通 bean:在配置檔案中定義 bean 型別就是返回型別
  3. 工廠 bean:在配置檔案定義 bean 型別可以和返回型別不一樣
    1. 第一步 建立類,讓這個類作為工廠 bean,實現介面 FactoryBean
    2. 第二步 實現接口裡面的方法,在實現的方法中定義返回的 bean 型別
    public class MyBean implements FactoryBean<Course> {
        //定義返回 bean
        @Override
        public Course getObject() throws Exception {
            Course course = new Course();
            course.setCname("abc");
            return course;
        }
        @Override
        public Class<?> getObjectType() {
            return null;
        }
        @Override
        public boolean isSingleton() {
            return false;
        } 
    }
    <bean id="myBean" class="com.atguigu.spring5.factorybean.MyBean"></bean>
    @Test
    public void test3() {
        ApplicationContext context =
                new ClassPathXmlApplicationContext("bean3.xml");
        Course course = context.getBean("myBean", Course.class);
        System.out.println(course);
    }

IOC 操作 Bean 管理(bean 作用域)

  1. 在 Spring 裡面,設定建立 bean 例項是單例項還是多例項
  2. 在 Spring 裡面,預設情況下,bean 是單例項物件
  3. 如何設定單例項還是多例項
    1. 在 spring 配置檔案 bean 標籤裡面有屬性(scope)用於設定單例項還是多例項
    2. scope 屬性值
      1. 第一個值 預設值,singleton,表示是單例項物件
      2. 第二個值 prototype,表示是多例項物件

    3. singleton 和 prototype 區別
      1. 第一 singleton 單例項,prototype 多例項
      2. 第二 設定 scope 值是 singleton 時候,載入 spring 配置檔案時候就會建立單例項物件
        設定 scope 值是 prototype 時候,不是在載入 spring 配置檔案時候建立 物件,在呼叫 getBean 方法時候建立多例項物件

IOC 操作 Bean 管理(bean 生命週期)

  1. 生命週期
    1. 從物件建立到物件銷燬的過程
  2. bean 生命週期
    1. 通過構造器建立 bean 例項(無引數構造)
    2. 為 bean 的屬性設定值和對其他 bean 引用(呼叫 set 方法)
    3. 呼叫 bean 的初始化的方法(需要進行配置初始化的方法)
    4. bean 可以使用了(物件獲取到了)
    5. 當容器關閉時候,呼叫 bean 的銷燬的方法(需要進行配置銷燬的方法)
  3. 演示 bean 生命週期

    public class Orders {
        //無引數構造
        public Orders() {
            System.out.println("第一步 執行無引數構造建立 bean 例項");
        }
        private String oname;
        public void setOname(String oname) {
            this.oname = oname;
            System.out.println("第二步 呼叫 set 方法設定屬性值");
        }
        //建立執行的初始化的方法
        public void initMethod() {
            System.out.println("第三步 執行初始化的方法");
        }
        //建立執行的銷燬的方法
        public void destroyMethod() {
            System.out.println("第五步 執行銷燬的方法");
        } 
    }
    <bean id="orders" class="com.atguigu.spring5.bean.Orders" init￾method="initMethod" destroy-method="destroyMethod">
        <property name="oname" value="手機"></property>
    </bean>
    @Test
    public void testBean3() {
        // ApplicationContext context =
        // new ClassPathXmlApplicationContext("bean4.xml");
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("bean4.xml");
        Orders orders = context.getBean("orders", Orders.class);
        System.out.println("第四步 獲取建立 bean 例項物件");
        System.out.println(orders);
        //手動讓 bean 例項銷燬
        context.close();
    }
  4. bean 的後置處理器,bean 生命週期有七步
    1. 通過構造器建立 bean 例項(無引數構造)
    2. 為 bean 的屬性設定值和對其他 bean 引用(呼叫 set 方法)
    3. 把 bean 例項傳遞 bean 後置處理器的方法 postProcessBeforeInitialization
    4. 呼叫 bean 的初始化的方法(需要進行配置初始化的方法)
    5. 把 bean 例項傳遞 bean 後置處理器的方法 postProcessAfterInitialization
    6. bean 可以使用了(物件獲取到了)
    7. 當容器關閉時候,呼叫 bean 的銷燬的方法(需要進行配置銷燬的方法)
  5. 演示新增後置處理器效果
    1. 建立類,實現介面 BeanPostProcessor,建立後置處理器

      public class MyBeanPost implements BeanPostProcessor {
          @Override
          public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
              System.out.println("在初始化之前執行的方法");
              return bean;
          }
          @Override
          public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
              System.out.println("在初始化之後執行的方法");
              return bean;
          }
      }
      <!--配置後置處理器-->
      <bean id="myBeanPost" class="com.atguigu.spring5.bean.MyBeanPost">
      </bean>

IOC 操作 Bean 管理(xml 自動裝配)

  1. 什麼是自動裝配
    1. 根據指定裝配規則(屬性名稱或者屬性型別),Spring 自動將匹配的屬性值進行注入
  2. 演示自動裝配過程
    1. 根據屬性名稱自動注入

      <!--實現自動裝配
          bean 標籤屬性 autowire,配置自動裝配
          autowire 屬性常用兩個值:
          byName 根據屬性名稱注入 ,注入值 bean 的 id 值和類屬性名稱一樣
          byType 根據屬性型別注入
      -->
      <bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byName">
       <!--<property name="dept" ref="dept"></property>-->
      </bean>
      <bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean>
    2. 根據屬性型別自動注入

      <!--實現自動裝配
          bean 標籤屬性 autowire,配置自動裝配
          autowire 屬性常用兩個值:
          byName 根據屬性名稱注入 ,注入值 bean 的 id 值和類屬性名稱一樣
          byType 根據屬性型別注入
      -->
      <bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byType">
          <!--<property name="dept" ref="dept"></property>-->
      </bean>
      <bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean>

IOC 操作 Bean 管理(外部屬性檔案)

  1. 直接配置資料庫資訊
    1. 配置德魯伊連線池
    2. 引入德魯伊連線池依賴 jar 包
    <!--直接配置連線池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/userDb"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
  2. 引入外部屬性檔案配置資料庫連線池
    1. 建立外部屬性檔案,properties 格式檔案,寫資料庫資訊
    2. 把外部 properties 屬性檔案引入到 spring 配置檔案中
      引入 context 名稱空間
    <beans xmlns="http://www.springframework.org/schema/beans" 
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
           xmlns:p="http://www.springframework.org/schema/p" 
           xmlns:util="http://www.springframework.org/schema/util" 
           xmlns:context="http://www.springframework.org/schema/context" 
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 
           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd 
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> 

    在 spring 配置檔案使用標籤引入外部屬性檔案

    <!--引入外部屬性檔案-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置連線池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${prop.driverClass}"></property>
        <property name="url" value="${prop.url}"></property>
        <property name="username" value="${prop.userName}"></property>
        <property name="password" value="${prop.password}"></property>
    </bean>

IOC 操作 Bean 管理(基於註解方式)

  1. 什麼是註解
    1. 註解是程式碼特殊標記,格式:@註解名稱(屬性名稱=屬性值, 屬性名稱=屬性值..)
    2. 使用註解,註解作用在類上面,方法上面,屬性上面
    3. 使用註解目的:簡化 xml 配置
  2. Spring 針對 Bean 管理中建立物件提供註解
    1. @Component
    2. @Service
    3. @Controller
    4. @Repository

      上面四個註解功能是一樣的,都可以用來建立 bean 例項

  3. 基於註解方式實現物件建立
    1. 第一步 引入依賴
    2. 第二步 開啟元件掃描

      <!--開啟元件掃描
       1 如果掃描多個包,多個包使用逗號隔開
       2 掃描包上層目錄
      -->
      <context:component-scan base-package="com.atguigu"></context:component-scan>
    3. 第三步 建立類,在類上面新增建立物件註解

      //在註解裡面 value 屬性值可以省略不寫,
      //預設值是類名稱,首字母小寫
      //UserService -- userService
      @Component(value = "userService") //<bean id="userService" class=".."/>
      public class UserService {
          public void add() {
              System.out.println("service add.......");
          } 
      }
  4. 開啟元件掃描細節配置

    <!--示例 1
     use-default-filters="false" 表示現在不使用預設 filter,自己配置 filter
     context:include-filter ,設定掃描哪些內容
    -->
    <context:component-scan base-package="com.atguigu" use-default￾filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    
    <!--示例 2
     下面配置掃描包所有內容
     context:exclude-filter: 設定哪些內容不進行掃描
    -->
    <context:component-scan base-package="com.atguigu">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
  5. 基於註解方式實現屬性注入
    1. @Autowired:根據屬性型別進行自動裝配
      1. 第一步 把 service 和 dao 物件建立,在 service 和 dao 類新增建立物件註解
      2. 第二步 在 service 注入 dao 物件,在 service 類新增 dao 型別屬性,在屬性上面使用註解
      @Service
      public class UserService {
          //定義 dao 型別屬性
          //不需要新增 set 方法
          //添加註入屬性註解
          @Autowired 
          private UserDao userDao;
          public void add() {
          System.out.println("service add.......");
              userDao.add();
          } 
      }
    2. @Qualifier:根據名稱進行注入
      這個@Qualifier 註解的使用,和上面@Autowired 一起使用

      //定義 dao 型別屬性
      //不需要新增 set 方法
      //添加註入屬性註解
      @Autowired //根據型別進行注入
      @Qualifier(value = "userDaoImpl1") //根據名稱進行注入
      private UserDao userDao;
    3. @Resource:可以根據型別注入,可以根據名稱注入

      //@Resource //根據型別進行注入
      @Resource(name = "userDaoImpl1") //根據名稱進行注入
      private UserDao userDao;
    4. @Value:注入普通型別屬性

      @Value(value = "abc")
      private String name; 
  6. 完全註解開發
    1. 建立配置類,替代 xml 配置檔案

      @Configuration //作為配置類,替代 xml 配置檔案
      @ComponentScan(basePackages = {"com.atguigu"})
      public class SpringConfig {
      }
    2. 編寫測試類

      @Test
      public void testService2() {
          //載入配置類
          ApplicationContext context = 
              new AnnotationConfigApplicationContext(SpringConfig.class);
          UserService userService = context.getBean("userService", UserService.class);
          System.out.println(userService);
          userService.add();
      }