首页 / Spring Boot 入门教程 / Spring Beans 与依赖注入

Spring Boot 入门教程

Spring Beans 与依赖注入

本教程共 48 篇 · 第 7 篇 · 更新于 2026-08-13 · 约 5 分钟阅读

Spring Bean依赖注入构造器注入组件扫描Bean作用域生命周期

本节目标:理解 Bean 是什么,学会用注解声明 Bean、用构造器注入组装依赖,掌握作用域与生命周期。

对象谁在管

你写的每个类,都要自己 new 吗?在 Spring 里不用。Spring 容器帮你创建对象、管理对象、销毁对象。被容器管理的对象,叫 Bean

容器管到什么程度?创建、装配、销毁全包。你只管声明「我要什么」,容器负责「给什么」。这就是控制反转(IoC):创建对象的控制权,从你手里反转给了容器。

依赖注入(DI)是 IoC 的实现手段。对象需要的依赖,容器在创建时主动塞进来,不用你自己找。

声明 Bean:四个「组件」注解

@Component 是通用组件注解。它还有三个语义更明确的兄弟:

注解语义典型位置
@Component通用组件工具类、无明确分层
@Service业务逻辑Service 层
@Repository数据访问Dao / Repository 层
@Controller接收请求Web 层

它们功能上等价,都能让类变成 Bean。区别在语义和附加行为。@Repository 会把持久层异常翻译成 Spring 的数据访问异常;@Controller 会被 Spring MVC 识别为控制器。分层写清楚,代码可读性高,AOP 也好定位。

一个简单的 Service Bean:

package com.example.demo;

import org.springframework.stereotype.Service;

@Service
public class GreetingService {

    public String greet(String name) {
        return "你好," + name + "!";
    }
}

找到 Bean:@ComponentScan

注解标了,容器怎么知道?靠组件扫描。@SpringBootApplication 自带 @ComponentScan,默认扫描主类所在包及子包。所以第 3 章强调主类要放顶层包——包放错了,Bean 扫不到,启动报「No qualifying bean」错误。

Warning

Bean 的类必须放在主类所在包的子包里。初学者最常见的错误:主类放在 com.example,业务类放在 com.other,结果一个都扫不到。

Bean 默认名字是类名首字母小写:GreetingService 的 Bean 名是 greetingService@Autowired 按类型找 Bean,一般用不上名字;同名冲突时,容器启动会直接报错。

注入依赖:构造器注入是主流

Bean 之间要协作。GreetingController 要用 GreetingService,怎么拿?Spring 官方推荐构造器注入:把依赖写进构造器参数。

package com.example.demo;

import org.springframework.stereotype.Controller;

@Controller
public class GreetingController {

    private final GreetingService greetingService;

    public GreetingController(GreetingService greetingService) {
        this.greetingService = greetingService;
    }
}

容器创建 GreetingController 时,自动找 GreetingService 的 Bean 传进来。只有一个构造器时,不用加任何注解,Spring 直接选它。

构造器注入好在哪?依赖字段能标 final,对象一旦创建,依赖不可变。测试也好写——new GreetingController(mockService) 直接传假对象,不用起容器。

构造器多于一个,必须用 @Autowired 指明用哪个:

package com.example.demo;

import java.io.PrintStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class MyAccountService {

    private final GreetingService greetingService;
    private final PrintStream out;

    @Autowired
    public MyAccountService(GreetingService greetingService) {
        this.greetingService = greetingService;
        this.out = System.out;
    }

    public MyAccountService(GreetingService greetingService, PrintStream out) {
        this.greetingService = greetingService;
        this.out = out;
    }
}

字段注入长这样,很多旧教程这么写:

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class GreetingController {

    @Autowired
    private GreetingService greetingService;
}

代码最短,但缺点明显:字段不能 final,测试必须靠容器,依赖关系藏在字段里。对照可以,主线别用

Setter 注入适合「可选依赖」,用得也不多。记住一句话:默认构造器注入,可选依赖用 setter,字段注入避开

Note

版本差异:老教程里注入的是 javax.annotationjavax.inject 这些包。Spring Boot 4 基于 Jakarta EE 11,主线代码统一 jakarta.*。看到 javax.* 的代码,别直接抄进新项目。

手动声明 Bean:@Bean

第三方类没法加 @Component 注解,就用 @Bean 手动声明。放在 @Configuration 类里:

package com.example.demo;

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

@Configuration
public class AppConfig {

    @Bean
    public GreetingService greetingService() {
        return new GreetingService();
    }
}

方法名是 Bean 名,返回值是 Bean 本身。凡是 new 出来的对象想交给容器管,都走这条路。RestClient 这类外部客户端,常用 @Bean 声明后再注入。

作用域:Bean 活多久

Bean 默认是单例(singleton):整个容器里只有一个实例,大家都用它。适合无状态的服务类,性能最好。

需要每次拿新实例,用 prototype

package com.example.demo;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class Counter {

    private int count;

    public void increment() {
        count++;
    }
}

Web 应用还有三个作用域:request(每次请求一个实例)、session(每个会话一个)、application(ServletContext 级别一个),只在 Web 环境可用。

Tip

90% 的场景用默认单例就够。prototype 用在「有状态、要隔离」的对象上。初学者先记住这两个,Web 的三个用到再查。

单例 Bean 是共享的,天然要小心多线程。Bean 里别放「会变的成员变量」——多个请求同时改同一个字段,数据就乱了。无状态(不存可变状态)是单例 Bean 的基本修养。

生命周期:出生与善后

Bean 创建后、销毁前,可以挂回调。@PostConstruct 在依赖注入完成后执行,常用来做初始化;@PreDestroy 在容器关闭前执行,常用来释放资源。注意包名是 jakarta.annotation

package com.example.demo;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;

@Component
public class CacheWarmer {

    @PostConstruct
    public void init() {
        System.out.println("启动完成,预热缓存");
    }

    @PreDestroy
    public void cleanup() {
        System.out.println("关闭前,释放资源");
    }
}

Spring 容器关闭时,会按依赖关系逆序销毁 Bean,依次调用 @PreDestroy。日志里看到「Closing org.springframework.context…」时,就是容器在收尾。

@Bean 声明时也能指定回调方法:

@Bean(initMethod = "init", destroyMethod = "close")
public SomeClient someClient() {
    return new SomeClient();
}

destroyMethod 默认值很聪明:容器会尝试调用 Bean 的 close()shutdown() 方法。有这类方法的外部资源类,销毁不用你操心。

Tip

初始化逻辑优先放 @PostConstruct,别在构造器里做重活。构造器执行时依赖还没注入完,而且构造器里抛异常,排查起来更费劲。

小结

  • Bean 是容器管理的对象,注解声明 + 组件扫描注册。
  • 构造器注入是官方推荐,字段注入仅作对照。
  • 单例是默认作用域,prototype 每次新建。
  • @PostConstruct / @PreDestroy 管出生与善后。