1. 程式人生 > 其它 >【spring】spring_IOC和DI

【spring】spring_IOC和DI

Spring概述

Spring是分層的 Java SE/EE應用full-stack輕量級開源框架以IOC(Inverse Of Control:反轉控制)和 AOP(Aspect Oriented Programming:面向切面程式設計)為核心

提供了表現層SpringMVC和持久層Spring JDBCTemplate以及業務層事務管理等眾多的企業級應用技術,簡單的說就是簡化java開發。還能整合開源世界眾多著名的第三方框架和類庫,逐漸成為使用最多的Java EE 企業應用開源框架。

這裡需要理解幾個基本概念

  • 控制反轉 :不是什麼技術,它是一種降低物件耦合關係的一種設計思想。在Java開發中,Ioc意味著將物件交給容器控制

    其中最常見的方式叫做 依賴注入(Dependency Injection,簡稱DI),還有一種方式叫“依賴查詢”(Dependency Lookup)

    依賴注入 :指容器負責建立和維護物件之間的依賴關係,而不是通過物件本身負責自己的建立和解決自己的依賴。在當前類需要用到其他類的物件,由Spring為我們提供(就是省略new ),我們只需要在配置中說明。

  • 面向切面程式設計 : 自己的理解:提取相同的程式碼,例如後臺所有內容都需要登入為前提,呢麼這個判斷是否登入的方法就是面向切面程式設計

    • 作用:在程式執行期間,在不修改原始碼的情況下對方法進行功能增強。

Spring快速入門使用

ioc和依賴注入的由來

我們知道在面向物件設計的軟體系統中,它的底層都是由N個物件構成的,各個物件之間通過相互合作,最終實現系統地業務邏輯,例如一個懷錶,它擁有多個獨立的齒輪,這些齒輪相互齧合在一起,協同工作,共同完成某項任務。我們可以看到,在這樣的齒輪組中,如果有一個齒輪出了問題,就可能會影響到整個齒輪組的正常運轉。這就是耦合關係非常緊密的關係,所以為了解決,軟體專家Michael Mattson 1996年提出了IOC理論,用來實現物件之間的“解耦”,目前這個理論已經被成功地應用到實踐當中。

IOC是Inversion of Control的縮寫,多數書籍翻譯成“控制反轉”。IOC理論提出的觀點大體是這樣的:藉助於“第三方”實現具有依賴關係的物件之間的解耦。如下圖:

  大家看到了吧,由於引進了中間位置的“第三方”,也就是IOC容器,使得A、B、C、D這4個物件沒有了耦合關係,齒輪之間的傳動全部依靠“第三方”了,全部物件的控制權全部上繳給“第三方”IOC容器,所以,IOC容器成了整個系統的關鍵核心,它起到了一種類似“粘合劑”的作用,把系統中的所有物件粘合在一起發揮作用,如果沒有這個“粘合劑”,物件與物件之間會彼此失去聯絡,這就是有人把IOC容器比喻成“粘合劑”的由來。

  我們再來做個試驗:把上圖中間的IOC容器拿掉,然後再來看看這套系統:

我們現在看到的畫面,就是我們要實現整個系統所需要完成的全部內容。這時候,A、B、C、D這4個物件之間已經沒有了耦合關係,彼此毫無聯絡,這樣的話,當你在實現A的時候,根本無須再去考慮B、C和D了,物件之間的依賴關係已經降低到了最低程度。所以,如果真能實現IOC容器,對於系統開發而言,這將是一件多麼美好的事情,參與開發的每一成員只要實現自己的類就可以了,跟別人沒有任何關係!

我們再來看看,控制反轉(IOC)到底為什麼要起這麼個名字?我們來對比一下:

  • 軟體系統在沒有引入IOC容器之前,如圖1所示,物件A依賴於物件B,那麼物件A在初始化或者執行到某一點的時候,自己必須主動去建立物件B或者使用已經建立的物件B。無論是建立還是使用物件B,控制權都在自己手上。

  • 軟體系統在引入IOC容器之後,這種情形就完全改變了,如圖3所示,由於IOC容器的加入,物件A與物件B之間失去了直接聯絡,所以,當物件A執行到需要物件B的時候,IOC容器會主動建立一個物件B注入到物件A需要的地方。

