Artio Blog

layout: mypost
title: Spring中的机制与实现
categories: [Spring系统学习]

Spring中的机制与实现

1、AOP

Spring AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在将横切关注点(如日志记录、事务管理、权限控制等)与核心业务逻辑分离,使代码更加模块化、灵活和易于维护。AOP 可以通过“切面”在不修改现有代码的情况下,动态地向现有的业务逻辑中添加行为。

AOP的核心概念

Spring AOP 的几个核心概念如下:

  1. 切面(Aspect) 切面是一个模块化的关注点,比如日志、安全、事务等。它是横切多个业务模块的行为。切面包含多个“通知”(Advice),是具体的增强行为。
  2. 连接点(Join Point) 连接点是程序执行过程中可以插入切面的具体位置。Spring AOP 支持方法级别的连接点,因此连接点可以是方法的调用、方法的执行等。
  3. 通知(Advice) 通知是定义在切面中的实际动作,即在特定的连接点执行的代码逻辑。通知可以在方法执行的不同阶段执行,包括方法前、方法后、方法抛出异常时等。
    • 前置通知(Before Advice):在方法执行之前执行。
    • 后置通知(After Advice):在方法执行之后执行,不管方法是否抛出异常。
    • 返回通知(After Returning Advice):在方法正常返回之后执行。
    • 异常通知(After Throwing Advice):在方法抛出异常时执行。
    • 环绕通知(Around Advice):可以在方法执行前后执行,甚至可以阻止方法的执行。这是最强大的通知类型。
  4. 切入点(Pointcut) 切入点是用于匹配连接点的表达式,即决定在哪些连接点上应用切面。Spring 使用 AspectJ 的切入点表达式来定义哪些方法应该被拦截。
  5. 目标对象(Target Object) 目标对象是那些被 AOP 代理的对象,即被切面增强的对象。
  6. 代理(Proxy) 代理是一个增强的目标对象,通过它可以添加横切关注点的逻辑。Spring AOP 使用两种代理机制:
    • JDK 动态代理:用于基于接口的代理。
    • CGLIB 代理:用于基于类的代理。
  7. 织入(Weaving) 织入是将切面应用到目标对象并创建代理对象的过程。Spring AOP 在运行时完成织入过程,依赖于代理对象。

Spring AOP的实现方式

Spring AOP 实现是基于代理模式的,主要通过两种方式来生成代理对象:

  1. JDK 动态代理 如果目标类实现了接口,Spring AOP 默认使用 JDK 动态代理,它基于接口创建代理对象。JDK 动态代理仅代理接口中的方法。
  2. CGLIB 代理 如果目标类没有实现任何接口,Spring AOP 会使用 CGLIB 来生成目标类的子类代理对象。CGLIB 是基于字节码的动态代理技术,可以代理类中的所有方法。

AOP的应用场景

Spring AOP 主要用于解决横切关注点问题,常见的应用场景包括:

  1. 日志记录 可以通过 AOP 在应用程序的各个层面记录日志,而无需在每个方法中手动添加日志代码。
  2. 事务管理 Spring AOP 允许通过注解或配置文件为业务方法添加事务处理逻辑,简化了事务管理代码。
  3. 权限验证 可以在方法调用之前通过 AOP 检查用户是否有权限执行某些操作。
  4. 性能监控 通过 AOP 可以记录方法执行的时间、资源使用情况等,帮助进行性能优化。

AOP的实现步骤

使用 Spring AOP 的基本步骤如下:

  1. 定义切面类 切面类是一个普通的 Java 类,可以使用 @Aspect 注解将其标记为切面。
  2. 定义切入点表达式 在切面类中定义切入点表达式,用于匹配要增强的方法。可以使用 @Pointcut 注解来定义切入点表达式。
  3. 定义通知 在切面类中使用 @Before@After@Around 等注解定义通知逻辑,通知的逻辑会在匹配的切入点处执行。
  4. 配置 AOP 支持 使用注解驱动的方式,只需在配置类中启用 AOP 功能即可。可以通过 @EnableAspectJAutoProxy 注解来启用 Spring AOP 的自动代理功能。

例子:日志记录切面

@Aspect
@Component
public class LoggingAspect {

    // 定义一个切入点,匹配所有 controller 包下的类
    @Pointcut("execution(* com.example.controller..*(..))")
    public void controllerMethods() {}

