spring 快速入門

王延領發表於2021-09-03

1.spring

Spring 框架可以說是Java 世界最為成功的框架,在企業實際應用中,大部分的企業架構都基於Spring 框架。它的成功來自於理念,而不是技術,它最為核心的理念是IoC (控制反轉)和AOP (面向切面程式設計),其中IoC 是Spring的基礎,而AOP 則是其重要的功能,最為典型的當屬資料庫事務的使用。

Spring最根本的使命是解決企業級應用開發的複雜性,即簡化Java開發。

1.1.優點

  1. 方便解耦,簡化開發

    Spring就是一個大工廠,可以將所有物件的建立和依賴關係的維護,交給Spring管理。

  2. AOP程式設計的支援

    Spring提供面向切面程式設計,可以方便的實現對程式進行許可權攔截、執行監控等功能。

  3. 宣告式事務的支援

    只需要通過配置就可以完成對事務的管理,而無需手動程式設計。

  4. 方便程式的測試

    Spring對Junit4支援,可以通過註解方便的測試Spring程式。

  5. 方便整合各種優秀框架

    Spring不排斥各種優秀的開源框架,其內部提供了對各種優秀框架的直接支援(如:Struts、Hibernate、MyBatis等)。

  6. 降低JavaEE API的使用難度

    Spring對JavaEE開發中非常難用的一些API(JDBC、JavaMail、遠端呼叫等),都提供了封裝,使這些API應用難度大大降低。

1.2.缺點

  1. Spring明明一個很輕量級的框架,卻給人感覺大而全
  2. Spring依賴反射,反射影響效能
  3. 使用門檻升高,入門Spring需要較長時間

1.3.Spring框架的組成結構圖

Spring 總共大約有 20 個模組, 由 1300 多個不同的檔案構成。 而這些元件被分別整合在核心容器(Core Container) 、 AOP(Aspect Oriented Programming)和裝置支援(Instrmentation) 、資料訪問與整合(Data Access/Integeration) 、 Web、 訊息(Messaging) 、 Test等 6 個模組中。 以下是 Spring 5 的模組結構圖:

spring-overview

組成 Spring 框架的每個模組(或元件)都可以單獨存在,或者與其他一個或多個模組聯合實現。每個模組的功能如下:

1.3.1.核心容器

Spring的核心容器是其他模組建立的基礎,有spring-core、spring-beans、spring-context、spring-context-support和spring-expression(Spring表示式語言)等模組組成。

spring-core 模組:提供了框架的基本組成部分,包括控制反轉(Inversion of Control,IOC)和依賴注入(Dependency Injection,DI)功能。

spring-beans 模組:提供了BeanFactory,是工廠模式的一個經典實現,Spring將管理物件稱為Bean。

spring-context 模組:建立在Core和Beans模組的基礎之上,提供一個框架式的物件訪問方式,是訪問定義和配置的任何物件的媒介。ApplicationContext介面是Context模組的焦點。

spring-context-support 模組:支援整合第三方庫到Spring應用程式上下文,特別是用於快取記憶體(EhCache、JCache)和任務排程(CommonJ、Quartz)的支援。

Spring-expression 模組:提供了強大的表示式語言去支援執行時查詢和操作物件圖。這是對JSP2.1規範中規定的統一表示式語言(Unified EL)的擴充套件。該語言支援設定和獲取屬性值、屬性分配、方法呼叫、訪問陣列、集合和索引器的內容、邏輯和算術運算、變數命名以及從Spring的IOC容器中以名稱檢索物件。它還支援列表投影、選擇以及常用的列表聚合。

1.3.2.AOP 和裝置支援

由spring-aop、 spring-aspects 和 spring-instrument等 3 個模組組成。

spring-aop 模組:是 Spring 的另一個核心模組,提供了一個符合 AOP 要求的面向切面的程式設計實現。 作為繼 OOP(物件導向程式設計) 後, 對程式設計師影響最大的程式設計思想之一, AOP 極大地開拓了人們對於程式設計的思路。 在 Spring 中, 以動態代理技術為基礎,允許定義方法攔截器和切入點,將程式碼按照功能進行分離,以便乾淨地解耦。

spring-aspects 模組:提供了與AspectJ的整合功能,AspectJ是一個功能強大且成熟的AOP框架。

spring-instrument 模組:是 AOP 的一個支援模組, 提供了類植入(Instrumentation)支援和類載入器的實現,可以在特定的應用伺服器中使用。主要作用是在 JVM 啟用時, 生成一個代理類, 程式設計師通過代理類在執行時修改類的位元組, 從而改變一個類的功能, 實現 AOP 的功能。

1.3.3.資料訪問與整合

由 spring-jdbc、spring-orm、spring-oxm、spring-jms 和 spring-tx 等 5 個模組組成。

spring-jdbc 模組:提供了一個JDBC的抽象層,消除了煩瑣的JDBC編碼和資料庫廠商特有的錯誤程式碼解析, 用於簡化JDBC。主要是提供 JDBC 模板方式、 關聯式資料庫物件化方式、 SimpleJdbc 方式、 事務管理來簡化 JDBC 程式設計, 主要實現類是 JdbcTemplate、 SimpleJdbcTemplate 以及 NamedParameterJdbcTemplate。

spring-orm 模組:是 ORM 框架支援模組, 主要整合 Hibernate, Java Persistence API (JPA) 和Java Data Objects (JDO) 用於資源管理、 資料訪問物件(DAO)的實現和事務策略。

spring-oxm 模組:主要提供一個抽象層以支撐 OXM(OXM 是 Object-to-XML-Mapping 的縮寫, 它是一個 O/M-mapper, 將 java 物件對映成 XML 資料, 或者將 XML 資料對映成 java 物件) , 例如: JAXB,Castor,XMLBeans,JiBX 和 XStream 等。

spring-jms模組(Java Messaging Service):指Java訊息傳遞服務,包含用於生產和使用訊息的功能。自Spring4.1以後,提供了與spring-messaging模組的整合。

spring-tx 模組:事務模組,支援用於實現特殊介面和所有POJO(普通Java物件)類的程式設計和宣告式事務管理。

1.3.4.Web

由spring-websocket、spring-webmvc、spring-web、portlet和spring-webflux模組等 5 個模組組成。

spring-websocket 模組:Spring4.0以後新增的模組,實現雙工非同步通訊協議,實現了WebSocket和SocketJS,提供Socket通訊和web端的推送功能。

spring-webmvc 模組:也稱為Web-Servlet模組,包含用於web應用程式的Spring MVC和REST Web Services實現。Spring MVC框架提供了領域模型程式碼和Web表單之間的清晰分離,並與Spring Framework的所有其他功能整合。

spring-web 模組:提供了基本的Web開發整合功能,包括使用Servlet監聽器初始化一個IOC容器以及Web應用上下文,自動載入WebApplicationContext特性的類,Struts整合類、檔案上傳的支援類、Filter類和大量輔助工具類。

