logo Fonnpo

联系我
2026年5月30日 3 分钟阅读 Fonnpo

Spring 核心详解

Spring 是 Java 生态中最流行的应用框架。本文深入讲解 IoC 与 DI 的核心思想、Bean 的生命周期与作用域、AOP 面向切面编程、Spring MVC 请求处理、Spring Boot 自动装配原理、事务传播行为与隔离级别、Spring Data JPA,以及循环依赖等常见坑点,帮你系统掌握 Spring 全貌。

#Java#Spring#Backend

Spring 是 Java 生态中最流行的应用框架,提供了 IoC 容器、AOP、MVC、事务管理等一系列功能,极大地简化了企业级应用开发。

Spring 核心模块

Spring 框架由多个模块组成,可以按需引入:

模块说明
spring-coreIoC 容器的核心基础
spring-beansBeanFactory,负责 Bean 的创建与管理
spring-contextApplicationContext,在 beans 基础上提供更丰富的功能
spring-aop面向切面编程支持
spring-webmvcSpring MVC 框架
spring-tx事务管理
spring-data-jpaJPA 集成
spring-boot约定优于配置,自动装配

IoC(控制反转)与 DI(依赖注入)

什么是 IoC

传统模式下,对象自己负责创建所依赖的对象:

Java
        public class UserService {
    // 自己 new,耦合度高
    private UserRepository repo = new UserRepositoryImpl();
}

    

IoC(Inversion of Control)把对象的创建和依赖关系的管理交给 Spring 容器,对象只声明自己需要什么,由容器注入:

Java
        @Service
public class UserService {
    private final UserRepository repo;

    // Spring 自动注入
    public UserService(UserRepository repo) {
        this.repo = repo;
    }
}

    

控制权从对象本身"反转"给了容器,这就是 IoC 的核心思想。

DI 的三种方式

1. 构造器注入(推荐)

Java
        @Service
public class OrderService {
    private final UserRepository userRepo;
    private final ProductRepository productRepo;

    // 只有一个构造器时,@Autowired 可以省略
    public OrderService(UserRepository userRepo, ProductRepository productRepo) {
        this.userRepo = userRepo;
        this.productRepo = productRepo;
    }
}

    

优点:依赖可以声明为 final,不可变,便于测试,且在对象构造完成时依赖就已经就绪。

2. Setter 注入

Java
        @Service
public class NotificationService {
    private EmailSender emailSender;

    @Autowired
    public void setEmailSender(EmailSender emailSender) {
        this.emailSender = emailSender;
    }
}

    

适合可选依赖,或需要在运行时动态替换实现的场景。

3. 字段注入(不推荐)

Java
        @Service
public class UserService {
    @Autowired
    private UserRepository userRepo; // 无法声明 final,不易单元测试
}

    

代码简洁,但无法使用 final,且绕过了容器直接实例化时依赖为 null,不推荐在生产代码中使用。

Bean 管理

Bean 的定义方式

注解方式(最常用)

Java
        @Component        // 通用组件
@Service          // 业务层
@Repository       // 数据访问层(同时开启 JPA 异常转换)
@Controller       // MVC 控制器
@RestController   // = @Controller + @ResponseBody

    

Java Config 方式

Java
        @Configuration
public class AppConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public EmailSender emailSender() {
        return new SmtpEmailSender();
    }
}

    

XML 方式(遗留项目仍在使用)

Xml
        <bean id="userService" class="com.example.UserService">
    <constructor-arg ref="userRepository"/>
</bean>

    

Bean 的作用域

Java
        @Component
@Scope("singleton")   // 默认,容器中只有一个实例
public class AppConfig { }

@Component
@Scope("prototype")   // 每次 getBean() 都创建新实例
public class RequestProcessor { }

// Web 环境专用
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class RequestContext { }

@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class UserSession { }

    
作用域说明
singleton默认,容器中唯一实例
prototype每次注入 / getBean() 创建新实例
request每次 HTTP 请求一个实例(Web 环境)
session每个 HTTP Session 一个实例(Web 环境)

Bean 的生命周期

Properties
        实例化
  → 属性注入
  → BeanNameAware / BeanFactoryAware / ApplicationContextAware
  → BeanPostProcessor#postProcessBeforeInitialization
  → @PostConstruct
  → InitializingBean#afterPropertiesSet
  → init-method
  → BeanPostProcessor#postProcessAfterInitialization
  → 【Bean 可用】
  → @PreDestroy
  → DisposableBean#destroy
  → destroy-method
  → 销毁

    

