【Spring學習筆記】—— (一)IOC/Bean/依賴注入DI


思維導圖

在這裏插入圖片描述



一、Spring 概述

1. Spring是什麼

IOC(反轉控制)和AOP(面向切面編程)爲內核

Spring是分層的 Java SE/EE應用 full-stack 輕量級開源框架,以 IoC(Inverse Of Control: 反轉控制)和 AOP(Aspect Oriented Programming:面向切面編程)爲內核,提供了展現層 Spring MVC 和持久層 Spring JDBC 以及業務層事務管理等衆多的企業級應用技術,還能整合開源世界衆多 著名的第三方框架和類庫,逐漸成爲使用最多的Java EE 企業應用開源框架。

  • 在軟件業,AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,可以通過預編譯方式和運行期動態代理實現在不修改源代碼的情況下給程序動態統一添加功能的一種技術。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
  • 針對業務處理過程中的切面進行提取,它所面對的是處理過程中的某個步驟或階段,以獲得邏輯過程中各部分之間低耦合性的隔離效果

2. Spring體系結構

在這裏插入圖片描述
ORM: 對象關係映射(Object Relational Mapping,簡稱ORM)是通過使用描述對象和數據庫之間映射的元數據,將面嚮對象語言程序中的對象自動持久化到關係數據庫中。本質上就是將數據從一種形式轉換到另外一種形式。

3. Spring的優勢

  • 方便解耦,簡化開發
    通過 Spring提供的 IoC容器,可以將對象間的依賴關係交由 Spring進行控制,避免硬編碼所造 成的過度程序耦合。用戶也不必再爲單例模式類、屬性文件解析等這些很底層的需求編寫代碼,可 以更專注於上層的應用。
  • AOP編程的支持
    通過 Spring的 AOP 功能,方便進行面向切面的編程,許多不容易用傳統OOP(面向對象編程) 實現的功能可以通過 AOP 輕鬆應付。
  • 聲明式事務的支持
    可以將我們從單調煩悶的事務管理代碼中解脫出來,通過聲明式方式靈活的進行事務的管理, 提高開發效率和質量。
  • 方便程序的測試
    可以用非容器依賴的編程方式進行幾乎所有的測試工作,測試不再是昂貴的操作,而是隨手可 做的事情。
  • 方便集成各種優秀框架
    Spring可以降低各種框架的使用難度,提供了對各種優秀框架(Struts、Hibernate、Hessian、Quartz 等)的直接支持。
  • 降低 JavaEE API的使用難度
    Spring對 JavaEE API(如 JDBC、JavaMail、遠程調用等)進行了薄薄的封裝層,使這些 API 的 使用難度大爲降低。

二、IOC 的概念和作用

1. 程序的耦合

  • 類之間的依賴

  • 方法間的依賴

    解耦:降低程序間的依賴關係

    實際開發中應該做到,編譯期不依賴,運行時才依賴

我們在開發中,有些依賴關係是必須的,有些依賴關係可以通過優化代碼來解除的。

// 賬戶的業務層實現類  
 public class AccountServiceImpl implements IAccountService {  
 	private IAccountDao accountDao = new AccountDaoImpl(); 
 }  

上面的代碼表示: 業務層調用持久層,並且此時業務層在依賴持久層的接口和實現類。如果此時沒有持久層實現類,編譯將不能通過。這種編譯期依賴關係,應該在我們開發中杜絕。我們需要優化代碼解決。

再比如:

DriverManager.registerDriver(new com.mysql.jdbc.Driver())
改爲
Class.forName("com.mysql.jdbc.Driver");

原因就是: 我們的類依賴了數據庫的具體驅動類(MySQL),如果這時候更換了數據庫品牌(比> 如 Oracle),需要 修改源碼來重新數據庫驅動。這顯然不是我們想要的。

2. 解耦的思路

使用反射來創建對象,而避免使用new關鍵字