portlet 模組:實現web模組功能的聚合,類似於Servlet模組的功能,提供了Portlet環境下的MVC實現。

spring-webflux 模組:是一個新的非堵塞函式式 Reactive Web 框架, 可以用來建立非同步的, 非阻塞,事件驅動的服務, 並且擴充套件性非常好。

1.3.5.訊息(Messaging)

即 spring-messaging 模組。

spring-messaging 是從 Spring4 開始新加入的一個模組, 該模組提供了對訊息傳遞體系結構和協議的支援。

1.3.6.Test

即 spring-test 模組。

spring-test 模組主要為測試提供支援的,支援使用JUnit或TestNG對Spring元件進行單元測試和整合測試。

2.Spring核心ioc

Ioc—Inversion of Control,即“控制反轉”,不是什麼技術,而是一種設計思想。在Java開發中,Ioc意味著將你設計好的物件交給容器控制,而不是傳統的在你的物件內部直接控制。就是不例項化了。先注入。

誰控制誰,控制什麼:傳統Java SE程式設計,我們直接在物件內部通過new進行建立物件,是程式主動去建立依賴物件;而IoC是有專門一個容器來建立這些物件,即由Ioc容器來控制物件的建立;誰控制誰?當然是IoC 容器控制了物件;控制什麼?那就是主要控制了外部資源獲取

為何是反轉,哪些方面反轉了:有反轉就有正轉,傳統應用程式是由我們自己在物件中主動控制去直接獲取依賴物件,也就是正轉;而反轉則是由容器來幫忙建立及注入依賴物件;為何是反轉?因為由容器幫我們查詢及注入依賴物件,物件只是被動的接受依賴物件,所以是反轉;哪些方面反轉了?依賴物件的獲取被反轉了。

ps:控制反轉是目標,依賴注入是手段。

2.1.ioc容器

image-20210830155718002

IoC 容器是 Spring 的核心,也可以稱為 Spring 容器。Spring 通過 IoC 容器來管理物件的例項化和初始化,以及物件從建立到銷燬的整個生命週期。

Spring 中使用的物件都由 IoC 容器管理,不需要我們手動使用 new 運算子建立物件。由 IoC 容器管理的物件稱為 Spring Bean,Spring Bean 就是 Java 物件,和使用 new 運算子建立的物件沒有區別。

Spring 通過讀取 XML 或 Java 註解中的資訊來獲取哪些物件需要例項化。

Spring 提供 2 種不同型別的 IoC 容器,即 BeanFactory 和 ApplicationContext 容器

2.1.BeanFactory 容器

BeanFactory 是最簡單的容器,由 org.springframework.beans.factory.BeanFactory 介面定義,採用懶載入(lazy-load),所以容器啟動比較快。BeanFactory 提供了容器最基本的功能。

為了能夠相容 Spring 整合的第三方框架(如 BeanFactoryAware、InitializingBean、DisposableBean),所以目前仍然保留了該介面。

簡單來說,BeanFactory 就是一個管理 Bean 的工廠,它主要負責初始化各種 Bean,並呼叫它們的生命週期方法。

BeanFactory 介面有多個實現類,最常見的是 org.springframework.beans.factory.xml.XmlBeanFactory。使用 BeanFactory 需要建立 XmlBeanFactory 類的例項,通過 XmlBeanFactory 類的建構函式來傳遞 Resource 物件。如下所示。

Resource resource = new ClassPathResource("applicationContext.xml");
BeanFactory factory = new XmlBeanFactory(resource);  

2.1.2. ApplicationContext 容器

ApplicationContext 繼承了 BeanFactory 介面,由 org.springframework.context.ApplicationContext 介面定義,物件在啟動容器時載入。ApplicationContext 在 BeanFactory 的基礎上增加了很多企業級功能,例如 AOP、國際化、事件支援等。

ApplicationContext 介面有兩個常用的實現類,具體如下。

2.1.2.1.ClassPathXmlApplicationContext

該類從類路徑 ClassPath 中尋找指定的 XML 配置檔案,並完成 ApplicationContext 的例項化工作,具體如下所示。

ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation);

在上述程式碼中,configLocation 引數用於指定 Spring 配置檔案的名稱和位置,如 Beans.xml。

2.1.2.2.FileSystemXmlApplicationContext

該類從指定的檔案系統路徑中尋找指定的 XML 配置檔案,並完成 ApplicationContext 的例項化工作,具體如下所示。

ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation);

它與 ClassPathXmlApplicationContext 的區別是:在讀取 Spring 的配置檔案時,FileSystemXmlApplicationContext 不會從類路徑中讀取配置檔案,而是通過引數指定配置檔案的位置。即 FileSystemXmlApplicationContext 可以獲取類路徑之外的資源,如“F:/workspaces/Beans.xml”。

2.1.2.3.AnnotationConfigApplicationContext

讀取用註解建立容器

通常在 Java 專案中,會採用 ClassPathXmlApplicationContext 類例項化 ApplicationContext 容器的方式,而在 Web 專案中,ApplicationContext 容器的例項化工作會交由 Web 伺服器完成。Web 伺服器例項化 ApplicationContext 容器通常使用基於 ContextLoaderListener 實現的方式,它只需要在 web.xml 中新增如下程式碼:

<!--指定Spring配置檔案的位置,有多個配置檔案時,以逗號分隔-->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <!--spring將載入spring目錄下的applicationContext.xml檔案-->
    <param-value>
        classpath:spring/applicationContext.xml
    </param-value>
</context-param>
<!--指定以ContextLoaderListener方式啟動Spring容器-->
<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

需要注意的是,BeanFactory 和 ApplicationContext 都是通過 XML 配置檔案載入 Bean 的。

二者的主要區別在於,如果 Bean 的某一個屬性沒有注入,使用 BeanFacotry 載入後,第一次呼叫 getBean() 方法時會丟擲異常,而 ApplicationContext 則會在初始化時自檢,這樣有利於檢查所依賴的屬性是否注入。

因此,在實際開發中,通常都選擇使用 ApplicationContext,只有在系統資源較少時,才考慮使用 BeanFactory。

2.2.使用ioc容器

2.2.1.beans.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">
    <bean id="user" class="com.wyl.pojo.User">
        <property name="name" value="王延領"/>
    </bean>
</beans>

2.2.2.pojo.User

public class User { 
    private String name; 
    public User() {
        System.out.println("user無參構造方法");
    } 
    public void setName(String name) {
        this.name = name;
    } 
    public void show(){
        System.out.println("name="+ name );
    }
}

2.2.2.test

@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    //在執行getBean的時候, user已經建立好了 , 通過無參構造
    User user = (User) context.getBean("user");
    //呼叫物件的方法 .
    user.show();
}

2.3.bean

2.3.1.定義

由 Spring IoC 容器管理的物件稱為 Bean,Bean 根據 Spring 配置檔案中的資訊建立。可以把 Spring IoC 容器看作是一個大工廠,Bean 相當於工廠的產品,如果希望這個大工廠生產和管理 Bean,則需要告訴容器需要哪些 Bean,以及需要哪種方式裝配 Bean。

