本教程源碼請訪問: "tutorial_demo" 上篇教程我們學習瞭如何使用工廠模式解耦,把對象的創建由程式員交給自定義的工廠類,在這篇教程我們將學到如何使用Spring的IOC解決程式的耦合問題。 一、什麼是IOC IOC:Inversion of Control,控制反轉,將創建對象的權力交給 ...
本教程源碼請訪問:tutorial_demo
上篇教程我們學習瞭如何使用工廠模式解耦,把對象的創建由程式員交給自定義的工廠類,在這篇教程我們將學到如何使用Spring的IOC解決程式的耦合問題。
一、什麼是IOC
IOC:Inversion of Control,控制反轉,將創建對象的權力交給框架。過去創建對象由開發人員通過new的方式創建,有了IOC之後,開發人員不需要new了,只需要從Spring容器(我們可以認為是保存對象的容器)中獲取就可以了,創建對象的控制權發生了轉移,由開發人員轉移給了Spring容器或者說轉移給了Spring框架。這種控制權的轉移,我們稱之為控制反轉。
目的:減少電腦程式的耦合,解除代碼之間的依賴關係。
二、使用IOC(第一個Spring程式)
2.1、創建項目
-
在Idea中新建Maven工程;
-
工程創建完成後添加相應的坐標。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>ioc</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.6.RELEASE</version> </dependency> </dependencies> </project>
2.2、添加相關類
2.2.1、創建持久層介面
package org.codeaction.dao;
public interface IAccountDao {
void saveAccount();
}
2.2.2、創建持久層介面實現類
package org.codeaction.dao.impl;
import org.codeaction.dao.IAccountDao;
public class AccountDaoImpl implements IAccountDao {
@Override
public void saveAccount() {
System.out.println("賬戶保存成功");
}
}
2.2.3、創建業務層介面
package org.codeaction.service;
public interface IAccountService {
void saveAccount();
}
2.2.4、創建業務層介面實現類
package org.codeaction.service.impl;
import org.codeaction.dao.IAccountDao;
import org.codeaction.dao.impl.AccountDaoImpl;
import org.codeaction.service.IAccountService;
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl();
@Override
public void saveAccount() {
accountDao.saveAccount();
}
}
註意這個類的實現,本教程最後會說明。
2.3、添加XML配置文件
XML文件在resource目錄下。
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--把對象的創建交給spring來管理-->
<bean id="accountDao" class="org.codeaction.dao.impl.AccountDaoImpl"></bean>
<bean id="accountService" class="org.codeaction.service.impl.AccountServiceImpl"></bean>
</beans>
bean標簽作用:配置讓Spring創建對象。預設情況下調用無參構造函數,如果沒有無參構造函數則不能創建成功。
bean標簽屬性:
- id:為對象在容器中提供一個唯一標識,用於獲取對象;
- class:指定類的全限定類名,用於反射創建對象,預設情況下調用無參構造函數;
- scope:指定對象的作用範圍,預設是singleton(單例),3.1中會講到;
- init-method:指定類中的初始化方法名稱;
- destroy-method:指定類中銷毀方法名稱。
2.4、添加測試類
創建帶有main方法的類,用來進行測試
package org.codeaction.ui;
import org.codeaction.dao.IAccountDao;
import org.codeaction.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AccountUI {
public static void main(String[] args) {
//1.獲取核心容器對象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//2.根據id獲取Bean對象,這個id是在bean標簽中配置的id
IAccountService accountService = (IAccountService) context.getBean("accountService");
IAccountDao accountDao = context.getBean("accountDao", IAccountDao.class);
System.out.println(accountService);
System.out.println(accountDao);
accountService.saveAccount();
}
}
運行main方法,控制台輸出如下:
org.codeaction.service.impl.AccountServiceImpl@754ba872
org.codeaction.dao.impl.AccountDaoImpl@146ba0ac
賬戶保存成功
2.4.1、BeanFactory和ApplicationContext的區別
- BeanFactory是Spring容器中的頂層介面,ApplicationContext是它的子介面;
- ApplicationContext只要一讀取配置文件,預設情況下就會創建對象;
- BeanFactory什麼時候使用時候創建對象;
- ApplicationContext用來創建單例對象,BeanFactory用來創建多例對象。
2.4.2、ApplicationContext介面的實現類
- ClassPathXmlApplicationContext可以載入類路徑下的配置文件,要求配置文件必須在類路徑下,不在的話,載入不了。
- FileSystemXmlApplicationContext可以載入磁碟任意路徑下的配置文件(必須有訪問許可權)
- AnnotationConfigApplicationContext用於讀取註解創建容器的,後面的文章會講到。
三、深入說明
3.1、bean的作用範圍和生命周期
bean的作用範圍由bean標簽中的scope屬性設置,scope屬性可以有如下值:
- singleton:預設值,單例的;
- prototype:多例的;
- request:web項目中,Spring創建一個Bean的對象,將對象存入到request域中;
- session:web項目中,Spring創建一個Bean的對象,將對象存入到session域中;
- global session:web項目中,應用在Portlet環境。如果沒有Portlet環境那麼globalSession相當於session。
下麵我們重點說一下singleton和prototype。
3.1.1、singleton
一個應用只有一個對象的實例,它的作用範圍就是整個應用。
生命周期:
- 對象出生:當應用載入,創建容器時,對象就被創建了;
- 對象活著:只要容器在,對象一直活著;
- 對象死亡:當應用卸載,銷毀容器時,對象就被銷毀了。
3.1.2、prototype
每次訪問對象時,都會重新創建對象實例。
生命周期:
- 對象出生:當使用對象時,創建新的對象實例;
- 對象活著:只要對象在使用中,就一直活著;
- 對象死亡:當對象長時間不用時,被Java的垃圾回收器回收了。
3.1.3、案例(代碼基於第一個Spring程式)
3.1.3.1、修改業務層實現類
添加init和destroy方法
package org.codeaction.dao.impl;
import org.codeaction.dao.IAccountDao;
public class AccountDaoImpl implements IAccountDao {
@Override
public void saveAccount() {
System.out.println("賬戶保存成功");
}
public void init() {
System.out.println("dao init");
}
public void destroy() {
System.out.println("dao destroy");
}
}
3.1.3.2、修改持久層實現類
添加init和destroy方法
package org.codeaction.dao.impl;
import org.codeaction.dao.IAccountDao;
public class AccountDaoImpl implements IAccountDao {
@Override
public void saveAccount() {
System.out.println("賬戶保存成功");
}
public void init() {
System.out.println("dao init");
}
public void destroy() {
System.out.println("dao destroy");
}
}
3.1.3.3、修改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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--把對象的創建交給spring來管理-->
<!--配置bean時指定初始化和銷毀方法及作用範圍-->
<bean
id="accountDao"
class="org.codeaction.dao.impl.AccountDaoImpl"
scope="singleton"
init-method="init"
destroy-method="destroy"></bean>
<bean
id="accountService"
class="org.codeaction.service.impl.AccountServiceImpl"
scope="prototype"
init-method="init"
destroy-method="destroy"></bean>
</beans>
3.1.3.4、修改測試類
package org.codeaction.ui;
import org.codeaction.dao.IAccountDao;
import org.codeaction.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AccountUI {
public static void main(String[] args) {
//1.獲取核心容器對象
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//2.根據id獲取Bean對象,這個id是在bean標簽中配置的id
IAccountService accountService1 = (IAccountService) context.getBean("accountService");
IAccountDao accountDao1 = (IAccountDao) context.getBean("accountDao");
IAccountService accountService2 = (IAccountService) context.getBean("accountService");
IAccountDao accountDao2 = (IAccountDao) context.getBean("accountDao");
System.out.println("accountDao1 == accountDao2 ? " + (accountDao1 == accountDao2));
System.out.println("accountService1 == accountService2 ? " + (accountService1 == accountService2));
//容器銷毀
context.close();
}
}
單步調試該程式,輸出如下:
dao init
service init
service init
accountDao1 == accountDao2 ? true
accountService1 == accountService2 ? false
dao destroy
通過輸出我們驗證了:
- singleton的bean對象在容器中只會創建一次,並且創建容器時,就被創建了;
- prototype的bean對象在容器中能夠創建多次,當使用時,就創建新的對象;
- singleton的bean對象在容器銷毀時,也被銷毀;
- prototype的bean對象會被垃圾回收(通過控制台觀察不到)。
3.2、實例化bean的三種方式
3.2.1、三種方式說明
- 使用預設無參構造函數;
- 使用靜態工廠的方法創建對象;
- 使用實例工廠的方法創建對象。
3.2.2、案例(代碼基於第一個Spring程式)
3.2.2.1、創建靜態工廠類
package org.codeaction.factory;
import org.codeaction.service.IAccountService;
import org.codeaction.service.impl.AccountServiceImpl;
/**
* 模擬一個靜態工廠,創建業務層實現類
*/
public class StaticFactory {
public static IAccountService createAccountService() {
return new AccountServiceImpl();
}
}
3.2.2.2、創建實例工廠類
package org.codeaction.factory;
import org.codeaction.service.IAccountService;
import org.codeaction.service.impl.AccountServiceImpl;
/**
* 模擬一個實例工廠,創建業務層實現類
* 此工廠創建對象,必須現有工廠實例對象,再調用方法
*/
public class InstanceFactory {
public IAccountService createAccountService(){
return new AccountServiceImpl();
}
}
3.2.2.3、修改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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 方式1:使用預設的無參構造方式 -->
<bean id="accountService1" class="org.codeaction.service.impl.AccountServiceImpl"></bean>
<!--
方式2:使用靜態工廠的方法創建對象
id屬性:指定bean的id,用於從容器中獲取
class屬性:指定靜態工廠的全限定類名
factory-method屬性:指定生產對象的靜態方法
-->
<bean
id="accountService2"
class="org.codeaction.factory.StaticFactory"
factory-method="createAccountService"></bean>
<!--
方式3:使用實例工廠的方法創建對象
先把工廠的創建交給spring來管理。
然後在使用工廠的bean來調用裡面的方法。
factory-bean屬性:用於指定實例工廠bean的id。
factory-method屬性:用於指定實例工廠中創建對象的方法。
-->
<bean id="factory" class="org.codeaction.factory.InstanceFactory"></bean>
<bean id="accountService3" factory-bean="factory" factory-method="createAccountService"></bean>
</beans>
3.2.2.4、修改測試類
package org.codeaction.ui;
import org.codeaction.dao.IAccountDao;
import org.codeaction.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AccountUI {
public static void main(String[] args) {
//1.獲取核心容器對象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//2.根據id獲取Bean對象,這個id是在bean標簽中配置的id
IAccountService accountService1 = (IAccountService) context.getBean("accountService1");
IAccountService accountService2 = (IAccountService) context.getBean("accountService2");
IAccountService accountService3 = (IAccountService) context.getBean("accountService3");
System.out.println(accountService1);
System.out.println(accountService2);
System.out.println(accountService3);
}
}
運行測試類,控制台輸出如下:
org.codeaction.service.impl.AccountServiceImpl@42dafa95
org.codeaction.service.impl.AccountServiceImpl@6500df86
org.codeaction.service.impl.AccountServiceImpl@402a079c
四、目前存在的問題
通過這篇教程的學習,我們對IOC有了一個初步的認識。通過IOC將對象創建的權力交給Spring容器,實現控制權的轉移。本篇教程2.2.4中,創建業務層介面的實現類,裡面的屬性依然使用new對象的方式賦值,在這裡依然沒有解耦,service和dao的對象依然攪在一起,那麼怎麼解決這個問題呢?下一篇我們將學習DI(依賴註入),DI就可以解決目前存在的問題。