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

来源:https://www.cnblogs.com/xxctx/p/18416247
-Advertisement-
Play Games

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,提供了更豐富的功能,如事件傳播、聲明式事務管理等。常用的實現有 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 精準地指定哪些方法需要被增強,從而實現功能的橫切關註點(如日誌記錄、事務管理等)。

先寫到這裡... ~


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 對程式員來說,架構是一個常見辭彙。如果想成為一名架構師,對架構概念的理解必須清晰。否則,在制定架構方案時,肯定會漏洞百出,問題頻發,這將對你的面試、晉升和團隊領導產生負面影響。 我們看下維基百科關於架構的定義: 軟體架構是抽象描述系統的一組結構,以及構建這些結構的規則。這些結構包括:軟體要素、要素之 ...
  • PLC Structured Text Object Oriented Programming PLC結構化文本(ST)——結構體屬性外部調用(Issue) 問題 很久之前在做OOP編程時,遇到過這樣一個問題,創建一個屬性類型是結構體,而我在外部訪問結構體內部成員時編譯器(TwinCAT3)直接報錯 ...
  • 1. 基本信息 軟體設計的要素 丹尼爾·傑克遜著 浙江教育出版社,2024年5月出版 1.1. 讀薄率 書籍總字數236千字,筆記總字數18853字。 讀薄率18853÷236000≈8% 1.2. 讀厚方向 構建可擴展分散式系統方法與實踐 設計模式:可復用面向對象軟體的基礎 程式員修煉之道:通向務 ...
  • 1. 概念完整性 1.1. 當概念組合成一個軟體時,它們可以同步以便協調行為 1.1.1. 同步可能會消除一個概念的某些行為,但決不會添加與該概念的規範不一致的新行為 1.1.2. 在使用概念設計軟體時,即使你沒有精確定義同步,至少要說服自己,概念之間的每次交互至少在原則上都可以被視為同步 1.2. ...
  • 1. 字元集與字元編碼 1.1. 字元集 1.2. 字元編碼 1.3. 兩者的關係 2. 字元編碼的發展歷史 2.1. 第一個階段 ASCII編碼 2.1.1. ASCII 2.1.2. EASCII 1. 字元集與字元編碼 1.1. 字元集 字元集(Charcater Set或Charset): ...
  • 目錄C語言的四種取整方式:零向取整trunc函數(C99)trunc的使用地板取整floor函數的使用向上取整ceil函數的使用四捨五入round函數(C99)round函數的使用四種取整方式演示 C語言的四種取整方式: 零向取整 如圖: 可以發現C語言a和b的取整方式都不是四捨五入,而是直接捨棄小 ...
  • 線程安全的級別用於描述在多線程環境下,某個對象或類在處理併發訪問時的安全性程度。它幫助開發者瞭解不同數據結構或代碼在多線程中使用時,需要什麼樣的處理措施,以確保數據一致性和避免競態條件(數據競爭)。 線程安全性可以分為不同的級別,取決於對併發訪問的控制和保證數據一致性的程度。以下是線程安全的幾個主要 ...
  • 泛型是一種類型參數。將數據的類型,當做一個參數。 1. 泛型的作用 在創建集合對象的時候,限定集合存儲元素的類型; 在編譯的時候,就進行類型檢查; 2. 泛型的使用規則 如果不指定泛型,預設是Object類型 泛型擦除:泛型應用於代碼編譯期,程式運行的後,泛型就被擦除了。即運行期,泛型被擦除。 同時 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...