Spring 配置檔案支援兩種格式,即 XML 檔案格式和 Properties 檔案格式。

  • Properties 配置檔案主要以 key-value 鍵值對的形式存在,只能賦值,不能進行其他操作,適用於簡單的屬性配置。

  • XML 配置檔案是樹形結構,相對於 Properties 檔案來說更加靈活。XML 配置檔案結構清晰,但是內容比較繁瑣,適用於大型複雜的專案。

通常情況下,Spring 的配置檔案使用 XML 格式。XML 配置檔案的根元素是 ,該元素包含了多個子元素 。每一個 元素都定義了一個 Bean,並描述了該 Bean 如何被裝配到 Spring 容器中。

2.3.2.建立

2.3.2.1 預設方式

無參

<!-- 1. 預設建構函式,如果類中沒有預設建構函式則無法建立物件;bean標籤中只有id和class就預設使用建構函式建立物件 -->
<bean id="userService" class="com.wyl.pojo.User"/>

有參

<!-- 第一種根據index引數下標設定 -->
<bean id="userService" class="com.wyl.pojo.User">
    <!-- index指構造方法 , 下標從0開始 -->
    <constructor-arg index="0" value="wyl"/>
</bean>
 
<!-- 第二種根據引數名字設定 -->
<bean id="userService" class="com.wyl.pojo.User">
    <!-- name指引數名 -->
    <constructor-arg name="name" value="wyl"/>
</bean>
<!-- 第三種根據引數型別設定 -->
<bean id="userService" class="com.wyl.pojo.User">
    <constructor-arg type="java.lang.String" value="wyl"/>
</bean>

2.3.2.2 工廠類中的方法

<!-- 2. 使用工廠中的方法建立物件;工廠中有一個方法可以建立物件,先建立工廠物件,通過factory-bean指向工廠,使用factory-method方法獲取物件 -->
<bean id="beanFactory" class="org.factory.BeanFactory"/>
<bean id="userService" factory-bean="beanFactory" factory-method="getUserService"/>

2.3.2.3 靜態工廠中的靜態方法

<!-- 3. 使用靜態工廠中的靜態方法建立物件 -->
<bean id="userService" class="org.factory.StaticBeanFactory" factory-method="getUserService"/>

2.3.2.配置

2.3.2.1.別名

<!--  別名 : 如果新增了別名,我們也可以使用別名獲取到這個物件 -->
<alias name="User" alias="u1"></alias>

2.3.2.2.bean 別名

<!--
  bean標籤常用屬性:

id屬性:起名稱,id屬性值名稱任意命名,不能包含特殊符號
class屬性:建立物件所在類的全路徑
name屬性:功能和id屬性一樣的,但是在name屬性值裡面可以包含特殊符號
scope屬性
singleton:預設值,單例
prototype:多例
request:建立物件把物件放到request域裡面
session:建立物件把物件放到session域裡面
globalSession:建立物件把物件放到globalSession裡面
  -->
<bean id="UserT" class="com.wyl.pojo.User" scope="singleton" name="u2 u21,u22;u23">
    <property name="name" value="123"/>
</bean>

2.3.2.2.import

團隊的合作通過import來實現 .

<import resource="beans.xml"/>

能將多個人開發的不同的配置xml檔案整合到applicationContext.xml檔案中,並且能夠合適的去重。

2.3.3.作用域

<bean id="..." class="..." scope="singleton"/>

Spring 容器在初始化一個 Bean 例項時,同時會指定該例項的作用域。Spring 5 支援以下 6 種作用域。

singleton

預設值,單例模式,表示在 Spring 容器中只有一個 Bean 例項,Bean 以單例的方式存在。

prototype

原型模式,表示每次通過 Spring 容器獲取 Bean 時,容器都會建立一個 Bean 例項。

request

每次 HTTP 請求,容器都會建立一個 Bean 例項。該作用域只在當前 HTTP Request 內有效。

session

同一個 HTTP Session 共享一個 Bean 例項,不同的 Session 使用不同的 Bean 例項。該作用域僅在當前 HTTP Session 內有效。

application

同一個 Web 應用共享一個 Bean 例項,該作用域在當前 ServletContext 內有效。
類似於 singleton,不同的是,singleton 表示每個 IoC 容器中僅有一個 Bean 例項,而同一個 Web 應用中可能會有多個 IoC 容器,但一個 Web 應用只會有一個 ServletContext,也可以說 application 才是 Web 應用中貨真價實的單例模式。

websocket

websocket 的作用域是 WebSocket ,即在整個 WebSocket 中有效
equest、session、application、websocket 和 global Session 作用域只能在 Web 環境下使用,如果使用 ClassPathXmlApplicationContext 載入這些作用域中的任意一個的 Bean,就會丟擲以下異常。

2.3.4.生命週期

Bean的生命週期

  1. Spring 啟動,查詢並載入需要被 Spring 管理的 Bean,並例項化 Bean。

  2. 利用依賴注入完成 Bean 中所有屬性值的配置注入。

  3. 如果 Bean 實現了 BeanNameAware 介面,則 Spring 呼叫 Bean 的 setBeanName() 方法傳入當前 Bean 的 id 值。

  4. 如果 Bean 實現了 BeanFactoryAware 介面,則 Spring 呼叫 setBeanFactory() 方法傳入當前工廠例項的引用。

  5. 如果 Bean 實現了 ApplicationContextAware 介面,則 Spring 呼叫 setApplicationContext() 方法傳入當前 ApplicationContext 例項的引用。

  6. 如果 Bean 實現了 [BeanPostProcessor] 介面,則 Spring 呼叫該介面的預初始化方法 postProcessBeforeInitialzation() 對 Bean 進行加工操作,此處非常重要,Spring 的 AOP 就是利用它實現的。

  7. 如果 Bean 實現了 InitializingBean 介面,則 Spring 將呼叫 afterPropertiesSet() 方法。

  8. 如果在配置檔案中通過 init-method 屬性指定了初始化方法,則呼叫該初始化方法。

  9. 如果 [BeanPostProcessor ]和 Bean 關聯,則 Spring 將呼叫該介面的初始化方法 postProcessAfterInitialization()。此時,Bean 已經可以被應用系統使用了。

  10. 如果在 中指定了該 Bean 的作用域為 singleton,則將該 Bean 放入 Spring IoC 的快取池中,觸發 Spring 對該 Bean 的生命週期管理; 如果在 中指定了該 Bean 的作用域為 prototype,則將該 Bean 交給呼叫者,呼叫者管理該 Bean 的生命週期,Spring 不再管理該 Bean。

  11. 如果 Bean 實現了 DisposableBean 介面,則 Spring 會呼叫 destory() 方法銷燬 Bean;如果在配置檔案中通過 destory-method 屬性指定了 Bean 的銷燬方法,則 Spring 將呼叫該方法對 Bean 進行銷燬。

2.3.4.1.單例

public class UserBean {
	private String name;  
    
