spring 是什麼
Spring 的發展歷程
spring 的優勢
方便解耦,簡化開發
AOP 編程的支持
聲明式事務的支持
方便程序的測試
方便集成各種優秀框架
降低 JavaEE API 的使用難度
Java 源碼是經典學習範例
spring 的體系結構
IoC 的概念和作用
什麼是程序的耦合
它有如下分類:
內聚與耦合
我們在開發中,有些依賴關係是必須的,有些依賴關係可以通過優化代碼來解除的。
請看下面的示例代碼:
/**
* 賬戶的業務層實現類
* @author
* @Company
* @Version 1.0
*/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl();
}
上面的代碼表示:
業務層調用持久層,並且此時業務層在依賴持久層的接口和實現類。如果此時沒有持久層實現類,編譯
將不能通過。這種編譯期依賴關係,應該在我們開發中杜絕。我們需要優化代碼解決。
再比如:
早期我們的 JDBC 操作,註冊驅動時,我們爲什麼不使用 DriverManager 的 register 方法,而是採
用 Class.forName 的方式?
public class JdbcDemo1 {
public static void main(String[] args) throws Exception {
//1.註冊驅動
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Class.forName("com.mysql.jdbc.Driver");
//2.獲取連接
//3.獲取預處理 sql 語句對象
//4.獲取結果集
//5.遍歷結果集
}
}
原因就是:
我們的類依賴了數據庫的具體驅動類(MySQL),如果這時候更換了數據庫品牌(比如 Oracle),需要
修改源碼來重新數據庫驅動。這顯然不是我們想要的。
解決程序耦合的思路
當是我們講解 jdbc 時,是通過反射來註冊驅動的,代碼如下:
Class.forName("com.mysql.jdbc.Driver");//此處只是一個字符串
此時的好處是,我們的類中不再依賴具體的驅動類,此時就算刪除 mysql 的驅動 jar 包,依然可以編譯(運
行就不要想了,沒有驅動不可能運行成功的)。
同時,也產生了一個新的問題,mysql 驅動的全限定類名字符串是在 java 類中寫死的,一旦要改還是要修改
源碼。
解決這個問題也很簡單,使用配置文件配置。
工廠模式解耦
在實際開發中我們可以把三層的對象都使用配置文件配置起來,當啓動服務器應用加載的時候,讓一個類中的
方法通過讀取配置文件,把這些對象創建出來並存起來。在接下來的使用的時候,直接拿過來用就好了。
那麼,這個讀取配置文件,創建和獲取三層對象的類就是工廠。
控制反轉-Inversion Of Control
解耦的思路有 2 個問題:
1、存哪去?
分析:由於我們是很多對象,肯定要找個集合來存。這時候有 Map 和 List 供選擇。
到底選 Map 還是 List 就看我們有沒有查找需求。有查找需求,選 Map。
所以我們的答案就是在應用加載時,創建一個 Map,用於存放三層對象。
我們把這個 map 稱之爲容器。
2、什麼是工廠?
工廠就是負責給我們從容器中獲取指定對象的類。這時候我們獲取對象的方式發生了改變。
原來:我們在獲取對象時,都是採用 new 的方式。是主動的
現在:我們獲取對象時,同時跟工廠要,有工廠爲我們查找或者創建對象。是被動的。
使用 spring 的 IOC 解決程序耦合
案例的前期準備
準備 spring 的開發包
官網:http://spring.io/
下載地址:
http://repo.springsource.org/libs-release-local/org/springframework/spring
解壓:(Spring 目錄結構:)
* docs :API 和開發規範.
* libs :jar 包和源碼.
* schema :約束.
創建業務層接口和實現類
/**
* 賬戶的業務層接口
*
*/
public interface IAccountService {
/**
* 保存賬戶(此處只是模擬,並不是真的要保存)
*/
void saveAccount();
}
/**
* 賬戶的業務層實現類
*
*/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl();//此處的依賴關係有待解決
@Override
public void saveAccount() {
accountDao.saveAccount();
}
}
創建持久層接口和實現類
/**
* 賬戶的持久層接口
*
*/
public interface IAccountDao {
/**
* 保存賬戶
*/
void saveAccount();
}
/**
* 賬戶的持久層實現類
*
*/
public class AccountDaoImpl implements IAccountDao {
@Override
public void saveAccount() {
System.out.println("保存了賬戶");
}
}
基於 XML 的配置
第一步:拷貝必備的 jar 包到工程的 lib 目錄中
第二步:在類的根路徑下創建一個任意名稱的 xml 文件
給配置文件導入約束:
/spring-framework-5.0.2.RELEASE/docs/spring-framework-reference/html5/core.html
<?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">
</beans>
第三步:讓 spring 管理資源,在配置文件中配置 service 和 dao
<!-- bean 標籤:用於配置讓 spring 創建對象,並且存入 ioc 容器之中
id 屬性:對象的唯一標識。
class 屬性:指定要創建對象的全限定類名
-->
<!-- 配置 service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!-- 配置 dao -->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"></bean>
測試配置是否成功
/**
* 模擬一個表現層
*
*/
public class Client {
/**
* 使用 main 方法獲取容器測試執行
*/
public static void main(String[] args) {
//1.使用 ApplicationContext 接口,就是在獲取 spring 容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根據 bean 的 id 獲取對象
IAccountService aService = (IAccountService) ac.getBean("accountService");
System.out.println(aService);
IAccountDao aDao = (IAccountDao) ac.getBean("accountDao");
System.out.println(aDao);
}
}
Spring 基於 XML 的 IOC 細節
spring 中工廠的類結構圖
BeanFactory 和 ApplicationContext 的區別
BeanFactory 纔是 Spring 容器中的頂層接口。
ApplicationContext 是它的子接口。
BeanFactory 和 ApplicationContext 的區別:
創建對象的時間點不一樣。
ApplicationContext:只要一讀取配置文件,默認情況下就會創建對象。
BeanFactory:什麼使用什麼時候創建對象。
ClassPathXmlApplicationContext:
它是從類的根路徑下加載配置文件 推薦使用這種
FileSystemXmlApplicationContext:
它是從磁盤路徑上加載配置文件,配置文件可以在磁盤的任意位置。
AnnotationConfigApplicationContext:
當我們使用註解配置容器對象時,需要使用此類來創建 spring 容器。它用來讀取註解。
IOC 中 bean 標籤和管理對象細節
bean 標籤
作用:
用於配置對象讓 spring 來創建的。
默認情況下它調用的是類中的無參構造函數。如果沒有無參構造函數則不能創建成功。
屬性:
id:給對象在容器中提供一個唯一標識。用於獲取對象。
class:指定類的全限定類名。用於反射創建對象。默認情況下調用無參構造函數。
scope:指定對象的作用範圍。
* singleton :默認值,單例的.
* prototype :多例的.
* request :WEB 項目中,Spring 創建一個 Bean 的對象,將對象存入到 request 域中.
* session :WEB 項目中,Spring 創建一個 Bean 的對象,將對象存入到 session 域中.
* global session :WEB 項目中,應用在 Portlet 環境.如果沒有 Portlet 環境那麼globalSession 相當於
session.
init-method:指定類中的初始化方法名稱。
destroy-method:指定類中銷燬方法名稱。
單例對象:scope="singleton"
一個應用只有一個對象的實例。它的作用範圍就是整個引用。
生命週期:
對象出生:當應用加載,創建容器時,對象就被創建了。
對象活着:只要容器在,對象一直活着。
對象死亡:當應用卸載,銷燬容器時,對象就被銷燬了。
多例對象:scope="prototype"
每次訪問對象時,都會重新創建對象實例。
生命週期:
對象出生:當使用對象時,創建新的對象實例。
對象活着:只要對象在使用中,就一直活着。
對象死亡:當對象長時間不用時,被 java 的垃圾回收器回收了。
第一種方式:使用默認無參構造函數
<!--在默認情況下:
它會根據默認無參構造函數來創建類對象。如果 bean 中沒有默認無參構造函數,將會創建失敗。
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"/>
第二種方式:spring 管理靜態工廠-使用靜態工廠的方法創建對象
/**
* 模擬一個靜態工廠,創建業務層實現類
*/
public class StaticFactory {
public static IAccountService createAccountService(){
return new AccountServiceImpl();
}
}
<!-- 此種方式是:
使用 StaticFactory 類中的靜態方法 createAccountService 創建對象,並存入 spring 容器
id 屬性:指定 bean 的 id,用於從容器中獲取
class 屬性:指定靜態工廠的全限定類名
factory-method 屬性:指定生產對象的靜態方法
-->
<bean
id="accountService"
class="com.itheima.factory.StaticFactory"
factory-method="createAccountService">
</bean>
第三種方式:spring 管理實例工廠 再 用實例工廠的方法創建對象
/**
* 模擬一個實例工廠,創建業務層實現類
* 此工廠創建對象,必須現有工廠實例對象,再調用方法
*/
public class InstanceFactory {
public IAccountService createAccountService(){
return new AccountServiceImpl();
}
}
<!-- 此種方式是:
先把工廠的創建交給 spring 來管理。
然後在使用工廠的 bean 來調用裏面的方法
factory-bean 屬性:用於指定實例工廠 bean 的 id。
factory-method 屬性:用於指定實例工廠中創建對象的方法。
-->
<bean id="instancFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService"
factory-bean="instancFactory"
factory-method="createAccountService"></bean>
spring 的依賴注入
依賴注入的概念
依賴注入:Dependency Injection。它是 spring 框架核心 ioc 的具體實現。
我們的程序在編寫時,通過控制反轉,把對象的創建交給了 spring,但是代碼中不可能出現沒有依賴的情況。
ioc 解耦只是降低他們的依賴關係,但不會消除。例如:我們的業務層仍會調用持久層的方法。
那這種業務層和持久層的依賴關係,在使用 spring 之後,就讓 spring 來維護了。
簡單的說,就是坐等框架把持久層對象傳入業務層,而不用我們自己去獲取。
構造函數注入
顧名思義,就是使用類中的構造函數,給成員變量賦值。注意,賦值的操作不是我們自己做的,而是通過配置
的方式,讓 spring 框架來爲我們注入。具體代碼如下:
/**
*/
public class AccountServiceImpl implements IAccountService {
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;
}
@Override
public void saveAccount() {
System.out.println(name+","+age+","+birthday);
}
}
<!-- 使用構造函數的方式,給 service 中的屬性傳值
要求:
類中需要提供一個對應參數列表的構造函數。
涉及的標籤:
constructor-arg
屬性:
index:指定參數在構造函數參數列表的索引位置
type:指定參數在構造函數中的數據類型
name:指定參數在構造函數中的名稱 用這個找給誰賦值
=======上面三個都是找給誰賦值,下面兩個指的是賦什麼值的==============
value:它能賦的值是基本數據類型和 String 類型
ref:它能賦的值是其他 bean 類型,也就是說,必須得是在配置文件中配置過的 bean-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="張三"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<bean id="now" class="java.util.Date"></bean>
優勢:獲取bean對象的時候,必須注入數據,否則無法創建對象
弊端:改變了bean對象的實例化方式,使我們在用不到 數據的時候,也必須注入數據
set 方法注入
顧名思義,就是在類中提供需要注入成員的 set 方法。具體代碼如下:
/** */
public class AccountServiceImpl implements IAccountService {
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;
}
@Override
public void saveAccount() {
System.out.println(name+","+age+","+birthday);
}
}
<!-- 通過配置文件給 bean 中的屬性傳值:使用 set 方法的方式
涉及的標籤:
property
屬性:
name:找的是類中 set 方法後面的部分,用於調用set方法
ref:給屬性賦值是其他 bean 類型的
value:給屬性賦值是基本數據類型和 string 類型的
實際開發中,此種方式用的較多。
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="name" value="test"></property>
<property name="age" value="21"></property>
<property name="birthday" ref="now"></property>
</bean>
<bean id="now" class="java.util.Date"></bean>
創建對象沒有明確限制,但是如果某個成員必須有值,則獲取對象可能沒有調用set方法,也就沒有值。
使用 p 名稱空間注入數據(本質還是調用 set 方法)
此種方式是通過在 xml 中導入 p 名稱空間,使用 p:propertyName 來注入數據,它的本質仍然是調用類中的
set 方法實現注入功能。
Java 類代碼:
/**
* 使用 p 名稱空間注入,本質還是調用類中的 set 方法
*/
public class AccountServiceImpl4 implements IAccountService {
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;
}
@Override
public void saveAccount() {
System.out.println(name+","+age+","+birthday);
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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="accountService"
class="com.itheima.service.impl.AccountServiceImpl4"
p:name="test" p:age="21" p:birthday-ref="now"/>
</beans>
注入集合屬性
顧名思義,就是給類中的集合成員傳值,它用的也是set方法注入的方式,只不過變量的數據類型都是集合。
我們這裏介紹注入數組,List,Set,Map,Properties。具體代碼如下:
/***/
public class AccountServiceImpl implements IAccountService {
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String,String> myMap;
private Properties myProps;
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 setMyProps(Properties myProps) {
this.myProps = myProps;
}
@Override
public void saveAccount() {
System.out.println(Arrays.toString(myStrs));
System.out.println(myList);
System.out.println(mySet);
System.out.println(myMap);
System.out.println(myProps);
}
}
<!-- 注入集合數據
List 結構的:
array,list,set
Map 結構的
map,entry,props,prop
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
//在注入集合數據時,只要結構相同,標籤可以互換
<!-- 給數組注入數據 -->
<property name="myStrs">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<!-- 注入 list 集合數據 -->
<property name="myList">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<!-- 注入 set 集合數據 -->
<property name="mySet">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<!-- 注入 Map 數據 -->
<property name="myMap">
<props>
<prop key="testA">aaa</prop>
<prop key="testB">bbb</prop>
</props>
</property>
<!-- 注入 properties 數據 -->
<property name="myProps">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB">
<value>bbb</value>
</entry>
</map>
</property>
</bean>