常用的生命周期钩子:

Java
        @Component
public class DataSourcePool {

    @PostConstruct
    public void init() {
        // Bean 初始化完成后执行,适合做连接池预热
        System.out.println("连接池初始化完成");
    }

    @PreDestroy
    public void destroy() {
        // 容器关闭前执行,适合做资源释放
        System.out.println("释放连接池资源");
    }
}

    

常用注解

注入与配置

Java
        // 按类型自动注入
@Autowired
private UserRepository userRepo;

// 当有多个同类型 Bean 时,按名称指定
@Autowired
@Qualifier("mysqlUserRepo")
private UserRepository userRepo;

// 等价于 @Autowired + @Qualifier,更简洁(JSR-250 标准)
@Resource(name = "mysqlUserRepo")
private UserRepository userRepo;

// 注入配置文件中的值
@Value("${app.name}")
private String appName;

// 注入时附带默认值
@Value("${app.timeout:30}")
private int timeout;

// 注入 SpEL 表达式
@Value("#{systemProperties['user.home']}")
private String userHome;

    

配置类常用注解

Java
        @Configuration                             // 标识配置类
@ComponentScan("com.example")              // 指定扫描包
@PropertySource("classpath:app.properties") // 加载额外配置文件
@Import(SecurityConfig.class)              // 导入其他配置类
public class AppConfig { }

    

条件装配(Spring Boot 常用)

Java
        // 配置项存在且为指定值时才装配
@ConditionalOnProperty(name = "feature.cache.enabled", havingValue = "true")
@Bean
public CacheManager cacheManager() {
    return new RedisCacheManager(...);
}

// 容器中没有指定 Bean 时才装配(提供默认实现)
@ConditionalOnMissingBean(DataSource.class)
@Bean
public DataSource embeddedDataSource() {
    return new EmbeddedDatabaseBuilder().build();
}

// 指定类在 classpath 上时才装配
@ConditionalOnClass(RedisTemplate.class)
@Configuration
public class RedisAutoConfig { }

    

AOP(面向切面编程)

AOP 允许在不修改业务代码的情况下,统一处理横切关注点,如日志、权限校验、性能监控、事务等。

核心概念

概念说明
Aspect(切面)封装横切逻辑的类,用 @Aspect 标注
Pointcut(切入点)定义在哪些方法上执行,用表达式描述
Advice(通知)切入点触发时执行的代码
JoinPoint(连接点)可以被切入的位置(方法执行、异常抛出等)
Weaving(织入)将切面逻辑应用到目标对象的过程

通知类型

Java
        @Aspect
@Component
public class LoggingAspect {

    // 切入点表达式,匹配 service 包下所有方法
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}

    // 方法执行前
    @Before("serviceMethods()")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("调用方法:" + joinPoint.getSignature().getName());
    }

    // 方法执行后(无论是否抛出异常)
    @After("serviceMethods()")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("方法执行完毕");
    }

    // 方法正常返回后
    @AfterReturning(pointcut = "serviceMethods()", returning = "result")
    public void logAfterReturning(Object result) {
        System.out.println("返回值:" + result);
    }

    // 方法抛出异常后
    @AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")
    public void logAfterThrowing(Exception ex) {
        System.out.println("异常:" + ex.getMessage());
    }

    // 环绕通知(最强大,可以控制方法是否执行)
    @Around("serviceMethods()")
    public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            Object result = pjp.proceed(); // 执行目标方法
            long elapsed = System.currentTimeMillis() - start;
            System.out.println(pjp.getSignature() + " 耗时:" + elapsed + "ms");
            return result;
        } catch (Exception e) {
            System.out.println("发生异常:" + e.getMessage());
            throw e;
        }
    }
}

    
收起

切入点表达式

Java
        // 精确匹配某个方法
execution(public String com.example.service.UserService.findById(Long))

// 匹配某个类的所有方法
execution(* com.example.service.UserService.*(..))

// 匹配某个包及其子包的所有方法(.. 表示任意子包)
execution(* com.example.service..*.*(..))

// 匹配带有某个注解的方法
@annotation(com.example.annotation.Log)