    public UserBean(){  
        System.out.println("UserBean()建構函式");  
    }  
    public String getName() {  
        return name;  
    }  
    public void setName(String name) {  
        System.out.println("setName()");  
        this.name = name;  
    }  
    public void init(){  
        System.out.println("this is init of UserBean");  
    }  
      
    public void destory(){  
        System.out.println("this is destory of UserBean " + this);  
    }  
}

<bean id="user_singleton" class="com.wyl.userBean" scope="singleton" 
			init-method="init" destroy-method="destory" lazy-init="true"/>

當scope="singleton",即預設情況下,會在啟動容器時(即例項化容器時)時例項化。但我們可以指定Bean節點的lazy-init="true"來延遲初始化bean,這時候,只有在第一次獲取bean時才會初始化bean,即第一次請求該bean時才初始化.

如果想對所有的預設單例bean都應用延遲初始化,可以在根節點beans設定default-lazy-init屬性為true,如下所示:

<beans default-lazy-init="true">
public class LifeTest {
	@Test 
	public void test() {
		AbstractApplicationContext container = 
		new ClassPathXmlApplicationContext("user.xml");
		UserBean user = (UserBean)container.getBean("user_singleton");
		System.out.println(user);
		container.close();
	}
}

UserBean()建構函式
this is init of UserBean
com.wyl.UserBean@573f2bb1
……
this is destory of UserBeancom.wyl.UserBean@573f2bb1