3. 工廠模式解耦

  • 工廠模式中的問題

  1. 通過 Idea 創建 maven 項目
  2. 編寫接口及實現類
  • IAccountDao

      /**
       * 賬戶的持久層接口
       */
      public interface IAccountDao {
      
          /**
           * 模擬保存賬戶
           */
          void saveAccount();
      }
    
  • IAccountService

      /**
       * 賬戶業務層的接口
       */
      public interface IAccountService {
      
          /**
           * 模擬保存賬戶
           */
          void saveAccount();
      }
    
  • AccountDaoImpl

      /**
       * 賬戶的持久層實現類
       */
      public class AccountDaoImpl implements IAccountDao {
      
          public  void saveAccount(){
      
              System.out.println("保存了賬戶");
          }
      }
    
  • AccountServiceImpl

      /**
       * 賬戶的業務層實現類
       */
      public class AccountServiceImpl implements IAccountService {
      
          private IAccountDao accountDao = new AccountDaoImpl();
      
          public void  saveAccount(){
              accountDao.saveAccount();
          }
      }
    
  • 模擬一個表現層,用於調用業務層 ui/Client

    public class Client {
        public static void main(String[] args) {
            IAccountService as = new AccountServiceImpl();
            as.saveAccount();
        }
    }
    
    
    

    傳統JavaWeb寫法耦合性太高,所有文件缺一不可

  • 解耦(多例)

    Bean: 可重用組件

    javaBean: 用java語言編寫的可重用組件。 javaBean > 實體類

    解耦方法:

    • 需要一個配置文件(xml/properties)來配置我們的service和dao

      • 配置的內容:唯一標識 = 全限定類名

        resource/bean.properties

        accountService = com.smallbeef.service.impl.AccountServiceImpl;
        accountDao = com.smallbeef.service.impl.AccountDaoImpl;
        
    • 通過過讀取配置文件中內容,反射創建對象(用工廠模式創建對象)

      bean/BeanFactory.java

    // 一個創建Bean對象的工廠
    public class BeanFactory{
        //定義一個properties對象
        private static Properties props;
        //使用靜態代碼塊爲Properties對象賦值
        static{
            //實例化對象
            props = new Properties();
            //獲取properties文件的流對象
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            try {
                props.load(in);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 根據Bean的名稱獲取bean對象
         * @param beanName
         * @return
         */
        public static Object getBean(String beanName){
            Object bean = null;
            String beanPath = props.getProperty(beanName);
            try {
                // 通過反射new一個對象
                //newInstance每次都會調用默認構造函數創建對象
                bean = Class.forName(beanPath).newInstance();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            return bean;
        }
    }
    

    修改創建對象的方式

    public class Client {
        public static void main(String[] args) {
    //        IAccountService as = new AccountServiceImpl();
            IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
            as.saveAccount();
    
        }
    }
    
    
    public class AccountServiceImpl implements IAccountService {
    
    //    private IAccountDao accountDao = new AccountDaoImpl();
        private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
    
        public void  saveAccount(){
            accountDao.saveAccount();
        }
    
    }
    

    即使缺少 serviceimpl 或 daoimpl 文件代碼在編譯時並不會報錯

    在這裏插入圖片描述

  • 工廠模式解耦升級版(單例)

    利用容器存儲每次new出來的對象

    // 一個創建Bean對象的工廠
    public class BeanFactory{
        
        //定義一個properties對象
        private static Properties props;
        
        // 定義一個Map,用於存放我們要創建的對象,我們把它稱之爲容器
       	private static Map<String, Object> beans;
        
        //使用靜態代碼塊爲Properties對象賦值
        static{
            //實例化對象
            props = new Properties();
            //獲取properties文件的流對象
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            try {
                props.load(in);
                
                //實例化容器
                beans = new HashMap<String,Object>();
                //取出配置文件中所有的key
                Enumeration keys = props.keys();
                //遍歷枚舉
                while(keys.hasMoreElements()){
                    //取出每個key
                    String key = keys.nextElement().toString();
                    //根據key獲取value
                    String beanPath = props.getProperty(key);
                    //反射創建對象
                    Object value = Class.forName(beanPath).newInstance();
                    //把key和value存入容器中
                    beans.put(key,value);
                    
                }
                
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
    	 /**
         * 根據Bean的名稱獲取對象 單例
         * @param beanName
         * @return
         */
         public static Object getBean(String beanName){
             return beans.get(beanName);
         }
        
        /**
         * 根據Bean的名稱獲取bean對象 多例
         * @param beanName
         * @return
         */
    //    public static Object getBean(String beanName){
    //        Object bean = null;
    //        String beanPath = props.getProperty(beanName);
    //        try {
    //            // 通過反射new一個對象
    //            //newInstance每次都會調用默認構造函數創建對象
    //            bean = Class.forName(beanPath).newInstance();
    //        } catch (InstantiationException e) {
    //            e.printStackTrace();
    //        } catch (IllegalAccessException e) {
    //            e.printStackTrace();
    //        } catch (ClassNotFoundException e) {
    //            e.printStackTrace();
    //        }
    //        return bean;
    //    }
    }
    

4. IOC的概念

IOC = 控制反轉 = Inversion Of Control

什麼是工廠?
在這裏插入圖片描述
在這裏插入圖片描述
ioc只能解決程序間的依賴關係,別的事情都幹不了


三、使用 spring的 IOC解決程序耦合

1. 案例

  • 建立Maven工程

  • 導入springframework依賴

     <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.2.2.RELEASE</version>
            </dependency>
        </dependencies>
        
    
  • 創建配置文件bean.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">
        <!--把對象的創建交割spring來管理-->
        <bean id = "accountService" class = "com.smallbeef.service.impl.AccountServiceImpl"></bean>
        <bean id = "accountDao" class = "com.smallbeef.dao.impl.AccountDaoImpl"></bean>
    </beans>
    

    ioc容器根據id唯一獲取對象

  • 測試

    public class Client {
    
        /**
         * 獲取spring的Ioc容器,並根據id獲取對象
         * @param args
         */
        public static void main(String[] args) {
            //1.使用 ApplicationContext 接口,就是在獲取 spring 容器
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            // 2.根據 bean 的 id 獲取對象
            IAccountService aService = (IAccountService) ac.getBean("accountService");
            System.out.println(aService);
    
            IAccountDao aDao = (IAccountDao) ac.getBean("accountDao");
            System.out.println(aDao);
    
        }
    }
    

    在這裏插入圖片描述

2. ApplicationContext 的三個常用實現類

  • ClassPathXmlApplicationContext
    它可以加載類路徑下的配置文件,要求配置文件必須在類路徑下。
 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
  • FileSystemXmlApplicationContext
    它可以加載磁盤任意路徑下的配置文件(必須有訪問權限)
ApplicationContext ac = new FileSystemXmlApplicationContext("E:\\Codes\\Spring-Programs\\spring01\\src\\main\\resources\\bean.xml");
  • AnnotationConfigApplicationContext
    它是用於讀取註解創建容器的,見後續博客

3. BeanFactory和 ApplicationContext 的區別

  • ApplicationContext 單例對象適用 (一般採用此接口,Spring可根據配置文件智能決定創建對象的方式)

    ​ 它在創建容器時,創建對象採取的策略是採用立即加載的方式,也就是說,:只要一讀取配置文件,默認情況下就馬上會創建對象。

    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");

    執行完這條語句即讀取配置文件之後對象就被創建了

  • BeanFactory 多例對象適用

    ​ 它在創建容器時,創建對象採取的策略是採用延遲加載的方式,也就是說,什麼時候根據id獲取對象了,什麼時候才真正創建對象

    //--------------BeanFactory-----------------
            Resource resource =  new ClassPathResource("bean.xml");
            BeanFactory factory = new XmlBeanFactory(resource);
            IAccountService aService = (IAccountService) factory.getBean("accountService");
            System.out.println(aService);
    

    執行到IAccountService aService = (IAccountService) factory.getBean("accountService");這條語句,即對象需要被使用的時候對象纔會被創建


四、IOC 中 bean 標籤和管理對象細節

1. 創建bean的三種方式

  • 第一種:使用默認構造函數創建

    在spring的配置文件中使用bean標籤,配以id和class屬性後,且沒有其他屬性和標籤時。採用的就是默認構造函數創建bean對象,此時如果 bean(類) 中沒有默認無參構造函數,將會創建失敗

    bean標籤:

    作用: 用於配置對象讓 spring 來創建的。 默認情況下它調用的是類中的無參構造函數。如果沒有無參構造函數則不能創建成功。

    屬性:

    id:給對象在容器中提供一個唯一標識。用於獲取對象。

    class:指定類的全限定類名。用於反射創建對象。默認情況下調用無參構造函數。

    scope:指定對象的作用範圍。 (見bean的生命週期)

    <bean id = "accountService" class = "com.smallbeef.service.impl.AccountServiceImpl"></bean>

  • 第二種:spring管理普通工廠- 使用普通工廠的方法創建對象(使用某個類中的方法創建對象,並存入spring容器)

  /** 
   * 模擬一個工廠類
   * 該類可能是存在於jar包中的,我們無法通過修改源碼的方式來提供默認構造函數
   * 此工廠創建對象,必須現有工廠實例對象,再調用方法  
   */ 
  public class InstanceFactory {   
      public IAccountService createAccountService(){   
          return new AccountServiceImpl();  
      }
}
  <bean id = "InstanceFactory" 
  		class = "com.smallbeef.factory.InstanceFactory">
  </bean>
  
  <bean id="accountService"  
  		factory-bean="InstanceFactory"     
  	 	factory-method="createAccountService">
  </bean>  

先把工廠的創建交給 spring 來管理。 然後在使用工廠的 bean 來調用裏面的方法

id 屬性:指定 bean 的 id,用於從容器中獲取

factory-bean 屬性:用於指定實例工廠 bean 的 id。

factory-method 屬性:用於指定實例工廠中創建對象的方法。

  • 第三種:spring管理靜態工廠-使用靜態工廠的方法創建對象 (使用某個類中的靜態方法創建對象,並存入spring容器)

    /** 
     * 模擬一個靜態工廠類
     * 該類可能是存在於jar包中的,我們無法通過修改源碼的方式來提供默認構造函數
     */ 
    public class StaticFactory {   
        public static IAccountService createAccountService(){   
            return new AccountServiceImpl();  
        } 
    } 
    
    <bean id="accountService"  
       	  class="com.itheima.factory.StaticFactory"     
          factory-method="createAccountService">
    </bean> 
    

2. bean對象的作用範圍

bean標籤的scope屬性:

​ 作用:指定bean的作用範圍

​ 取值:

  • singleton : 默認值,單例的. (bean對象默認是單例模式)

  • prototype : 多例的.

  • request : 作用於web應用的請求範圍。WEB 項目中,Spring 創建一個 Bean 的對象,將對象存入到 request 域中.

  • session : 作用於web應用的會話範圍。WEB 項目中,Spring 創建一個 Bean 的對象,將對象存入到 session 域中.

  • global-session :作用於集羣環境的會話範圍。WEB 項目中,應用在 Portlet(集羣) 環境.如果沒有 Portlet 環境那麼 globalSession 相當於 session.

全局session在這裏插入圖片描述

3. bean對象的生命週期

bean 標籤:

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

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

  • 單例對象:scope="singleton"

​ 一個應用只有一個對象的實例。它的作用範圍就是整個引用。

​ 生命週期:

  • 對象出生:當應用加載,創建容器時,對象就被創建了。
  • 對象活着:只要容器在,對象一直活着。
  • 對象死亡:當應用卸載,銷燬容器時,對象就被銷燬了。

總結: 單例對象的生命週期和容器相同

  • 多例對象:scope="prototype"

​ 每次訪問對象時,都會重新創建對象實例。

生命週期:

  • 對象出生:當使用對象時,創建新的對象實例。
  • 對象活着:只要對象在使用中,就一直活着。
  • 對象死亡:當對象長時間不用,且沒有別的對象引用時,由 java 的垃圾回收器進行回收。

五、spring的依賴注入 DI

1. 什麼是依賴注入

依賴注入:Dependency Injection。它是 spring 框架核心 ioc 的具體實現。

我們的程序在編寫時,通過控制反轉,把對象的創建交給了 spring,但是代碼中不可能出現沒有依賴的情況。 ioc 解耦只是降低他們的依賴關係,但不會消除。

例如:我們的業務層仍會調用持久層的方法。 那這種業務層和持久層的依賴關係,在使用 spring 之後,就讓 spring 來維護了。

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

  • IOC的作用:
    降低程序間的耦合(依賴關係)

  • 依賴關係的管理:
    都交給spring來管理。
    在當前類需要用到其他類的對象,由spring爲我們提供,我們只需要在配置文件種說明

  • 依賴關係的維護:就稱之爲依賴注入

  • 依賴注入:
    ​ a. 能注入的數據有三類:

    • 基本類型和String
    • 其他bean類型(在配置文件中或者註解配置過的bean)
    • 複雜類型/集合類型

    b. 注入的方式有三種:

    • 使用構造函數提供
    • 使用set方法提供
    • 使用註解提供(第二天內容)

2. 構造函數注入

顧名思義,就是使用類中的構造函數,給成員變量賦值。注意,賦值的操作不是我們自己做的,而是通過配置的方式,讓 spring 框架來爲我們注入。

  • 構造函數注入:
    ​ 使用的便籤:constructor-arg
    ​ 標籤出現的位置:bean標籤的內部
  • 標籤中的屬性:
    • index:指定要注入的數據在構造函數參數列表的索引位置 ,從0開始
    • type: 用於指定要注入的數據的數據類型,該數據類型也是構造函數中某個或某些參數的類型
    • name:用於給構造函數中指定名稱的參數賦值
    • value:它能賦的值是基本數據類型和 String 類型
    • ref:它能賦的值是其他 bean 類型,也就是說,必須得是在配置文件中或者註解中配置過的 bean

上面三個都是用於指定給誰賦值,下面兩個指的是賦什麼值的

具體代碼如下:

public class AccountServiceImpl implements IAccountService {    
    private String name;  
    private Integer age;  
    private Date birthday;     
    public AccountServiceImpl(String name, Integer age, Date birthday) {   
        this.name = name;   
        this.age = age;   
        this.birthday = birthday;  
    }

    @Override  
    public void saveAccount() {   
        System.out.println(name+","+age+","+birthday);   
    } 
} 
<bean id = "accountService" class = "com.smallbeef.service.impl.AccountServiceImpl">
        <constructor-arg name="name" value="test"></constructor-arg>
        <constructor-arg name = "age" value="20"></constructor-arg>
        <constructor-arg name = "birthday" ref="now"></constructor-arg>
    </bean>

    <!--配置一個日期對象
        讀取這個類名通過反射創建對象並存入spring容器中,我們可以通過id來使用它
    -->
    <bean id="now" class="java.util.Date"></bean>
public class Client {

    /**
     * 獲取spring的Ioc容器,並根據id獲取對象
     * @param args
     */
    public static void main(String[] args) {
        //1.使用 ApplicationContext 接口,就是在獲取 spring 容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        // 2.根據 bean 的 id 獲取對象
        IAccountService aService = (IAccountService) ac.getBean("accountService");
        aService.saveAccount();

    }
}
  • 構造函數的優點:
    在獲取bean對象時,注入數據是必須的操作,否則對象無法創建成功

  • 缺點:
    改變了bean對象的實例化方式,使我們在創建對象時,即使用不到這些數據時,也得給他們都賦值

2. set方法注入 常用

顧名思義,就是在類中提供需要注入成員的 set 方法。

  • 涉及的標籤:property

  • 出現的位置:bean標籤的內部

  • 標籤的屬性:

    • name: 指定注入時所調用的set方法名稱
    • value: 它能賦的值是基本數據類型和 String 類型
    • ref:它能賦的值是其他 bean 類型,也就是說,必須得是在配置文件中或者註解中配置過的 bean

具體代碼如下:

public class AccountServiceImpl implements IAccountService {
    private String name;
    private Integer age;
    private Date birthday;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public void saveAccount() {
        System.out.println(name+","+age+","+birthday);
    }
}
<bean id = "accountService" class = "com.smallbeef.service.impl.AccountServiceImpl">
        <property name="name" value="test"></property>
        <property name="age" value="20"></property>
        <property name="birthday" ref = "now"></property>
    </bean>

    <bean id="now" class="java.util.Date"></bean>
  • set注入的優勢:
    ​ 創建對象時沒有明確的限制,可以直接使用默認構造函數

  • 缺點:
    ​ 如果某個成員必須有值,則獲取對象時有可能set方法沒有執行

3. 集合類型的注入(本質還是set)

用於給list結構集合注入數據的標籤:list、array、set

用於給Map結構集合注入數據的標籤 : map、props

結構相同,標籤可以互換

public class AccountServiceImpl implements IAccountService {
    private String[] myStrs;
    private List<String> myList;
    private Set<String> mySet;
    private Map<String,String> myMap;
    private Properties myProps;

    public void setMyStrs(String[] myStrs) {
        this.myStrs = myStrs;
    }

    public void setMyList(List<String> myList) {
        this.myList = myList;
    }

    public void setMySet(Set<String> mySet) {
        this.mySet = mySet;
    }

    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap;
    }

    public void setMyProps(Properties myProps) {
        this.myProps = myProps;
    }

    @Override
    public void saveAccount() {
        System.out.println(Arrays.toString(myStrs));
        System.out.println(myList);
        System.out.println(mySet);
        System.out.println(myMap);
        System.out.println(myProps);
    }
}
<bean id = "accountService" class = "com.smallbeef.service.impl.AccountServiceImpl">

        <property name="myStrs">
            <array>
                <value>A</value>
                <value>B</value>
                <value>C</value>
            </array>
        </property>

        <property name="myList">
            <list>
                <value>A</value>
                <value>B</value>
                <value>C</value>
            </list>
        </property>

        <property name="mySet">
            <set>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </set>
        </property>

        <property name="myMap">
            <map>
                <entry key="testA" value="A"></entry>
                <entry key="testB">
                    <value>B</value>
                </entry>
            </map>
        </property>

        <property name="myProps">
            <props>
                <prop key="testC">C</prop>
                <prop key="testD">D</prop>
            </props>
        </property>

    </bean>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章