通過前後的對比,我們不難看出來:物件A獲得依賴物件B的過程,由主動行為變為了被動行為,控制權顛倒過來了,這就是“控制反轉”這個名稱的由來。

  2004年,Martin Fowler探討了同一個問題,既然IOC是控制反轉,那麼到底是“哪些方面的控制被反轉了呢?”,經過詳細地分析和論證後,他得出了答案:“獲得依賴物件的過程被反轉了”。控制被反轉之後,獲得依賴物件的過程由自身管理變為了由IOC容器主動注入。於是,他給“控制反轉”取了一個更合適的名字叫做“依賴注入(Dependency Injection)”。他的這個答案,實際上給出了實現IOC的方法:注入。所謂依賴注入,就是由IOC容器在執行期間,動態地將某種依賴關係注入到物件之中。

  所以,依賴注入(DI)和控制反轉(IOC)是從不同的角度的描述的同一件事情,就是指通過引入IOC容器,利用依賴關係注入的方式,實現物件之間的解耦。

Spring的優勢

1) 方便解耦,簡化開發

通過Spring提供的IOC容器,可以將物件間的依賴關係交由Spring進行控制,避免硬編碼所造成的過度耦合。

使用者也不必再為單例模式類、屬性檔案解析等這些很底層的需求編寫程式碼,可以更專注於上層的應用。

2) AOP 程式設計的支援

通過Spring的AOP功能,方便進行面向切面程式設計,許多不容易用傳統OOP實現的功能可以通過AOP輕鬆實現。

3) 宣告式事務的支援

可以將我們從單調煩悶的事務管理程式碼中解脫出來,通過宣告式方式靈活的進行事務管理,提高開發效率和質量

4) 方便程式的測試

可以用非容器依賴的程式設計方式進行幾乎所有的測試工作,測試不再是昂貴的操作,而是隨手可做的事情。

Spring的體系結構

Spring配置檔案 => IoC容器

Bean標籤基本配置

作用:通過配置將物件的建立交給Spring容器進行管理。

預設情況下它呼叫的是類中的無參建構函式,如果沒有無參建構函式則不能建立成功。

相關屬性

  • id:Bean例項在Spring容器中的唯一標識;
  • class:Bean的全限定名稱。

Bean標籤範圍配置

scope,指物件的作用範圍,取值如下:

取值範圍 說明
singleton 預設值,單例的
prototype 多例的
request WEB專案中,Spring建立一個Bean的物件,將物件存入到request域中
session WEB專案中,Spring建立一個Bean的物件,將物件存入到session域中
global session WEB專案中,應用在Portlet環境,如果沒有Portlet環境那麼globalSession相當於session

當scope的取值為singleton時

  • Bean的例項化個數:1個
  • Bean的例項化時機:當Spring核心檔案被載入時,例項化配置的Bean例項
  • Bean的生命週期:
    • 物件建立:當應用載入,建立容器時,物件就被建立了;
    • 物件執行:只要容器在,物件一直活著;
    • 物件銷燬:當應用解除安裝,銷燬容器時,物件就被銷燬了。

當scope的取值為prototype時

  • Bean的例項化個數:多個
  • Bean的例項化時機:當呼叫getBean()方法時例項化Bean
  • Bean的生命週期:
    • 物件建立:當使用物件時,建立新的物件例項;
    • 物件執行:只要物件在使用中,就一直活著;
    • 物件銷燬:當物件長時間不用時,被 Java 的垃圾回收器回收了。

bean生命週期配置

init-method:指定類中的初始化方法名稱

destroy-method:指定類中銷燬方法名稱

bean例項化三種方式

Spring快速入門使用

使用無參構造方法例項化

根據預設無參構造方法來建立類物件,如果bean中沒有預設無參建構函式,將會建立失敗。

<bean id="userDao" class="com.qfedu.dao.impl.UserDaoImpl">

工廠靜態方法例項化

建立靜態工廠

public class StaticBeanFactory {
    public static UserDao getUserDaoImpl() {
        return new UserDaoImpl();
    }
}

在Spring配置檔案中配置

<!-- 靜態工廠初始化 -->
<bean id="userDao" class="com.qfedu.factory.StaticBeanFactory" factory-method="getUserDaoImpl"></bean>

測試

//演示通過靜態工廠建立Bean
@Test
public void test1() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserDao userDao = (UserDao) context.getBean("userDao");
    userDao.save();
}

工廠例項方法例項化

建立動態工廠

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

在Spring配置檔案中配置

<bean id="factory" class="com.qfedu.factory.DynamicBeanFactory"></bean>
<bean id="userDao" factory-bean="factory" factory-method="getUserDao"></bean>

測試同上

DI(依賴注入)

