學習地址: B站-動力節點
個人代碼: GitHub
1. Spring 概述
1.1 Spring 簡介
??Spring Framework 是一個使用Java開發的、輕量級的、開源框架,它的主要作用是為了解耦合,Spring 的核心技術是 IOC(控制反轉) 和 AOP(面向切面編程),
- 官方網站: https://spring.io
??Spring 框架提高了很多功能,包括IOC容器、AOP、資料訪問、事務、測驗功能、定時任務、快取等等,

1.2 優點
輕量、解耦、面向切面編程、方便與其他框架集成、方便測驗、減低開發難度,
2. IOC 控制反轉
2.1 IOC 是什么
??IOC (Inversion of Control, 控制反轉) 是一種理論,指導開發人員如何使用物件、管理物件,將物件的生命周期交給容器來管理,通過容器管理物件,開發人員只需要拿到物件,執行物件的方法即可,
- 控制:管理物件的創建、屬性賦值、生命周期的管理,
- 正轉:讓開發人員掌控物件的創建、屬性賦值,即整個生命周期的管理,
- 反轉:把開發人員管理物件的權限轉移給容器來實作,讓容器完成管理,
2.2 IOC 的技術實作
??DI (Dependency Injection, 依賴注入) 是 IOC 的一種技術實作,開發人員通過物件的名稱獲取已初始化的物件,而物件的創建、屬性賦值、物件間的呼叫等都由容器內部實作,
2.3 IOC-創建物件 牛刀小試
Source Code2.3.1 測驗步驟
- 創建 maven-quickstart 專案,并調整專案結構(字符編碼、JDK版本等)
- 添加依賴
- spring-context
- junit
- 定義介面和實作類
- 介面: SomeService
- 方法:
doSome(): void
- 方法:
- 實作類: SomeServiceImpl
- 介面: SomeService
- 創建 Spring 組態檔(.xml),宣告需要創建的物件
- 通過
<bean>標簽宣告物件,一個標簽對應一個物件,
- 通過
- 使用容器中的物件
- 創建
ApplicationContext物件 - 通過
getBean()獲取容器中的物件
- 創建
2.3.2 依賴檔案
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bpf</groupId>
<artifactId>M01-ioc-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
2.3.3 介面與實作類
- HIDE
- SomeService.java
- SomeServiceImpl.java
package com.bpf.service;
public interface SomeService {
void doSome();
}
package com.bpf.service.impl;
import com.bpf.service.SomeService;
public class SomeServiceImpl implements SomeService {
public SomeServiceImpl() {
System.out.println("[SomeServiceImpl] 無參構造方法");
}
@Override
public void doSome() {
System.out.println("[SomeServiceImpl] someService()...");
}
}
2.3.4 組態檔
<?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 自定義物件的名稱,保持唯一,
class 自定義物件的全限定類名,不能是介面,
>>> Spring 根據 id 和 class 創建物件,并將物件放入一個 map 物件中,
-->
<bean id="someService" />
<bean id="someService1" />
<bean id="mydate" />
</beans>
2.3.5 測驗創建物件
測驗創建物件: CreateBeanTest.java
package com.bpf.service;
import com.bpf.service.impl.SomeServiceImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.Arrays;
import java.util.Date;
public class CreateBeanTest {
/**
* 傳統方式: new 獲取物件
*/
@Test
public void testCreateBeanClassical() {
SomeService someService = new SomeServiceImpl();
someService.doSome();
}
/**
* 使用 Spring 容器方式獲取物件
*/
@Test
public void testCreateBean() {
// 創建容器物件
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// 通過 getBean() 獲取 bean 物件
SomeService someService = (SomeService) ctx.getBean("someService");
// 呼叫物件方法
someService.doSome();
}
/**
* Spring 創建物件,呼叫的是類的哪個構造器呢?
* 默認呼叫的是類的無參構造器!
*/
@Test
public void testCreateStyle() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
SomeService someService = (SomeService) ctx.getBean("someService");
someService.doSome();
// 在無參構造器上添加輸出陳述句,如果把無參構造器改成有參構造器,執行測驗方法時會報錯:無法找到默認的構造方法,
/** 執行結果
* [SomeServiceImpl] 無參構造方法
* [SomeServiceImpl] someService()...
*/
}
/**
* Spring 創建物件,是什么時候創建的呢?
* Spring在創建容器物件 ApplicationContext時,會讀取組態檔,并創建檔案中宣告的所有java物件,
*
* 優點:獲取物件速度快,
* 缺點:占用記憶體,
*/
@Test
public void testCreateTime() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
/** 執行結果
* [SomeServiceImpl] 無參構造方法
* [SomeServiceImpl] 無參構造方法
*/
}
/**
* 獲取Spring容器中的物件資訊
*/
@Test
public void testGetCtxInfo() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// 容器中物件的數量
int count = ctx.getBeanDefinitionCount();
// 容器中物件的名稱
String[] names = ctx.getBeanDefinitionNames();
System.out.println("容器中物件的數量:" + count);
System.out.println("容器中物件的名稱:" + Arrays.toString(names));
/** 執行結果
* [SomeServiceImpl] 無參構造方法
* [SomeServiceImpl] 無參構造方法
* 容器中物件的數量:2
* 容器中物件的名稱:[someService, someService1]
*/
}
/**
* 創建非自定義物件
*/
@Test
public void testOtherBean() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
Date date = (Date) ctx.getBean("mydate");
System.out.println("date = " + date);
/** 執行結果
* [SomeServiceImpl] 無參構造方法
* [SomeServiceImpl] 無參構造方法
* date = Wed Dec 22 19:35:37 CST 2021
*/
}
}
2.4 Spring 的組態檔
??Spring 組態檔通常命名為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">
<!--
1) 根標簽是 beans
2) xxx.xsd 是當前XML檔案的約束檔案
3) 在 beans 標簽內宣告 bean 物件,
一個 bean 就是一個java物件,
-->
</beans>

