Skip to content

Spring Framework

Spring 的核心是 IoC 容器:容器创建并装配 Bean,应用代码声明依赖而不自行查找或构造协作对象。Spring Boot 在此基础上提供自动配置、外部化配置和应用启动能力。

Bean、IoC 与配置方式

Bean 是由 Spring 容器创建、配置和管理的对象。现代项目优先使用 Java 配置和组件扫描;XML 仍适用于维护遗留系统或需要外部化装配定义的场景。

java
import java.time.Clock;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.example.app")
class AppConfig {
    @Bean
    Clock clock() {
        return Clock.systemUTC();
    }
}

@Configuration 声明配置类,@Bean 声明第三方库或需要显式构造的 Bean。业务组件通常使用:

注解适用对象
@Component通用组件
@Service业务服务
@Repository数据访问组件;可参与数据访问异常转换
@Controller / @RestControllerWeb 层控制器

这些注解的主要区别是语义表达;扫描与注册机制相同。

依赖注入

优先使用构造器注入:依赖不可变、可在单元测试中直接传入,并能尽早暴露循环依赖。只有一个构造器时不需要标记 @Autowired

java
import org.springframework.stereotype.Service;

@Service
public class OrderService {
    private final OrderRepository orderRepository;
    private final PaymentClient paymentClient;

    public OrderService(OrderRepository orderRepository, PaymentClient paymentClient) {
        this.orderRepository = orderRepository;
        this.paymentClient = paymentClient;
    }
}

同一类型存在多个 Bean 时,用 @Qualifier 指定注入目标,或用 @Primary 指定默认候选者。配置值优先通过类型安全的 @ConfigurationProperties 绑定;不要在业务代码中散落读取环境变量。

java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@ConfigurationProperties(prefix = "app.mail")
public record MailProperties(String host, int port) {}

@Configuration
@EnableConfigurationProperties(MailProperties.class)
class PropertiesConfig {}

作用域与生命周期

作用域含义
singleton默认值;每个 Spring 容器一个实例
prototype每次获取 Bean 创建一个实例
requestsessionWeb 环境中按 HTTP 请求或会话创建

singleton 不等于 JVM 全局单例;它只在对应容器内唯一。容器创建 Bean 时依次完成实例化、依赖注入、初始化回调和后置处理。需要资源初始化与释放时可使用 @PostConstruct@PreDestroy;对于 prototype Bean,销毁回调通常需要由获取方负责触发。

java
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;

public class FileClient {
    @PostConstruct
    void connect() {}

    @PreDestroy
    void close() {}
}

不要依赖字段注入或容器查找 API 来隐藏依赖;它们会增加测试和重构成本。

AOP

Spring AOP 通过代理在方法调用前后织入横切逻辑,适合日志、鉴权、指标、缓存和事务。核心概念:

概念含义
Aspect横切逻辑的模块,例如审计切面
Pointcut匹配连接点的规则
Advice在匹配处执行的逻辑,如 @Before@Around
ProxySpring 创建的代理对象,负责拦截调用
java
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TimingAspect {
    @Around("execution(* com.example.app.service..*(..))")
    public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
        long startedAt = System.nanoTime();
        try {
            return joinPoint.proceed();
        } finally {
            long elapsed = System.nanoTime() - startedAt;
            // 记录耗时,不记录密码、令牌等敏感参数
        }
    }
}

代理只拦截经过代理对象的调用。同一个 Bean 内部使用 this.method() 的自调用不会触发通常的 Spring AOP 通知;应重构职责边界,或改由另一个 Bean 调用。

声明式事务

@Transactional 通常标注在 Service 的公共业务方法上。事务边界应覆盖完成一个业务动作所需的所有数据库写入,而非单个 Repository 或 Mapper 调用。

java
import java.math.BigDecimal;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TransferService {
    private final AccountRepository accountRepository;

    public TransferService(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }

    @Transactional
    public void transfer(long fromId, long toId, BigDecimal amount) {
        accountRepository.debit(fromId, amount);
        accountRepository.credit(toId, amount);
    }
}

默认情况下,未捕获的 RuntimeExceptionError 会触发回滚;受检异常需要通过 rollbackFor 明确声明,或转换为合适的运行时异常。常用属性:

属性用途
propagation定义嵌套调用如何参与现有事务
isolation设置隔离级别;按数据库与并发需求选择
readOnly声明只读意图,不能替代权限控制
timeout限制事务最长执行时间
rollbackFor扩展触发回滚的异常类型

事务同样基于代理,因此自调用、非代理管理对象和异步线程中的调用不会自动继承当前事务。捕获异常后若继续正常返回,事务可能提交;需要回滚时应重新抛出异常或显式标记回滚。

数据访问与测试

  • 数据访问层只负责持久化;事务、编排和业务规则放在 Service 层。
  • 使用连接池和 DataSource;不要在业务方法中手动创建数据库连接。
  • 集成测试根据范围选择加载完整上下文或数据访问切片;单元测试直接构造被测对象并替换其依赖。
  • 配置、密钥和运行环境差异通过外部化配置处理,密钥不进入源码或日志。

XML 的定位

XML Bean 定义、<aop:config><tx:advice> 仍受 Spring 支持,但新项目优先使用 Java 配置、@Aspect@Transactional。迁移遗留系统时可逐步替换,避免在同一功能上混用两套配置来源。

复习要点

  • IoC 容器管理 Bean;构造器注入优先。
  • singleton 是容器级单例,prototype 的销毁通常由使用方管理。
  • AOP 与事务依赖代理,自调用是常见失效点。
  • 事务放在 Service 层,回滚规则和传播行为按业务语义显式设计。

参考

基于 MIT 许可发布