Spring 框架既可以從 廣義 和 狹義 兩個角度理解,下麵講解這兩個層面的概念: (本文主要講解的是狹義上的spring,廣義上的簡單概括) 1、spring 的含義 1. 廣義上的 Spring 從廣義上講,Spring 是一個涵蓋多個模塊的企業級應用開發框架,它提供了從基礎架構到複雜企業應用 ...
Spring 框架既可以從 廣義 和 狹義 兩個角度理解,下麵講解這兩個層面的概念:
(本文主要講解的是狹義上的spring,廣義上的簡單概括)
1、spring 的含義
1. 廣義上的 Spring
從廣義上講,Spring 是一個涵蓋多個模塊的企業級應用開發框架,它提供了從基礎架構到複雜企業應用開發所需的全面解決方案。Spring 框架的模塊化設計幫助開發者在不同的場景中選擇合適的模塊或子項目。
廣義的 Spring 包含以下幾個子項目:
- Spring Framework:最核心的模塊,提供了 IoC(控制反轉)和 AOP(面向切麵編程)支持,是整個 Spring 體系的基礎。
- Spring Boot:簡化 Spring 應用開發的框架,提供自動配置、內嵌伺服器等特性,大大減少了項目配置的複雜度。
- Spring Data:用於處理數據訪問層,簡化與資料庫的交互,支持多種持久化技術(如 JPA、MongoDB、Redis)。
- Spring Security:一個功能強大的安全框架,提供認證和授權的功能。
- Spring Cloud:用於微服務架構,提供服務註冊與發現、負載均衡、配置管理等功能。
- Spring Batch:用於批處理任務,支持大數據量的批量操作。
- Spring Integration:用於企業應用集成,支持消息驅動的架構和非同步通訊。
因此,廣義上的 Spring 不僅僅是一個框架,而是一個生態系統,可以用於構建從小型應用到複雜分散式系統的各種項目。
2. 狹義上的 Spring
從狹義上講,Spring 特指 Spring Framework,它是 Spring 生態系統中的核心部分,主要提供 IoC(控制反轉)容器和 AOP(面向切麵編程)功能。
狹義上的 Spring 主要包括以下幾個模塊:
- Spring Core:核心容器模塊,提供了 IoC 和 DI(依賴註入)的功能,是 Spring 應用的基礎。
- Spring AOP:提供面向切麵編程的支持,幫助開發者將橫切關註點(如日誌、事務)從業務邏輯中分離出來。
- Spring Context:提供上下文支持,是 IoC 容器的高級封裝。
- Spring ORM:為與 Hibernate、JPA 等 ORM 框架集成提供支持。
- Spring MVC:用於構建 Web 應用的模型-視圖-控制器框架。
狹義的 Spring 主要指圍繞核心容器(IoC)與面向切麵編程(AOP)的功能,它是企業級應用開發的基礎,能夠幫助開發者通過解耦、簡化配置等方式高效開發應用程式。
總結
- 廣義上的 Spring 是一個完整的生態系統,包括 Spring Framework、Spring Boot、Spring Cloud 等多個子項目,涵蓋了從基礎應用到分散式系統開發的方方面面。
- 狹義上的 Spring 是指 Spring Framework,它是 Spring 生態的核心,主要提供 IoC、AOP 等核心功能。
下麵會先簡單介紹一下 Spring DAO 模塊 和 Spring ORM 模塊(我們主要講解的是 ioc、依賴註入、aop相關內容)
1. Spring DAO(Data Access Object)
Spring DAO 模塊主要用於簡化對資料庫的訪問,特別是簡化 JDBC(Java Database Connectivity) 編程。直接使用 JDBC 進行資料庫操作通常會涉及到大量樣板代碼,例如創建連接、執行查詢、處理異常、關閉資源等。而 Spring DAO 模塊通過封裝這些底層操作,提供了更簡潔的 API。
核心功能:
- 簡化 JDBC 編程:Spring DAO 通過
JdbcTemplate
等工具類,極大簡化了資料庫操作,不需要手動管理資料庫連接和資源的關閉。 - 統一異常處理:Spring 將不同資料庫訪問技術(JDBC、Hibernate 等)的異常抽象成統一的異常層次結構,避免了捕獲特定資料庫的異常。
JdbcTemplate 是 Spring DAO 最常用的類,它可以執行 SQL 查詢、插入、更新和刪除操作,封裝了底層的 JDBC API。
示例:使用 JdbcTemplate
進行資料庫操作
// 定義 JdbcTemplate bean
@Autowired
private JdbcTemplate jdbcTemplate;
public void insertUser(User user) {
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
jdbcTemplate.update(sql, user.getName(), user.getEmail());
}
public User findUserById(Long id) {
String sql = "SELECT * FROM users WHERE id = ?";
return jdbcTemplate.queryForObject(sql, new Object[]{id}, new BeanPropertyRowMapper<>(User.class));
}
在上面的例子中,JdbcTemplate
幫助我們省去了手動管理資料庫連接和處理 SQL 異常的複雜工作,只需要編寫簡潔的 SQL 語句即可。
2. Spring ORM(Object-Relational Mapping)
Spring ORM 模塊用於簡化與 ORM(對象關係映射)框架的集成,例如 Hibernate、JPA(Java Persistence API)、MyBatis 等。ORM 框架用於將 Java 對象映射到資料庫中的表,使得開發者可以通過操作對象來進行資料庫操作,而不是直接編寫 SQL 語句。
Spring ORM 模塊通過封裝和簡化 ORM 框架的配置和使用,使得它們能夠無縫集成到 Spring 應用中。Spring ORM 不是自己實現 ORM,而是幫助開發者更好地使用現有的 ORM 工具,如 Hibernate 或 JPA。
核心功能:
- 集成主流 ORM 框架:Spring ORM 可以與 Hibernate、JPA、MyBatis 等主流 ORM 框架進行集成,簡化配置和事務管理。
- 簡化持久化操作:開發者可以使用 JPA 或 Hibernate 註解定義實體類,將它們與資料庫中的表進行映射。
- 事務管理:Spring 提供了統一的事務管理機制,ORM 框架可以與 Spring 的事務管理器無縫集成。
示例:使用 JPA 結合 Spring ORM 進行資料庫操作
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
}
// Spring Data JPA 提供的介面
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByName(String name);
}
在這個例子中:
- User 類是一個 JPA 實體類,它與資料庫中的
users
表對應。每個欄位(如id
,name
,email
)對應表中的一列。 - UserRepository 是一個數據訪問介面,繼承自
JpaRepository
,可以自動生成常用的資料庫操作方法,如保存、查詢等。你甚至不需要寫 SQL,只需要通過定義介面來操作資料庫。
總結:
- Spring DAO 是直接操作資料庫的模塊,主要通過簡化 JDBC 編程來進行 SQL 操作,它適合那些需要手動編寫 SQL 的場景。
- Spring ORM 是與對象關係映射框架(如 Hibernate、JPA)集成的模塊,適用於希望使用對象操作資料庫、減少 SQL 編寫的場景。
簡單來說,Spring DAO 更傾向於手動管理 SQL,而 Spring ORM 則是通過映射 Java 對象與資料庫表來進行操作。如果你的項目是以 ORM 框架為主,可以使用 Spring ORM;如果你需要更多的 SQL 自定義控制,可以使用 Spring DAO。
2、IoC(IoC, Inversion of Control)
1. IoC 的概念
IoC 是 Spring Framework 的核心理念。它通過將對象的創建和管理職責交給容器,使對象之間的依賴關係由外部容器來處理,從而解耦組件之間的關係。
傳統的編程方式下,對象 A 需要依賴對象 B 時,通常由對象 A 直接創建或獲取對象 B。例如:
public class A {
private B b;
public A() {
b = new B(); // A 負責創建 B 對象
}
}
這種方式的問題是,當需要改變對象 B 的實現或配置時,必須修改 A 的代碼,從而增加了耦合度,降低了系統的靈活性。
將控制權從對象 A 手中交給外部的 IoC 容器,讓容器負責創建和管理對象 B,並將它註入到對象 A 中。對象 A 不再關心 B 的創建過程,只需使用 B。這樣,系統中的對象依賴關係就被 "反轉" 了。
IoC(Inversion of Control) 是 Spring 框架的核心概念之一,它用於管理對象的生命周期和依賴關係。通過 IoC,Spring 框架接管了對象的創建和管理,使得應用程式的組件解耦,從而提高了代碼的可維護性和可測試性。下麵詳細講解 IoC 如何管理 Bean 以及相關的概念。
2. IoC 容器 (spring 容器)
IoC 容器是 Spring 框架中的核心組件,它負責管理應用程式中的對象(即 Bean)。主要的 IoC 容器有兩個:
- BeanFactory:基礎容器,提供了基本的容器功能。
- ApplicationContext:繼承自 BeanFactory,提供了更豐富的功能,如事件傳播、聲明式事務管理等。常用的實現有
ClassPathXmlApplicationContext
、FileSystemXmlApplicationContext
和AnnotationConfigApplicationContext
。
3. @Bean
註解的主要功能和用途
1. 定義 Bean
-
作用:
@Bean
註解用於標記一個方法,使其返回的對象被 Spring 容器作為 Bean 管理。該方法的返回值將會作為 Bean 被註冊到 Spring 容器中,並可以通過依賴註入來使用。 -
適用場景:當需要在 Java 配置類中手動創建和配置 Bean 時使用。它允許開發者更靈活地定義 Bean 的創建邏輯和初始化過程。
-
示例:
@Configuration public class AppConfig { @Bean public MyBean myBean() { return new MyBean(); // 創建並返回一個 MyBean 實例 } }
在上述示例中,
myBean()
方法使用@Bean
註解標記,該方法返回的MyBean
實例將被 Spring 管理,並可以在其他地方通過依賴註入來使用。
2. 自定義 Bean 配置
-
作用:通過
@Bean
註解,開發者可以在 Java 配置類中自定義 Bean 的初始化參數、配置屬性等。可以使用方法參數來傳遞依賴。 -
示例:
@Configuration public class AppConfig { @Bean public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/mydb"); dataSource.setUsername("user"); dataSource.setPassword("password"); return dataSource; } }
在這個示例中,
dataSource()
方法返回了一個配置好的DataSource
實例,Spring 容器將管理該 Bean 並提供它的註入。
3. Bean 的生命周期管理
-
作用:
@Bean
註解的方法支持配置 Bean 的生命周期,包括初始化和銷毀回調。可以使用@Bean
註解的initMethod
和destroyMethod
屬性指定初始化和銷毀方法。 -
示例:
@Configuration public class AppConfig { @Bean(initMethod = "init", destroyMethod = "cleanup") public MyBean myBean() { return new MyBean(); // 創建並返回一個 MyBean 實例 } }
在這個示例中,
MyBean
類的init
方法會在 Bean 初始化後調用,而cleanup
方法會在 Bean 銷毀前調用。
4. 配置 Bean 的作用域
-
作用:可以通過
@Bean
註解的@Scope
註解指定 Bean 的作用域。例如,singleton
、prototype
等。 -
示例:
@Configuration public class AppConfig { @Bean @Scope("prototype") public MyBean myBean() { return new MyBean(); // 創建並返回一個 MyBean 實例 } }
在這個示例中,
myBean
的作用域被設置為prototype
,每次請求都會創建一個新的MyBean
實例。 @Bean 註解時沒有顯式指定 scope,則使用的是 Spring 的預設作用域。Spring 的預設作用域是 singleton。
4. Bean 的定義與配置
1. Bean 的定義
Bean對象 是指被 Spring 容器 管理的對象。Bean 是 Spring 容器中的核心概念之一,它代表了一個受 Spring 管理的對象實例。Spring 提供了多種方式來定義 Bean:(spring 容器就是 ioc容器)
- XML 配置:在 XML 文件中定義 Bean 和它們的依賴關係。
- 註解配置:使用註解(如
@Component
、@Service
、@Repository
、@Controller
)自動註冊 Bean,並通過@Autowired
自動註入依賴。 - Java 配置:通過
@Configuration
註解的類和@Bean
註解的方法定義 Bean。
a. XML 創建bean
在 XML 配置文件中定義 Bean 是 Spring 的傳統方式。這種方式在 Spring 2.x 和之前版本中廣泛使用,雖然現在註解和 Java 配置更常見,但 XML 配置依然有效。
示例 XML 配置:
<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 -->
<bean id="myBean" class="com.example.MyBean">
<!-- 配置 Bean 屬性 -->
<property name="name" value="example"/>
</bean>
</beans>
id
:Bean 的唯一標識符。class
:Bean 實現的類名。property
:配置 Bean 的屬性。
b. 註解創建bean
註解配置是 Spring 2.5 引入的,提供了更加簡潔的方式來定義和管理 Bean。
常用註解:
@Component
:通用的組件註解。@Service
:用於服務層的 Bean。@Repository
:用於數據訪問層的 Bean。@Controller
:用於控制器層的 Bean(MVC 模式下)。
示例註解配置:
@Component
public class MyBean {
@Value("example")
private String name;
// Getter 和 Setter
}
@Component
:標識MyBean
是一個 Spring 管理的 Bean。@Value
:註入屬性值。
c. Java 創建bean
Java 配置是 Spring 3.0 引入的,通過 @Configuration
註解的類和 @Bean
註解的方法來定義 Bean。這種方式將配置邏輯與代碼放在一起,提高了類型安全性和可重構性。
示例 Java 配置:
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean("example");
}
}
@Configuration
:標識這是一個配置類。@Bean
:方法返回的對象會被註冊為 Spring 容器中的 Bean。
5. Bean 的獲取
Bean 的獲取是指從 Spring 容器中獲取已定義的 Bean 實例。Spring 提供了多種方法來獲取 Bean:
a. 使用 ApplicationContext
ApplicationContext
是 Spring 容器的主要介面,通過它可以獲取 Bean。
示例獲取 Bean:
// 使用 XML 配置
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
MyBean myBean = context.getBean("myBean", MyBean.class);
// 使用 Java 配置
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyBean myBean = context.getBean(MyBean.class);
getBean(String name, Class<T> requiredType)
:通過 Bean 的名稱和類型獲取 Bean。getBean(Class<T> requiredType)
:通過 Bean 的類型獲取 Bean。
b. 使用 @Autowired
註解
@Autowired
註解用於自動註入依賴的 Bean。Spring 會自動查找容器中匹配的 Bean 並註入。(下麵 依賴註入 的時候會講)
示例自動註入:
@Component
public class MyService {
@Autowired
private MyBean myBean;
// 使用 myBean
}
@Autowired
:標識自動註入依賴的 Bean。Spring 根據類型自動註入對應的 Bean。
6. Bean 的生命周期
Spring 容器在創建、初始化和銷毀 Bean 時,會經過以下幾個生命周期階段:
- 實例化:根據配置創建 Bean 的實例。
- 填充屬性:將配置中的屬性註入到 Bean 中。
- 初始化:調用 Bean 的初始化方法(如果有配置)。
- 使用:Bean 在應用程式中被使用。
- 銷毀:當容器關閉時,調用 Bean 的銷毀方法(如果有配置)。
示例:初始化和銷毀方法
@Component
public class MyBean {
@PostConstruct
public void init() {
// 初始化代碼
}
@PreDestroy
public void destroy() {
// 銷毀代碼
}
}
7. Bean 的作用域
Spring 支持多種 Bean 的作用域,決定了 Bean 的生命周期和可見性:
- Singleton(預設):整個應用程式中只有一個實例。
- Prototype:每次請求都會創建一個新的實例。
- Request:每次 HTTP 請求創建一個新的 Bean 實例(只在 Web 應用中有效)。
- Session:每個 HTTP 會話創建一個新的 Bean 實例(只在 Web 應用中有效)。
- GlobalSession:每個全局 HTTP 會話創建一個新的 Bean 實例(只在 Portlet 應用中有效)。
示例:指定 Bean 的作用域
@Component
@Scope("prototype")
public class MyBean {
// 每次註入都創建新的實例
}
3、依賴註入
1. 依賴註入概述
依賴註入 是一種設計模式,它將對象的依賴關係從對象的內部管理轉移到外部容器(如 Spring)。通過這種方式,Spring 容器負責創建和管理對象的依賴關係,從而降低組件之間的耦合,提高應用的靈活性和可測試性。
2. Bean 的定義與依賴註入
在 Spring 中,Bean 的定義和依賴註入有多種方式,可以通過 XML 配置、註解或 Java 配置來完成。
a. XML 配置中的依賴註入
XML 配置 是 Spring 的傳統方式,通過 XML 文件定義 Bean 的屬性和依賴關係。
示例 XML 配置:
<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">
<!-- 定義 MyBean -->
<bean id="myBean" class="com.example.MyBean">
<property name="name" value="example"/>
</bean>
<!-- 定義 MyService,依賴於 myBean -->
<bean id="myService" class="com.example.MyService">
<property name="myBean" ref="myBean"/> <!-- 註入 myBean -->
</bean>
</beans>
<bean>
元素定義了一個 Bean,其中id
是 Bean 的唯一標識,class
是 Bean 實現的類。<property>
元素用於設置 Bean 的屬性值。name
是屬性的名稱,value
是屬性的值,ref
指定引用的其他 Bean(註入 bean 對象)。
b. 註解配置中的依賴註入
註解配置 提供了更為簡潔和靈活的方式來定義 Bean 和註入依賴。它使用註解來標識 Bean 和依賴關係。
示例註解配置:
@Component
public class MyBean {
@Value("example")
private String name;
// Getter 和 Setter
}
@Component
public class MyService {
@Autowired
private MyBean myBean; // 自動註入
// 使用 myBean
}
@Component
:標識MyBean
和MyService
是 Spring 管理的 Bean。@Autowired
:自動註入MyBean
到MyService
中。Spring 根據類型自動查找和註入MyBean
實例。@Value
:註入屬性值。
c. Java 配置中的依賴註入
Java 配置 允許使用 Java 類來定義 Bean 和註入依賴。這種方式將配置和代碼放在一起,提供了類型安全性。
示例 Java 配置:
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean("example");
}
@Bean
public MyService myService() {
return new MyService(myBean()); // 通過構造函數註入
}
}
@Configuration
:標識一個配置類,該類用於定義 Bean。@Bean
:定義 Bean 的實例,並可以通過方法參數註入其他 Bean。
3. 依賴註入的方式
依賴註入的方式的不通 主要體現在在 @Autowired
註解 的位置不通
a. 構造器註入
通過構造函數將依賴註入到 Bean 中。構造器註入確保了 Bean 在創建時就有所有的依賴項。
示例:
@Component
public class MyService {
private final MyBean myBean;
@Autowired
public MyService(MyBean myBean) {
this.myBean = myBean;
}
// 使用 myBean
}
- 構造器註入 優點:依賴項是不可變的,強制要求在 Bean 創建時提供所有必需的依賴項,適用於必須的依賴項。
b. 屬性註入
通過 Setter 方法或欄位直接註入依賴。
示例:Setter 方法註入:
@Component
public class MyService {
private MyBean myBean;
@Autowired
public void setMyBean(MyBean myBean) {
this.myBean = myBean;
}
// 使用 myBean
}
示例:欄位註入:
@Component
public class MyService {
@Autowired
private MyBean myBean;
// 使用 myBean
}
- Setter 方法註入 和 欄位註入 優點:代碼簡潔,允許依賴項在 Bean 創建後進行設置,適用於可選的依賴項。
c. 方法註入
通過普通方法註入依賴,通常用於更靈活的場景。
示例:
@Component
public class MyService {
private MyBean myBean;
@Autowired
public void init(MyBean myBean) {
this.myBean = myBean;
}
// 使用 myBean
}
- 方法註入:允許在 Bean 創建後註入依賴,適用於複雜的初始化邏輯。
4. 依賴註入的自動裝配
補充內容:
- 預設 Bean 名稱:
@Component
、@Service
、@Repository
、@Controller
:如果沒有指定value
屬性,Spring 會使用類名的首字母小寫形式作為 Bean 的預設名稱。例如,MyComponent
的預設 Bean 名稱是myComponent
。@Bean
:如果沒有指定name
屬性,Bean 名稱將是方法名。例如,myBean()
方法定義的 Bean 名稱是myBean
。
a. 按類型自動裝配
Spring 根據 Bean 的類型自動匹配依賴項。
示例:
@Component
public class MyService {
@Autowired
private MyBean myBean; // 按類型自動註入 (MyBean這個類)
// 如果 MyBean 是一個介面, 並且它有兩個實現類都註冊為 bean 了, 那麼根據類型自動註入就會報錯, 我們需要用 按照名稱自動裝配
}
- 優點:簡化了依賴註入的配置。
- 缺點:當有多個匹配的 Bean 時,可能會引發衝突。
b. 按名稱自動裝配
Spring 根據 Bean 的名稱進行註入。
示例:
@Component
public class MyService {
@Resource(name = "myBean")
private MyBean myBean; // 按名稱註入
}
- 優點:可以通過 Bean 名稱明確指定註入對象。
- 缺點:需要在配置中明確指定 Bean 的名稱。
c. 使用 @Qualifier
註解
當有多個符合條件的 Bean 時,可以使用 @Qualifier
註解指定具體的 Bean。
示例:
@Component
public class MyService {
@Autowired
@Qualifier("myBean1")
private MyBean myBean; // 指定具體的 Bean
}
- 優點:在多個候選 Bean 中指定具體的 Bean 進行註入。
4、aop
1. AOP(面向切麵編程,Aspect-Oriented Programming)
AOP(Aspect-Oriented Programming,面向切麵編程)是一種編程範式,旨在將 橫切關註點(cross-cutting concerns)與核心業務邏輯分離。橫切關註點是指那些影響多個模塊的功能,例如 日誌記錄、性能監控、事務管理、許可權控制 等。這些功能通常與業務邏輯無關,但需要在多個地方執行。
通過 AOP,開發者可以將這些功能提取出來,以模塊化的方式進行管理,而不是將這些功能分散到業務代碼中,從而提高代碼的可讀性和可維護性。
Spring AOP 是 Spring 框架的一部分,專門用於簡化橫切關註點的處理。它允許開發者通過 聲明式 方式(使用註解或 XML 配置)來定義切麵,而不需要手動修改原有的業務代碼。
2. AOP 的核心概念
在 AOP 中,有幾個關鍵的概念需要理解:
-
Aspect(切麵)
切麵是橫切關註點的模塊化實現。一個切麵通常包含多個橫切功能,例如日誌記錄或事務管理。切麵由 通知(advice) 和 切點(pointcut) 組成。 -
Join Point(連接點)
連接點是程式執行過程中的某個點,在 Spring 中通常是方法調用(還可以是異常拋出、欄位訪問等)。切麵可以在這些連接點執行某些操作。 -
Pointcut(切點)
切點是一個定義了哪些連接點會被攔截的表達式。切麵會在匹配切點的連接點上執行。 -
Advice(通知)
通知定義了切麵在連接點上執行的具體操作。通知可以在方法執行的 之前、之後、拋出異常時 或 最終 執行。Spring 提供了以下幾種常見的通知類型:
- Before Advice:在方法執行之前執行通知。
- After Returning Advice:在方法成功執行之後執行通知。
- After Throwing Advice:在方法拋出異常後執行通知。
- After (Finally) Advice:無論方法是否成功執行,都會執行通知。
- Around Advice:在方法執行的 前後 都可以執行的通知。
-
Target(目標對象)
被 AOP 代理的對象。切麵功能最終是應用在目標對象的某些方法上的。 -
Proxy(代理對象)
AOP 框架通過為目標對象生成代理對象來實現切麵的功能。Spring AOP 基於 動態代理 機制,在運行時為目標對象生成一個代理類,攔截方法調用,併在調用之前、之後或拋出異常時執行通知。 -
Weaving(織入)
織入是將切麵應用到目標對象並創建代理對象的過程。Spring AOP 是 運行時織入,即在運行時動態創建代理對象。
Spring AOP 的實現方式
在 Spring 中,AOP 可以通過以下兩種方式實現:(我們主要講解的是註解方法實現)
-
基於註解的方式
這種方式最為常見,開發者可以通過註解來聲明切麵、通知和切點。使用起來簡潔且直觀。示例:
@Aspect @Component public class LoggingAspect { @Before("execution(* com.example.service.UserService.*(..))") public void logBefore(JoinPoint joinPoint) { System.out.println("Before method: " + joinPoint.getSignature().getName()); } }
這裡,
@Aspect
聲明瞭一個切麵類,@Before
定義了一個前置通知,攔截UserService
的所有方法調用。 -
基於 XML 配置的方式
在 Spring 的 XML 配置文件中,可以配置切麵、通知和切點。這種方式較少使用,更多用於老項目中。示例:
<aop:config> <aop:pointcut id="userServiceMethods" expression="execution(* com.example.service.UserService.*(..))"/> <aop:aspect ref="loggingAspect"> <aop:before method="logBefore" pointcut-ref="userServiceMethods"/> </aop:aspect> </aop:config>
在 XML 配置中,
<aop:before>
用於定義前置通知,<aop:pointcut>
用於定義切點表達式。
3. AOP 通知類型詳解
-
Before Advice(前置通知)
前置通知會在目標方法執行之前運行。它通常用於做一些前置處理,比如驗證參數、日誌記錄等。示例:
@Before("execution(* com.example.service.UserService.*(..))") public void logBefore(JoinPoint joinPoint) { System.out.println("Before method: " + joinPoint.getSignature().getName()); }
在這個例子中,
logBefore
方法會在UserService
的所有方法執行前運行,列印出方法名。 -
After Returning Advice(返回通知)
返回通知在目標方法成功執行並返回結果之後運行。它通常用於記錄方法的返回值。示例:
@AfterReturning(pointcut = "execution(* com.example.service.UserService.*(..))", returning = "result") public void logAfterReturning(JoinPoint joinPoint, Object result) { System.out.println("Method returned: " + result); }
這裡的
logAfterReturning
方法會在UserService
的方法執行成功後,記錄其返回值。 -
After Throwing Advice(異常通知)
異常通知會在目標方法拋出異常時執行。它常用於異常處理和記錄異常信息。示例:
@AfterThrowing(pointcut = "execution(* com.example.service.UserService.*(..))", throwing = "error") public void logAfterThrowing(JoinPoint joinPoint, Throwable error) { System.out.println("Exception thrown: " + error); }
該通知會在
UserService
的方法拋出異常時執行,記錄異常信息。 -
After (Finally) Advice(最終通知)
最終通知在目標方法執行完成後,無論是否拋出異常,都會執行。常用於清理資源等操作。示例:
@After("execution(* com.example.service.UserService.*(..))") public void logAfter(JoinPoint joinPoint) { System.out.println("After method: " + joinPoint.getSignature().getName()); }
不管方法是否拋出異常,該通知都會執行。
-
Around Advice(環繞通知)
環繞通知是功能最強大的通知類型,它可以在方法調用的 前後 進行自定義操作,還可以決定是否執行目標方法。常用於性能監控、事務管理等複雜場景。示例:
@Around("execution(* com.example.service.UserService.*(..))") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 執行目標方法 System.out.println("After method: " + joinPoint.getSignature().getName()); return result; }
在這個例子中,
logAround
方法會在目標方法執行前後都列印日誌,併在proceed()
處執行目標方法。
4. AOP 的實際應用場景
- 日誌記錄:可以使用 AOP 攔截方法調用,在方法執行前後記錄日誌。
- 許可權控制:在某些方法調用前檢查用戶是否有許可權執行該操作。
- 性能監控:環繞通知可以用於計算方法執行的時間,從而進行性能分析。
- 事務管理:在業務方法調用時自動開啟、提交或回滾事務,通常通過環繞通知實現。
5. 基於註解方式實現 Spring AOP
在基於註解的方式中,主要使用三個註解:
@Aspect
:用於聲明切麵類。@Before
、@After
、@Around
等:用於定義通知(Advice)。@Pointcut
:用於定義切點(Pointcut)。
1. 準備工作
在開始之前,確保 Spring AOP 的依賴已經包含在項目中。
<!-- Maven依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
接下來,啟用 Spring AOP 功能。在 Spring Boot 項目中,Spring AOP 是預設開啟的。如果在非 Spring Boot 項目中,則需要手動啟用:
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
// 配置類
}
@EnableAspectJAutoProxy
註解用於開啟基於 AspectJ 註解風格的 AOP 支持。
2. 定義業務類
假設我們有一個簡單的業務類 UserService
,該類包含一個方法 getUserById
,用來獲取用戶信息。
@Service
public class UserService {
public String getUserById(int userId) {
System.out.println("Fetching user with ID: " + userId);
return "User" + userId;
}
}
3. 定義切麵類
接下來,我們定義一個切麵類 LoggingAspect
來實現日誌記錄功能。這個切麵會在 UserService
的方法調用前後列印日誌。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
// 定義切點,匹配 UserService 類中的所有方法
@Pointcut("execution(* com.example.service.UserService.*(..))")
public void userServiceMethods() {}
// 前置通知:在方法執行前執行
@Before("userServiceMethods()")
public void logBefore() {
System.out.println("Before method execution");
}
// 後置通知:在方法執行後執行
@After("userServiceMethods()")
public void logAfter() {
System.out.println("After method execution");
}
// 返回通知:方法成功返回結果後執行
@AfterReturning(pointcut = "userServiceMethods()", returning = "result")
public void logAfterReturning(Object result) {
System.out.println("Method returned with value: " + result);
}
// 異常通知:方法拋出異常時執行
@AfterThrowing(pointcut = "userServiceMethods()", throwing = "error")
public void logAfterThrowing(Throwable error) {
System.out.println("Method threw an exception: " + error);
}
// 環繞通知:在方法執行前後都執行,可以控制方法是否執行
@Around("userServiceMethods()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Before proceeding the method");
Object result = joinPoint.proceed(); // 執行目標方法
System.out.println("After proceeding the method");
return result;
}
}
6. 詳細講解各個註解
6.1 @Aspect
@Aspect
註解用於聲明一個切麵類。這個類中的方法會包含橫切關註點(如日誌、事務等),這些方法可以在目標方法執行的不同階段執行。
6.2 @Pointcut
@Pointcut
註解用於定義切點。切點是一個表達式,用於匹配哪些連接點(方法)會被切麵攔截。Spring AOP 中常用的切點表達式有:
- execution():匹配方法執行連接點。最常用,格式如
execution(* 包名.類名.方法名(..))
。 - within():限制匹配某個類或包中的所有方法。
- bean():匹配 Spring 容器中的 bean,格式如
bean(beanName)
。
示例:
@Pointcut("execution(* com.example.service.UserService.*(..))")
public void userServiceMethods() {}
這個切點匹配 UserService
類中的所有方法。
6.3 @Before
@Before
註解定義了前置通知,它會在目標方法執行前執行。例如:
@Before("userServiceMethods()")
public void logBefore() {
System.out.println("Before method execution");
}
在 UserService
類的方法執行前,這段代碼會先列印出 "Before method execution"
。
6.4 @After
@After
註解定義了後置通知,無論目標方法是否正常返回,都會在方法執行後執行。例如:
@After("userServiceMethods()")
public void logAfter() {
System.out.println("After method execution");
}
這個通知會在目標方法執行完後列印 "After method execution"
。
6.5 @AfterReturning
@AfterReturning
註解定義了返回通知,它會在目標方法正常返回結果後執行,可以獲取到方法的返回值。
@AfterReturning(pointcut = "userServiceMethods()", returning = "result")
public void logAfterReturning(Object result) {
System.out.println("Method returned with value: " + result);
}
這裡,result
參數用於接收目標方法的返回值,並列印它。
6.6 @AfterThrowing
@AfterThrowing
註解定義了異常通知,它會在目標方法拋出異常時執行,可以獲取到異常信息。
@AfterThrowing(pointcut = "userServiceMethods()", throwing = "error")
public void logAfterThrowing(Throwable error) {
System.out.println("Method threw an exception: " + error);
}
當目標方法拋出異常時,這個通知會列印出異常信息。
6.7 @Around
@Around
註解定義了環繞通知,是功能最強大的一種通知類型。它不僅可以在目標方法執行前後執行,還可以決定是否執行目標方法。通常用於性能監控、事務管理等場景。
@Around("userServiceMethods()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Before proceeding the method");
Object result = joinPoint.proceed(); // 執行目標方法
System.out.println("After proceeding the method");
return result;
}
ProceedingJoinPoint
對象可以用來執行目標方法。proceed()
方法執行目標方法,目標方法的返回值會被返回給調用者。
7. 運行效果
- 當調用
UserService
的方法時,比如userService.getUserById(1)
:@Before
通知會首先執行,列印"Before method execution"
。- 目標方法執行,列印
"Fetching user with ID: 1"
。 @AfterReturning
通知會執行,列印"Method returned with value: User1"
。@After
通知會執行,列印"After method execution"
。
如果目標方法拋出異常:
@AfterThrowing
通知會執行,列印異常信息。
環繞通知會在目標方法執行前後都執行,包裹住目標方法的執行過程。
總結
基於註解的 Spring AOP 提供了一種簡潔且強大的方式來實現橫切關註點的管理。通過 @Aspect
、@Before
、@After
、@Around
等註解,開發者可以在不改變業務代碼的情況下,將日誌記錄、事務管理、性能監控等功能模塊化地註入到代碼中。
9、execution
表達式匹配詳解
execution
是 Spring AOP 中最常用的切點表達式之一,它用來匹配方法執行的連接點。通過 execution
表達式,可以靈活定義要攔截的目標方法。execution
表達式的語法格式如下:
execution(modifiers-pattern? return-type-pattern declaring-type-pattern? method-name-pattern(param-pattern) throws-pattern?)
modifiers-pattern
:可選,方法的修飾符(如public
、protected
等)。return-type-pattern
:方法的返回類型,使用通配符*
表示任意返回類型。declaring-type-pattern
:可選,方法所在的類或介面。method-name-pattern
:方法名,支持通配符*
,表示匹配任意方法。param-pattern
:參數列表,使用(..)
表示匹配任意參數,使用(*)
表示匹配一個參數,使用(*,String)
表示匹配兩個參數且第一個參數為任意類型,第二個為String
。throws-pattern
:可選,聲明拋出的異常。
execution
匹配的常見例子
- 匹配指定包下的所有方法:
execution(* com.example.service.*.*(..))
- 匹配
com.example.service
包下所有類中的所有方法。 *
表示任意返回類型,.*(..)
表示任意方法名和任意參數列表。
- 匹配某個類的所有方法:
execution(* com.example.service.UserService.*(..))
- 匹配
UserService
類中的所有方法。
- 匹配帶有特定參數的方法:
execution(* com.example.service.UserService.getUserById(int))
- 匹配
UserService
類中參數為int
的getUserById
方法。
- 匹配返回值類型為
String
的方法:
execution(String com.example.service.UserService.*(..))
- 匹配
UserService
類中返回類型為String
的所有方法。
- 匹配帶有特定修飾符的方法:
execution(public * com.example.service.UserService.*(..))
- 匹配
UserService
類中所有public
方法。
- 匹配帶有兩個參數的方法:
execution(* com.example.service.UserService.updateUser(String, int))
- 匹配
UserService
類中帶有String
和int
參數的updateUser
方法。
通配符的使用
*
:匹配任意返回值、任意方法名或任意參數。..
:匹配零個或多個參數。
例如:
@Before("execution(* com.example.*.*(..))")
- 該切點表達式匹配
com.example
包下的所有類的所有方法。
總結
execution
表達式 用於匹配方法執行的連接點,可以通過靈活的模式匹配包、類、方法名、參數列表等。- 通常在 AOP 中,通過
execution
精準地指定哪些方法需要被增強,從而實現功能的橫切關註點(如日誌記錄、事務管理等)。
先寫到這裡... ~