學習Spring的小夥伴一定對IoC容器或多或少有些瞭解了,IoC總結爲一句話就是爲了方便解耦。我們都知道Java中要使用類必須要先new對象,但是在開發中這樣做對話會增加程序之間的耦合度,使得程序的維護成本直線上升。
所以有了IoC容器我們可以將對象之間的依賴關係交由Spring進行控制,避免硬編碼所造成的過渡程序耦和。用戶也不必再爲單例模式類、屬性文件解析等這些很底層的需求編寫代碼,可以更專注於上層的應用。
在com.user.factory下我們可以創建一個BeanFactory的類(com.user.dao和com.user.service中的接口及其實現類這邊省略不談)。Bean在計算機英語中,有可重用組件的含義。而JavaBean就用java語言編寫的可重用組件。實際上javabean ≠ 實體類,很初學者認爲javabean就是實體類,但是實際上是javabean > 實體類。在我們的Spring項目中,它就是創建我們的service和dao對象的。在Spring的IoC中,我們可以通過配置文件來進行對象之間依賴的管理,配置文件可以是xml也可以是properties,這邊我選擇的是properties。所以對於解耦的思路是:
- 需要一個配置文件來配置我們的service和dao,配置的內容:唯一標識=全限定類名(key=value)
- 通過讀取配置文件中配置的內容,反射創建對象
public class BeanFactory {
//定義一個Properties對象
private static Properties props;
//使用靜態代碼塊爲Properties對象賦值
static {
try {
//實例化對象
props = new Properties();
//獲取properties文件的流對象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
}
}catch(Exception e){
throw new ExceptionInInitializerError("初始化properties失敗!");
}
}
/**
* 根據Bean的名稱獲取bean對象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
Object bean = null;
try {
String beanPath = props.getProperty(beanName);
bean = Class.forName(beanPath).newInstance(); //用反射的方式創建對象,每次都會調用默認構造函數創建對象
}catch(Exception e){
e.printStackTrace();
}
return bean;
}
}
下面展示的是bean.properties中的內容,該文件存放在resources文件夾中,有唯一標誌符和全限定類名組成。
accountService = com.itheima.service.impl.AccountServiceImpl
acoountDao = com.itheima.dao.impl.AccountDaoImpl
這樣做的話就可以減少程序之間的耦合(只能減少,不可能避免,new有的地方是必須的)。可以通過private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("acoountDao");
來創建對象,避免使用private IAccountDao accountDao = new AccountDaoImpl();
,即通過new來創建對象,從而減少了程序之間的耦合度。
但是這樣做存在的問題是bean = Class.forName(beanPath).newInstance();
通過反射機制實例化對象時,每次都會調用默認構造函數創建對象,即每次訪問時都會創建一個對象,這樣就變成了多例對象,這並不是我們想要的。直觀的,我們通過在control層調用:
for(int i=0;i<5;i++){
IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
System.out.println(as);
}
打印出來控制檯的結果爲,可以看到這事多例對象:
com.user.service.impl.AccountServiceImpl@610455d6
com.user.service.impl.AccountServiceImpl@511d50c0
com.user.service.impl.AccountServiceImpl@60e53b93
com.user.service.impl.AccountServiceImpl@5e2de80c
com.user.service.impl.AccountServiceImpl@1d44bcfa
簡要比較下單例和多例對象:
- 多例對象:對象被創建多次,執行效率沒有單例對象高,不存在線程問題
- 單例對象:只被創建一次,從而類中的成員也就只會初始化一次。但是存在線程問題
而我們在工程中想要的是單例對象。解決的思路就是創建一個容器來保存我們初識創建的對象,在之後的過程中每次使用到這個對象時可以直接到容器中去取,所用到的容器就是Map。
下面展示修改過後的代碼:
public class BeanFactory {
//定義一個Properties對象
private static Properties props;
//定義一個Map,用於存放我們要創建的對象。我們把它稱爲容器
//這樣就能保證對象只被創建了一次,就是單例的了
private static Map<String,Object> beans;
//使用靜態代碼塊爲Properties對象賦值
static {
try {
//實例化對象
props = new Properties();
//獲取properties文件的流對象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
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(Exception e){
throw new ExceptionInInitializerError("初始化properties失敗!");
}
}
/**
* 根據bean的名稱獲取對象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
return beans.get(beanName);
}
}
這樣,我們就構造出來單例對象,控制檯打印信息如下所示:
com.user.service.impl.AccountServiceImpl@610455d6
com.user.service.impl.AccountServiceImpl@610455d6
com.user.service.impl.AccountServiceImpl@610455d6
com.user.service.impl.AccountServiceImpl@610455d6
com.user.service.impl.AccountServiceImpl@610455d6
補充:
- servlet是單例的
- 我們在service和dao的開發中,一般不定義成員變量,而是將變量定義到成員方法中對其進行操控修改