// 匹配某个包内所有类型
within(com.example.service.*)

// 组合(排除 get 开头的方法)
@Pointcut("execution(* com.example.service.*.*(..)) && !execution(* com.example.service.*.get*(..))")

    

AOP 实战:统一操作日志

Java
        // 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {
    String value() default "";
}

// 切面实现
@Aspect
@Component
public class OperationLogAspect {

    @Autowired
    private LogService logService;

    @Around("@annotation(operationLog)")
    public Object record(ProceedingJoinPoint pjp, OperationLog operationLog) throws Throwable {
        String username = SecurityContextHolder.getContext().getAuthentication().getName();
        String operation = operationLog.value();

        try {
            Object result = pjp.proceed();
            logService.save(username, operation, "SUCCESS");
            return result;
        } catch (Exception e) {
            logService.save(username, operation, "FAILED: " + e.getMessage());
            throw e;
        }
    }
}

// 使用注解
@Service
public class UserService {
    @OperationLog("删除用户")
    public void deleteUser(Long id) {
        // 业务代码无需关心日志
    }
}

    
收起

Spring MVC

请求处理流程

Properties
        客户端请求
  → DispatcherServlet(前端控制器)
  → HandlerMapping(找到对应 Controller 方法)
  → HandlerAdapter(执行 Controller 方法)
  → 返回 ModelAndView 或 @ResponseBody 数据
  → ViewResolver(如果是视图名,找到对应视图)
  → 渲染 → 响应客户端

    

Controller 常用注解

Java
        @RestController
@RequestMapping("/api/users")
public class UserController {

    @Autowired
    private UserService userService;

    // GET /api/users
    @GetMapping
    public List<User> list() {
        return userService.findAll();
    }

    // GET /api/users/1
    @GetMapping("/{id}")
    public ResponseEntity<User> getById(@PathVariable Long id) {
        return userService.findById(id)
            .map(ResponseEntity::ok)
            .orElse(ResponseEntity.notFound().build());
    }

    // POST /api/users
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public User create(@RequestBody @Valid CreateUserRequest req) {
        return userService.create(req);
    }

    // PUT /api/users/1
    @PutMapping("/{id}")
    public User update(@PathVariable Long id, @RequestBody @Valid UpdateUserRequest req) {
        return userService.update(id, req);
    }

    // DELETE /api/users/1
    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable Long id) {
        userService.delete(id);
    }

    // GET /api/users?page=0&size=10&keyword=Alice
    @GetMapping("/search")
    public Page<User> search(
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "10") int size,
        @RequestParam(required = false) String keyword
    ) {
        return userService.search(keyword, PageRequest.of(page, size));
    }
}

    
收起

参数绑定

Java
        // 路径参数
@GetMapping("/{id}")
public User get(@PathVariable Long id) { ... }

// 查询参数(带默认值)
@GetMapping
public List<User> list(
    @RequestParam String name,
    @RequestParam(defaultValue = "0") int page
) { ... }

// 请求体(JSON → 对象)
@PostMapping
public User create(@RequestBody CreateUserRequest req) { ... }

// 请求头
@GetMapping
public String auth(@RequestHeader("Authorization") String token) { ... }

// Cookie
@GetMapping
public String session(@CookieValue("sessionId") String sid) { ... }

// 查询参数自动绑定到对象(?name=Alice&age=25 → UserSearchRequest)
@GetMapping
public Page<User> search(UserSearchRequest req) { ... }

    

全局异常处理

