1、Spring概述
1.1 簡介
- Spring : 春天 —->給軟件行業帶來了春天
- 2002年,Rod Jahnson首次推出了Spring框架雛形interface21框架。
- 2004年3月24日,Spring框架以interface21框架為基礎,經過重新設計,發布了1.0正式版。
- 很難想象Rod Johnson的學歷 , 他是悉尼大學的博士,然而他的專業不是計算機,而是音樂學。
- Spring理念 : 使現有技術更加實用 . 本身就是一個大雜燴 , 整合現有的框架技術
官網 : http://spring.io/
官方下載地址 : https://repo.spring.io/libs-release-local/org/springframework/spring/
GitHub : https://github.com/spring-projects
1.2 優點
- Spring是一個開源免費的框架 , 容器 .
- Spring是一個輕量級的框架 , 非侵入式的 .
- 控制反轉 IoC , 面向切面 Aop
- 對事物的支持 , 對框架的支持
一句話概括:Spring是一個輕量級的控制反轉(IoC)和面向切面(AOP)的容器(框架)
1.3 組成
Spring 框架是一個分層架構,由 7 個定義良好的模塊組成。Spring 模塊構建在核心容器之上,核心容器定義了創建、配置和管理 bean 的方式 .
組成 Spring 框架的每個模塊(或組件)都可以單獨存在,或者與其他一個或多個模塊聯合實現。每個模塊的功能如下:
- 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要組件是
BeanFactory
,它是工廠模式的實現。BeanFactory
使用控制反轉(IOC) 模式將應用程序的配置和依賴性規范與實際的應用程序代碼分開。 - Spring 上下文:Spring 上下文是一個配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企業服務,例如 JNDI、EJB、電子郵件、國際化、校驗和調度功能。
- Spring AOP:通過配置管理特性,Spring AOP 模塊直接將面向切面的編程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的對象。Spring AOP 模塊為基於 Spring 的應用程序中的對象提供了事務管理服務。通過使用 Spring AOP,不用依賴組件,就可以將聲明性事務管理集成到應用程序中。
- Spring DAO:JDBC DAO 抽象層提供了有意義的異常層次結構,可用該結構來管理異常處理和不同數據庫供應商拋出的錯誤消息。異常層次結構簡化了錯誤處理,並且極大地降低了需要編寫的異常代碼數量(例如打開和關閉連接)。Spring DAO 的面向 JDBC 的異常遵從通用的 DAO 異常層次結構。
- Spring ORM:Spring 框架插入了若干個 ORM 框架,從而提供了 ORM 的對象關系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有這些都遵從 Spring 的通用事務和 DAO 異常層次結構。
- Spring Web 模塊:Web 上下文模塊建立在應用程序上下文模塊之上,為基於 Web 的應用程序提供了上下文。所以,Spring 框架支持與 Jakarta Struts 的集成。Web 模塊還簡化了處理多部分請求以及將請求參數綁定到域對象的工作。
- Spring MVC 框架:MVC 框架是一個全功能的構建 Web 應用程序的 MVC 實現。通過策略接口,MVC 框架變成為高度可配置的,MVC 容納了大量視圖技術,其中包括 JSP、Velocity、Tiles、iText 和 POI。
1.4 拓展
Spring Boot與Spring Cloud
- Spring Boot 是 Spring 的一套快速配置腳手架,可以基於Spring Boot 快速開發單個微服務;
- Spring Cloud是基於Spring Boot實現的;
- Spring Boot專注於快速、方便集成的單個微服務個體,Spring Cloud關注全局的服務治理框架;
- Spring Boot使用了約束優於配置的理念,很多集成方案已經幫你選擇好了,能不配置就不配置 , Spring Cloud很大的一部分是基於Spring Boot來實現,Spring Boot可以離開Spring Cloud獨立使用開發項目,但是Spring Cloud離不開Spring Boot,屬於依賴的關系。
- SpringBoot在SpringClound中起到了承上啟下的作用,如果你要學習SpringCloud必須要學習SpringBoot。
2、IOC(控制反轉)
控制反轉IoC(Inversion of Control),是一種設計思想,DI(依賴注入)是實現IoC的一種方法
沒有IoC的程序中 , 我們使用面向對象編程 , 對象的創建與對象間的依賴關系完全硬編碼在程序中,對象的創建由程序自己控制;控制反轉后將對象的創建轉移給第三方,個人認為所謂控制反轉就是:獲得依賴對象的方式反轉了。
IoC是Spring框架的核心內容,使用多種方式完美的實現了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置實現IoC。
2.1 原理
Spring容器在初始化時先讀取配置文件,根據配置文件或元數據創建與組織對象存入容器中,程序使用時再從Ioc容器中取出需要的對象。
采用XML方式配置Bean的時候,Bean的定義信息是和實現分離的,而采用注解的方式可以把兩者合為一體,Bean的定義信息直接以注解的形式定義在實現類中,從而達到了零配置的目的。
控制反轉是一種通過描述(XML或注解)並通過第三方去生產或獲取特定對象的方式。在Spring中實現控制反轉的是IoC容器,其實現方法是依賴注入(Dependency Injection,DI)。
控制反轉 :
- 控制 : 誰來控制對象的創建 , 傳統應用程序的對象是由程序本身控制創建的 , 使用Spring后 , 對象是由Spring來創建的
- 反轉 : 程序本身不創建對象 , 而變成被動的接收對象 .
依賴注入 : 利用set方法來進行注入的.(還有其他方法,通過set屬性注入最為常見)
IOC是一種編程思想,由主動的編程變成被動的接收,DI是實現他的其中一種方法
2.1 IOC創建對象方式
2.1.1 無參構造方法
<?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="user" class="com.kuang.pojo.User">
<property name="name" value="kuangshen"/>
</bean>
</beans>
2.1.2 有參構造方法
beans.xml 有三種方式編寫
<!-- 第一種根據index參數下標設置 -->
<bean id="userT" class="com.kuang.pojo.UserT">
<!-- index指構造方法 , 下標從0開始 -->
<constructor-arg index="0" value="kuangshen2"/>
</bean>
<!-- 第二種根據參數名字設置 -->
<bean id="userT" class="com.kuang.pojo.UserT">
<!-- name指參數名 -->
<constructor-arg name="name" value="kuangshen2"/>
</bean>
<!-- 第三種根據參數類型設置 -->
<bean id="userT" class="com.kuang.pojo.UserT">
<constructor-arg type="java.lang.String" value="kuangshen2"/>
</bean>
結論:在配置文件加載的時候。其中管理的對象都已經初始化了!
3、依賴注入(DI)
- 依賴注入(Dependency Injection,DI)
- 依賴 : 指Bean對象的創建依賴於容器 . Bean對象的依賴資源
- 注入 : 指Bean對象所依賴的資源 , 由容器來設置和裝配
3.1 構造器注入
即 第2點中 IOC創建對象 的內容
3.2 set注入 (重點)
要求被注入的屬性 , 必須有set方法 , set方法的方法名由set + 屬性首字母大寫
如果屬性是boolean類型 , 沒有set方法 , 是 is
包含多種注入方式:常量、bean、數組、List、Map、Set、Null、Properties 注入:
<bean id="addr" class="com.kuang.pojo.Address">
<property name="address" value="重慶"/>
</bean>
<bean id="student" class="com.kuang.pojo.Student">
<!-- 常量注入 value-->
<property name="name" value="小明"/>
<!-- bean注入 ref應用另一個bean -->
<property name="address" ref="addr"/>
<!-- 數組注入 -->
<property name="books">
<array>
<value>西游記</value>
<value>紅樓夢</value>
<value>水滸傳</value>
</array>
</property>
<!-- List注入 -->
<property name="hobbys">
<list>
<value>聽歌</value>
<value>看電影</value>
<value>爬山</value>
</list>
</property>
<!-- Map注入-->
<property name="card">
<map>
<entry key="中國郵政" value="456456456465456"/>
<entry key="建設" value="1456682255511"/>
</map>
</property>
<!-- Set注入-->
<property name="games">
<set>
<value>LOL</value>
<value>BOB</value>
<value>COC</value>
</set>
</property>
<!-- null注入-->
<property name="wife"><null/></property>
<!-- properties注入-->
<property name="info">
<props>
<prop key="學號">20190604</prop>
<prop key="性別">男</prop>
<prop key="姓名">小明</prop>
</props>
</property>
</bean>
3.3 拓展注入實現
1、P命名空間注入 : 需要在頭文件中假如約束文件
這種情況下 類中不能有 有參構造器!
導入約束 : xmlns:p="http://www.springframework.org/schema/p"
<!--P(屬性: properties)命名空間 , 屬性依然要設置set方法-->
<bean id="user" class="com.kuang.pojo.User" p:name="狂神" p:age="18"/>
2、c 命名空間注入 : 需要在頭文件中假如約束文件
這種情況下 類中需要有 有參構造器!
導入約束 : xmlns:c="http://www.springframework.org/schema/c"
<!--C(構造: Constructor)命名空間 , 屬性依然要設置set方法-->
<bean id="user" class="com.kuang.pojo.User" c:name="狂神" c:age="18"/>
4、Bean的作用域
在Spring中,那些組成應用程序的主體及由Spring IoC容器所管理的對象,被稱之為bean。
簡單地講,bean就是由IoC容器初始化、裝配及管理的對象 .
幾種作用域中,request、session作用域僅在基於web的應用中使用(不必關心你所采用的是什么web應用框架),只能用在基於web的Spring ApplicationContext環境。(不做總結)
4.1 Singleton(單例模式)
Spring的默認機制
當一個bean的作用域為Singleton,那么Spring IoC容器中只會存在一個共享的bean實例,並且所有對bean的請求,只要id與該bean定義相匹配,則只會返回bean的同一實例。Singleton是單例類型,就是在創建起容器時就同時自動創建了一個bean的對象,不管你是否使用,他都存在了,每次獲取到的對象都是同一個對象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中將bean定義成singleton,可以這樣配置:
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">
4.2 Prototype(原型模式)
當一個bean的作用域為Prototype,表示一個bean定義對應多個對象實例。Prototype作用域的bean會導致在每次對該bean請求(將其注入到另一個bean中,或者以程序的方式調用容器的getBean()方法)時都會創建一個新的bean實例。Prototype是原型類型,它在我們創建容器的時候並沒有實例化,而是當我們獲取bean的時候才會去創建一個對象,而且我們每次獲取到的對象都不是同一個對象。根據經驗,對有狀態的bean應該使用prototype作用域,而對無狀態的bean則應該使用singleton作用域。在XML中將bean定義成prototype,可以這樣配置:
<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>
或者
<bean id="account" class="com.foo.DefaultAccount" singleton="false"/>
5、Bean的自動裝配
- 自動裝配是使用spring滿足bean依賴的一種方法
- spring會在應用上下文中為某個bean尋找其依賴的bean,自動給bean裝配屬性
Spring中bean有三種裝配機制,分別是:
- 在xml中顯式配置; (寫xml配置文件,即上面的方法)
- 在java中顯式配置; (寫java的config配置文件)
- 隱式的bean發現機制和自動裝配。【重點,掌握】
由於在手動配置xml過程中,常常發生字母缺漏和大小寫等錯誤,而無法對其進行檢查,使得開發效率降低。
采用自動裝配將避免這些錯誤,並且使配置簡單化。
Spring的自動裝配需要從兩個角度來實現,或者說是兩個操作:
- 組件掃描(component scanning):spring會自動發現應用上下文中所創建的bean;
- 自動裝配(autowiring):spring自動滿足bean之間的依賴,也就是我們說的IoC/DI;
組件掃描和自動裝配組合發揮巨大威力,使的顯示的配置降低到最少。
推薦不使用自動裝配xml配置 , 而使用注解 .
5.1 byName
按 名稱 自動裝配
<bean id="class" class="com.whot.pojo.class"/>
<bean id="grade" class="com.whot.pojo.grade"/>
<!-- 在此處自動裝配bean -->
<bean id="user" class="com.whot.pojo.User" autowire="byName">
<property name="str" value="halo"/>
</bean>
在bean中添加屬性 autowrie,值為 byname
原理:根據bean中的 set 方法匹配:
-
user類中,存在 setClass 方法,則會 自動裝配 spring容器中 beanId 為 class 的bean
-
同理,若 存在 setGrade 方法 ,則會 自動裝配 spring容器中 beanId 為 grade 的bean
-
如果有,就取出注入;如果沒有,就報空指針異常。
一句話總結:
byName,會自動在容器上下文中查找 ,和自己對象 set 方法后面的值對應的 beanid !
5.2 byType
按 類型 自動裝配
使用autowire byType首先需要保證:同一類型的對象,在spring容器中唯一。如果不唯一,會報不唯一的異常。
NoUniqueBeanDefinitionException
使用方法:
<bean id="class" class="com.whot.pojo.class"/>
<bean id="grade" class="com.whot.pojo.grade"/>
<!-- 在此處自動裝配bean -->
<bean id="user" class="com.whot.pojo.User" autowire="byType">
<property name="str" value="halo"/>
</bean>
原理:
byType,會自動在容器上下文中查找 ,和自己對象中屬性類型相同的 bean !
5.3 @Autowired 【必須掌握】
- @Autowired是按類型自動轉配的,不支持id匹配。
- 需要導入 spring-aop的包!
- 可以寫在屬性上,方法上
5.4 @Qualifier
- @Autowired是根據類型自動裝配的,加上@Qualifier則可以根據byName的方式自動裝配
- @Qualifie不能單獨使用,必須在Autowired基礎上
@Autowired
@Qualifier(value = "cat2")
private Cat cat;
@Autowired
@Qualifier(value = "dog2")
private Dog dog;
5.5 @Resource
- 如有指定的name屬性,先按該屬性進行byName方式查找裝配;
- 其次再進行默認的byName方式進行裝配;
- 如果以上都不成功,則按byType的方式自動裝配。
- 都不成功,則報異常。
5.6 @Autowired與@Resource
- @Autowired與@Resource都可以用來裝配bean。都可以寫在字段上,或寫在setter方法上。
- @Autowired默認按類型裝配(屬於spring規范),默認情況下必須要求依賴對象必須存在,如果要允許null 值,可以設置它的required屬性為false,如:@Autowired(required=false) ,如果我們想使用名稱裝配可以結合@Qualifier注解進行使用
- @Resource(屬於J2EE復返),默認按照名稱進行裝配,名稱可以通過name屬性進行指定。如果沒有指定name屬性,當注解寫在字段上時,默認取字段名進行按照名稱查找,如果注解寫在setter方法上默認取屬性名進行裝配。 當找不到與名稱匹配的bean時才按照類型進行裝配。但是需要注意的是,如果name屬性一旦指定,就只會按照名稱進行裝配。
它們的作用相同都是用注解方式注入對象,但執行順序不同。@Autowired先byType,@Resource先byName。
6、代理模式
AOP的底層機制就是動態代理!
6.1 靜態代理
靜態代理角色分析
- 抽象角色 : 一般使用接口或者抽象類來實現
- 真實角色 : 被代理的角色
- 代理角色 : 代理真實角色 ; 代理真實角色后 , 一般會做一些附屬的操作 .
- 客戶 : 使用代理角色來進行一些操作 .
代碼實現:
Rent . java 即抽象角色
//抽象角色:租房
public interface Rent {
public void rent();
}
Host . java 即真實角色
//真實角色: 房東,房東要出租房子
public class Host implements Rent{
public void rent() {
System.out.println("房屋出租");
}
}
Proxy . java 即代理角色
//代理角色:中介
public class Proxy implements Rent {
private Host host;
public Proxy() { }
public Proxy(Host host) {
this.host = host;
}
//租房
public void rent(){
seeHouse();
host.rent();
fare();
}
//看房
public void seeHouse(){
System.out.println("帶房客看房");
}
//收中介費
public void fare(){
System.out.println("收中介費");
}
}
Client . java 即客戶
//客戶類,一般客戶都會去找代理!
public class Client {
public static void main(String[] args) {
//房東要租房
Host host = new Host();
//中介幫助房東
Proxy proxy = new Proxy(host);
//你去找中介!
proxy.rent();
}
}
靜態代理的好處
- 可以使得我們的真實角色更加純粹 . 不再去關注一些公共的事情 .
- 公共的業務由代理來完成 . 實現了業務的分工 ,
- 公共業務發生擴展時變得更加集中和方便 .
缺點 :
- 類多了 , 多了代理類 , 工作量變大了 . 開發效率降低 .
我們想要靜態代理的好處,又不想要靜態代理的缺點,所以 , 就有了動態代理 !
我們在不改變原來的代碼的情況下,實現了對原有功能的增強,這是AOP中最核心的思想
6.2 動態代理
- 動態代理的角色和靜態代理的一樣 .
- 動態代理的代理類是動態生成的 . 靜態代理的代理類是我們提前寫好的
- 動態代理分為兩類 : 一類是基於接口動態代理 , 一類是基於類的動態代理
- 基於接口的動態代理——JDK動態代理
- 基於類的動態代理—cglib
- 現在用的比較多的是 javasist 來生成動態代理 . 百度一下javasist
- 我們這里使用JDK的原生代碼來實現,其余的道理都是一樣的!
動態代理的本質,就是使用反射機制實現的!!!
代碼實現:
抽象角色和真實角色和之前的一樣!
ProxyInvocationHandler. java 即代理角色
public class ProxyInvocationHandler implements InvocationHandler {
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
//生成代理類,重點是第二個參數,獲取要代理的抽象角色!之前都是一個角色,現在可以代理一類角色
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),this);
}
// proxy : 代理類 method : 代理類的調用處理程序的方法對象.
// 處理代理實例上的方法調用並返回結果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse();
//核心:本質利用反射實現!
Object result = method.invoke(rent, args);
fare();
return result;
}
//看房
public void seeHouse(){
System.out.println("帶房客看房");
}
//收中介費
public void fare(){
System.out.println("收中介費");
}
}
Client . java
//租客
public class Client {
public static void main(String[] args) {
//真實角色
Host host = new Host();
//代理實例的調用處理程序
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setRent(host); //將真實角色放置進去!
Rent proxy = (Rent)pih.getProxy(); //動態生成對應的代理類!
proxy.rent();
}
}
核心:一個動態代理 , 一般代理某一類業務 , 一個動態代理可以代理多個類,代理的是接口!
編寫一個通用的動態代理實現的類!所有的代理對象設置為Object即可!
public class ProxyInvocationHandler implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成代理類
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
// proxy : 代理類
// method : 代理類的調用處理程序的方法對象.
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
Object result = method.invoke(target, args);
return result;
}
public void log(String methodName){
System.out.println("執行了"+methodName+"方法");
}
}
動態代理的好處:
靜態代理有的它都有,靜態代理沒有的,它也有!
- 可以使得我們的真實角色更加純粹 . 不再去關注一些公共的事情 .
- 公共的業務由代理來完成 . 實現了業務的分工 ,
- 公共業務發生擴展時變得更加集中和方便 .
- 一個動態代理 , 一般代理某一類業務
- 一個動態代理可以代理多個類,代理的是接口!
7、AOP
7.1 什么是AOP
AOP(Aspect Oriented Programming)意為:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生范型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
7.2 Aop在Spring中的作用
提供聲明式事務
允許用戶自定義切面
- 橫切關注點:跨越應用程序多個模塊的方法或功能。即是,與我們業務邏輯無關的,但是我們需要關注的部分,就是橫切關注點。如日志 , 安全 , 緩存 , 事務等等 ….
- 切面(ASPECT):橫切關注點 被模塊化 的特殊對象。即,它是一個類。
- 通知(Advice):切面必須要完成的工作。即,它是類中的一個方法。
- 目標(Target):被通知對象。
- 代理(Proxy):向目標對象應用通知之后創建的對象。
- 切入點(PointCut):切面通知 執行的 “地點”的定義。
- 連接點(JointPoint):與切入點匹配的執行點。
SpringAOP中,通過Advice定義橫切邏輯,Spring中支持5種類型的Advice:
即 Aop 在 不改變原有代碼 的情況下 , 去增加新的功能 .
Spring的Aop就是將公共的業務 (日志 , 安全等) 和領域業務結合起來 , 當執行領域業務時 , 將會把公共業務加進來 . 實現公共業務的重復利用 . 領域業務更純粹 , 程序猿專注領域業務 , 其本質還是動態代理 .
7.3 Spring中實現AOP
- 方法一:通過 Spring API 接口實現
- 方法二:通過自定義 切面 類來實現Aop
- 方法三:使用注解實現
代碼實現和學習筆記 參考:狂神說SSM教程
8、事務
- 事務在項目開發過程非常重要,涉及到數據的一致性的問題,不容馬虎!
- 事務管理是企業級應用程序開發中必備技術,用來確保數據的完整性和一致性。
事務就是把一系列的動作當成一個獨立的工作單元,這些動作要么全部完成,要么全部不起作用。
事務四個屬性ACID
- 原子性(atomicity)
- 事務是原子性操作,由一系列動作組成,事務的原子性確保動作要么全部完成,要么完全不起作用
- 一致性(consistency)
- 一旦所有事務動作完成,事務就要被提交。數據和資源處於一種滿足業務規則的一致性狀態中
- 隔離性(isolation)
- 可能多個事務會同時處理相同的數據,因此每個事務都應該與其他事務隔離開來,防止數據損壞
- 持久性(durability)
- 事務一旦完成,無論系統發生什么錯誤,結果都不會受到影響。通常情況下,事務的結果被寫到持久化存儲器中
Spring中的事務管理:
- 聲明式事務:AOP
- 編程式事務:需要在代碼中進行對事務的管理
為什么需要配置事務?
- 如果不配置,就需要我們手動提交控制事務;
- 事務在項目開發過程非常重要,涉及到數據的一致性的問題,不容馬虎!