什么是Spring?
Spring 是分層的 Java SE/EE 應用 full-stack 輕量級開源框架,以 IoC(Inverse Of Control: 反轉控制)和 AOP(Aspect Oriented Programming:面向切面編程)為內核,提供了展現層 Spring MVC 和持久層
Spring JDBC 以及業務層事務管理等眾多的企業級應用技術,還能整合開源世界眾多著名的第三方框架和類庫,逐漸成為使用最多的 Java EE 企業應用開源框架。
為什么要使用Spring?
在軟件設計中,如果項目的耦合程度高,那么維護項目所需要的崇本也高。設計一個軟件,其耦合度和內聚度通常作為衡量模塊獨立的標准。而划分模塊的一個准則就是高內聚低耦合。
耦合是不可能被完全消除的,只能降低一部分的耦合能用數據耦合(模塊間通過參數來傳遞數據,最低的耦合關系)就不用控制耦合(傳遞控制信號來執行操作),能用公共耦合(多個模塊共擁有給全局數據項)就不用內容耦合(一個模塊直接操作另一個模塊的數據,或不通過正常入口而轉入另一個模塊,最高程度的耦合)。
如何解決耦合?
所以使用配置文件配置key value的全限定類名字符串,就可以動態配置不同的驅動。
並且jdbc的這種方式不再依賴具體的驅動類。
所以我們可以使用工廠模式,通過配置文件來配置創建對象的條件,來使用一個讀取配置文件,並能創建和獲取三層對象的工廠來解耦。
比起直接創建對象的主動方式,用工廠來創建對象的方式是被動的。而這種被動接收的方式來獲取對象,就是控制反轉IOC的思想(降低程序的耦合),他是spring框架的核心之一。
如何使用Spring?
下面我們用一個銀行轉賬的小demo用3種方式來進行配置(事務控制用了2種方式)
一、純注解方式配置
項目結構:
config是配置目錄:
由SpringConfiguration作為總配置類
JdbcConfig類和TransactionConfig類是配置數據庫連接的類和事務控制的類
dao是持久層
domain存放的是實體類
service是服務層
resources
jdbcConfig.properties中存放連接數據庫的連接數據
test
AccountServiceTest測試類
由運行順序講解代碼
通過測試類
因為junit的原理,所以他是無法識別我們是否使用了Spring框架的,所以必須要用spring的@Runwith來替換Junit原有的運行器
然后使用@ContextConfiguration的classes屬性指定 spring 配置文件的位置
@ContextConfiguration 注解:
locations 屬性:用於指定配置文件的位置。如果是類路徑下,需要用 classpath:表明
classes 屬性:用於指定注解的類。當不使用 xml 配置時,需要用此屬性指定注解類的位置。
然后我們通過注解進入配置文件類
掃描所有包就可以識別出所有的注解配置
導入了2個配置類
開啟對AOP注解的支持
掃描包的話給出幾個配置的典型例子
持久層的實現類:
@Repository持久層注解,
與@Component功能一致
作用:
把資源讓 spring 來管理。相當於在 xml 中配置一個 bean。
屬性:
value:指定 bean 的 id。如果不指定 value 屬性,默認 bean 的 id 是當前類的類名。首字母小寫。
@Controller @Service @Repository
他們三個注解都是針對第一個的衍生注解,他們的屬性都是一模一樣的。
他們只不過是提供了更加明確的語義化。
@Controller:一般用於表現層的注解。
@Service:一般用於業務層的注解。
@Repository:一般用於持久層的注解。
細節:如果注解中有且只有一個屬性要賦值時,且名稱是 value,value 在賦值是可以不寫。
@Autowired
作用:
自動按照類型注入。當使用注解注入屬性時,set 方法可以省略。它只能注入其他 bean 類型。當有多個
類型匹配時,使用要注入的對象變量名稱作為 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到
就報錯。
服務層的實現類:
@Service:業務層的注解
@Autowired自動注入
@Transactional
該注解的屬性和 xml 中的屬性含義一致。該注解可以出現在接口上,類上和方法上。
出現接口上,表示該接口的所有實現類都有事務支持。
出現在類上,表示類中所有方法有事務支持
出現在方法上,表示方法有事務支持。
以上三個位置的優先級:方法>類>接口
進入導入的第一個配置類
@Value注入基本類型和String類型的數據
@Bean標簽,
作用:
該注解只能寫在方法上,表明使用此方法創建一個對象,並且放入 spring 容器。
屬性:
name:給當前@Bean 注解方法創建的對象指定一個名稱(即 bean 的 id)。
然后看另一個子配置類
仍然是提供了一個獲取txManager對象的方法
然后是讀取jdbc屬性的配置,參數為類路徑下的配置文件位置
最后@EnableTransactionManagement的配置是開啟AOP的支持,配置了就開啟,不配置就關閉
然后就實現了完全基於注解的配置
<<<<<<<<<<<<<<<<<注解控制的細節<<<<<<<<<<<<<<<<<<
可以和上面進行對照更便於理解、使用
用於創建對象的
* 他們的作用就和在xml配置文件中編寫一個<bean>標簽實現的功能是一樣的
* Component
* 作用:把當前對象存入spring容器中
* 屬性:
* value:用於指定bean的id,當我們不寫時,默認值是當前類名且首字母變為小寫
* Controller:一般用在表現層
* Service:一般用在業務層
* Repository:一般用在持久層
* 以上三個注解他們的作用和屬性與Component是一模一樣的
* 他們三個是spring框架為我們提供明確的三層使用的注解,使我們的三層對象更加清晰
*
* 用於注入數據的
* 他們的作用就和在xml配置文件中的<bean>標簽中寫<property>標簽的作用是一樣的
* Autowired
* private IAccountDao accountDao1=null;
* 訪問修飾符 數據類型 變量名稱 數據類型
* 注入時主要考慮變量名稱與id的匹配
* 作用:
* 自動按照類型注入(接口的名稱,而非自己起的id)。只要容器中有唯一的一個bean對象類型和要注入的變量類型匹配就可以注入成功。
* 如果ioc容器中沒有任何bean的類型和要注入的變量類型匹配就報錯。
* 出現位置:
* 變量、方法
* 細節:
* 在使用注解注入時,set方法不是必須的。
* Qualifier:
* 作用:
* 在按照類中注入的基礎上再按照名稱注入。他在給類成員注入時不能單獨使用(要與Autowired配合)。但是給方法參數注入時可以
* Resource:
* 作用:
* 直接按照bean的id注入。他可以獨立使用
* 屬性:
* name:用於指定bean的id。
* 以上3中注入都只能注入其他bean類型的數據,而基本類型和String類型無法使用上述注解實現
* 另外集合類型的注入只能通過xml來實現。
*
* value
* 作用:
* 用於注入基本類型和String類型的數據
* 屬性:
* value:用於指定數據的值。它可以使用spring中SpEL(也就是spring的el表達式)
* SpEL的寫法:${表達式},看el表達式出現的位置
*
* 用於改變作用范圍的
* 他們的作用和在bean標簽中使用scope屬性實現的功能是一樣的
* Scope
* 作用:
* 用於指定bean的作用范圍
* 屬性:
* value:指定范圍的取值。常用取值:singleton prototype,不寫默認是單例的
* 和生命周期相關的
* 他們的作用和在bean標簽中使用init-method和destroy-method的作用是一樣的
* PostConstruct
* 作用:
* 用於指定初始化方法
* PreDestroy
* 作用:
* 用於指定銷毀方法(多例對象的銷毀,spring並不負責)
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
二、下面是以xml為主的配置方式
事務控制方面選Z額了spring編程式事務控制
dao是持久層
domain存放的是實體類
service是服務層
resources
bean.xml中存放了基於xml的spring配置信息
test
AccountServiceTest測試類
以下所有重復的部分不做解釋:
@ContextConfiguration 注解:使用locations 屬性來讀取配置文件
locations 屬性:用於指定配置文件的位置。如果是類路徑下,需要用 classpath:表明
classes 屬性:用於指定注解的類。當不使用 xml 配置時,需要用此屬性指定注解類的位置。
然后我們進入配置文件
關於bean標簽和依賴注入的細節在下面:
依賴注入使用的是下面依賴注入方法中的set方法
用TransactionTemplate手動管理事務
<<<<<<<<<<<<<<<<xml配置中的細節<<<<<<<<<<<<<<<<
bean標簽把對象的創建交給spring來管理
spring對bean的管理細節
1、創建bean的三種方式
2、bean對象的作用范圍
3、bean對象的生命周期
創建bean的三種方式
a.使用默認的構造函數創建
<!--此種方式是:
在spring配置文件中使用bean標簽,配以id與class屬性之后,且沒有其他屬性和標簽時。
采用的就是默認構造函數創建bean對象,此時如果類中沒有默認構造函數,對象則無法創建。
-->
<!--<bean id="accountService" class="com.lky.service.impl.AccountServiceImpl"></bean>-->
b.使用普通工廠中的方法創建對象(使用某個類中的方法創建對象,並存取容器)
<!-- 此種方式是:
先把工廠的創建交給 spring 來管理。
然后在使用工廠的 bean 來調用里面的方法
factory-bean 屬性:用於指定實例工廠 bean 的 id。
factory-method 屬性:用於指定實例工廠中創建對象的方法。
-->
<!--<bean id="instanceFactory" class="com.lky.factory.InstanceFactory"></bean>-->
<!--<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>-->
c.使用靜態工廠中的靜態方法創建對象(使用摸個類中的靜態方法創建對象,並存入容器)
<!-- 此種方式是:
使用 StaticFactory 類中的靜態方法 createAccountService 創建對象,並存入 spring 容器
id 屬性:指定 bean 的 id,用於從容器中獲取
class 屬性:指定靜態工廠的全限定類名
factory-method 屬性:指定生產對象的靜態方法
-->
<!--<bean id="accountService" class="com.lky.factory.StaticFactory" factory-method="getAccountService"></bean>—>
bean的作用范圍調整(spring對象默認為單例的)
<!--
bean標簽中的scope屬性:
作用:用於指定bean的作用范圍
取值:
singleton:單例,默認值
prototype:多例的
request:作用於web應用的請求范圍
session:作用於web應用的會話范圍
global-session:作用於集群環境的會話范圍(全局會話范圍),當不是集群環境時,就是session
-->
<!--<bean id="accountService" class="com.lky.service.impl.AccountServiceImpl" scope="prototype"></bean>—>
bean對象的生命周期
<!--
單例對象
出生:容器創建時
活着:容器還在
死亡:容器銷毀
多例對象
出生:使用對象時,spring為我們創建
活着:對象在使用就活着
死亡:當對象長時間不用且沒有別的對象引用時,由java的垃圾回收器回收
—>
sprig中的依賴注入
<!--
依賴注入:
Dependency Injection
IOC的作用:
降低程序間的耦合(依賴關系)
依賴關系的管理:
都交給spring來維護
當前類需要用到其他類的對象,由spring為我們提供,我們只需要在配置文件中說明
依賴關系的維護:
稱之為依賴注入
依賴注入:
能注入的數據有3類:
基本類型和String
其他bean類型(在配置文件中或注解配置過的bean)
復雜類型/集合類型
注入的方式:
1。使用構造函數提供
2。使用set方法
3。使用注解提供
—>
<!--構造函數注入
使用的標簽:constructor-arg
出現的位置:bean標簽內部
標簽中的屬性
type:用於指定要注入的數據的類型,該數據類型也是構造函數中某個或某些參數的類型(無法區分多個,所以不能獨立實現)
index:用於指定要注入的數據給構造函數中指定索引位置的參數賦值,從0開始(可以獨立實現,但必須清楚類型)
name:用於指定給構造函數中指定名稱的參數賦值(常用)
=============以上3個用於指定給構造函數中的參數賦值=================================
value:用於提供基本類型和String類型的數據
ref:用於指定其他的bean類型的數據。指的是spring的IOC核心容器中出現過的bean對象
優勢:
在獲取bean對象時,注入數據時必須的操作,否則對象無法創建成功。
缺點:
改變了bean對象的實例化方式,使創建對象時,如果不使用這些數據,也必須提供。
-->
<bean id="accountService" class="com.lky.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="柯"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="time"></constructor-arg>
</bean>
<!--配置一個時間對象-->
<bean id="time" class="java.util.Date"></bean>
<!--set方法注入 更常用
使用的標簽:property
出現的位置:bean標簽內部
標簽中的屬性:
name:用於指定給構造函數中指定名稱的參數賦值(常用),從set方法中尋找
=============以上3個用於指定給構造函數中的參數賦值=================================
value:用於提供基本類型和String類型的數據
ref:用於指定其他的bean類型的數據。指的是spring的IOC核心容器中出現過的bean對象
優勢:
創建對象時沒有明確的限制,可以直接使用默認構造函數
缺點:
如果摸個成員必須有值,則獲取對象時,set方法無法保證一定注入(可能不執行set),
-->
<bean id="accountService2" class="com.lky.service.impl.AccountServiceImpl2">
<property name="name" value="柯雨"></property>
<property name="age" value="21"></property>
<property name="birthday" ref="time"></property>
</bean>
<!--復雜類型的注入,集合類型
用於給list集合注入的標簽有:
list array set
用於給Map集合注入的標簽有:
map props
結構相同,標簽可以互換
-->
<bean id="accountService3" class="com.lky.service.impl.AccountServiceImpl3">
<property name="myStrs">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="myList">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<property name="mySet">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="a" value="AAA"></entry>
<entry key="b">
<value>BBB</value>
</entry>
</map>
</property>
<property name="myProps">
<props>
<prop key="c">CCC</prop>
<prop key="d">DDD</prop>
</props>
</property>
</bean>
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
因為采用xml文件配置,所以類中都是基本代碼。
重點帖一下服務層實現的部分,因為服務層進行着事務控制
spring編程式事務控制,通過使用transactionTemplate中的excute方法
excute在執行時會使用doInTransaction(status)的方法,並且在執行時,這個doInTransaction的部分實現了rollback,commit等事務操作。
因為通過代碼來實現事務非常麻煩,增加開發難度,並且會增加業務層重復的代碼。所以一般很少使用。
為什么使用銀行轉賬為例子?
是因為銀行轉賬實際上是進行事務控制,而事務控制是許多業務的必要部分。
持久層connection 對象的 setAutoCommit(true)方法會使單條sql語句的事務得到成功的執行,但如果業務層同時執行多個持久層方法,並且在中間產生了異常,業務層的這次調用在不進行處理的情況下是無法進行整體自動回滾的,他會執行能成功的語句,回滾失敗的語句。此時我們的銀行轉賬會出現嚴重的問題。我們也可以是用try catch finally的方式去處理,但是因為許多業務都涉及了事務控制,這樣去處理,重復的代碼量極大,是會增加開發成本的。
如果解決代碼重復的問題?
動態代理,我們可以通過動態代理,使需要進行事務控制的方法經過代理類,從而實現事務控制。
而動態代理的實現方式有兩種,基於接口和基於子類。
而在spring中,通過配置,框架會根據目標類是否實現了接口來決定采用哪種動態代理的方式。
這這種解決上述問題的spring配置,稱之為AOP
<<<<<<<<<<<這里是aop的相關術語解釋<<<<<<<<<<<
Joinpoint(連接點):
所謂連接點是指那些被攔截到的點。在 spring 中,這些點指的是方法,因為 spring 只支持方法類型的連接點。
Pointcut(切入點):
所謂切入點是指我們要對哪些 Joinpoint 進行攔截的定義。
Advice(通知/增強):
所謂通知是指攔截到 Joinpoint 之后所要做的事情就是通知。
通知的類型:前置通知,后置通知,異常通知,最終通知,環繞通知。
Introduction(引介):
引介是一種特殊的通知在不修改類代碼的前提下, Introduction 可以在運行期為類動態地添加一些方法或 Field。
Target(目標對象):
代理的目標對象。
Weaving(織入):
是指把增強應用到目標對象來創建新的代理對象的過程。
spring 采用動態代理織入,而 AspectJ 采用編譯期織入和類裝載期織入。
Proxy(代理):
一個類被 AOP 織入增強后,就產生一個結果代理類。
Aspect(切面):
是切入點和通知(引介)的結合。
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
三、基於AOP方式的聲明式事務管理的配置過程:
重復的部分不再贅述
首先是執行的測試類
然后是讀取的bean.xml配置文件
這里的spring關於aop的配置約束可以在spring官網的Core包中找到
首先配置事務管理器
然后配置事務的通知(增強)去引用事務管理器,而這個通知,
就是公共重復的代碼部分,也是我們之前想通過動態代理去增強的部分。
對轉賬方法設定了一定有事務的事務傳播行為
對所有的find開頭方法進行了查詢方式的事務增強
切入點代表我們對業務層實現下的所有方法(連接點)進行攔截
然后把切入點與上述的通知建立關系。
即通過使用AOP的方式實現了事務的控制。