Java
        @RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(EntityNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ErrorResponse handleNotFound(EntityNotFoundException ex) {
        return new ErrorResponse("NOT_FOUND", ex.getMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorResponse handleValidation(MethodArgumentNotValidException ex) {
        List<String> errors = ex.getBindingResult().getFieldErrors()
            .stream()
            .map(f -> f.getField() + ": " + f.getDefaultMessage())
            .collect(Collectors.toList());
        return new ErrorResponse("VALIDATION_FAILED", errors.toString());
    }

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorResponse handleGeneral(Exception ex) {
        return new ErrorResponse("INTERNAL_ERROR", "服务器内部错误");
    }
}

    

Spring Boot

自动装配原理

Spring Boot 通过 @SpringBootApplication 开启自动装配,它是三个注解的组合:

Java
        @SpringBootConfiguration   // 等价于 @Configuration
@EnableAutoConfiguration   // 核心:开启自动装配
@ComponentScan             // 扫描当前包及子包
public @interface SpringBootApplication { }

    

@EnableAutoConfiguration 通过 AutoConfigurationImportSelector 读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 中列出的所有自动配置类,再根据 @ConditionalOnXxx 注解判断条件是否成立,选择性地装配 Bean。

整个流程可以简化为:

Properties
        启动
  → 扫描 AutoConfiguration.imports
  → 逐个检查 @ConditionalOnXxx 条件
  → 条件满足 → 注册 Bean
  → 条件不满足 → 跳过

    

配置文件

Yaml
        # application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb
    username: root
    password: secret
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false

server:
  port: 8080
  servlet:
    context-path: /api

app:
  jwt:
    secret: my-secret-key
    expiration: 86400

    

多环境配置

Yaml
        # application.yml
spring:
  profiles:
    active: dev # 默认激活 dev 环境

---
spring:
  config:
    activate:
      on-profile: dev
  datasource:
    url: jdbc:h2:mem:testdb

---
spring:
  config:
    activate:
      on-profile: prod
  datasource:
    url: jdbc:mysql://prod-server:3306/mydb

    

绑定配置到类

Java
        @ConfigurationProperties(prefix = "app.jwt")
@Component
public class JwtProperties {
    private String secret;
    private long expiration;
    // getters & setters
}

    

事务管理

基础使用

Java
        @Service
public class TransferService {

    @Transactional
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        Account from = accountRepo.findById(fromId).orElseThrow();
        Account to   = accountRepo.findById(toId).orElseThrow();

        from.deduct(amount);
        to.add(amount);

        accountRepo.save(from);
        accountRepo.save(to);
        // 方法正常结束 → 自动提交
        // 抛出 RuntimeException → 自动回滚
    }
}

    

事务传播行为

Java
        // REQUIRED(默认):有事务就加入,没有就新建
@Transactional(propagation = Propagation.REQUIRED)

// REQUIRES_NEW:总是新建事务,将当前事务挂起
@Transactional(propagation = Propagation.REQUIRES_NEW)

// SUPPORTS:有事务就加入,没有就以非事务方式运行
@Transactional(propagation = Propagation.SUPPORTS)

// NOT_SUPPORTED:以非事务方式运行,有事务则挂起当前事务
@Transactional(propagation = Propagation.NOT_SUPPORTED)

// NEVER:不允许在事务中运行,有事务则抛出异常
@Transactional(propagation = Propagation.NEVER)

// MANDATORY:必须在事务中运行,没有事务则抛出异常
@Transactional(propagation = Propagation.MANDATORY)

// NESTED:嵌套事务(使用保存点),外层回滚会回滚内层,内层回滚不影响外层
@Transactional(propagation = Propagation.NESTED)

    

事务隔离级别

隔离级别脏读不可重复读幻读
READ_UNCOMMITTED✅ 可能✅ 可能✅ 可能
READ_COMMITTED❌ 不会✅ 可能✅ 可能
REPEATABLE_READ❌ 不会❌ 不会✅ 可能
SERIALIZABLE❌ 不会❌ 不会❌ 不会
Java
        @Transactional(isolation = Isolation.READ_COMMITTED)   // Oracle 默认
@Transactional(isolation = Isolation.REPEATABLE_READ)  // MySQL InnoDB 默认
@Transactional(isolation = Isolation.SERIALIZABLE)     // 最严格,性能最低

    

注意事项

Java
        @Service
public class UserService {

    // ❌ 自调用无法触发事务(this 不是代理对象,AOP 代理失效)
    public void outer() {
        this.inner();
    }

    @Transactional
    public void inner() { ... }

    // ✅ 通过容器拿到代理对象再调用
    @Autowired
    private ApplicationContext context;

    public void outer2() {
        context.getBean(UserService.class).inner();
    }

    // ❌ 默认只回滚 RuntimeException,受检异常不会触发回滚
    @Transactional
    public void riskyOp() throws IOException {
        // IOException 默认不回滚
    }

    // ✅ 显式指定所有异常都回滚
    @Transactional(rollbackFor = Exception.class)
    public void safeOp() throws IOException { ... }

    // ❌ private 方法上的 @Transactional 无效(AOP 无法代理 private)
    @Transactional
    private void internalUpdate() { ... }
}

    
收起

Spring Data JPA

Repository 层

Java
        @Repository
public interface UserRepository extends JpaRepository<User, Long> {

    // 方法命名约定,Spring 自动生成实现
    List<User> findByEmail(String email);
    List<User> findByAgeGreaterThan(int age);
    Optional<User> findByEmailAndStatus(String email, UserStatus status);
    boolean existsByEmail(String email);
    long countByStatus(UserStatus status);

    // 分页与排序
    Page<User> findByStatus(UserStatus status, Pageable pageable);
    List<User> findAll(Sort sort);

    // 自定义 JPQL
    @Query("SELECT u FROM User u WHERE u.name LIKE %:keyword% OR u.email LIKE %:keyword%")
    List<User> search(@Param("keyword") String keyword);

    // 原生 SQL
    @Query(value = "SELECT * FROM users WHERE created_at > :date", nativeQuery = true)
    List<User> findCreatedAfter(@Param("date") LocalDate date);

    // 更新 / 删除操作需要 @Modifying
    @Modifying
    @Query("UPDATE User u SET u.status = :status WHERE u.id IN :ids")
    int batchUpdateStatus(@Param("ids") List<Long> ids, @Param("status") UserStatus status);
}

    

实体定义

Java
        @Entity
@Table(name = "users", indexes = {
    @Index(name = "idx_email", columnList = "email", unique = true)
})
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, length = 50)
    private String name;

    @Column(nullable = false, unique = true)
    private String email;

    @Enumerated(EnumType.STRING)
    private UserStatus status;

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;

    // 懒加载,避免 N+1 问题
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<Order> orders;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "department_id")
    private Department department;
}

    
收起