    // 前置通知:方法执行前记录日志
    @Before("controllerMethods()")
    public void logBeforeMethod(JoinPoint joinPoint) {
        System.out.println("Before Method: " + joinPoint.getSignature().getName());
    }

    // 后置通知:方法执行后记录日志
    @After("controllerMethods()")
    public void logAfterMethod(JoinPoint joinPoint) {
        System.out.println("After Method: " + joinPoint.getSignature().getName());
    }
}

在这个例子中,LoggingAspect 是一个切面,定义了两个通知:一个是在方法执行之前记录日志,另一个是在方法执行之后记录日志。通过 @Pointcut 注解指定了切入点,这里匹配了 controller 包下的所有方法。

切入点表达式详解

Spring AOP 使用 AspectJ 的切入点表达式,常见的表达式有:

AOP 与 Proxy 的局限性

2、placeHolder动态替换

在 Spring 中,占位符(Placeholder)动态替换是一种常见的技术,用于将外部化配置(如属性文件中的配置)注入到 Spring 管理的 Bean 中。通过占位符,开发者可以方便地将静态值与外部化配置解耦,使得应用程序更加灵活、可配置。

Spring 通过 @Value 注解或 XML 配置中的占位符语法来实现占位符替换,并通过 PropertySourcesPlaceholderConfigurer@PropertySource 来加载配置文件。

占位符(Placeholder)概述

占位符是一种格式为 ${property.name} 的表达式,Spring 会在运行时替换占位符并将其注入到 Bean 的属性中。这通常与外部配置文件(如 .properties 文件、YAML 文件等)结合使用,以实现动态配置。

例如,在 application.properties 文件中,可能包含以下内容:

app.name=SpringApplication
app.version=1.0

然后,在 Spring 应用中,使用占位符注入这些值:

@Value("${app.name}")
private String appName;

@Value("${app.version}")
private String appVersion;

Spring 会自动将 ${app.name}${app.version} 替换为属性文件中的值。

配置方式

  1. 使用 @Value 注解

    @Value 注解用于将属性文件中的值注入到字段或方法参数中。它的常见语法为 ${property.name:defaultValue},其中 property.name 是属性的名称,defaultValue 是默认值(如果没有找到指定属性时会使用此默认值)。

    例子:

    @Component
    public class AppConfig {
       
        @Value("${app.name:DefaultAppName}")
        private String appName;
       
        @Value("${app.version:1.0}")
        private String appVersion;
       
        // Getter and Setter
        public String getAppName() {
            return appName;
        }
       
        public String getAppVersion() {
            return appVersion;
        }
    }
    

    在此例子中,如果 application.properties 中没有定义 app.nameapp.version,Spring 将使用 DefaultAppName1.0 作为默认值。

    使用 XML 配置文件

    在 Spring XML 配置中,也可以使用占位符来动态注入属性。需要在 XML 文件中引入 context:property-placeholder 进行配置:

    application.properties 文件:

    app.name=SpringAppXML
    app.version=2.0
    

    Spring XML 配置:

    <context:property-placeholder location="classpath:application.properties" />
       
    <bean id="appConfig" class="com.example.AppConfig">
        <property name="appName" value="${app.name}" />
        <property name="appVersion" value="${app.version}" />
    </bean>
    

    在 XML 配置中使用 ${} 语法来动态地从属性文件加载配置。

    使用 @PropertySource 加载属性文件

    在 Spring 的 Java 配置类(@Configuration)中,可以使用 @PropertySource 注解来指定外部配置文件,并结合 @Value 注解实现动态配置注入:

    例子:

    @Configuration
    @PropertySource("classpath:application.properties")
    public class AppConfig {
       
        @Value("${app.name}")
        private String appName;
       
        @Value("${app.version}")
        private String appVersion;
       
        @Bean
        public SomeService someService() {
            return new SomeService(appName, appVersion);
        }
    }
    

    在这个例子中,@PropertySource 用于加载 application.properties 文件,@Value 用于将属性文件中的值动态注入到 appNameappVersion 字段中。

    PropertySourcesPlaceholderConfigurer

    如果在 XML 配置中使用 @Value,或者使用 Java 配置时,可能需要显式地定义 PropertySourcesPlaceholderConfigurer 来确保占位符能够被正确解析。

    例子:

    @Configuration
    @PropertySource("classpath:application.properties")
    public class AppConfig {
       
        @Bean
        public static PropertySourcesPlaceholderConfigurer propertyConfig() {
            return new PropertySourcesPlaceholderConfigurer();
        }
    }
    

    PropertySourcesPlaceholderConfigurer 是 Spring 的一个专门类,用于处理占位符解析。通常情况下,它会自动加载,但有时需要手动配置。

    占位符的默认值

    在某些情况下,配置文件中的某些属性可能不存在。为了防止应用程序抛出错误,可以为占位符提供默认值:

    @Value("${app.description:No description available}")
    private String appDescription;
    

    如果属性文件中没有定义 app.description,那么 Spring 会使用 No description available 作为默认值。

    配置多个属性文件

    Spring 允许加载多个属性文件,通过 @PropertySource 注解可以加载多个文件:

    @Configuration
    @PropertySource({"classpath:application.properties", "classpath:additional.properties"})
    public class AppConfig {
        //...
    }
    

    在这种情况下,application.propertiesadditional.properties 中的属性会被加载,Spring 会按照属性文件的顺序解析值,后面的文件可以覆盖前面的配置。

    结合 Spring Boot 的应用

    在 Spring Boot 中,application.propertiesapplication.yml 文件是默认加载的属性源,不需要额外的配置。通过 @Value 注解可以直接从这些文件中获取属性。

    Spring Boot 还提供了更丰富的外部化配置机制,支持从命令行、环境变量、系统属性等多种来源读取配置。Spring Boot 默认会自动加载 application.propertiesapplication.yml 文件:

    app.name=SpringBootApp
    app.version=1.0
    

    然后在 Spring Boot 应用中使用:

    @RestController
    public class AppController {
       
        @Value("${app.name}")
        private String appName;
       
        @Value("${app.version}")
        private String appVersion;
       
        @GetMapping("/info")
        public String getAppInfo() {
            return "App Name: " + appName + ", Version: " + appVersion;
        }
    }
    

