spring 详细讲解(ioc,依赖注入,aop)

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 精准地指定哪些方法需要被增强,从而实现功能的横切关注点(如日志记录、事务管理等)。

先写到这里... ~