常见问题与最佳实践

循环依赖

Spring Boot 2.6+ 默认禁止循环依赖,触发时会抛出 BeanCurrentlyInCreationException

Java
        // ❌ 循环依赖(A 依赖 B,B 依赖 A)
@Service class A { @Autowired B b; }
@Service class B { @Autowired A a; }

// ✅ 方案 1:重新设计,提取公共逻辑到新的 C(推荐)
@Service class C { /* 共同逻辑 */ }
@Service class A { @Autowired C c; }
@Service class B { @Autowired C c; }

// ✅ 方案 2:@Lazy 延迟注入,打破初始化闭环
@Service class A {
    @Autowired @Lazy B b;
}

// ✅ 方案 3:Setter 注入(不推荐,只是规避而非解决)
@Service class A {
    private B b;
    @Autowired
    public void setB(B b) { this.b = b; }
}

    

多实例 Bean 的选择

Java
        // 两个 UserRepository 实现
@Component("mysqlUserRepo")
public class MysqlUserRepository implements UserRepository { ... }

@Component("redisUserRepo")
public class RedisUserRepository implements UserRepository { ... }

// 方式 1:@Qualifier 按名称指定
@Autowired
@Qualifier("mysqlUserRepo")
private UserRepository userRepo;

// 方式 2:@Primary 标记默认实现
@Primary
@Component
public class MysqlUserRepository implements UserRepository { ... }

// 方式 3:注入全部实现,自行选择
@Autowired
private List<UserRepository> allRepos;

@Autowired
private Map<String, UserRepository> repoMap; // key 是 bean name

    

常见坑

Java
        // 1. @Transactional 加在 private 方法上无效
@Transactional        // ❌ 无效,AOP 无法代理 private
private void internalUpdate() { ... }

// 2. 静态方法上的注解无效(AOP 基于实例代理)
@Cacheable("users")   // ❌ 无效
public static User staticGet(Long id) { ... }

// 3. prototype Bean 注入 singleton,每次拿到的是同一个实例
// ✅ 用 @Lookup 让 Spring 在每次调用时新建 prototype
@Component
public class SingletonBean {
    @Lookup
    public PrototypeBean getPrototype() {
        return null; // Spring 会覆盖此方法,实际每次返回新实例
    }
}

// 4. JPA 懒加载在 Session 关闭后访问报 LazyInitializationException
// ✅ 方案:在 Service 层使用 @Transactional 保持 Session,或使用 JOIN FETCH
@Query("SELECT u FROM User u LEFT JOIN FETCH u.orders WHERE u.id = :id")
Optional<User> findByIdWithOrders(@Param("id") Long id);

    
Fonnpo