1.耦合概念以及解耦
耦合: 簡單來說就是指程序間的依賴關係。一般包括:類之間的關係,方法間的關係。
解耦: 目的是爲了降低程序間的依賴關係。實際開發中應該做到:編譯時不依賴,運行時才依賴。
1.1.兩個例子:JDBC連接以及持久層業務層調用關係
創建一個demo類,在不導入mysql-connector-java.jar的情況下,執行程序。
(在導入jar包後,都可正常運行)
嘗試通過Class.forName(“驅動類全類名”)(反射機制)獲取驅動類:此時即使驅動類不存在,也不會在編譯的時候報錯
持久層和業務層的調用關係:此時若不存在持久層類,將無法通過編譯,這就是編譯期依賴。
1.2.解耦的思路:通過工廠模式解耦(手動實現)
用工廠模式,通過反射機制,創建對象
第一步: 創建一個配置文件來配置我們的service和dao。內容爲:唯一標識 = 全限定類名(key - value)
第二步: 通過讀取配置文件中配置的內容,通過反射創建對象。
配置文件:
手寫的BeanFactory工廠類(實現單例模式):
PS:單例模式與多例模式:
單例模式: 多次創建(new)同一對象時,爲相同的對象,擁有相同的地址。
多例模式: 多次創建(new)同一對象時,爲不同的對象,擁有的地址不同。
本例實現單例模式的思路: 在類中新建一個Map用於存儲<String,Object>,在第一次運行的時候,將配置文件中的多個對象一次性創建並且加到Map中,之後在調用getBean方法的時候通過傳入的類名從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的名稱獲取對象
* 且從Map中獲取對象,實現了單例模式
* */
public static Object getBean(String beanName) {
return beans.get(beanName);//通過類名找到對應的對象地址
}
}
業務層使用getBean()方法從工廠中獲取對應類名的對象。
工廠模式優點:
1、一個調用者想創建一個對象,只要知道其名稱就可以了。
2、擴展性高,如果想增加一個產品,只要擴展一個工廠類就可以。
3、屏蔽產品的具體實現,調用者只關心產品的接口。
工廠模式缺點:
每次增加一個產品時,都需要增加一個具體類和對象實現工廠,使得系統中類的個數成倍增加,在一定程度上增加了系統的複雜度,同時也增加了系統具體類的依賴。這並不是什麼好事。
2.使用SpringIOC解決耦合(將對象的創建交給spring)
使用spring的方法獲取類,不再需要自己寫工廠。
2.1.基本實現
1.創建一個maven工程。pom.xml導入spring相關jar包
2.創建xml配置文件
導入spring約束,配置唯一標識id與相應類的對應關係。使得可以根據id獲得對應的對象。
3.創建測試類:從上到下代表了持久層,業務層,表現層的調用關係。
包和類的結構如下:
持久層:
/**
* 賬戶的持久層實現類
**/
public class AccountDaoImpl implements IAccountDao {
public void saveAccount() {
System.out.println("保存了賬戶");
}
}
/**
* 持久層接口
**/
public interface IAccountDao {
/*
* 模擬保存賬戶
* */
void saveAccount();
}
業務層:
/**
* 業務層實現類
**/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl();
public void saveAccount() {
accountDao.saveAccount();
}
}
/**
* 業務層的接口
**/
public interface IAccountService {
/**
* 模擬保存賬戶
*/
void saveAccount();
}
表現層:
/**
* 表現層調用業務層,業務層調用持久層
* 模擬一個表現層,用於調用業務層
**/
public class Client {
/**
* 獲取spring的Ioc核心容器,並根據id獲取對象
* **/
public static void main(String[] args) {
//1,獲取核心容器對象。通過容器創建對象.通過核心容器的getBean()方法獲取具體對象.
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根據id獲取bean對象
IAccountService as = (IAccountService) ac.getBean("accountService");//根據唯一標識獲得對象
System.out.println(as);//嘗試輸出對象,默認情況爲單例模式
}
}
2.1.1.ApplicationContext的三個實現類
ClassPathXmlApplicationContext: 它可以加載類路徑下的配置文件,要求配置文件必須在類路徑下,不在的話,加載不了。
FileSystemXmlApplicationContest: 它可以加載磁盤任意路徑下的配置文件(必須有訪問權限)
AnnotationConfigApplicationContest: 讀取註解創建容器。
2.1.2.核心容器BeanFactory和ApplicationContext有什麼區別
ApplicationContext: 它在構建核心容器時,創建對象採取的策略是採用立即加載的方式。也就是說,只要一讀取完配置文件馬上就創建文件中配置的對象。(單例對象適用)
BeanFactory: 它在構建核心容器時,創建對象採取的策略是採用延遲加載的方式。也就是說,什麼時候根據id獲取對象了,什麼時候才真正的創建對象。(多例對象適用)
PS:沒有特殊要求的情況下,應該使用ApplicationContext完成。因爲BeanFactory能完成的事情,ApplicationContext都能完成,並且提供了更多接近現在開發的功能。
3.XML文件的配置
使用配置文件實現IOC,要將託管給spring的類寫進bean.xml配置文件中。
3.1.bean標籤
作用: 配置託管給spring的對象,默認情況下調用類的無參構造函數,若果沒有無參構造函數則不能創建成功。
屬性:
1.id: 指定對象在容器中的標識,將其作爲參數傳入getBean()方法可以獲取獲取對應對象.
2.class: 指定類的全類名,默認情況下調用無參構造函數。
3.scope: 指定對象的作用範圍,可選值如下:
3.1.singleton:單例模式。(默認)
3.2.prototype: 多例模式。
3.3.request: 將對象存入到web項目的request域中。
3.4.session: 將對象存入到web項目的session域中。
3.5.global session: 作用於集羣環境的會話範圍(全局會話範圍),當不是集羣環境時,他就是session
4.init-method: 指定類中的初始化方法名稱,在對象創建成功之後執行
5.destroy-method: 指定類中銷燬方法名稱,對prototype多例對象沒有作用,因爲多利對象的銷燬時機不受容器控制。
3.2.bean的作用範圍和生命週期
1.單例對象: scope="singleton"
作用範圍: 每個應用只有一個該對象的實例,它的作用範圍就是整個應用。
生命週期: 單例對象的創建與銷燬 和 容器的創建與銷燬時機一致。
-------------出生:當容器創建時,對象出生。
-------------活着:只要容器存在,對象一直活着。
-------------死亡:容器銷燬,對象消亡
2.多例對象: scope="prototype"
作用範圍: 每次訪問對象時,都會重新創建對象實例。
生命週期: 多例對象的創建與銷燬時機不受容器控制。
-------------出生:當我們使用對象時,spring框架爲我們創建。
-------------活着:對象只要是在使用過程中就一直活着。
-------------死亡:當對象長時間不用且沒有別的對象引用時,由java的垃圾回收器回收。
3.3.實例化bean的三種方式
1.構造器實例化: 用默認構造函數創建。
在spring的配合文件中使用bean標籤,配以id和class屬性之後,且沒有其他屬性和標籤時。
採用的就是默認構造函數的bean對象,此時如果類沒有默認構造函數,則對象無法創建。(類沒有被重載的情況)
<bean id="accountService" class="service.impl.AccountServiceImpl"></bean>//xml文件
//id爲標識符,class爲要通過getBean()創建的類
2.靜態工廠實例化: 使用靜態工廠中的靜態方法創建對象(使用某個類中的靜態方法創建對象,並存入spring容器)(靜態方法可以直接用類獲取。)
// 標識符 指定類 直接指定類中的靜態方法
<bean id="accountService" class="factory.StaticFactory" factory-method="getAccountService"></bean>
PS:類的構造函數也是靜態方法,因此默認無參構造函數也可以看作一種靜態工廠方法
3.實例工廠實例化: 使用普通工廠中的方法創建對象(使用某個類中的非靜態方法創建對象,並存入spring容器)。
/**
* 模擬一個實例工廠類(該類可能是存在於jar包中的,我們無法通過修改源碼的方式來提供默認構造函數)
**/
public class InstanceFactory {
public IAccountService getAccountService(){//不是靜態方法
return new AccountServiceImpl();
}
}
目的:先創建實例工廠instanceFactory
,然後調用getAccountService()
方法創建對象。
//標識符 指定類
<bean id="instanceFactory" class="factory.InstanceFactory"></bean>
//標識符 指定實例工廠的id 指定實例工廠中生產對象的方法
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
4.Spring的依賴注入(DI)
4.1.依賴注入(Dependency Injection)
在通過Spring創建對象的時候,因爲使用的時反射機制而不是使用new關鍵字,因此我們需要在配置文件指定創建出對象各字段的取值。
能注入的數據有三類:
第一種:基本數據類型和String。
第二種:其他bean類型(在配置文件中或者註解配置過的bean)
第三種:複雜/集合類型
注入的方式有三種:
第一種:使用構造函數提供。
第二種:使用set方法提供(最常用)
第三種:使用註解提供(筆記二整理)
4.2.使用構造函數進行注入
首先是一個類:目的是在類創建的時候,指定字段name,age和birthday的值
public class AccountServiceImpl{
//如果是經常變化的數據,並不適用於注入的方式
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;
}
}
使用構造器注入:
使用的標籤:constructor-arg
標籤出現的位置:bean標籤的內部
標籤中的屬性:
type:
用於指定要注入數據的數據類型,該數據類型也是構造函數中某個或某些參數的類型
index:
用於指定要注入的數據給構造函數中指定索引位置的參數賦值。索引的位置是從0開始
name:
用於指定給構造函數中指定名稱的參數賦值 (常用!這裏用的也是name)
上面三個用來指定給構造函數中的哪個參數賦值
value:
用於提供基本類型和String類型的數據
ref:
用於指定其他的bean類型數據。它指的就是在spring的Ioc核心容器中出現過的bean對象
優勢:在獲取bean對象時,必須注入操作,否則對象無法創建成功。
弊端:改變了bean對象的實例化方式,使我們在創建對象時,如果用不到這些數據,也必須提供。
<bean id="accountService" class="service.impl.AccountServiceImpl">
// name設置對應字段,下面同理 value爲設置的初始值 ref爲引用的是bean標籤(爲了調用Date類生成日期)
<constructor-arg name="name" value="test"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<!--配置一個日期對象,可以使用ref引用關聯的bean對象-->
<bean id="now" class="java.util.Date"></bean>
4.3.使用set方法注入
首先是一個類:要生成對應的set方法
public class AccountServiceImpl2{
//如果是經常變化的數據,並不適用於注入的方式
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;}
}
使用set方法注入:
使用的標籤:property
出現的位置:bean標籤的內部
標籤的屬性:
name:
用於指定主時所調用的set方法名稱。
value:
用於提供基本類型和String類型的數據。
ref:
用於指定其他的bean類型數據。它指的就是在spring的Ioc核心容器中出現過的bean對象。
優勢:創建對象時沒有明確的限制,可以直接使用默認構造函數。
弊端:如果有某個成員必須有值,則獲取對象時,有可能set方法沒有執行。
<bean id="accountService2" class="service.impl.AccountServiceImpl2">
<property name="age" value="21"></property>//相當方便
<property name="name" value="TEST"></property>
<property name="birthday" ref="now"></property>
</bean>
<bean id="now" class="java.util.Date"></bean>
4.4.複雜類型的注入/集合類型的注入
準備一個演示類:
public class AccountServiceImpl3{
private String[] myStrs;//數組類型
private List<String> myList;//集合類型
private Set<String> mySet;//set類型
private Map<String,String> myMap;//hash表類型
private Properties myPros;//也是類似hash表類型
//各自生成set方法
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 setMyPros(Properties myPros) { this.myPros = myPros; }
}
各自的標籤以及方法:
<bean id="accountService3" class="service.impl.AccountServiceImpl3">
<property name="myStrs">
<array>
<value>value1</value>
<value>value2</value>
<value>value3</value>
</array>
</property>
<property name="myList">
<list>
<value>value1</value>
<value>value2</value>
<value>value3</value>
</list>
</property>
<property name="mySet">
<set>
<value>value1</value>
<value>value2</value>
<value>value3</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB">
<value>BBB</value>
</entry>
</map>
</property>
<property name="myPros">
<props>
<prop key="testC"></prop>
<prop key="testD"></prop>
</props>
</property>
</bean>
注意:總體來說分爲兩大結構,List和Map,相同結構的標籤內容可以替換使用。
用於給List結構集合注入的標籤:list,array,set
用於給Map結構集合注入的標籤:map,props
4.5.使用註解進行注入
待續。