3、事物

在 Spring 中,事务管理 是一个重要的功能,它确保数据操作的一致性和可靠性。事务(Transaction)是指一组要么全部执行成功、要么全部回滚的操作,具有原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),通常简称为 ACID 特性。Spring 提供了对事务的透明支持,可以基于声明式或编程式的方式进行管理。

Spring 事务管理的核心概念

1. 事务的本质

事务是一系列对数据库的操作,它们在逻辑上是一个整体。要么所有的操作都成功执行并提交,要么如果其中任何一个操作失败,整个事务将被回滚,以确保数据的完整性和一致性。

2. 事务的四大特性(ACID)

Spring 事务管理的方式

Spring 提供了两种主要的事务管理方式:

  1. 编程式事务管理:通过手动代码实现事务管理。
  2. 声明式事务管理:使用 Spring 的注解或 XML 配置,实现自动的事务管理。

1. 编程式事务管理

编程式事务管理允许在代码中通过显式调用事务管理 API 来控制事务的边界。Spring 提供了 TransactionTemplatePlatformTransactionManager 来帮助开发者实现这种方式。

例子:

@Service
public class AccountService {

    @Autowired
    private PlatformTransactionManager transactionManager;

    public void transferMoney(Long fromAccountId, Long toAccountId, Double amount) {
        TransactionDefinition def = new DefaultTransactionDefinition();
        TransactionStatus status = transactionManager.getTransaction(def);

        try {
            // 业务逻辑:从一个账户转账到另一个账户
            debit(fromAccountId, amount);
            credit(toAccountId, amount);
            // 提交事务
            transactionManager.commit(status);
        } catch (Exception ex) {
            // 回滚事务
            transactionManager.rollback(status);
            throw ex;
        }
    }
}

在这个例子中,通过 PlatformTransactionManager 明确控制事务的开始、提交和回滚。这种方式虽然灵活,但需要手动管理事务的生命周期,容易导致代码复杂。

2. 声明式事务管理

声明式事务管理是 Spring 推荐的事务管理方式。它可以通过注解或者 XML 配置来定义事务边界,开发者无需显式地在代码中管理事务,Spring 框架会自动处理。

注解方式:

Spring 支持使用 @Transactional 注解来定义事务。这个注解可以用于类或方法级别,标记该类或方法应当在事务上下文中执行。

例子:

@Service
public class AccountService {

    @Autowired
    private AccountRepository accountRepository;

    @Transactional
    public void transferMoney(Long fromAccountId, Long toAccountId, Double amount) {
        // 业务逻辑:从一个账户转账到另一个账户
        debit(fromAccountId, amount);
        credit(toAccountId, amount);
    }

