spring 詳細講解(ioc,依賴注入,aop)

小程xy發表於2024-09-16

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,提供了更豐富的功能,如事件傳播、宣告式事務管理等。常用的實現有 ClassPathXmlApplicationContextFileSystemXmlApplicationContextAnnotationConfigApplicationContext

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 註解的 initMethoddestroyMethod 屬性指定初始化和銷燬方法。

  • 示例

    @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 的作用域。例如,singletonprototype 等。

  • 示例

    @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:標識 MyBeanMyService 是 Spring 管理的 Bean。
  • @Autowired:自動注入 MyBeanMyService 中。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 中,有幾個關鍵的概念需要理解:

  1. Aspect(切面)
    切面是橫切關注點的模組化實現。一個切面通常包含多個橫切功能,例如日誌記錄或事務管理。切面由 通知(advice)切點(pointcut) 組成。

  2. Join Point(連線點)
    連線點是程式執行過程中的某個點,在 Spring 中通常是方法呼叫(還可以是異常丟擲、欄位訪問等)。切面可以在這些連線點執行某些操作。

  3. Pointcut(切點)
    切點是一個定義了哪些連線點會被攔截的表示式。切面會在匹配切點的連線點上執行。

  4. Advice(通知)
    通知定義了切面在連線點上執行的具體操作。通知可以在方法執行的 之前之後丟擲異常時最終 執行。

    Spring 提供了以下幾種常見的通知型別:

    • Before Advice:在方法執行之前執行通知。
    • After Returning Advice:在方法成功執行之後執行通知。
    • After Throwing Advice:在方法丟擲異常後執行通知。
    • After (Finally) Advice:無論方法是否成功執行,都會執行通知。
    • Around Advice:在方法執行的 前後 都可以執行的通知。
  5. Target(目標物件)
    被 AOP 代理的物件。切面功能最終是應用在目標物件的某些方法上的。

  6. Proxy(代理物件)
    AOP 框架透過為目標物件生成代理物件來實現切面的功能。Spring AOP 基於 動態代理 機制,在執行時為目標物件生成一個代理類,攔截方法呼叫,並在呼叫之前、之後或丟擲異常時執行通知。

  7. Weaving(織入)
    織入是將切面應用到目標物件並建立代理物件的過程。Spring AOP 是 執行時織入,即在執行時動態建立代理物件。

Spring AOP 的實現方式

在 Spring 中,AOP 可以透過以下兩種方式實現:(我們主要講解的是註解方法實現)

  1. 基於註解的方式
    這種方式最為常見,開發者可以透過註解來宣告切面、通知和切點。使用起來簡潔且直觀。

    示例

    @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 的所有方法呼叫。

  2. 基於 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 通知型別詳解

  1. Before Advice(前置通知)
    前置通知會在目標方法執行之前執行。它通常用於做一些前置處理,比如驗證引數、日誌記錄等。

    示例

    @Before("execution(* com.example.service.UserService.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Before method: " + joinPoint.getSignature().getName());
    }
    

    在這個例子中,logBefore 方法會在 UserService 的所有方法執行前執行,列印出方法名。

  2. 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 的方法執行成功後,記錄其返回值。

  3. 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 的方法丟擲異常時執行,記錄異常資訊。

  4. After (Finally) Advice(最終通知)
    最終通知在目標方法執行完成後,無論是否丟擲異常,都會執行。常用於清理資源等操作。

    示例

    @After("execution(* com.example.service.UserService.*(..))")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("After method: " + joinPoint.getSignature().getName());
    }
    

    不管方法是否丟擲異常,該通知都會執行。

  5. 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 的實際應用場景

  1. 日誌記錄:可以使用 AOP 攔截方法呼叫,在方法執行前後記錄日誌。
  2. 許可權控制:在某些方法呼叫前檢查使用者是否有許可權執行該操作。
  3. 效能監控:環繞通知可以用於計算方法執行的時間,從而進行效能分析。
  4. 事務管理:在業務方法呼叫時自動開啟、提交或回滾事務,通常透過環繞通知實現。

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:可選,方法的修飾符(如 publicprotected 等)。
  • return-type-pattern:方法的返回型別,使用萬用字元 * 表示任意返回型別。
  • declaring-type-pattern:可選,方法所在的類或介面。
  • method-name-pattern:方法名,支援萬用字元 *,表示匹配任意方法。
  • param-pattern:引數列表,使用 (..) 表示匹配任意引數,使用 (*) 表示匹配一個引數,使用 (*,String) 表示匹配兩個引數且第一個引數為任意型別,第二個為 String
  • throws-pattern:可選,宣告丟擲的異常。

execution 匹配的常見例子

  1. 匹配指定包下的所有方法:
execution(* com.example.service.*.*(..))
  • 匹配 com.example.service 包下所有類中的所有方法。
  • * 表示任意返回型別,.*(..) 表示任意方法名和任意引數列表。
  1. 匹配某個類的所有方法:
execution(* com.example.service.UserService.*(..))
  • 匹配 UserService 類中的所有方法。
  1. 匹配帶有特定引數的方法:
execution(* com.example.service.UserService.getUserById(int))
  • 匹配 UserService 類中引數為 intgetUserById 方法。
  1. 匹配返回值型別為 String 的方法:
execution(String com.example.service.UserService.*(..))
  • 匹配 UserService 類中返回型別為 String 的所有方法。
  1. 匹配帶有特定修飾符的方法:
execution(public * com.example.service.UserService.*(..))
  • 匹配 UserService 類中所有 public 方法。
  1. 匹配帶有兩個引數的方法:
execution(* com.example.service.UserService.updateUser(String, int))
  • 匹配 UserService 類中帶有 Stringint 引數的 updateUser 方法。

萬用字元的使用

  • *:匹配任意返回值、任意方法名或任意引數。
  • ..:匹配零個或多個引數。

例如:

@Before("execution(* com.example.*.*(..))")
  • 該切點表示式匹配 com.example 包下的所有類的所有方法。

總結

  • execution 表示式 用於匹配方法執行的連線點,可以透過靈活的模式匹配包、類、方法名、引數列表等。
  • 通常在 AOP 中,透過 execution 精準地指定哪些方法需要被增強,從而實現功能的橫切關注點(如日誌記錄、事務管理等)。

先寫到這裡... ~

相關文章