依賴注入:Dependency Injection ,指容器負責建立和維護物件之間的依賴關係,而不是通過物件本身負責自己的建立和解決自己的依賴。在當前類需要用到其他類的物件,由Spring為我們提供,我們只需要在配置中說明。

業務層和持久層的依賴關係,在使用 Spring 之後,就讓 Spring 來維護了。

簡單的說,就是坐等框架把持久層物件傳入業務層,而不用我們自己去獲取。

即service中的private UserDao userDao = new 物件(),變成 private UserDao userDao;,就是省略new

構造方法注入

  1. 建立介面UserService和實現類UserServiceImpl
public interface UserService {
    void save();
}

public class UserServiceImpl implements UserService {
    //這裡一定要有該屬性,我們最終的目的是讓該屬性關聯一個UserDaoImpl的物件
    private UserDao userDao;

    public UserServiceImpl() {
    }
    
    //一定要有該有參的構造方法,通過該方法完成依賴注入
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }
}
  1. 在Spring配置檔案中配置
<bean id="userDao" class="com.qfedu.dao.impl.UserDaoImpl"></bean>

<bean id="userService" class="com.qfedu.service.impl.UserServiceImpl">
    <!-- 構造方法注入,通過ref將id為“userDao”的bean傳遞給了UserServiceImpl構造方法的userDao形參 -->
    <constructor-arg name="userDao" ref="userDao" />
</bean>
  1. 測試
@Test
public void test3() {
    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    UserService userService = (UserService)context.getBean("userService");
    userService.save();
}

set方法注入(重點)

在UserServiceImpl中新增set方法

public class UserServiceImpl implements UserService {
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

在Spring配置檔案中配置

<bean id="userService" class="UserServiceImpl">
    <!-- set方法注入 -->
    <property name="userDao" ref="userDao"></property>
</bean>

測試方法同上

p名稱空間注入

p名稱空間注入本質也是set方法注入,但比起上述的set方法注入更加方便,主要體現在配置檔案中

引入P名稱空間

xmlns:p="http://www.springframework.org/schema/p"

在Spring配置檔案中配置

<!-- p名稱空間注入 -->
<bean id="userService" class="com.qfedu.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>

依賴注入其他型別

上面的案例,我們學習瞭如何注入引用型別的資料,除了引用資料型別,普通資料型別,集合資料型別也可以注入。

普通資料型別注入

建立Department實體類

//表示部門的實體類
public class Department {
    private Integer id;//部門編號
    private String name;//部門名稱
    private String desc;//部門描述
    
    //set、get方法
    //toString方法
}
  1. 在Spring配置檔案中配置
<!--
    通過Spring的IOC容器建立Department類的物件,併為其屬性注入值
    無參構造方法例項化
-->
<bean id="department" class="com.qfedu.entity.Department">
    <!-- set方法注入
        value:簡單型別
    -->
    <property name="id" value="1" />
    <property name="name" value="研發部" />
    <property name="desc" value="專案研發" />
</bean>

測試

@Test
public void test6() {
    //解析配置檔案 -- 建立物件 -- 物件交給Spring的IOC容器進行管理
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    //獲取Department的物件
    Department department = (Department)context.getBean("department");
    //列印物件
    System.out.println(department);
}

引用型別注入

  1. 建立實體類Department
//表示部門的實體類
public class Department {
    private Integer id;//部門編號
    private String name;//部門名稱
    private String desc;//部門描述
    private Address address;//部門地址
    
    //set、get
    //toString
}

在Spring配置檔案中配置

<bean id="address" class="com.qfedu.entity.Address">
    <property name="province" value="山東省" />
    <property name="city" value="青島市" />
    <property name="county" value="市北區" />
    <property name="street" value="龍城路" />
    <property name="no" value="31號" />
</bean>

<!--
    通過Spring的IOC容器建立Department類的物件,併為其屬性注入值
    無參構造方法例項化
-->
<bean id="department" class="com.qfedu.entity.Department">
    <!-- set方法注入
        value:簡單型別
    -->
    <property name="id" value="1" />
    <property name="name" value="研發部" />
    <property name="desc" value="專案研發" />
    <!-- set方法注入
        ref:引用型別
    -->
    <property name="address" ref="address" />
</bean>

測試同上

集合資料型別(List<String>)的注入

建立Employee實體類

//表示員工的實體類
public class Employee {
    private Integer id;//員工編號
    private String name;//姓名
    private Integer age;//年齡
    private String gender;//性別
    private List<String> hobby;//愛好
        
