1. 程式人生 > >Spring入門 --- IOC

Spring入門 --- IOC

配置環境

我們在開發中,會寫很多的類,而有些類之間不可避免的產生依賴關係,這種依賴關係稱之為耦合。有些依賴關係是必須的,有些依賴關係可以通過優化程式碼來解除的。請看下面的示例程式碼:

1 建立maven工程;

2 匯入必要的依賴

<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </
dependency
>
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId
>
</dependency> </dependencies>

2.1 引入log4j的配置檔案

### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c:/mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###

log4j.rootLogger=debug, stdout

3 建立UserDao介面

public interface UserDao {
   void save();
}

4 建立UserDao介面的實現類UserDaoImpl

public class UserDaoImpl implements UserDao {

   @Override
   public void save() {
      System.out.println("持久層:使用者儲存...");
   }

}

5 建立 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="userDao" class="cn.cosco.impl.UserDaoImpl"></bean>

</beans>

6 建立測試類

 	@Test
    public void test1(){
        //建立Spring工廠(建立IOC容器)
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");//1
        UserDao userDao  = (UserDao) ac.getBean("userDao");//2
        userDao.save();
    }

測試結果:

控制檯輸出如下結果:

持久層:使用者儲存...

測試發現:我們可以從spring容器中獲取物件,但是,這個物件是如何創建出來的呢?

這其實是利用了 Spring IOC 方法。

Spring IOC

1 什麼是 Spring IOC

IOC,它是Inverse of Control的縮寫,中文含義是控制反轉,表示將物件的建立權力反轉給Spring框架!!

IOC解決的問題:使用IOC可以解決的程式耦合性高的問題!!

2 Spring中的工廠

在spring中提供了兩個工廠介面:

1、ApplicationContext

2、BeanFactory

2.1 ApplicationContext介面

使用該介面可以獲取到具體的Bean物件

該介面下有兩個具體的實現類

ClassPathXmlApplicationContext – 載入類路徑下的Spring配置檔案 FileSystemXmlApplicationContext – 載入本地磁碟下的Spring配置檔案

下面演示FileSystemXmlApplicationContext的用法:把src下的applicationContext.xml拷貝到你電腦的某個目錄,例如:c:/spring,可以通過FileSystemXmlApplicationContext載入本地磁碟下的spring配置檔案

public class TestIOC {
    @Test
    public void test1(){
        //建立Spring工廠(建立IOC容器)
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao  = (UserDao) ac.getBean("userDao");
        userDao.save();
    }

    @Test
    public void test2(){
        //建立Spring工廠(建立IOC容器)
        ApplicationContext ac = new FileSystemXmlApplicationContext("C:/spring/applicationContext.xml");
        UserDao userDao  = (UserDao) ac.getBean("userDao");
        userDao.save();
    }

}

2.2 BeanFactory工廠 ---- 已過時

BeanFactory是Spring框架早期的建立Bean物件的工廠介面。在TestIOC中建立test3測試方法:

/**
 * 早期版本的spring工廠:BeanFactory
 */
@Test
public void test3() {
    //此方法已經過時
    BeanFactory factory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
    UserDao userDao = (UserDao) factory.getBean("userDao");
    userDao.save();

} 

2.3 BeanFactory和ApplicationContext的區別 :

BeanFactory                – BeanFactory採取延遲載入,第一次getBean時才會初始化Bean

ApplicationContext        – 在載入applicationContext.xml時候就會建立具體的Bean物件的例項

修改UserDaoImpl,增加一個無參構造方法,執行test1和test3兩個單元測試方法,驗證ApplicationContext和BeanFactory這兩個工廠到底什麼時候建立bean物件??

public class UserDaoImpl implements UserDao {

   public UserDaoImpl() {
      System.out.println("呼叫了無參構造方法...");
   }


   @Override
   public void save() {
      System.out.println("持久層:使用者儲存...");
   }

}

修改test1單元測試方法:

/**
 * 建立ioc容器時就已經把物件建立好了
 */
@Test
public void test1(){
    //建立Spring工廠(建立IOC容器)
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    System.out.println("===============");
    UserDao userDao  = (UserDao) ac.getBean("userDao");
    userDao.save();
}

執行結果:

呼叫了無參構造方法...
===============

執行test1方法,發現:ApplicationContext是在建立ioc容器時就已經把物件建立好了