預設情況下,Spring在讀取xml檔案的時候,就會建立物件。在建立物件的時候先呼叫構造器[UserBean(),然後呼叫init-method屬性值中所指定的方法。物件在被銷燬的時候,會呼叫destroy-method屬性值中所指定的方法.

2.3.4.2.非單例管理的物件

當scope="prototype"時,容器也會延遲初始化bean,Spring讀取xml檔案的時候,並不會立刻建立物件,而是在第一次請求該bean時才初始化(如呼叫getBean方法時)。

在第一次請求每一個prototype的bean時,Spring容器都會呼叫其構造器建立這個物件,然後呼叫init-method屬性值中所指定的方法。物件銷燬的時候,Spring容器不會幫我們呼叫任何方法,因為是非單例,這個型別的物件有很多個,Spring容器一旦把這個物件交給你之後,就不再管理這個物件了。

<bean id="user_prototype" class="com.bean.UserBean" scope="prototype" init-method="init" destroy-method="destroy"/>
public class UserTest {
	@Test 
	public void test() {
		AbstractApplicationContext container = new ClassPathXmlApplicationContext("User.xml");
		UserBean User1 = (UserBean)container.getBean("User_singleton");
		System.out.println(User1);
		
		UserBean User2 = (UserBean)container.getBean("User_prototype");
		System.out.println(User2);
		container.close();
	}
}

結果

UserBean()建構函式
this is init of UserBean
com.wyl.UserBean@573f2bb1
LifeBean()建構函式
this is init of UserBean
com.wyl.UserBean@5ae9a829
……
this is destory of lifeBean com.wyl.UserBean@573f2bb1

2.4.DI(依賴注入)

依賴注入Dependency Injection,在解耦的過程中,我們將物件的建立交給Spring容器管理,當我們需要用其他類的物件,由Spring提供,我們只需在配置檔案裡宣告即可。A類使用B類,就產生依賴關係,Spring給我們解決依賴關係就是依賴注入(DI)

2.4.1.構造器注入

private String name;
private Integer age;
private Date birthday;
// 建構函式
public UserServiceImpl(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
<!-- name:按欄位名稱輔助;index:欄位索引,給第幾個欄位賦值;type:指定注入值的型別,該型別也是建構函式中某個或某些欄位的型別; -->
<!-- value:要注入的值,基本型別和String;ref:注入其他型別資料,指向外部bean物件;這個外部bean需要存在於Spring容器 -->
<bean id="userService" class="org.service.impl.UserServiceImpl">
    <constructor-arg name="name" value="張三"/>
    <constructor-arg name="age" value="12"/>
    <constructor-arg name="birthday" ref="date"/>
</bean>
<!-- 建立日期物件 -->
<bean id="date" class="java.util.Date"/>

2.4.2.Set方式注入

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;
}
<bean id="userService2" class="org.service.impl.UserServiceImpl2">
    <property name="name" value="李四"/>
    <property name="age" value="12"/>
    <property name="birthday" ref="date"/>
</bean>
<!-- 建立日期物件 -->
<bean id="date" class="java.util.Date"/>

2.4.3.物件型別注入

<!-- 注入物件型別屬性 -->
<!-- 1 配置service和dao物件 -->
<bean id="userDao" class="cn.ioc.UserDao"></bean>
<bean id="userService" class="cn.ioc.UserService">
    <!-- 注入dao物件-->
    <property name="userDao" ref="userDao"></property>
</bean>

2.4.4.複雜型別注入

<!-- 注入複雜型別屬性值 -->
  <bean id="person" class="cn.property.Person">
    <!-- 陣列 -->
    <property name="arrs">
       <list>
         <value>小王</value>
         <value>小馬</value>
         <value>小宋</value>
       </list>
    </property>
    
    <!-- list -->
    <property name="list">
       <list>
         <value>小奧</value>
         <value>小金</value>
         <value>小普</value>
       </list>      
    </property>

    <!-- map -->
    <property name="map">
       <map>
         <entry key="aa" value="lucy"></entry>
         <entry key="bb" value="mary"></entry>
         <entry key="cc" value="tom"></entry>
       </map>
    </property>

    <!-- properties -->
    <property name="properties">
       <props>
         <prop key="driverclass">com.mysql.jdbc.Driver</prop>
         <prop key="username">root</prop>
       </props>
    </property>
  </bean>
<!--set-->
<property name="set">
            <set>
                <value>LOL</value>
                <value>COC</value>
                <value>WOW</value>
            </set>
</property>
<!--null-->
<property name="marne">
            <null/>
</property>

2.4.5.擴充方式注入

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>

p命名注入 property

<!-- p名稱空間注入,可以直接注入屬性的值:property -->
<bean id="User"  class="com.wyl.pojo.User" p:name ="老秦" p:age ="18"/>

c名稱空間注入 constructor

<!-- c名稱空間注入,通過構造器注入:construct-args -->
<bean id="User2" class="com.wyl.pojo.User" c:age="18" c:name="老李"/>

注意點:p命名和c命名不能直接使用,需要匯入xml約束

2.5.自動裝配

自動裝配是Spring滿足bean依賴的一種方式!Spring會在上下文中自動尋找,並自動給bean裝配屬性。

在Spring中有三種裝配的方式

  1. 在xml中顯示的配置

  2. 在java中顯示配置

  3. 隱式的自動裝配bean

    名稱 說明
    no 預設值,表示不使用自動裝配,Bean 依賴必須通過 ref 元素定義。
    byName 根據 Property 的 name 自動裝配,如果一個 Bean 的 name 和另一個 Bean 中的 Property 的 name 相同,則自動裝配這個 Bean 到 Property 中。
    byType 根據 Property 的資料型別(Type)自動裝配,如果一個 Bean 的資料型別相容另一個 Bean 中 Property 的資料型別,則自動裝配。
    constructor 類似於 byType,根據構造方法引數的資料型別,進行 byType 模式的自動裝配。
    autodetect(3.0版本不支援) 如果 Bean 中有預設的構造方法,則用 constructor 模式,否則用 byType 模式。

2.5.1.byName

<!--
byName:會自動在容器上下文中查詢,和自己物件set方法後面的值對應的beanid!
-->
<bean id="people" class="com.wyl.pojo.People" autowire="byName">
    <property name="name" value="wangyanling"/>
</bean>

2.5.2.byType

<bean id="cat" class="com.wyl.pojo.Cat"/>
    <bean id="dog" class="com.wyl.pojo.Dog"/>
    <!--
    byName:會自動在容器上下文中查詢,和自己物件set方法後面的值對應的beanid!
    byType:會自動在容器上下文中查詢,和自己物件屬性型別相同的bean!
    -->
    <bean id="people" class="com.wyl.pojo.People" autowire="byType">
        <property name="name" value="WANGAYNLING"/>
    </bean>

2.5.3.註解

jdk1.5支援的註解,Spring2.5就支援註解了!

要使用註解須知:

  1. 匯入約束 context約束

  2. 配置註解的支援: context:annotation-config/

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            https://www.springframework.org/schema/context/spring-context.xsd">
    
        <context:annotation-config/>
    
    </beans>
    

@Autowired

@Autowired是按型別自動轉配的,不支援id匹配。byType
需要匯入 spring-aop的包!
直接在屬性上使用即可!也可以在set方式上使用!

使用Autowired我們可以不用編寫Set方法了,前提是這個自動裝配的屬性在IOC容器中存在,且符合名字byname。

 @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;
@Nullable     // 欄位標記了這個註解,說明這個欄位可以為null

或者 如果顯示定義了Autowired的required 的屬性為false ,說明這個物件可以為null,允許為空

autowired 註解應該是隻能是別的,當注入 在IOC容器中該型別只有一個時,就通過byType進行裝配,當注入容器存在多個同意型別的物件是,就是根據byName進行裝配

如果@Autowired自動裝配的環境比較複雜,自動裝配無法通過一個註解[@Autowired]完成的時候,我們可以使用@Qualifier(value=“XXX”)去配置@Autowired的使用,指定一個唯一的bean物件注入。

@Qualifier

@Autowired是根據型別自動裝配的,加上@Qualifier則可以根據byName的方式自動裝配
@Qualifier不能單獨使用。

public class People {
    private String name;
    @Autowired
    @Qualifier("cat")
    private Cat cat;
    @Autowired
    @Qualifier("dog")
    private Dog dog;
}

@Resource註解

  • @Resource如有指定的name屬性,先按該屬性進行byName方式查詢裝配;
  • 其次再進行預設的byName方式進行裝配;
  • 如果以上都不成功,則按byType的方式自動裝配。
  • 都不成功,則報異常。
public class People {
    private String name;
    @Resource(name = "cat")
    private Cat cat;
    @Resource(name = "dog")
    private Dog dog;

小結
@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。

2.6.ioc註解

@註解名稱(屬性名稱=屬性值)

2.6.1. Spring使用的註解大全和解釋

註解 解釋
@Controller 組合註解(組合了@Component註解),應用在MVC層(控制層),DispatcherServlet會自動掃描註解了此註解的類,然後將web請求對映到註解了@RequestMapping的方法上。
@Service 組合註解(組合了@Component註解),應用在service層(業務邏輯層)
@Repository 組合註解(組合了@Component註解),應用在dao層(資料訪問層)
@Component 表示一個帶註釋的類是一個“元件”,成為Spring管理的Bean。當使用基於註解的配置和類路徑掃描時,這些類被視為自動檢測的候選物件。同時@Component還是一個元註解。
@Autowired Spring提供的工具(由Spring的依賴注入工具(BeanPostProcessor、BeanFactoryPostProcessor)自動注入。)
@Resource JSR-250提供的註解
@Inject JSR-330提供的註解
@Configuration 宣告當前類是一個配置類(相當於一個Spring配置的xml檔案)
@ComponentScan 自動掃描指定包下所有使用@Service,@Component,@Controller,@Repository的類並註冊
@Bean 註解在方法上,宣告當前方法的返回值為一個Bean。返回的Bean對應的類中可以定義init()方法和destroy()方法,然後在@Bean(initMethod=”init”,destroyMethod=”destroy”)定義,在構造之後執行init,在銷燬之前執行destroy。
@Aspect 宣告一個切面(就是說這是一個額外功能)
@After 後置建言(advice),在原方法前執行。
@Before 前置建言(advice),在原方法後執行。
@Around 環繞建言(advice),在原方法執行前執行,在原方法執行後再執行(@Around可以實現其他兩種advice)
@PointCut 宣告切點,即定義攔截規則,確定有哪些方法會被切入
@Transactional 宣告事務(一般預設配置即可滿足要求,當然也可以自定義)
@Cacheable 宣告資料快取
@EnableAspectJAutoProxy 開啟Spring對AspectJ的支援
@Value 值得注入。經常與Sping EL表示式語言一起使用,注入普通字元,系統屬性,表示式運算結果,其他Bean的屬性,檔案內容,網址請求內容,配置檔案屬性值等等
@PropertySource 指定檔案地址。提供了一種方便的、宣告性的機制,用於向Spring的環境新增PropertySource。與@configuration類一起使用。
@PostConstruct 標註在方法上,該方法在建構函式執行完成之後執行。
@PreDestroy 標註在方法上,該方法在物件銷燬之前執行。
@Profile 表示當一個或多個指定的檔案是活動的時,一個元件是有資格註冊的。使用@Profile註解類或者方法,達到在不同情況下選擇例項化不同的Bean。@Profile(“dev”)表示為dev時例項化。
@EnableAsync 開啟非同步任務支援。註解在配置類上。
@Async 註解在方法上標示這是一個非同步方法,在類上標示這個類所有的方法都是非同步方法。
@EnableScheduling 註解在配置類上,開啟對計劃任務的支援。
@Scheduled 註解在方法上,宣告該方法是計劃任務。支援多種型別的計劃任務:cron,fixDelay,fixRate
@Conditional 根據滿足某一特定條件建立特定的Bean
@Enable* 通過簡單的@Enable來開啟一項功能的支援。所有@Enable註解都有一個@Import註解,@Import是用來匯入配置類的,這也就意味著這些自動開啟的實現其實是匯入了一些自動配置的Bean(1.直接匯入配置類2.依據條件選擇配置類3.動態註冊配置類)
@RunWith 這個是Junit的註解,springboot整合了junit。一般在測試類裡使用:@RunWith(SpringJUnit4ClassRunner.class) — SpringJUnit4ClassRunner在JUnit環境下提供Sprng TestContext Framework的功能
@ContextConfiguration 用來載入配置ApplicationContext,其中classes屬性用來載入配置類:@ContextConfiguration(classes = {TestConfig.class(自定義的一個配置類)})
@ActiveProfiles 用來宣告活動的profile–@ActiveProfiles(“prod”(這個prod定義在配置類中))
@EnableWebMvc 用在配置類上,開啟SpringMvc的Mvc的一些預設配置:如ViewResolver,MessageConverter等。同時在自己定製SpringMvc的相關配置時需要做到兩點:1.配置類繼承WebMvcConfigurerAdapter類2.就是必須使用這個@EnableWebMvc註解。
@RequestMapping 用來對映web請求(訪問路徑和引數),處理類和方法的。可以註解在類和方法上,註解在方法上的@RequestMapping路徑會繼承註解在類上的路徑。同時支援Serlvet的request和response作為引數,也支援對request和response的媒體型別進行配置。其中有value(路徑),produces(定義返回的媒體型別和字符集),method(指定請求方式)等屬性。
@ResponseBody 將返回值放在response體內。返回的是資料而不是頁面
@RequestBody 允許request的引數在request體中,而不是在直接連結在地址的後面。此註解放置在引數前。
@PathVariable 放置在引數前,用來接受路徑引數。
@RestController 組合註解,組合了@Controller和@ResponseBody,當我們只開發一個和頁面互動資料的控制層的時候可以使用此註解。
@ControllerAdvice 用在類上,宣告一個控制器建言,它也組合了@Component註解,會自動註冊為Spring的Bean。
@ExceptionHandler 用在方法上定義全域性處理,通過他的value屬性可以過濾攔截的條件:@ExceptionHandler(value=Exception.class)–表示攔截所有的Exception。
@ModelAttribute 將鍵值對新增到全域性,所有註解了@RequestMapping的方法可獲得次鍵值對(就是在請求到達之前,往model裡addAttribute一對name-value而已)。
@InitBinder 通過@InitBinder註解定製WebDataBinder(用在方法上,方法有一個WebDataBinder作為引數,用WebDataBinder在方法內定製資料繫結,例如可以忽略request傳過來的引數Id等)。
@WebAppConfiguration 一般用在測試上,註解在類上,用來宣告載入的ApplicationContext是一個WebApplicationContext。他的屬性指定的是Web資源的位置,預設為src/main/webapp,我們可以修改為:@WebAppConfiguration(“src/main/resources”)。
@EnableAutoConfiguration 此註釋自動載入應用程式所需的所有Bean——這依賴於Spring Boot在類路徑中的查詢。該註解組合了@Import註解,@Import註解匯入了EnableAutoCofigurationImportSelector類,它使用SpringFactoriesLoader.loaderFactoryNames方法來掃描具有META-INF/spring.factories檔案的jar包。而spring.factories裡宣告瞭有哪些自動配置。
@SpingBootApplication SpringBoot的核心註解,主要目的是開啟自動配置。它也是一個組合註解,主要組合了@Configurer,@EnableAutoConfiguration(核心)和@ComponentScan。可以通過@SpringBootApplication(exclude={想要關閉的自動配置的類名.class})來關閉特定的自動配置。
@ImportResource 雖然Spring提倡零配置,但是還是提供了對xml檔案的支援,這個註解就是用來載入xml配置的。例:@ImportResource({“classpath
@ConfigurationProperties 將properties屬性與一個Bean及其屬性相關聯,從而實現型別安全的配置。例:@ConfigurationProperties(prefix=”authot”,locations={“classpath
@ConditionalOnBean 條件註解。當容器裡有指定Bean的條件下。
@ConditionalOnClass 條件註解。當類路徑下有指定的類的條件下。
@ConditionalOnExpression 條件註解。基於SpEL表示式作為判斷條件。
@ConditionalOnJava 條件註解。基於JVM版本作為判斷條件。
@ConditionalOnJndi 條件註解。在JNDI存在的條件下查詢指定的位置。
@ConditionalOnMissingBean 條件註解。當容器裡沒有指定Bean的情況下。
@ConditionalOnMissingClass 條件註解。當類路徑下沒有指定的類的情況下。
@ConditionalOnNotWebApplication 條件註解。當前專案不是web專案的條件下。
@ConditionalOnResource 條件註解。類路徑是否有指定的值。
@ConditionalOnSingleCandidate 條件註解。當指定Bean在容器中只有一個,後者雖然有多個但是指定首選的Bean。
@ConditionalOnWebApplication 條件註解。當前專案是web專案的情況下。
@EnableConfigurationProperties 註解在類上,宣告開啟屬性注入,使用@Autowired注入。例:@EnableConfigurationProperties(HttpEncodingProperties.class)。
@AutoConfigureAfter 在指定的自動配置類之後再配置。例:@AutoConfigureAfter(WebMvcAutoConfiguration.class)

2.6.1.1.建立物件的註解

  • @Component(標註當前類是Spring容器中的一個元件)
  • @Repository(一般用於持久層)
  • @Service(一般用於業務層)
  • @Controller(一般用於表現層)

2.6.1.2、注入資料的註解

  • @Autowired:自動按型別注入,常用在變數上;如果容器中有唯一一個型別與註解的變數型別相同則可以自動注入成功。當有多個bean匹配則按照變數名稱去查詢,找不到則注入失敗。

  • @Qualifier("userDaoImpl"):結合@Autowired使用,注入指定名稱的bean;在類的成員變數上不能單獨使用;在方法引數裡使用可以單獨使用;

  • @Resource:相當於@Autowired自動注入,而@Resource(name="xxx")注入指定的bean,相當於同時使用@Autowired和@Qualifier("userDaoImpl")兩個註解。

    上面三個註解都只能注入其他的bean型別,不能注入基本資料型別和String和複雜型別;複雜型別只能通過xml檔案來注入。

  • @Value:注入基本資料型別和String型別。指定資料的值,寫法:${表示式}。

2.6.1.3、改變作用範圍的註解

  • @Scope:取值有singleton單例(預設)和prototype多例

2.6.1.4、和生命週期相關注解

@PostConstruct
public void init() {
    System.out.println("初始化註解");
}
@PreDestroy
public void destroy() {
    System.out.println("銷燬註解");
}

這兩個註解和bean標籤裡面的init-method、destroy-method作用相同。

2.6.1.5.新註解

  • @Configuration:作用在類上面標明當前類是一個配置類

  • @ComponentScan(basePackages = "com.wyl"):掃描包註解:相當於下面這一行配置

    <!--<context:component-scan base-package="com.wyl"/>-->
    
  • @Bean:在配置類中寫在方法上,將方法返回的物件注入到Spring容器中。該註解的方法有引數時,會去容器中找bean物件,跟@Autowired註解一樣的。

  • @PropertySource("classpath:db.properties"):指定資料庫配置檔案的位置

  • @Import:存在多個配置檔案,用該註解引入其他配置檔案。

2.6.1.6.Spring測試註解

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ApplicationConfig.class) 純註解
// @ContextConfiguration(locations = "classpath:ApplicationContext.xml") xml配置檔案
public class SpringTest {

    @Autowired
    private AccountServiceImpl accountService;

    @Test
    public void findAll(){
        List<Account> accountList = accountService.findAll();
        for (Account account : accountList) {
            System.out.println(account);
        }
    }
   
}

@RunWith(SpringJUnit4ClassRunner.class):替換掉原來junit的runner執行方法,使用Spring自己的執行方法。

@ContextConfiguration(classes = ApplicationConfig.class):如果是使用註解建立Spring的容器使用classes;

@ContextConfiguration(locations = "classpath:ApplicationContext.xml"):使用xml配置檔案的方法

2.6.2.基於xml方式建立bean

public class User {
    private Integer id;
    private String name;
}
<?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.wyl.bean.User" >
        <property name="id" value="1"></property>
        <property name="name" value="wyl"></property>
    </bean>
</beans>
@test
public void UserTest{
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("Bean.xml");
        User userInfo=(User)context.getBean("user");
        System.out.println(userInfo);
}

2.6.3.基於@Configuration 和@Bean 註解

Configuration 配置類

@Configuration
public class MyTestConfig {
    //bean的id預設為方法名
    @Bean
    public User user(){
        User user =new User();
        user.setName("王延領");
        user.setId(2);
        return user;
    }
}
@test
public void UserTest{
    AnnotationConfigApplicationContext context=new 							AnnotationConfigApplicationContext(MyTestConfig.class);
        User userInfo=(User)context.getBean("user");
        System.out.println(userInfo.toString());
        }

3.Spring核心AOP

AOP(Aspect Oriented Programming):面向切面程式設計,在不修改原始碼的情況下增強程式碼的功能。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。

image-20210901112213781

3.1.AOP實現原理代理模式

代理模式,建立一個代理物件實現和被對代理物件相同的介面,這樣就擁有和被代理物件相同的功能,在這基礎上增強原有的方法。

  • 靜態代理,手動去實現一個代理類
  • 動態代理,通過反射動態的實現代理類

3.1.1 靜態代理

步驟:

  1. 抽象角色 : 一般使用介面或者抽象類來實現

    public interface Rent {
        public void rent();
    }
    
  2. 真實角色 : 被代理的角色

    public class Host implements Rent{
        @Override
        public void rent() {
            System.out.println("房東出租房子!");
        }
    }
    
  3. 代理角色 : 代理真實角色 ; 代理真實角色後 , 一般會做一些附屬的操作 .

    public class Proxy {
        private Host host;
        public Proxy(){
     
        }
        public Proxy(Host host){
            this.host=host;
        }
        public void rent(){
            seeHouse();
     
            host.rent();
            hetong();
            fare();
        }
        public void seeHouse(){
            System.out.println("中介帶你看房");
        }
        public void fare(){
            System.out.println("收中介費!");
        }
        public void hetong(){
            System.out.println("籤租領合同");
        } 
     
    }
    
  4. 客戶 : 使用代理角色來進行一些操作 .

public class Client {
    public static void main(String[] args) {
        Host host=new Host();
        //host.rent();
        Proxy proxy=new Proxy(host);
        proxy.rent();
    }
}

好處:

  • 可以使得我們的真實角色更加純粹 . 不再去關注一些公共的事情 .
  • 公共的業務由代理來完成 . 實現了業務的分工 ,
  • 公共業務發生擴充套件時變得更加集中和方便 .

缺點 :

  • 類多了 , 多了代理類 , 工作量變大了 . 開發效率降低 .
    我們想要靜態代理的好處,又不想要靜態代理的缺點,所以 , 就有了動態代理

3.1.2.動態代理

動態代理的代理類是動態生成的 . 靜態代理的代理類是我們提前寫好的

動態代理分為兩類 :

  1. 基於介面的動態代理----JDK動態代理

    //抽象角色:租房
    public interface Rent {
        public void rent();
    }
    //真實角色: 房東,房東要出租房子
    public class Host implements Rent{
        public void rent() {
            System.out.println("房屋出租");
        }
    }
    //代理:中介
    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("收中介費");
        }
    }
    //租客
    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();
        }
     
    }
    
  2. 基於類的動態代理–cglib

// 被代理的物件
Account account = new Account();
Account o = (Account) Enhancer.create(account.getClass(), new MethodInterceptor() {
    /**
    * 被代理物件的方法執行前會執行
    * @param obj 被代理的物件
    * @param method 方法
    * @param objects 引數
    * @param methodProxy 當前執行方法的代理的物件
    * @return 和被代理物件的方法相同的返回值
    * @throws Throwable 異常
    */
    @Override
    public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("增強前...");
        Object invoke = method.invoke(account, objects);
        System.out.println("增強後...");
        return invoke;
    }
});
o.findAll();