    //set、get方法
    //toString方法
}
  1. 在Spring配置檔案中配置
<!--
    通過Spring的IOC容器建立Employee類的物件,併為其屬性注入值
    無參構造方法例項化
-->
<bean id="e1" class="com.qfedu.entity.Employee">
    <property name="id" value="1" />
    <property name="name" value="zs" />
    <property name="age" value="30" />
    <property name="gender" value="男" />
    <!--        集合型別注入    -->
    <property name="hobby">
        <list>
            <value>學習1</value>
            <value>學習2</value>
            <value>學習3</value>
        </list>
    </property>
</bean>

測試同上

集合資料型別(List<物件>)的注入

  1. 修改Department實體類
//表示部門的實體類
public class Department {
    private Integer id;//部門編號
    private String name;//部門名稱
    private String desc;//部門描述
    private Address address;//部門地址
    private List<Employee> emps;//普通員工
    
    //set、get方法
    //toString方法
}
  1. 在Spring配置檔案中配置
<bean id="e1" class="com.qfedu.entity.Employee">
    <property name="id" value="1" />   <property name="name" value="zs" />    <property name="age" value="30" />    <property name="gender" value="男" />
    <!--  集合型別注入         -->
    <property name="hobby">        <list>            <value>學習1</value>            <value>學習2</value>            <value>學習3</value>        </list>    </property>
</bean>

<bean id="e2" class="com.qfedu.entity.Employee">
    <property name="id" value="1" />    <property name="name" value="ls" />    <property name="age" value="31" />    <property name="gender" value="男" />
    <!--集合型別注入 -->
    <property name="hobby">        <list>            <value>爬山</value>            <value>游泳</value>            <value>網遊</value>        </list>    </property>
</bean>

<bean id="department" class="com.qfedu.entity.Department">
    <!-- set方法注入
            value:簡單型別
        -->
    <property name="id" value="1" />
    <property name="name" value="研發部" />
    <property name="desc" value="專案研發" />
    <!-- set方法注入
            ref:引用型別
         -->
    <property name="address" ref="address" />
    <property name="emps">
        <list>
            <ref bean="e1" />
            <ref bean="e2" />
        </list>
    </property>
</bean>

測試同上

集合資料型別(Map<String>)的注入

  1. 修改Department,新增屬性
//表示部門的實體類
public class Department {
    private Integer id;//部門編號
    private Map<String, Employee> leader;//部門主管
    //set、get
    //toString
}
  1. 在Spring配置檔案中配置
<bean id="e1" class="com.qfedu.entity.Employee">
    <property name="id" value="1" />
</bean>

<bean id="e2" class="com.qfedu.entity.Employee">
    <property name="id" value="2" />
</bean>


<bean id="department" class="com.qfedu.entity.Department">
    <property name="id" value="1" />
    <property name="leader">
        <map>
            <entry key="CEO" value-ref="e1" />
            <entry key="CTO" value-ref="e2" />
        </map>
    </property>
</bean>

測試同上

集合資料型別(properties)的注入

建立實體類JdbcConfig,新增Properties

package com.qfedu.entity;

import java.util.Properties;

public class JdbcConfig {
    private Properties config;

    public Properties getConfig() {
        return config;
    }

    public void setConfig(Properties config) {
        this.config = config;
    }

    @Override
    public String toString() {
        return "JdbcConfig{" +
                "config=" + config +
                '}';
    }
}

在Spring配置檔案中配置

<bean id="jdbcConfig" class="com.qfedu.entity.JdbcConfig">
    <!-- Properties型別的注入 -->
    <property name="config">
        <props>
            <prop key="driverName">com.mysql.jdbc.Driver</prop>
            <prop key="url">jdbc:mysql://localhost:3306/test</prop>
            <prop key="username">root</prop>
            <prop key="password">root</prop>
        </props>
    </property>
</bean>

測試

@Test
public void test8() {
    //解析配置檔案 -- 建立物件 -- 物件交給Spring的IOC容器進行管理
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    //獲取Employee的物件
    JdbcConfig config = (JdbcConfig)context.getBean("jdbcConfig");
    //列印物件
    System.out.println(config);
}

引入其他配置檔案

實際開發中,Spring的配置內容非常多,這就導致Spring配置很繁雜且體積很大,所以,可以將部分配置拆解到其他配置檔案中,而在Spring主配置檔案通過import標籤進行載入。

<import resource="applicationContext-xxx.xml"/>