修改test3單元測試方法:

/**
 * 早期版本的spring工廠:BeanFactory
 * 建立ioc容器時並沒有建立物件,而是在第一次getBean時再建立物件
 */
@Test
public void test3() {
    BeanFactory factory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
    System.out.println("===============");
    UserDao userDao = (UserDao) factory.getBean("userDao");
    userDao.save();

}

執行結果:

===============
呼叫了無參構造方法...

執行test3方法,發現:BeanFactory是建立ioc容器時並沒有建立物件,而是在第一次getBean時再建立物件

2.4 Spring配置檔案 解析 ----- 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="userDao" class="cn.cosco.impl.UserDaoImpl"></bean>

</beans>

2.4.1 id屬性

id屬性是bean的唯一標識

2.4.2 class屬 性

javaBean 的全路徑名

其實,除了 id 屬性和 class 屬性外,還可以配置如下屬性:

2.4.3 scope屬性

scope屬性代表Bean的作用範圍

singleton: 單例(預設值)

prototype: 多例,在Spring框架整合Struts2框架的時候,Action 類也需要交給 Spring 做管理,配置把 Action 類配置成多例!!

request: 應用在 web 工程中,將建立的物件存入到 request 域中。

session: 應用在 web 工程中,將建立的物件存入到 session 域中。

globalsession: 應用在 porlet 環境下使用。將建立的物件存入到全域性的 session 中。

後三種比較少見。

在TestIOC中建立單元測試方法test4:

@Test
public void test4() {
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserDao userDao1 = (UserDao) ac.getBean("userDao");
    UserDao userDao2 = (UserDao) ac.getBean("userDao");
    System.out.println(userDao1==userDao2);

}

執行結果:

呼叫了無參構造方法...
true

測試發現:當scope為 singleton 時,無論例項化幾次 javaBean 物件,都只會建立一次。

修改 applicationContext.xml,把 UserDaoImpl 的作用域改為 prototype:

<?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="userDao" class="cn.cosco.dao.impl.UserDaoImpl" scope="prototype"></bean>
</beans>

執行結果:

呼叫了無參構造方法...
呼叫了無參構造方法...
false

測試發現:當scope為prototype時,每次獲取bean,都會重新例項化

2.4.4 init-method屬性

當bean被載入到容器的時候呼叫init-method屬性指定的方法

修改 UserDaoImpl ,在其中提供 init 方法

public class UserDaoImpl implements UserDao {

   public UserDaoImpl() {
      System.out.println("呼叫了無參構造方法...");
   }

   public void init(){
      System.out.println("呼叫了init方法...");
   }

   @Override
   public void save() {
      System.out.println("持久層:使用者儲存...");
   }

}

修改 applicationContext.xml,為 UserDaoImpl 指定初始化方法

<?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="userDao" class="cn.cosco.dao.impl.UserDaoImpl" init-method="init"></bean>
</beans>

執行 test1 單元測試方法,測試結果:

呼叫了無參構造方法...
呼叫了init方法...
===============
持久層:使用者儲存...

2.4.5 destroy-method屬性

當bean從容器中刪除的時候呼叫destroy-method屬性指定的方法

想檢視destroy-method的效果,有如下條件:

scope= singleton 有效 

web容器中會自動呼叫,但是 main 函式或測試用例需要手動呼叫(需要使用 ClassPathXmlApplicationContext 的close() 方法) 

在applicationContext.xml中為UserDaoImpl指定銷燬方法

<?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="userDao" class="cn.cosco.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"></bean>
</beans>

修改UserDaoImpl,在其中提供destroy方法

public class UserDaoImpl implements UserDao {

   public UserDaoImpl() {
      System.out.println("呼叫了無參構造方法...");
   }

   public void init(){
      System.out.println("呼叫了init方法...");
   }


   @Override
   public void save() {
      System.out.println("持久層:使用者儲存...");
   }

   public void destroy(){
      System.out.println("呼叫了銷燬方法...");
   }


}

修改test1單元測試方法,顯示關閉ioc容器。測試結果如下:

呼叫了無參構造方法...
呼叫了init方法...
===============
持久層:使用者儲存...
呼叫了銷燬方法...

3 Spring 生成bean的三種方式

3.1 無參構造方法

預設呼叫無參構造方法例項化bean。在此之前,都是呼叫無參構造來例項化的。

