Java開發框架Spring——Spring基礎、Spring體系結構
一、程序的耦合
1、概述
- 耦合性(Coupling),也叫耦合度,是對模塊間關聯程度的度量。耦合的強弱取決於模塊間接口的複雜性、調用模塊的方式以及通過界面傳送數據的多少。模塊間的耦合度是指模塊之間的依賴關係,包括控制關係、調用關係、數據傳遞關係。模塊間聯繫越多,其耦合性越強,同時表明其獨立性越差( 降低耦合性,可以提高其獨立 性)。
- 在軟件工程中,耦合指的就是就是對象之間的依賴性。對象之間的耦合越高,維護成本越高。因此對象的設計應使類和構件之間的耦合最小。軟件設計中通常用耦合度和內聚度作爲衡量模塊獨立程度的標準。
- 劃分模塊的一個準則就是高內聚低耦合。
2、耦合性的分類
- 內容耦合:當一個模塊直接修改或操作另一個模塊的數據時,或一個模塊不通過正常入口而轉入另一個模塊時,這樣的耦合被稱爲內容耦合。內容耦合是最高程度的耦合,應該避免使用之。
- 公共耦合:兩個或兩個以上的模塊共同引用一個全局數據項,這種耦合被稱爲公共耦合。在具有大量公共耦合的結構中,確定究竟是哪個模塊給全局變量賦了一個特定的值是十分困難的。
- 外部耦合 :一組模塊都訪問同一全局簡單變量而不是同一全局數據結構,而且不是通過參數表傳遞該全局變量的信息,則稱之爲外部耦合。
- 控制耦合 :一個模塊通過接口向另一個模塊傳遞一個控制信號,接受信號的模塊根據信號值而進行適當的動作,這種耦合被稱爲控制耦合。
- 標記耦合 :若一個模塊 A 通過接口向兩個模塊 B 和 C 傳遞一個公共參數,那麼稱模塊 B 和 C 之間存在一個標記耦合。
- 數據耦合:模塊之間通過參數來傳遞數據,那麼被稱爲數據耦合。數據耦合是最低的一種耦合形式,系統中一般都存在這種類型的耦合,因爲爲了完成一些有意義的功能,往往需要將某些模塊的輸出數據作爲另一些模塊的輸入數據。
- 非直接耦合 :兩個模塊之間沒有直接關係,它們之間的聯繫完全是通過主模塊的控制和調用來實現的。
3、總結
- 耦合是影響軟件複雜程度和設計質量的一個重要因素,在設計上我們應採用以下原則:如果模塊間必須存在耦合,就儘量使用數據耦合,少用控制耦合,限制公共耦合的範圍,儘量避免使用內容耦合。
4、內聚與耦合
- 內聚標誌一個模塊內各個元素彼此結合的緊密程度,它是信息隱蔽和局部化概念的自然擴展。
- 內聚是從功能角度來度量模塊內的聯繫,一個好的內聚模塊應當恰好做一件事。它描述的是模塊內的功能聯繫。
- 耦合是軟件結構中各模塊之間相互連接的一種度量,耦合強弱取決於模塊間接口的複雜程度、進入或訪問一個模塊的點以及通過接口的數據。
- 程序講究的是低耦合,高內聚。就是同一個模塊內的各個元素之間要高度緊密,但是各個模塊之間的相互依存度卻要不那麼緊密。
- 內聚和耦合是密切相關的,同其他模塊存在高耦合的模塊意味着低內聚,而高內聚的模塊意味着該模塊同其他模塊之間是低耦合。
- 在進行軟件設計時,應力爭做到高內聚,低耦合。
5、實例:
我們在開發中,有些依賴關係是必須的,有些依賴關係可以通過優化代碼來解除
package cn.lemon.service.impl;
import cn.lemon.dao.IAccountDao;
import cn.lemon.dao.impl.AccountDaoImpl;
import cn.lemon.service.IAccountService;
public class AccountServiceImpl implements IAccountService {
@Override
public void saveAccount() {
IAccountDao iAccountDao = new AccountDaoImpl();
iAccountDao.saveAccount();
}
}
上面的代碼中:
業務邏輯層調用數據訪問層,並且此時業務邏輯層在依賴數據訪問層的接口和實現類。如果此時沒有數據訪問層實現類,編譯將不能通過。這種編譯期依賴關係,應該在我們開發中杜絕。我們需要優化代碼解決。
二、工廠模式解耦合
在實際開發中我們可以把三層的對象都使用配置文件配置起來,當啓動服務器應用加載的時候,讓一個類中的方法通過讀取配置文件,把這些對象創建出來並存起來。在接下來的使用的時候,直接拿過來用就好了。那麼,這個讀取配置文件,創建和獲取三層對象的類就是工廠。
1、分析:
由於我們是很多對象,肯定要找個集合來存。這時候有 Map 和 List 供選擇,到底選 Map 還是 List 就看我們有沒有查找需求。有查找需求,選 Map。
所以我們的答案就是:
在應用加載時,創建一個 Map,用於存放三層對象。我們把這個 map 稱之爲容器。
2、什麼是工廠
工廠就是負責給我們從容器中獲取指定對象的類。這時候我們獲取對象的方式發生了改變。
- 原來: 我們在獲取對象時,都是採用 new 的方式。是主動
- 現在:我們獲取對象時,同時跟工廠要,有工廠爲我們查找或者創建對象。是被動的
三、控制反轉IoC(Inversion of Control)
1、概述
- 控制反轉(Inversion of Control,縮寫爲IoC),是面向對象編程中的一種設計原則,可以用來減低計算機代碼之間的耦合度。
- 其中最常見的方式叫做依賴注入(Dependency Injection,簡稱DI),還有一種方式叫“依賴查找”(Dependency Lookup)。
- 通過控制反轉,對象在被創建的時候,由一個調控系統內所有對象的外界實體將其所依賴的對象的引用傳遞給它。也可以說,依賴被注入到對象中。
四、使用 spring 的 IOC 解決程序耦合
第一步:在 dao 中創建接口以及實現類
IAccountDao.java
package cn.lemon.dao;
public interface IAccountDao {//賬戶的數據訪問層接口
public void saveAccount();//保存賬戶方法
}
實現類 AccountDaoImpl.java
package cn.lemon.dao.impl;
import cn.lemon.dao.IAccountDao;
public class AccountDaoImpl implements IAccountDao {//賬戶數據訪問層的實現類
@Override
public void saveAccount() {
System.out.println("數據訪問層的接口實現類,保存賬戶.......");
}
}
第二步:在 service 中創建接口以及實現類,來調用dao
IAccountService.java
package cn.lemon.service;
public interface IAccountService {//業務邏輯層接口
public void saveAccount();
}
實現類 AccountServiceImpl.java
package cn.lemon.service.impl;
import cn.lemon.dao.IAccountDao;
import cn.lemon.dao.impl.AccountDaoImpl;
import cn.lemon.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AccountServiceImpl implements IAccountService {//接口實現類
//service 依賴 dao ,傳統的做法是
/* private IAccountDao iAccountDao = new AccountDaoImpl();
@Override
public void saveAccount() {
iAccountDao.saveAccount();
}
*/
//使用spring框架 ioc 控制反轉,首先在resource文件夾中創建配置文件 applicationContext.xml,Spring Config
@Override
public void saveAccount() {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
IAccountDao iAccountDao = (IAccountDao) ac.getBean("daoId");
iAccountDao.saveAccount();
}
}
第三步:在resource中創建配置文件
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">
<!--ioc : 由spring 創建和管理工廠類-->
<!-- 1:AccountDaoImpl dao = new AccountDaoImpl(); -->
<!-- 2:把dao這個對象放到ioc容器中管理-->
<bean id="daoId" class="cn.lemon.dao.impl.AccountDaoImpl"></bean>
<bean id="serviceId" class="cn.lemon.service.impl.AccountServiceImpl"></bean>
</beans>
第四步:創建測試類 Client.java
package cn.lemon.ui;
import cn.lemon.service.IAccountService;
import cn.lemon.service.impl.AccountServiceImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Client {//模擬頁面的表示層
//傳統的做法
@Test
public void test1(){
IAccountService iAccountService = new AccountServiceImpl();//直接調用業務邏輯層
iAccountService.saveAccount();
}
//使用 ioc 容器
@Test
public void test2(){
//首先從spring ioc 容器中獲取service對象
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");//讀取 ClassPath 下的applicationContext.xml
IAccountService iAccountService =(IAccountService) ac.getBean("serviceId");//從spring ioc 容器中讀取 id=serviceId 的值,然後強轉成 IAccountservice
iAccountService.saveAccount();
}
}
五、Spring 中的工廠類
1、BeanFactory 和 ApplicationContext 的區別
- BeanFactory 是 Spring 容器中的頂層接口。
- ApplicationContext 是 BeanFactory 的子接口。
BeanFactory 和 ApplicationContext 的區別:創建對象的時間點不同
- ApplicationContext:只要一讀取配置文件,默認情況下就會創建對象。
- BeanFactory:什麼使用什麼時候創建對象。
2、ApplicationContext 接口的實現類
ClassPathXmlApplicationContext
:它是從類的根路徑下加載配置文件 推薦使用FileSystemXmlApplicationContext
:它是從磁盤路徑上加載配置文件,配置文件可以在磁盤的任意位置。AnnotationConfigApplicationContext
: 當我們使用註解配置容器對象時,需要使用此類來創建 spring 容器。它用來讀取註解。
3、代碼
package cn.lemon.ui;
import cn.lemon.service.IAccountService;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
public class Client {
@Test
public void test01() {
//首先從spring ioc 容器中獲取service對象
//讀取 ClassPath 下的applicationContext.xml,它是從類的根路徑下加載配置文件
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//從spring ioc 容器中讀取 id=serviceId 的值,然後強轉成 IAccountservice
IAccountService iAccountService = (IAccountService) ac.getBean("serviceId");
iAccountService.saveAccount();
}
@Test
public void test02() {
//讀取 ClassPath 下的applicationContext.xml,它是從磁盤路徑上加載配置文件,配置文件可以在磁盤的任意位置。
ApplicationContext ac = new FileSystemXmlApplicationContext("D:\\JavaCode_02\\spring02\\src\\main\\resources\\applicationContext.xml");
IAccountService iAccountService = (IAccountService) ac.getBean("serviceId");
iAccountService.saveAccount();
}
@Test
public void test03() {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
}
@Test
public void test04() {
Resource resource = new ClassPathResource("applicationContext.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);//不推薦使用
IAccountService iAccountService = (IAccountService) beanFactory.getBean("serviceId");
iAccountService.saveAccount();
}
}
六、IOC 中 bean 標籤和管理對象
1、bean 標籤
1-1、作用:
- 用於配置對象讓 spring 來創建的。
- 默認情況下它調用的是類中的無參構造函數。如果沒有無參構造函數則不能創建成功。
1-2、屬性:
- id:給對象在容器中提供一個唯一標識。用於獲取對象。
- class:指定類的全限定類名。用於反射創建對象。默認情況下調用無參構造函數。
- init-method:指定類中的初始化方法名稱。
- destroy-method:指定類中銷燬方法名稱。
- scope:指定對象的作用範圍。
- singleton :默認值,單例的
- prototype :多例的.
- request :WEB 項目中,Spring 創建一個 Bean 的對象,將對象存入到 request 域中.
- session :WEB 項目中,Spring 創建一個 Bean 的對象,將對象存入到 session 域中.
- global session :WEB 項目中,應用在 Portlet 環境.如果沒有 Portlet 環境那麼globalSession 相當於 session.
如:上面的代碼中
<?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="daoId" class="cn.lemon.dao.impl.AccountDaoImpl"></bean>
<bean id="serviceId" class="cn.lemon.service.impl.AccountServiceImpl" scope="singleton"></bean>
</beans>
2、bean 的作用範圍和生命
2-1、單例對象:scope="singleton"
- 一個應用只有一個對象的實例。它的作用範圍就是整個引用。
生命週期:
- 對象出生:當應用加載,創建容器時,對象就被創建了。
- 對象活着:只要容器在,對象一直活着。
- 對象死亡:當應用卸載,銷燬容器時,對象就被銷燬了。
2-2、多例對象:scope="prototype"
- 每次訪問對象時,都會重新創建對象實例。
生命週期:
- 對象出生:當使用對象時,創建新的對象實例。
- 對象活着:只要對象在使用中,就一直活着。
- 對象死亡:當對象長時間不用時,被 java 的垃圾回收器回
3、實例化 Bean 的三種方式
3-1、第一種方式:使用默認無參構造函數
<!--在默認情況下:它會根據默認無參構造函數來創建類對象。如果 bean 中沒有默認無參構造函數,將會創建失 -->
<bean id="serviceId" class="cn.lemon.service.impl.AccountServiceImpl" init-method="init" destroy-method="destory"></bean>
3-2、第二種方式:spring 管理靜態工廠-使用靜態工廠的方法創建對象
新建靜態工廠 StaticFactoryBean.java
package cn.lemon.factory;
import cn.lemon.service.IAccountService;
import cn.lemon.service.impl.AccountServiceImpl;
public class StaticFactoryBean {
public static IAccountService createBean(){//靜態工廠類產生對象
System.out.println("靜態工廠類產生對象.....");
return new AccountServiceImpl();
}
}
然後在配置文件applicationContext.xml中添加靜態工廠類的IOC產生對象
<!-- 靜態工廠類產生對象 -->
<bean id="serviceStaticFactoryId" class="cn.lemon.factory.StaticFactoryBean" factory-method="createBean"></bean>
3-3、第三種方式:spring 管理實例工廠-使用實例工廠的方法創建對象
新建靜態工廠 InstanceFactoryBean.java
package cn.lemon.factory;
import cn.lemon.service.IAccountService;
import cn.lemon.service.impl.AccountServiceImpl;
public class InstanceFactoryBean {
public IAccountService createBean(){
System.out.println("實例工廠產生工廠類.......");
return new AccountServiceImpl();
}
}
然後在配置文件applicationContext.xml中添加實例工廠類的IOC產生對象
<!-- 實例工廠產生對象 -->
<bean id="InstanceFactoryId" class="cn.lemon.factory.InstanceFactoryBean"></bean>
<bean id="serviceInstanceFactoryId" factory-bean="InstanceFactoryId" factory-method="createBean"></bean>
七、Spring 的依賴注入
1、依賴注入的概述
- 依賴注入DI(Dependency Injection)是 spring 框架核心 ioc 的具體實現。
- 我們的程序在編寫時,通過控制反轉,把對象的創建交給了 spring,但是代碼中不可能出現沒有依賴的情況。ioc 解耦只是降低他們的依賴關係,但不會消除。
- 當業務層調用持久層的方法時,這種業務層和持久層的依賴關係,在使用 spring 之後,就讓 spring 來維護了。簡單的說,就是坐等框架把持久層對象傳入業務層,而不用我們自己去獲取。
2、set 方法注入
概述:顧名思義,就是在類中提供需要注入成員的 set 方法。
代碼:
第一步:新建持久層 dao ,接口IAccountDao.java以及實現類AccountDaoImpl.java
package cn.lemon.dao;
public interface IAccountDao {
public void saveAccount();
}
package cn.lemon.dao.impl;
import cn.lemon.dao.IAccountDao;
public class AccountDaoImpl implements IAccountDao {
@Override
public void saveAccount() {
System.out.println("dao 中的實現類AccountDaoImpl中的saveAccount方法,保存賬戶......");
}
}
第二步:新建業務層 service ,接口IAccountService.java以及實現類AccountServiceImpl.java,來調用持久層
package cn.lemon.service;
public interface IAccountService {
public void saveAccount();
}
在實現類中寫入 set 方法
package cn.lemon.service.impl;
import cn.lemon.dao.IAccountDao;
import cn.lemon.service.IAccountService;
public class AccountServiceImpl implements IAccountService {
private IAccountDao iAccountDao;
@Override
public void saveAccount() {
iAccountDao.saveAccount();
}
public void setiAccountDao(IAccountDao iAccountDao) {//set 方法注入,需要一個set 方法
this.iAccountDao = iAccountDao;
}
}
第三步:新建配置文件 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">
<!--spring 的 ico 容器中產生 dao 和 service-->
<bean id="accountDaoId" class="cn.lemon.dao.impl.AccountDaoImpl"></bean>
<bean id="accountServiceId" class="cn.lemon.service.impl.AccountServiceImpl">
<!--dependency injection 依賴注入:set 方法注入-->
<!--name:set方法中屬性名去掉首字母小寫-->
<!--ref:引用對象注入給指定的屬性-->
<property name="iAccountDao" ref="accountDaoId"></property>
</bean>
</beans>
第四步:寫測試類 Client.java
package cn.lemon.ui;
import cn.lemon.service.IAccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Client {
@Test
public void test01(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
IAccountService iAccountService =(IAccountService) applicationContext.getBean("accountServiceId");
iAccountService.saveAccount();
}
}
3、構造函數注入
概述:顧名思義,就是使用類中的構造函數,給成員變量賦值。注意,賦值的操作不是我們自己做的,而是通過配置的方式,讓 spring 框架來爲我們注入。
第一步:在業務層 service ,新建AccountServiceImpl2.java,來調用持久層
package cn.lemon.service.impl;
import cn.lemon.dao.IAccountDao;
import cn.lemon.service.IAccountService;
import java.util.Date;
public class AccountServiceImpl2 implements IAccountService {
private IAccountDao iAccountDao;
private Integer age;
private String name;
private Date birthday;
public AccountServiceImpl2() {
System.out.println("AccountServiceImpl2 中的無參構造函數.....");
}
public AccountServiceImpl2(IAccountDao iAccountDao, Integer age, String name, Date birthday) {//構造函數
this.iAccountDao = iAccountDao;
this.age = age;
this.name = name;
this.birthday = birthday;
}
@Override
public void saveAccount() {
System.out.println("age = " + age + "\t name = " + name + "\t birthday = " + birthday);
iAccountDao.saveAccount();
}
}
修改配置文件
<?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 的 ico 容器中產生 dao 和 service-->
<bean id="accountDaoId" class="cn.lemon.dao.impl.AccountDaoImpl"></bean>
<!-- 構造函數注入 -->
<bean id="accountService2Id" class="cn.lemon.service.impl.AccountServiceImpl2">
<constructor-arg index="0" ref="accountDaoId"></constructor-arg>
<constructor-arg index="1" value="18"></constructor-arg>
<constructor-arg index="2" value="馬雲"></constructor-arg>
<constructor-arg index="3" ref="riqi"></constructor-arg>
</bean>
<bean id="riqi" class="java.util.Date"></bean>
</beans>
4、使用 p 名稱空間注入數據(本質還是調用 set 方法)
概述:此種方式是通過在 xml 中導入 p 名稱空間,使用 p:propertyName 來注入數據,它的本質仍然是調用類中的set 方法實現注入功能。
5、注入集合屬性
概述:顧名思義,就是給類中的集合成員傳值,它用的也是set方法注入的方式,只不過變量的數據類型都是集合。我們這裏介紹注入數組List
, Set
, Map
, Properties
第一步:在業務層 service ,新建AccountServiceImpl4.java,來調用持久層
package cn.lemon.service.impl;
import cn.lemon.service.IAccountService;
import java.util.*;
public class AccountServiceImpl4 implements IAccountService {
private String[] array;
private List<String> list;
private Set<String> set;
private Map<String, String> map;
private Properties properties;
public void setArray(String[] array) {
this.array = array;
}
public void setList(List<String> list) {
this.list = list;
}
public void setSet(Set<String> set) {
this.set = set;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public void saveAccount() {
System.out.println("---------------數組---------------");
for (String s : array) {
System.out.println(s);
}
System.out.println("---------------List 集合---------------");
for (String l : list) {
System.out.println(l);
}
System.out.println("---------------Set 集合---------------");
for (String s : set) {
System.out.println(s);
}
System.out.println("---------------Map 集合---------------");
for (Map.Entry<String,String> entry : map.entrySet()) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
System.out.println("---------------Properties---------------");
for (Object key : properties.keySet()){
System.out.println(key + " : " + properties.get(key));
}
}
}
修改配置文件
<?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 的 ico 容器中產生 dao 和 service-->
<bean id="accountDaoId" class="cn.lemon.dao.impl.AccountDaoImpl"></bean>
<bean id="accountService4Id" class="cn.lemon.service.impl.AccountServiceImpl4">
<property name="array">
<array>
<value>array01</value>
<value>array01</value>
<value>array02</value>
<value>array02</value>
<value>array03</value>
<value>array03</value>
</array>
</property>
<property name="list">
<list>
<value>list01</value>
<value>list01</value>
<value>list02</value>
<value>list02</value>
<value>list03</value>
<value>list03</value>
</list>
</property>
<property name="set">
<set>
<value>set01</value>
<value>set01</value>
<value>set02</value>
<value>set02</value>
<value>set03</value>
<value>set03</value>
</set>
</property>
<property name="map">
<map>
<entry key="k1" value="v1"></entry>
<entry key="k1" value="v1"></entry>
<entry key="k2" value="v2"></entry>
<entry key="k2" value="v2"></entry>
<entry key="k3" value="v3"></entry>
<entry key="k3" value="v3"></entry>
</map>
</property>
<property name="properties">
<props>
<prop key="prop1">value1</prop>
<prop key="prop1">value1</prop>
<prop key="prop2">value2</prop>
<prop key="prop2">value2</prop>
<prop key="prop3">value3</prop>
<prop key="prop3">value3</prop>
</props>
</property>
</bean>
</beans>