??Spring 支持多組態檔方式,Spring 管理多組態檔常用的是包含關系,即在主組態檔中使用import標簽包含其他組態檔,在其他組態檔中定義宣告各自的資訊,
<!-- 主組態檔 -->
<!-- 路徑中可以使用通配符 * 同時引入多個檔案 -->
<import resource="classpath:其他組態檔路徑" />
2.5 Spring IOC ? 創建物件
2.5.1 Spring 容器創建物件的特點
Spring 框架使用 DI 實作 IOC 思想,底層通過反射機制創建物件、初始化物件,
- 容器物件是
ApplicationContext,它是一個介面,常用的實作類是ClassPathXmlApplicationContext,并且通過getBean()方法獲取已初始化的物件, - Spring 創建物件默認呼叫類的無參構造器,
- Spring 在創建容器物件后,會讀取組態檔,并創建檔案中宣告的所有java物件,然后都放在map物件(
ConcurrentMap)中,
2.5.2 XML方式
??Spring 通過在組態檔中使用bean標簽宣告物件,使用id屬性指定創建的物件名稱,使用class屬性指定創建的物件型別,
<!-- 組態檔中宣告一個 bean 標簽代表一個 java物件 -->
<bean id="物件名稱" />
2.5.3 注解方式
??使用注解代替組態檔中的bean標簽,在Java類上使用注解,通過value屬性指定創建的物件名稱(相對于標簽的id屬性),同時還需要在組態檔中開啟注解掃描并指定掃描的包路徑,
?Spring 提供了四個注解:
| 注解 | 說明 |
|---|---|
@Component |
表示普通的java物件 |
@Repository |
常用于創建DAO層的物件,持久層物件,表示可以訪問資料庫 |
@Service |
常用于創建Service層的物件,業務層物件,表示擁有事務功能 |
@Controller |
常用于創建Controller層的物件,控制器物件,表示可以接收和處理請求, |
?組態檔開啟注解掃描:
<!-- base-package 指定要掃描的包路徑,Spring 會自動掃描包及其子包內表有上述注解之一的類,并創建和管理, -->
<context:componet-scan base-package="包路徑" />
<!-- 如何掃描多個包? -->
<!-- 1. 使用多個標簽 -->
<context:componet-scan base-package="xx.yy.pack01" />
<context:componet-scan base-package="xx.yy.pack02" />
<!-- 2. 使用分隔符:分號(;)或逗號(,) -->
<context:componet-scan base-package="xx.yy.pack01;xx.yy.pack02" />
<!-- 3. 使用共同的父包 -->
<context:componet-scan base-package="xx.yy" />
2.6 Spring IOC ? 屬性注入
Source Code2.6.1 XML方式
(1)set注入(設值注入)
set注入:通過物件的
setXxx()方法給屬性賦值,
特點:
- 注入的屬性必須存在對應的 setter 方法
- 如果屬性在物件中不存在,但存在 setter 方法,依然不會報錯,
- Spring 容器只負責呼叫 setter 方法,與方法的具體實作無關,
<!-- 簡單型別注入: 基本資料型別、String型別 -->
<bean id="xxx" >
<property name="屬性名" value="https://www.cnblogs.com/bpf-1024/p/xxx" />
...
</bean>
<!-- 參考Java物件 -->
<bean id="xxx" >
<property name="屬性名" ref="其他bean標簽的id值" />
...
</bean>
<!-- 或 -->
<bean id="xxx" >
<property name="屬性名">
<bean ></bean>
</property>
...
</bean>
<!-- 注入null值 -->
<bean id="xxx" >
<property name="屬性名">
<null/>
</property>
...
</bean>
<!-- 集合型別 -->
<bean id="xxx" >
<property name="屬性名">
<!-- 陣列 -->
<array>
<value>xxx</value>
</array>
</property>
<property name="屬性名">
<!-- List -->
<list>
<value>xxx</value>
<ref bean="其他bean標簽的id值" />
</list>
</property>
<property name="屬性名">
<!-- Set -->
<set>
<value>xxx</value>
</set>
</property>
<property name="屬性名">
<!-- Map -->
<map>
<entry key="xxx" value="https://www.cnblogs.com/bpf-1024/p/yyy" />
</map>
</property>
<property name="屬性名">
<!-- 陣列 -->
<array>
<value>xxx</value>
</array>
</property>
</bean>
(2)構造注入
構造注入:通過物件的 含參構造器 方法給屬性賦值,
特點:
- 不需要屬性的 setter 方法
- 需要有相對應的含參構造器
<!--
index 對應構造器的形參索引,從0開始,可以省略
name 對應構造器的形參名
value 對應構造器的形參值
ref 對應其他的Java Bean
-->
<bean id="xxx" >
<constructor-arg name="構造器形參名" value="https://www.cnblogs.com/bpf-1024/p/xxx" />
<constructor-arg index="構造器形參索引" value="https://www.cnblogs.com/bpf-1024/p/xxx" />
...
</bean>
(3)參考型別自動注入
參考型別自動注入:只針對物件中的參考型別有效,可以指定根據名稱或型別自動注入屬性的值,
- byName: 根據名稱注入,當組態檔中bean標簽的id值與物件的屬性名匹配且屬于同個型別時,可以進行注入,
- byType: 根據型別注入,當組態檔中bean標簽的class值與物件的屬性型別同源時,可以進行注入,
- bean標簽的class值與物件的屬性型別相同時,
- bean標簽的class值與物件的屬性型別存在父子關系時,
- bean標簽的class值與物件的屬性型別存在介面-實作類關系時,
特點
- byName 方式通過 bean 標簽的id屬性,需要保證id唯一,
- byType 方式提供 bean 標簽的class屬性,需要保證只能存在一個同源的bean,否則會報錯,
- 參考型別自動注入本質上使用的是setter方法進行屬性賦值的,
<!-- 參考型別自動注入 -->
<bean id="xxx" autowired="byName | byType">
...
</bean>
(4)小作業
主要功能:模擬用戶注冊操作,
- 物體類 User,保存用戶資料,
- 定義一個 UserDao 介面,提供方法 insertUser(User),同時定義介面的實作類 MySqlUserDao,方法實作輸出 "通過MySQL插入用戶:用戶資料",
- 定義一個 UserService 介面,提供方法 addUser(User),同時定義介面的實作類 UserServiceImpl,并實作方法,
要求:使用 Spring 創建和管理介面的實作類物件,并通過 Spring 獲取物件完成用戶注冊操作,
Source Code2.6.2 注解方式
(1)@Value
@Value注解只能為屬性賦普通型別的值,@Value注解的位置:- 屬性宣告上:無需setter方法
- setter方法上:需要setter方法,并且會呼叫setter方法
- 賦的值可以通過外部組態檔(.properties)指定,
<!-- 組態檔中引入外部組態檔 -->
<context:property-placeholder location="classpath:properties檔案的路徑" />
- HIDE
- anno-value-applicationContext.xml
- bean-value.properties
- Student.java
- TestAnnoValue.java
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.bpf.anno.value" />
<context:property-placeholder location="classpath:bean-value.properties" />
</beans>
stu.name=凱特斯
stu.age=13
package com.bpf.anno.value;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Student {
/**
* @Value 注解:為屬性賦值
* 使用位置:
* 1. 屬性宣告上:無需setter方法
* 2. setter方法上:需要setter方法且會呼叫setter方法
*/
@Value("${stu.name}")
private String name;
private Integer age;
public void setName(String name) {
System.out.println("name = " + name);
this.name = name;
}
@Value("${stu.age}")
public void setAge(Integer age) {
System.out.println("age = " + age);
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
package com.bpf.xml;
import com.bpf.anno.value.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAnnoValue {
@Test
public void test() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("anno-value-applicationContext.xml");
Student student = (Student) ctx.getBean("student");
System.out.println("student = " + student);
/** 執行結果
* age = 13
* student = Student{name='凱特斯', age=13}
*/
}
}
(2)@Autowired
@Autowired注解可以為屬性賦參考型別的值,默認方式是byType,@Autowired注解的位置:- 屬性宣告上:無需setter方法
- setter方法上:需要setter方法,并且會呼叫setter方法
/**
* Autowired 注解原始碼
* 包含了 required 屬性,默認值為true,表示當賦值的屬性必須有值且賦值成功,當賦值的物件為null時,會拋出例外,
*/
public @interface Autowired {
boolean required() default true;
}
- HIDE
- anno-autowired-applicationContext.xml
- UserService.java
- StudentServiceImpl.java
- Student.java
- TestAnnoAutowired.java
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.bpf.anno.autowired" />
</beans>
package com.bpf.anno.service;
public interface UserService {
void sayHello();
}
package com.bpf.anno.autowired;
import com.bpf.anno.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class StudentServiceImpl implements UserService {
@Override
public void sayHello() {
System.out.println("<com.bpf.anno.autowired> [StudentServiceImpl] sayHello()...");
}
}
package com.bpf.anno.autowired;
import com.bpf.anno.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Student {
/**
* @Autowired 注解:為屬性賦值
* 使用位置:
* 1. 屬性宣告上:無需setter方法
* 2. setter方法上:需要setter方法且會呼叫setter方法
* 屬性:
* boolean required: 表示此屬性是否必須,默認值為true,表示當對應的java物件為null時會拋出例外,
* org.springframework.beans.factory.NoSuchBeanDefinitionException
*/
// @Autowired(required = false)
@Autowired
private UserService userService;
public void sayHello() {
userService.sayHello();
}
}
package com.bpf.xml;
import com.bpf.anno.autowired.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAnnoAutowired {
@Test
public void test() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("anno-autowired-applicationContext.xml");
Student student = (Student) ctx.getBean("student");
student.sayHello();
/** 執行結果
* <com.bpf.anno.autowired> [StudentServiceImpl] sayHello()...
*
* 當 StudentServiceImpl 類去掉 @Service 注解,Student 類中參考型別 userService 注解改成 @Autowired(required=false) 時:
* 會拋出空指標例外,因為在 Student 的 sayHello() 方法中,userService未成功賦值,所以在真正使用上并不會修改 required
*/
}
}
(3)@Qualifer
??當使用@Autowired注解進行參考型別注入時,由于默認方式為byType,當存在多個同源的bean時,會拋出例外:org.springframework.beans.factory.NoUniqueBeanDefinitionException,這時候就需要使用byName方式了,
@Qualifer注解結合@Autowired注解使用可以實作byName方式的參考型別自動注入,- 注解位置同上,
/**
* Qualifer 注解中只有一個屬性 value, 用來指定 bean 的名稱即 id,
*/
public @interface Qualifier {
String value() default "";
}
(4)@Resource
@Resource注解是JDK自帶的注解,但 Spring 支持這樣的注解使用,@Resource注解只能為屬性賦參考型別的值,默認方式是byName,- 當使用
byName無法匹配到任何bean時,會使用byType方式, - 通過指定
name屬性讓注解只通過byName方式注入bean,
- 當使用
- 在 JDK8 及之前是自帶此注解的,更高的版本需要手動匯入依賴,
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
2.7 Spring IOC 總結
??IOC 就是用來管理物件、管理依賴關系的,通過 IOC 可以實作解決處理業務邏輯物件之間的耦合關系,即 Service 和 DAO 之間的解耦合,
- 不適合交給Spring管理的物件:
- 物體類
- servlet、listener、filter 等 WEB 中的物件,因為它們是由 Tomcat 創建和管理的物件,
補充
> 完全注解開發
> Spring Bean 的生命周期
3. AOP 面向切面編程
3.1 AOP 是什么
??AOP (Aspect Orient Programming, 面向切面編程) 是一種編程思想,它可以在不改變源代碼的基礎上,給業務方法新增功能,
??AOP 是一種動態的思想,它是在程式運行期間,為特定的業務創建代理,通過代理來增加切面功能,而這個代理是存在于記憶體中的,
什么是切面?
- 給業務功能新增的功能就是切面,
- 切面一般是非業務功能,而且一般都是可復用的,
- 比如:日志功能、事務功能、權限檢查、引數檢查、資訊統計等等,
AOP的作用?
- 給業務功能新增方法不需改變源代碼,
- 讓開發人員專注業務邏輯,提高開發效率,
- 實作業務功能與非業務功能解耦合,
- 切面復用,
3.2 AOP 中的重要術語
| 術語 | 翻譯 | 解釋 |
|---|---|---|
| Aspect | 切面 | 給業務方法新增的功能 |
| JoinPoint | 連接點 | 即業務方法 |
| Pointcut | 切入點 | 切面的執行位置,一個或多個連接點的集合,即增加切面的所有業務方法, |
| Target | 目標物件 | 業務方法的執行者 |
| Advice | 通知 | 切面的執行時間, |
??AOP 中重要的三個要素:Aspect、Pointcut、Advice,表示在 Advice時間、在 Pointcut位置 執行 Aspect切面,
3.3 AOP 的使用時機
- 當某些方法需要增加相同功能,而源代碼又不方便修改時
- 當給業務方法增加非業務功能時
3.4 AOP 的技術實作
??常用的 AOP 實作技術是 Spring 和 AspectJ,
- Spring:Spring 框架實作了 AOP 思想中的部分功能,但其操作比較繁瑣和笨重,
- AspectJ:獨立的框架,專門負責 AOP,屬于 Eclipse 基金會,
- 官網: https://www.eclipse.org/aspectj/
3.5 AspectJ 框架
??AspectJ 框架中可以使用 注解 和 XML組態檔 的方式實作 AOP,
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.12</version>
</dependency>
3.5.1 注解方式
Source Code(1)Advice 通知注解
?AspectJ 框架中表示切面執行的時間是五種通知注解,分別代表不同的執行時間,
| 注解 | 通知型別 | 執行時間 |
|---|---|---|
@Before |
前置通知 | 業務方法前執行 |
@AfterReturning |
后置通知 | 業務方法后執行 |
@Around |
環繞通知 | 業務方法前和后都執行 |
@AfterThrowing |
例外通知 | 業務方法程序中出現例外時執行 |
@After |
最終通知 | 業務方法后執行 |
(2)Pointcut 切入點運算式
?AspectJ 框架中表示切面執行的位置是切入點運算式,本質上可以看作是業務方法的定位標志,
execution(訪問權限? 回傳值型別 全限定類名?方法名(引數串列) 例外型別?)
?代表可選,- 最簡形式:
execution(回傳值型別 方法名(引數串列))
- 最簡形式:
- 四個部分之間通過空格分開,并且都可以使用通配符??,
| 通配符 | 含義 |
|---|---|
* |
代表任意字符 |
.. |
用在方法引數中,表示任意引數串列 用在包名中,表示當前包及其子包路徑 |
+ |
用在類名后,表示當前類及其子類 用在介面后,表示當前介面及其實作類 |
(3)@Before 前置通知
- 注解
- 前置通知在目標方法執行之前起作用,
- 屬性
value: 切入點運算式
- 方法定義
public void 方法名(引數)- 第一個引數只能是
JoinPoint, JoinPoint: 表示連接點,即執行的業務方法,可以獲取方法的相關資訊,如引數、方法名等,
package com.bpf.before.handler;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Date;
@Component
@Aspect
public class MyBeforeAspect {
@Before("execution(public void com.bpf.before.service.impl.SomeServiceImpl.doSome(String) )")
public void addExecTime() {
System.out.println("[MyBeforeAspect] (前置通知) 當前執行時間:" + new Date());
}
@Before("execution(void do*(..))")
public void noteExecMethod(JoinPoint point) {
System.out.println("[MyBeforeAspect] (前置通知) 當前正在運行的方法是:");
System.out.println("\tSign: " + point.getSignature());
System.out.println("\tTarget: " + point.getTarget());
System.out.println("\tKind: " + point.getKind());
System.out.println("\tArgs: " + Arrays.toString(point.getArgs()));
}
}
(4)@AfterReturning 后置通知
- 注解
- 前置通知在目標方法執行之后起作用,
- 屬性
value: 切入點運算式returning: 宣告自定義變數名,必須與形參中的變數名一致,代表目標方法的執行結果,
- 方法定義
public void 方法名(引數)- 第一個引數只能是
JoinPoint, JoinPoint: 表示連接點,即執行的業務方法,可以獲取方法的相關資訊,如引數、方法名等,Object: 表示目標方法的執行結果,推薦使用Object,
- 特點
- 當業務方法的回傳值型別是 基本資料型別及其包裝類 或 String 時,切面方法無法改變回傳值內容,
- 當業務方法的回傳值型別是 其他參考型別的Java物件時,切面方法可以改變回傳值內容,
package com.bpf.afterreturning.handler;
import com.bpf.afterreturning.bean.Person;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAfterReturningAspect {
@AfterReturning(value = "https://www.cnblogs.com/bpf-1024/p/execution(* *..SomeServiceImpl.do*(..) )",
returning = "res")
public void process(JoinPoint point, Object res) {
System.out.println("[MyAfterReturningAspect] (后置通知) 目標方法的執行結果是:" + res);
// 當 回傳值型別為 String 時,嘗試修改,但修改失敗,
if (res instanceof String) {
res += " < AfterReturning";
System.out.println("[MyAfterReturningAspect] (后置通知) 修改方法回傳值:string = " + res);
}
// 當 回傳值型別為 其他參考型別的java物件時,可以修改成功,
if (res instanceof Person) {
Person person = (Person) res;
person.setName("ZH-" + person.getName());
System.out.println("[MyAfterReturningAspect] (后置通知) 修改方法回傳值:person = " + person);
}
}
}
(5)@Around 環繞通知
- 注解
- 前置通知在目標方法執行之前或之后起作用,
- 屬性
value: 切入點運算式
- 方法定義
public Object 方法名(引數)- 回傳值型別必須有,推薦是
Object,表示目標方法的執行結果回傳值, - 第一個引數只能是
ProceedingJoinPoint, ProceedingJoinPoint: 是JoinPoint的子類,代表執行的業務方法,可以執行目標方法proceed()、獲取方法的相關資訊,如引數、方法名等,
- 特點
- 可以選擇是否執行目標方法,
- 可以修改目標方法的回傳結果,
package com.bpf.around.handler;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
@Aspect
public class MyAroundAspect {
@Around(value = "https://www.cnblogs.com/bpf-1024/p/execution(* *..SomeServiceImpl.do*(..) )")
public Object process(ProceedingJoinPoint point) throws Throwable {
System.out.println("[MyAroundAspect] (環繞通知) 目標方法之前:記錄執行時間 " + new Date());
// 執行目標方法并拿到執行結果
Object result = point.proceed();
if (result instanceof String) {
String res = (String) result;
if (res.contains("doSome")) {
result = res.replace("doSome", "something here");
}
}
System.out.println("[MyAroundAspect] (環繞通知) 目標方法之后:執行事務功能");
return result;
}
}
(6)@AfterThrowing 例外通知
- 注解
- 前置通知在目標方法執行拋出例外后起作用,
- 屬性
value: 切入點運算式throwing: 宣告自定義變數名,必須與形參中的變數名一致,代表目標方法拋出的例外物件,
- 方法定義
public void 方法名(引數)- 第一個引數只能是
JoinPoint, JoinPoint: 表示連接點,即執行的業務方法,可以獲取方法的相關資訊,如引數、方法名等,Exception: 例外型別的引數表示目標方法執行時拋出的例外,
- 特點
- 只有在目標方法執行拋出例外時才執行,否則不執行,
- 此切面方法只適合當作目標方法的監控程式,不適合作為例外處理程式!
package com.bpf.afterthrowing.handler;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
@Aspect
public class MyAfterThrowingAspect {
@AfterThrowing(value = "https://www.cnblogs.com/bpf-1024/p/execution(* *..SomeServiceImpl.do*(..) )",
throwing = "ex")
public void process(JoinPoint point, Exception ex) {
System.out.println("[MyAfterThrowingAspect] (例外通知) 目標方法拋出例外時執行:");
System.out.println("\t記錄執行時間: " + new Date());
System.out.println("\t記錄例外資訊:" + ex.getMessage());
System.out.println("\t記錄例外型別:" + ex.getClass());
}
}
(7)@After 最終通知
- 注解
- 前置通知在目標方法的最后起作用,
- 屬性
value: 切入點運算式
- 方法定義
public void 方法名(引數)- 第一個引數只能是
JoinPoint, JoinPoint: 表示連接點,即執行的業務方法,可以獲取方法的相關資訊,如引數、方法名等,
- 特點
- 在目標方法的最后執行,無論有沒有拋出例外,
package com.bpf.after.handler;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
@Aspect
public class MyAfterAspect {
@After(value = "https://www.cnblogs.com/bpf-1024/p/execution(* *..SomeServiceImpl.do*(..) )")
public void process(JoinPoint point) {
System.out.println("[MyAfterAspect] (最終通知) 目標方法的最后執行:記錄完成時間 " + new Date());
}
}
(8)@Pointcut 切入點運算式注解
- 注解
- 用于定義可復用的切入點運算式,
- 屬性
value: 切入點運算式
- 方法定義
* void 方法名()
package com.bpf.pointcut.handler;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
@Aspect
public class MyAfterAspect {
@Pointcut("execution(* *..SomeServiceImpl.do*(..) )")
private void doMethods() {}
@After(value = "https://www.cnblogs.com/bpf-1024/p/doMethods()")
public void process() {
System.out.println("[MyAfterAspect] (最終通知) 目標方法的最后執行:記錄完成時間 " + new Date());
}
@AfterThrowing(value = "https://www.cnblogs.com/bpf-1024/p/doMethods()", throwing = "ex")
public void process(Exception ex) {
System.out.println("[MyAfterThrowingAspect] (例外通知) 目標方法拋出例外時執行:");
System.out.println("\t記錄執行時間: " + new Date());
System.out.println("\t記錄例外資訊:" + ex.getMessage());
System.out.println("\t記錄例外型別:" + ex.getClass());
}
}
3.5.2 XML方式
【詳見 5.4 AspectJ 事務控制 】
3.6 AOP 總結
?AOP 是一種動態的技術思想,目的是實作業務功能和非業務功能的解耦合,
?當目標方法需要增加功能,而不想修改或不能修改源代碼時,使用 AOP 技術就最適合不過了,
4. Spring 集成 MyBatis
Source Code4.1 集成步驟
- 使用 MySQL 資料庫,創建學生表
- 創建 maven 專案
- 匯入依賴
- 創建物體類 Student
- 創建 DAO 介面 和 Mapper檔案
- MyBatis 組態檔
- 創建 Service 介面和實作類
- Spring 組態檔
- 宣告資料源 DataSource, 用于連接資料庫
- 宣告 SqlSessionFactoryBean, 用于創建 SqlSessionFactory 物件
- 宣告 MapperScannerConfigurer, 用于創建 DAO 的代理物件
- 宣告 Service 物件,并注入 DAO
- 測驗方法測驗
注:當 MyBatis 整合 Spring 時,所有事務都默認是自動提交的,
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(80) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
4.2 編碼
- HIDE
- pom.xml
- StudentDao.java
- StudentDao.xml
- StudentService.java
- StudentServiceImpl.java
- mybatis.xml
- applicationContext.xml
- StudentServiceTest.java
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bpf</groupId>
<artifactId>M05-spring-mabtis</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.12</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<!-- 包含Mapper 檔案 -->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
package com.bpf.dao;
import com.bpf.bean.Student;
import java.util.List;
public interface StudentDao {
int insertStudent(Student student);
List<Student> selectStudents();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bpf.dao.StudentDao">
<!--int insertStudent(Student student);-->
<insert id="insertStudent">
insert into student(name, age) values(#{name}, #{age})
</insert>
<!--List<Student> selectStudents();-->
<select id="selectStudents" resultType="com.bpf.bean.Student">
select id,name,age from student
</select>
</mapper>
package com.bpf.service;
import com.bpf.bean.Student;
import java.util.List;
public interface StudentService {
int addStudent(Student student);
List<Student> queryStudent();
}
package com.bpf.service.impl;
import com.bpf.bean.Student;
import com.bpf.dao.StudentDao;
import com.bpf.service.StudentService;
import java.util.List;
public class StudentServiceImpl implements StudentService {
private StudentDao studentDao;
public void setStudentDao(StudentDao studentDao) {
this.studentDao = studentDao;
}
@Override
public int addStudent(Student student) {
return studentDao.insertStudent(student);
}
@Override
public List<Student> queryStudent() {
return studentDao.selectStudents();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 設定日志 -->
<setting name="logImpl" value="https://www.cnblogs.com/bpf-1024/p/STDOUT_LOGGING"/>
</settings>
<!-- 別名 -->
<typeAliases>
<package name="com.bpf.bean"/>
</typeAliases>
<!-- 指定Mapper檔案位置
package: 需要保證 介面 與 mapper映射檔案 在同一包下,且名稱相同,
-->
<mappers>
<package name="com.bpf.dao"/>
</mappers>
</configuration>
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 引入外部組態檔 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 配置資料源 -->
<bean id="dataSource" init-method="init" destroy-method="close">
<property name="url" value="https://www.cnblogs.com/bpf-1024/p/${jdbc.url}" />
<property name="username" value="https://www.cnblogs.com/bpf-1024/p/${jdbc.username}" />
<property name="password" value="https://www.cnblogs.com/bpf-1024/p/${jdbc.password}" />
</bean>
<!-- 宣告 SqlSessionFactoryBean 用來創建 SqlSessionFactory 物件 -->
<bean id="sqlSessionFactory" >
<!-- 指定資料源 -->
<property name="dataSource" ref="dataSource" />
<!-- 指定MyBatis組態檔 -->
<property name="configLocation" value="https://www.cnblogs.com/bpf-1024/p/classpath:mybatis.xml" />
</bean>
<!-- 宣告 MapperScannerConfigurer 用來創建 DAO介面的動態代理物件
回圈遍歷 basePackage 中所有的介面,然后使用 SqlSession.getMapper() 為每個介面創建對應的物件,并添加到容器中,
ApplicationContext ctx = ...
SqlSessionFactory factory = ctx.getBean("", SqlSessionFactory.class);
SqlSession session = factory.openSession();
for(介面: com.bpf.dao ) {
介面 物件 = session.getMapper(介面.class);
springMap.put(物件名,物件);
}
物件名:介面名首字母小寫
-->
<bean >
<!-- 指定 SqlSessionFactory -->
<property name="sqlSessionFactoryBeanName" value="https://www.cnblogs.com/bpf-1024/p/sqlSessionFactory" />
<!-- 指定 DAO 介面所在的包 -->
<property name="basePackage" value="https://www.cnblogs.com/bpf-1024/p/com.bpf.dao" />
</bean>
<!-- 宣告 StudentServiceImpl -->
<bean id="studentService" >
<property name="studentDao" ref="studentDao" />
</bean>
</beans>
package com.bpf;
import com.bpf.bean.Student;
import com.bpf.service.StudentService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class StudentServiceTest {
// 獲取 Spring 容器中的物件
@Test
public void testSpringInfo() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("定義的物件個數:" + ctx.getBeanDefinitionCount());
String[] names = ctx.getBeanDefinitionNames();
for (String name : names) {
System.out.println("\t" + name + " ==> " + ctx.getBean(name));
}
/** 執行結果
* 定義的物件個數:11
* org.springframework.context.support.PropertySourcesPlaceholderConfigurer#0 ==> org.springframework.context.support.PropertySourcesPlaceholderConfigurer@6221a451
* dataSource ==> {
* CreateTime:"2021-12-25 19:17:26",
* ActiveCount:0,
* PoolingCount:0,
* CreateCount:0,
* DestroyCount:0,
* CloseCount:0,
* ConnectCount:0,
* Connections:[]
* }
* sqlSessionFactory ==> org.apache.ibatis.session.defaults.DefaultSqlSessionFactory@3012646b
* org.mybatis.spring.mapper.MapperScannerConfigurer#0 ==> org.mybatis.spring.mapper.MapperScannerConfigurer@4a883b15
* studentService ==> com.bpf.service.impl.StudentServiceImpl@25641d39
* studentDao ==> org.apache.ibatis.binding.MapperProxy@7b36aa0c
* org.springframework.context.annotation.internalConfigurationAnnotationProcessor ==> org.springframework.context.annotation.ConfigurationClassPostProcessor@5824a83d
* org.springframework.context.annotation.internalAutowiredAnnotationProcessor ==> org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@537f60bf
* org.springframework.context.annotation.internalCommonAnnotationProcessor ==> org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@5677323c
* org.springframework.context.event.internalEventListenerProcessor ==> org.springframework.context.event.EventListenerMethodProcessor@18df8434
* org.springframework.context.event.internalEventListenerFactory ==> org.springframework.context.event.DefaultEventListenerFactory@65c7a252
*/
}
// 插入資料
@Test
public void testInsert() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
StudentService service = (StudentService) ctx.getBean("studentService");
service.addStudent(new Student("Tom", 14));
service.addStudent(new Student("Marry", 15));
}
// 查詢資料
@Test
public void testQuery() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
StudentService service = (StudentService) ctx.getBean("studentService");
service.queryStudent().forEach(System.out::println);
/** 執行結果
* Student{id=1, name='Tom', age=14}
* Student{id=2, name='Marry', age=15}
*/
}
}
5. Spring 事務
事務:可以理解為多個 sql 陳述句的組合,要么都執行成功,要么都執行失敗,
開發中,一般將事務放在 public 的業務方法上,
5.1 事務管理器
5.1.1 不同的資料庫訪問技術
(1)JDBC 的事務處理
public void updateAccount() {
Connection conn = ...
conn.setAutoCommit(false);
stat.insert(..);
stat.update(..);
conn.commit();
conn.setAutoCommit(true);
}
(2)MyBatis 的事務處理
public void updateAccount() {
SqlSession session = SqlSession.openSession(false);
try {
session.insert(..);
session.update();
session.commit();
} catch(Exception e) {
session.rollback();
}
}
5.1.2 Spring 統一事務管理
??由于不同的資料庫技術使用的事務管理方式也不同,當專案中使用不同資料庫且來回切換時,會導致代碼需要頻繁修改,
??Spring 提供了統一的事務管理器,用來管理不同資料庫訪問技術的事務處理,開發人員就只需要面對 Spring 的事務處理一種介面進行編程,省去了不同資料庫之間的差別,
5.1.3 Spring 事務管理器
?Spring 提供的統一事務管理器介面是 PlatformTransactionManager,
?這個介面提供了很多實作類,如對于 JDBC或MyBatis的DataSourceTransactionManager,Hibernate的HibernateTransactionManager等等,
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
5.1.4 Spring 事務的作業原理
?Spring 事務使用AOP 的環繞通知來實作目標業務方法的事務增強功能,這樣就無需修改源代碼了,
@Around("execution(* *..*.*(..))")
public Object myAround(ProceedingJoinPoint point) {
try {
// 事務開始
PlatformTransactionManager.beginTransaction();
// 執行目標業務方法
point.proceed();
PlatformTransactionManager.commit();
} catch (Exception e) {
PlatformTransactionManager.rollback();
}
}
5.2 事務定義介面 TransactionDefinition
?TransactionDefinition事務定義介面定義了事務隔離級別、事務傳播行為、事務超時時間三類事務屬性的常量值,
5.2.1 事務隔離級別
?隔離級別:控制事務之間影響的程度,
| 隔離級別 | 說明 |
|---|---|
DEFAULT |
根據資料庫型別選擇默認的隔離級別, ?MySQL: REPEATABLE_READ?Oracle: READ_COMMITTED |
READ_UNCOMMITTED |
讀未提交,為解決任何并發問題, |
READ_COMMITTED |
讀已提交,解決臟讀,存在不可重復讀與幻讀, |
REPEATABLE_READ |
可重復讀,解決臟讀、不可重復讀,存在幻讀, |
SERIALIZABLE |
串行化,不存在并發問題, |
5.2.2 事務超時時間
?超時時間:表示一個業務方法最長的執行時間,以秒為單位,整數值,默認值為-1,表示無限長,
5.2.3 事務傳播行為
?傳播行為:當業務方法被呼叫時,事務在方法之間的傳播和使用的變化,
| 傳播行為 | 說明 |
|---|---|
PROPAGATION_REQUIRED |
默認的傳播行為,如果已存在事務就使用當前的事務,否則創建新事務, |
PROPAGATION_REQUIRES_NEW |
必須創建新事務,如果已存在事務就將其掛起, |
PROPAGATION_SUPPORTS |
有無事務都能正常執行, |
PROPAGATION_NEVER |
|
PROPAGATION_NOT_SUPPORTED |
|
PROPAGATION_NESTED |
|
PROPAGATION_MANDATORY |
5.3 Spring 事務控制
Source Code5.3.1 Spring 事務控制的方式
??Spring 框架提供了@Transactional注解用于控制事務,使用這個注解可以定義事務的屬性,包括隔離級別、傳播行為、超時時間等等,
注解屬性:
| 屬性 | 型別 | 默認值 | 說明 |
|---|---|---|---|
| propagation | enum Propagation |
Propagation.REQUIRED |
事務的傳播行為 |
| isolation | enum Isolation |
Isolation.DEFAULT |
事務的隔離級別 |
| readOnly | boolean |
false |
是否只讀 |
| timeout | int |
-1 |
事務的超時時間,單位:秒 |
| rollbackFor | Class<? extends Throwable>[] |
- | 事務回滾的例外類串列,取值為例外型別別 |
| rollbackForClassName | String[] |
- | 事務回滾的例外類串列,取值為例外類名稱 |
| noRollbackFor | Class<? extends Throwable>[] |
- | 事務不回滾的例外類串列 |
| noRollbackForClassName | String[] |
- | 事務不回滾的例外類串列 |
rollbackFor: 當業務方法拋出的例外存在于引數串列中時,事務一定回滾;否則繼續判斷是否為RuntimeException或其子類,若是則事務一定回滾,
使用方法:
- 在 Spring 組態檔中宣告事務管理器,
- 在 Spring 組態檔中宣告開啟事務注解驅動,
- 在 public 的 Service 方法上使用
@Transactional注解,
特點:
- 優點:使用方便,效率高;適合中小型專案,
- 缺點:需要改動源代碼,
5.3.2 牛刀小試
Spring 組態檔:
<!-- 宣告事務管理器 -->
<bean id="transactionManager" >
<!-- 指定資料源 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 開啟事務注解驅動 -->
<tx:annotation-driven transaction-manager="transactionManager" />
Service方法:
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
timeout = 20, readOnly = false,
rollbackFor = {NullPointerException.class, GoodNotEnoughException.class})
/**
* rollbackFor: 表示當拋出的例外屬于 NullPointerException 或 GoodNotEnoughException,事務一定回滾,
* 否則如果是 RuntimeException,事務也一定回滾,
*/
public class BuyGoodServiceImpl implements BuyGoodService {...}
5.4 AspectJ 事務控制
Source Code5.4.1 AspectJ 事務控制的方式
??AspectJ 框架通過全組態檔的方式進行事務控制,無需改動代碼,
使用方法:
- 匯入依賴:spring-aspects
- 在 Spring 組態檔中宣告事務管理器,
- 在 Spring 組態檔中宣告業務方法的事務屬性和切入點運算式,
特點:
- 缺點:理解難,配置較復雜,
- 優點:實作代碼于事務配置解耦,實作事務功能無需修改源代碼;哪個快速的了解和掌控專案的全部事務;適合大型專案,
5.4.2 牛刀小試
匯入依賴:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.12</version>
</dependency>
Spring 組態檔:
<!-- 宣告事務管理器 -->
<bean id="transactionManager" >
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 宣告業務方法的事務屬性
在此,只宣告具有某種規則方法名的方法具有哪些事務屬性,而沒指定具體哪些方法具有事務功能
-->
<tx:advice id="serviceAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
tx:method 指定具有哪些規則方法名的方法的事務屬性
name 可以使用通配符,若只有*,表示除上述的方法之外
propagation 傳播行為
isolation 隔離級別
read-only 是否只讀
timeout 超時時間
rollback-for 事務回滾時的例外型別串列,使用逗號(,)隔開
-->
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
read-only="false" timeout="20"
rollback-for="java.lang.NullPointerException, com.bpf.except.GoodNotEnoughException"/>
<tx:method name="add*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception" />
<tx:method name="modify*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
<tx:method name="remove*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
<!-- "*" 表示除了上述方法之外使用下面的事務屬性: query*, search*, find*, get* ... -->
<tx:method name="*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
<aop:config>
<!-- 宣告切入點運算式:表示任何包下的service下的任何方法 -->
<aop:pointcut id="servicePoint" expression="execution(* *..service..*.*(..))"/>
<!-- 關聯切入點運算式 與 事務通知 -->
<aop:advisor advice-ref="serviceAdvice" pointcut-ref="servicePoint" />
</aop:config>
6. Spring Web
Source Code6.1 存在的問題
在每個 Servlet 程式中,如果每次的容器物件都是新建出來的,那一個請求就會創建一次容器,這不僅耗時,而且浪費空間,
public class AddStudentServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("name");
String age = req.getParameter("age");
Student student = new Student(name, Integer.valueOf(age));
// 直接創建容器物件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("ctx = " + ctx);
// 從容器中獲取 Service 物件
StudentService studentService = (StudentService) ctx.getBean("studentService");
studentService.addStudent(student);
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html");
resp.getWriter().write("<h1>注冊成功</h1>");
}
}
26-Dec-2021 15:48:06.790 資訊 [http-nio-8080-exec-4] com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl.info {dataSource-2} inited
ctx = org.springframework.context.support.ClassPathXmlApplicationContext@340804ae, started on Sun Dec 26 15:48:06 CST 2021
26-Dec-2021 15:48:12.408 資訊 [http-nio-8080-exec-5] com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl.info {dataSource-3} inited
ctx = org.springframework.context.support.ClassPathXmlApplicationContext@4db4beb9, started on Sun Dec 26 15:48:12 CST 2021
26-Dec-2021 15:48:15.947 資訊 [http-nio-8080-exec-6] com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl.info {dataSource-4} inited
ctx = org.springframework.context.support.ClassPathXmlApplicationContext@2f50dd69, started on Sun Dec 26 15:48:15 CST 2021
...
6.2 解決方法
目前要處理的問題是:只讓容器物件創建一次,并且能在多個Servlet程式之間共享,
?Spring 提供了監聽器ServletContextListener,它可以創建容器物件,并且能夠放入ServletContext全域共享作用域中,
?在這個介面中,定義了兩個方法:分別對應初始化時的操作和銷毀時的操作,
public interface ServletContextListener extends EventListener {
public void contextInitialized(ServletContextEvent sce);
public void contextDestroyed(ServletContextEvent sce);
}
?介面的常用實作類是ContextLoaderListener,類中的contextInitialized()方法呼叫的是父類ContextLoader的initWebApplicationContext方法,
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
try {
// 如果 容器物件為空,就創建
if (this.context == null) {
this.context = this.createWebApplicationContext(servletContext);
}
// 將創建的 容器物件 放入 ServletContext中
// WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE: 用于保存的key
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
return this.context;
} catch (Error | RuntimeException e) {
...
}
}
6.3 如何使用監聽器
匯入依賴
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.12</version>
</dependency>
在web.xml中宣告監聽器
<!-- 配置監聽器,用于創建容器物件 ApplicationContext 并放入全域共享域 ServletContext 中
ContextLoaderListener 默認讀取組態檔的地址是: /WEB-INF/applicationContext.xml
可以通過 <context-param> 重新指定組態檔路徑
-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 指定 Spring 組態檔路徑 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
Servlet程式中獲取容器物件
?從上述原始碼可見,監聽器將容器物件保存到ServletContext的key是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,于是:
public class AddStudentServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("name");
String age = req.getParameter("age");
Student student = new Student(name, Integer.valueOf(age));
// 方法一:直接創建容器物件
// ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 方法二:使用監聽器,然后從 ServletContext 中獲取容器物件
WebApplicationContext ctx = null;
// ContextLoaderListener 監聽器將容器物件保存到 ServletContext 中的key
String key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
Object attribute = getServletContext().getAttribute(key);
if (attribute != null) {
ctx = (WebApplicationContext) attribute;
}
System.out.println("ctx = " + ctx);
// 從容器中獲取 Service 物件
StudentService studentService = (StudentService) ctx.getBean("studentService");
studentService.addStudent(student);
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html");
resp.getWriter().write("<h1>注冊成功</h1>");
}
}
?如果覺得麻煩,可以使用 Spring 提供的工具類,用來獲取容器物件,其實就是上述代碼的封裝,
// 不同在于:第一個方法找不到時會拋例外 java.lang.IllegalStateException
WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/394918.html
標籤:Java