3.2 靜態工廠例項化方式

通過呼叫工廠類的靜態方法來生成bean

編寫DeptDao介面:

public Interface DeptDaoImpl {
    public void save();
}

編寫DeptDaoImpl實現類:

public class DeptDaoImpl implements DeptDao{
    @Override
    public void save() {
        System.out.println("持久層:部門儲存...");
    }
}

編寫工廠類,在其中建立靜態工廠方法

public class Factory {
    /**
     * 靜態工廠方法
     */
    public static DeptDao create(){
        System.out.println("呼叫了靜態工廠方法");
        return new DeptDaoImpl();
    }

}

編寫applicationContext.xml配置檔案,採用靜態工廠方式配置DeptDaoImpl類

<?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="userDao" class="cn.cosco.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"></bean>
    <bean id="deptDao" class="cn.cosco.factory.Factory" factory-method="create"></bean>
</beans>

在配置DeptDaoImpl這個bean時,class屬性寫的不是DeptDaoImpl的全路徑名,而是工廠類的全路徑名;

factory-method:指定工廠類中靜態方法的名字

在TestIOC類中編寫測試方法test5:

@Test
public void test5(){
    //建立Spring工廠(建立IOC容器)
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    DeptDao deptDao = (DeptDao) ac.getBean("deptDao");
    deptDao.save();

}

測試結果如下:

呼叫了靜態工廠方法
持久層:部門儲存...

3.3 例項工廠例項化方式

修改Factory工廠類,建立例項工廠方法:

public class Factory {
    /**
     * 靜態工廠方法
     */
//    public static DeptDao create(){
//        System.out.println("呼叫了靜態工廠方法");
//        return new DeptDaoImpl();
//    }

    /**
     * 例項工廠方法
     * @return
     */
    public  DeptDao create(){
        System.out.println("呼叫了例項工廠方法");
        return new DeptDaoImpl();
    }


}

編寫applicationContext.xml,採用例項工廠方式重寫配置DeptDaoImpl

<?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="userDao" class="cn.cosco.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"></bean>
    <!-- 例項工廠方法來例項化 -->
    <bean id="factory" class="cn.cosco.factory.Factory"></bean>
    <bean id="deptDao" factory-bean="factory" factory-method="create"></bean>
</beans>

factory-bean:指定工廠bean的id;

Factory-method:指定工廠bean的例項工廠方法的名字

執行test5測試方法,測試結果如下:

呼叫了例項工廠方法
持久層:部門儲存...

4 注入依賴

4.1 什麼是注入依賴

IOC和DI的概念: ​ * IOC – Inverse of Control,控制反轉,將物件的建立權反轉給Spring!! ​ * DI – Dependency Injection,依賴注入,在Spring框架負責建立Bean物件時,動態的將依賴物件注入到Bean元件中!!

如果UserServiceImpl的實現類中有一個屬性,那麼使用Spring框架的IOC功能時,可以通過依賴注入把該屬性的值傳入進來!!

4.2 構造方法注入

什麼是構造方法注入?構造方法注入就是利用bean的構造方法完成對bean中屬性的賦值。

建立Car實體類,提供有參構造方法

public class Car implements Serializable{
    private String name;
    private Double price;

    public Car(String name, Double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {

        return name;
    }

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

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}

建立applicationContext2.xml,在applicationContext2.xml配置Car這個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="car" class="cn.cosco.domain.Car">
       <constructor-arg name="name" value="蘭博基尼"></constructor-arg>
       <constructor-arg name="price" value="3000000.0"></constructor-arg>
   </bean>
</beans>

建立單元測試類TestDI,在其中建立單元測試方法test1測試,注意此處建立IOC容器時,應該載入applicationContext2.xml

public class TestDI {
    
    @Test
    public void test1(){
        //建立Spring工廠(建立IOC容器)
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext2.xml");
        Car car = (Car) ac.getBean("car");
        System.out.println(car);
    }

}

執行結果:

Car{name='蘭博基尼', price='3000000.0'}

4.3 set方法注入

什麼是set方法注入?set方法注入就是利用bean中屬性的set方法對屬性賦值。

建立People實體類,提供屬性的set方法,不需要提供有參構造方法。

public class People implements Serializable{
    private String name;//要提供屬性所對應的set方法
    private String address;
    private Car car;//物件屬性

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

    public void