    private void debit(Long accountId, Double amount) {
        // 扣款逻辑
        accountRepository.debit(accountId, amount);
    }

    private void credit(Long accountId, Double amount) {
        // 存款逻辑
        accountRepository.credit(accountId, amount);
    }
}

在这里,@Transactional 注解声明了 transferMoney() 方法应当在事务上下文中执行。Spring 会在方法执行之前自动启动事务,执行成功后提交事务,发生异常时自动回滚。

配置声明式事务

声明式事务管理可以通过注解方式实现,也可以通过 XML 配置实现。

  1. 基于注解的配置

    在 Spring Boot 中,声明式事务管理通常通过 @EnableTransactionManagement 注解开启:

    @Configuration
    @EnableTransactionManagement
    public class TransactionConfig {
        // 配置事务管理器
        @Bean
        public PlatformTransactionManager transactionManager() {
            return new DataSourceTransactionManager(dataSource());
        }
    }
    
  2. 基于 XML 的配置

    在传统的 Spring XML 配置中,事务管理可以通过以下方式配置:

    <tx:annotation-driven transaction-manager="transactionManager" />
       
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    

事务的传播行为(Propagation)

Spring 事务的传播行为决定了一个事务方法在被另一个事务方法调用时,应该如何工作。常见的传播行为包括:

例子:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void someMethod() {
    // 事务逻辑
}

事务的隔离级别(Isolation Level)

事务的隔离级别决定了多个事务之间的隔离程度,常见的隔离级别包括:

事务的回滚规则

Spring 默认情况下,如果方法抛出 运行时异常(RuntimeException)Error,事务将回滚。如果抛出的是受检异常(Checked Exception),事务不会回滚,除非显式配置了 rollbackFor 属性。

例子:

@Transactional(rollbackFor = Exception.class)
public void someMethod() throws Exception {
    // 事务逻辑
}

在这个例子中,指定了无论抛出何种异常,事务都会回滚。

4、核心接口类

Spring 框架中有许多核心接口类,它们提供了实现依赖注入、面向切面编程、数据访问、事务管理等功能的基础。理解这些接口是掌握 Spring 框架的关键。以下是 Spring 框架中一些重要的核心接口类及其具体用法的详细讲解:

1. BeanFactory 接口

BeanFactory 是 Spring IOC 容器的核心接口,用于定义和管理 Spring 中的 Bean。它是 Spring 框架的基础接口,负责创建和管理依赖关系的实例化和生命周期。BeanFactory 提供了 IoC 容器的最基本功能,通过它可以获取 Bean 实例。

BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
MyBean myBean = (MyBean) factory.getBean("myBean");

2. ApplicationContext 接口

ApplicationContextBeanFactory 的子接口,提供了更强大的功能,例如国际化支持、事件传播、Bean 自动装配、以及与 Spring AOP 的集成。它是 Spring 框架中使用最广泛的容器接口。

ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
MyBean myBean = context.getBean(MyBean.class);

3. BeanPostProcessor 接口

BeanPostProcessor 是 Spring 提供的一个扩展点接口,用于在 Spring 容器初始化 Bean 之前和之后对 Bean 进行自定义修改。Spring 容器通过 BeanPostProcessor 来增强或修改 Bean 的行为。

public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Before Initialization: " + beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("After Initialization: " + beanName);
        return bean;
    }
}

4. InitializingBean 接口

InitializingBean 是 Spring 提供的一个生命周期回调接口,提供了在 Bean 属性设置后进行初始化的回调方法。

public class MyBean implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        // Bean 初始化逻辑
        System.out.println("Initializing Bean");
    }
}

5. DisposableBean 接口

DisposableBean 是 Spring 提供的另一个生命周期回调接口,用于在 Bean 销毁时执行自定义的清理逻辑。

public class MyBean implements DisposableBean {
    @Override
    public void destroy() throws Exception {
        // Bean 销毁逻辑
        System.out.println("Destroying Bean");
    }
}

6. ApplicationListener 接口

ApplicationListener 是 Spring 的事件机制接口,用于监听和处理由 ApplicationEvent 触发的事件。任何实现了 ApplicationListener 接口的类都可以监听特定类型的事件。

public class MyEventListener implements ApplicationListener<MyEvent> {
    @Override
    public void onApplicationEvent(MyEvent event) {
        System.out.println("Received event: " + event.getMessage());
    }
}