3.2.AOP術語

  • Joinpoint(連線點):指的是方法,可以被動態代理增強的方法就是連線點,Spring只支援方法型別的連線點
  • Pointcut(切入點):定義要對哪些Joinpoint連線點(方法)進行攔截增強功能。被增強的方法叫做切入點,所有的方法都可以看做是一個連線點。只有被增強了的方法才叫做切入點。
  • Advice(通知/增強):攔截到Jointpoint(連線點)之後要做的事情就是通知。通知的型別:前置通知、後置通知、最終通知、環繞通知、異常通知。
  • Introduction(引介):一種特殊的通知,在不修改程式碼的前提下,可以在執行期為類動態的新增一些方法或欄位。
  • Target(目標物件):代理的目標物件
  • Weaving(織入):是把增強 應用到 目標物件來建立新的代理物件的過程(新增新功能程式碼的過程)。Spring採用的是動態代理織入,而AspectJ採用編譯期和類裝載織入。
  • Proxy(代理):一個類被AOP織入增強後,就產生一個結果代理類。
  • Aspect(切面):是切入點和通知(引介)的結合。

image-20210903141349489

3.3.使用Spring實現Aop

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

3.3.1.通過 Spring API 實現

//介面與業務
public interface UserService {
 
    public void add();
 
    public void delete();
 
    public void update();
 
    public void search();
 
} 
public class UserServiceImpl implements UserService{
 
    @Override
    public void add() {
        System.out.println("增加使用者");
    }
 
    @Override
    public void delete() {
        System.out.println("刪除使用者");
    }
 
    @Override
    public void update() {
        System.out.println("更新使用者");
    }
 
    @Override
    public void search() {
        System.out.println("查詢使用者");
    }
}
//增強
public class AfterLog implements AfterReturningAdvice {
    //returnValue 返回值
    //method被呼叫的方法
    //args 被呼叫的方法的物件的引數
    //target 被呼叫的目標物件
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("執行了" + target.getClass().getName()
        +"的"+method.getName()+"方法,"
        +"返回值:"+returnValue);
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
 
    <!--註冊bean-->
    <bean id="userService" class="com.wyl.service.UserServiceImpl"/>
    <bean id="log" class="com.kuang.log.Log"/>
    <bean id="afterLog" class="com.kuang.log.AfterLog"/>
 
    <!--aop的配置-->
    <aop:config>
        <!--切入點  expression:表示式匹配要執行的方法-->
        <aop:pointcut id="pointcut" expression="execution(* com.wyl.service.UserServiceImpl.*(..))"/>
        <!--執行環繞; advice-ref執行方法 . pointcut-ref切入點-->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>
 
</beans>
public class MyTest {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        UserService userService = (UserService) context.getBean("userService");
        userService.search();
    }
}