7. HandlerInterceptor 接口

HandlerInterceptor 是 Spring MVC 中的核心接口,允许开发者在处理 HTTP 请求的不同阶段拦截请求。这类似于 Servlet 的过滤器,但更加灵活和轻量。

public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("Pre-handle logic");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("Post-handle logic");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("After completion logic");
    }
}

8. ResourceLoader 接口

ResourceLoader 是 Spring 用于加载外部资源(如文件、URL 等)的核心接口。它可以根据不同的路径策略自动识别和加载资源。

@Resource
private ResourceLoader resourceLoader;

public void loadResource() {
    Resource resource = resourceLoader.getResource("classpath:application.properties");
    System.out.println(resource.exists());
}

9. PlatformTransactionManager 接口

PlatformTransactionManager 是 Spring 用于管理事务的核心接口,支持声明式和编程式的事务管理。Spring 提供了多种实现,如 DataSourceTransactionManagerJpaTransactionManager 等。

@Autowired
private PlatformTransactionManager transactionManager;

public void performTransaction() {
    TransactionDefinition def = new DefaultTransactionDefinition();
    TransactionStatus status = transactionManager.getTransaction(def);
    try {
        // 业务逻辑
        transactionManager.commit(status);
    } catch (Exception e) {
        transactionManager.rollback(status);
        throw e;
    }
}

5、Scope

在 Spring 框架中,Scope 定义了 Bean 的生命周期和可见性。换句话说,Scope 决定了一个 Bean 是如何创建、存储和共享的。在 Spring 中,Bean 的作用域可以通过注解 @Scope 来指定,或者在 XML 配置文件中通过 scope 属性来配置。

Spring 提供了五种常见的作用域:

  1. Singleton(单例,默认作用域)
  2. Prototype(原型)
  3. Request(仅限 Web 应用)
  4. Session(仅限 Web 应用)
  5. Application(仅限 Web 应用)

1. Singleton Scope(单例作用域,默认)

Singleton 是 Spring 容器中最常见、也是默认的作用域。表示在 Spring 容器中,每个 Bean 只有一个实例,无论在应用的哪一部分引用到该 Bean,容器都会返回同一个实例。

特点:

配置方式:

2. Prototype Scope(原型作用域)

Prototype 表示 每次请求该 Bean 时都会创建一个新的实例。与 Singleton 不同,Prototype 作用域的 Bean 并不在容器启动时创建,而是在每次获取时才创建新的实例。

特点:

配置方式:

3. Request Scope(请求作用域,Web 应用)

Request 作用域用于 Web 应用程序中,表示 每次 HTTP 请求都会创建一个新的 Bean 实例,并且该实例仅在当前请求内有效。在请求处理完成后,Bean 会被销毁。

特点:

配置方式:

基于注解:
@Component
@Scope("request")
public class MyRequestBean {
    // Bean定义
}

基于 XML:
<bean id="myRequestBean" class="com.example.MyRequestBean" scope="request"/>

4. Session Scope(会话作用域,Web 应用)

Session 作用域表示 每个 HTTP 会话会创建一个 Bean 实例,并且该实例在整个会话期间内有效。这个 Bean 会随着会话的结束而销毁。

特点:

配置方式:

基于注解:
@Component
@Scope("session")
public class MySessionBean {
    // Bean定义
}

基于 XML:
<bean id="mySessionBean" class="com.example.MySessionBean" scope="session"/>

5. Application Scope(应用作用域,Web 应用)

Application 作用域表示 整个 Web 应用程序共享一个 Bean 实例,该实例的生命周期与 ServletContext 一致。这个作用域与 Singleton 类似,但仅在 Web 应用中使用。

特点:

配置方式:

基于注解:
@Component
@Scope("application")
public class MyApplicationBean {
    // Bean定义
}

基于 XML:
<bean id="myApplicationBean" class="com.example.MyApplicationBean" scope="application"/>

Scope 配置方式

在 Spring 中,可以通过多种方式指定 Bean 的作用域:

  1. 注解方式: 使用 @Scope 注解,结合 Spring 提供的 @Component@Service@Controller 等注解,定义 Bean 的作用域。

  2. @Component
    @Scope("prototype")
    public class MyPrototypeBean {
        // 定义
    }
    

    XML 配置方式: 在 XML 配置中使用 scope 属性指定 Bean 的作用域。

    <bean id="myBean" class="com.example.MyBean" scope="singleton"/>