3.3.2.自定義類來實現Aop

//切入類
public class DiyPointcut {
 
    public void before(){
        System.out.println("---------方法執行前---------");
    }
    public void after(){
        System.out.println("---------方法執行後---------");
    }
    
}
<!--第二種方式自定義實現-->
<!--註冊bean-->
<bean id="diy" class="com.wyl.config.DiyPointcut"/
<!--aop的配置-->
<aop:config>
    <!--第二種方式:使用AOP的標籤實現-->
    <aop:aspect ref="diy">
        <aop:pointcut id="diyPonitcut" expression="execution(* com.wyl.service.UserServiceImpl.*(..))"/>
        <aop:before pointcut-ref="diyPonitcut" method="before"/>
        <aop:after pointcut-ref="diyPonitcut" method="after"/>
    </aop:aspect>
</aop:config>
public class MyTest {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        UserService userService = (UserService) context.getBean("userService");
        userService.add();
    }
}

3.3.3.使用註解實現AOP

//註解實現的增強類
package com.wyl.config;
 
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
 
@Aspect
public class AnnotationPointcut {
    @Before("execution(* com.wyl.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("---------方法執行前---------");
    }
 
    @After("execution(* com.wyl.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("---------方法執行後---------");
    }
 
    @Around("execution(* com.wyl.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("環繞前");
        System.out.println("簽名:"+jp.getSignature());
        //執行目標方法proceed
        Object proceed = jp.proceed();
        System.out.println("環繞後");
        System.out.println(proceed);
    }
}

4.事務和JdbcTemplate

4.1.JdbcTemplate使用

入門案例:

// Spring自帶的資料來源
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/spring?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai");
dataSource.setUsername("root");
dataSource.setPassword("root");
JdbcTemplate template = new JdbcTemplate(dataSource);

List<Account> accountList = template.query("select * from account", new BeanPropertyRowMapper<>(Account.class));
for (Account account : accountList) {
    System.out.println(account);
}

具體增刪改查用法:

@Autowired
private JdbcTemplate jdbcTemplate;

// 新增
@Test
public void insert(){
    String sql = "insert into account(name, money) VALUES (?,?)";
    Account account1 = new Account();
    account1.setName("迪迦");
    account1.setMoney(10000F);
    jdbcTemplate.update(sql, account1.getName(), account1.getMoney());
    find();
}

// 刪除
@Test
public void delete(){
    String sql = "delete from account where id = ?";
    jdbcTemplate.update(sql, 6);
    find();
}

// 更新
@Test
public void update(){
    List<Account> accounts = jdbcTemplate.query("select * from account where id = ?", new BeanPropertyRowMapper<>(Account.class), 1);
    Account account = accounts.get(0);
    account.setName("泰羅");
    String sql = "update account set name = ? where id = ?";
    jdbcTemplate.update(sql, account.getName(),account.getId());
    find();
}

// 查詢所有
@Test
public void find(){
    List<Account> accountList = jdbcTemplate.query("select * from account", new BeanPropertyRowMapper<>(Account.class));
    for (Account account : accountList) {
        System.out.println(account);
    }
}

// 查詢一個bean
@Override
public Account findByName(String name) {
    String sql = "select * from account where name = ?";
    return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), name);
}

// 查詢一個Object
@Test
public void findOne(){
    String sql = "select count(id) from account";
    Integer integer = jdbcTemplate.queryForObject(sql, Integer.class);
    System.out.println(integer);
}

4.2.Spring配置事務

在Spring中有兩種方法管理事務:宣告式事務管理和程式設計式事務管理;

  • 宣告式事務管理:

    1、xml配置檔案式:

    1、配置事務管理器
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/></bean>
    
    2、配置通知
    <!-- 配置事務的通知/增強 -->
    <tx:advice id="interceptor">
        <!-- 配置事務的屬性 -->
        <!-- isolation:事務隔離級別,預設使用資料庫的隔離級別
             no-rollback-for:指定一個異常,除了該異常都回滾。
             propagation:事務傳播行為,預設是required一定有事務,增刪改設定required,查詢設定supports
             read-only:是否只讀。只有查詢才能設定true。預設是false支援讀寫。
             rollback-for:指定一個異常,出現該異常就回滾,其他異常不回滾。
             timeout:事務超時時間,預設-1,永不超時。指定了以秒為單位。 -->
        <tx:attributes>
            <!-- 指定在哪種規則的方法上新增事務 -->
            <tx:method name="transfer*"/>
        </tx:attributes>
    </tx:advice>
    
    3、事務管理器和切入點表示式關聯起來
    <aop:config>
        <!-- service包下所有類的所有方法都新增事務 -->
        <aop:pointcut id="commonPointcut" expression="execution(* com.sample.service.*.*(..))"/>
        <!-- 將事務管理器和切入點表示式關聯起來 -->
        <aop:advisor advice-ref="interceptor" pointcut-ref="commonPointcut"/>
    </aop:config>
    

    2、註解式:

    1、配置事務管理器
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/></bean>
    
    2、開啟對事務註解的支援
    <!-- 開啟對事務註解的支援 -->
    <tx:annotation-driven/>
    
    3、在要新增事物的類上新增註解:@Transactional
    

    3、純註解式

    @Configuration
    @ComponentScan("com.sample")
    // 相當於<tx:annotation-driven/>
    @EnableTransactionManagement
    @PropertySource("classpath:db.properties")
    public class AppConfig {
    
        @Value("${db.driver}")
        private String driver;
        @Value("${db.url}")
        private String url;
        @Value("${db.username}")
        private String username;
        @Value("${db.password}")
        private String password;
    
        @Bean
        public DruidDataSource dataSource(){
            DruidDataSource dataSource = new DruidDataSource();
            dataSource.setDriverClassName(driver);
            dataSource.setUrl(url);
            dataSource.setUsername(username);
            dataSource.setPassword(password);
            return dataSource;
        }
    
        @Bean
        public JdbcTemplate jdbcTemplate(){
            return new JdbcTemplate(dataSource());
        }
    
        @Bean
        public DataSourceTransactionManager transactionManager(){
            return new DataSourceTransactionManager(dataSource());
        }
    
    }
    
    ==========
    在類上新增@Transactional註解即可
    
  • 程式設計式事務管理:通過程式碼去實現事務的管理,手動開啟事務、提交、回滾。

相關文章