跳至主要內容

如何理解 Spring 当中的 Bean?

程序员小富大约 5 分钟

如何理解 Spring 当中的 Bean?

"液体金属零件"这个比喻挺有意思的,但跟 Bean 的实际含义差得有点远。Bean 没有那么玄乎,说白了就是一个被 Spring 管起来的对象。


先忘掉所有比喻,看看没有 Bean 的时候你在干什么

你写 Java 的时候一定写过这样的代码:

public class OrderService {
    private UserDao userDao = new UserDao();
    private ProductDao productDao = new ProductDao();
    private LogService logService = new LogService();
    
    public void createOrder(Order order) {
        userDao.checkUser(order.getUserId());
        productDao.deductStock(order.getProductId());
        logService.log("订单创建成功");
    }
}

OrderService 需要用到 UserDao、ProductDao、LogService,你就在里面直接 new 了三个出来。

这样写能跑,但问题来了:如果 PaymentService 也需要用 UserDao 呢?它也得 new 一个。如果 RefundService 也需要呢?再 new 一个。整个系统跑起来之后,到处都是 new 出来的对象,每个人各管各的,谁也不知道系统里一共有多少个 UserDao 的实例在跑。

更麻烦的是,如果有一天 UserDao 的构造方式变了——比如它需要传一个数据库连接进去——你就得把所有写了 new UserDao() 的地方全找出来改一遍。散落在几十个文件里,改漏一个就是运行时报错。


Bean 就是让 Spring 替你管理这些对象

Bean 这个概念没有什么高深的,就是你告诉 Spring:"这个类的对象你帮我创建和管理,别人要用的时候你负责给他。"

@Service
public class UserDao {
    // 这个类的对象交给 Spring 管理,就是一个 Bean
}

@Service
public class OrderService {
    @Autowired
    private UserDao userDao;  // Spring 自动把 UserDao 的 Bean 塞进来
    
    public void createOrder(Order order) {
        userDao.checkUser(order.getUserId());
    }
}

加了 @Service 注解,UserDao 就变成了一个 Bean。Spring 启动的时候会创建一个 UserDao 的实例,然后谁需要就给谁。OrderService 需要用 UserDao,不用自己 new,Spring 会自动注入进来。PaymentService 需要用?也给同一个。RefundService 需要用?还是同一个。

整个系统里只有一个 UserDao 的实例(默认是单例的),大家共用。不浪费资源,也好管理。


用一个更贴切的比喻

如果一定要类比的话,Bean 更像是公司里的共享资源。

你公司有一台打印机。以前没有行政管理的时候,每个部门想打印就自己买一台打印机放在办公室,采购部门买一台、财务部门买一台、法务部门买一台。浪费钱不说,坏了还得各自找人修。

后来公司搞了个行政部(Spring 容器),行政部买了一台好的打印机(Bean),放在公共区域。哪个部门需要打印,跟行政部说一声(@Autowired),行政部就告诉你打印机在哪。大家用同一台,行政部负责维护、换墨、修故障。

这个比喻里:打印机就是 Bean,行政部就是 Spring 容器,"跟行政部说一声"就是依赖注入。


Bean 的几个关键特征,用代码说话

默认是单例的。 整个应用里只有一个实例。你在 Controller 里注入的 UserService 和在 Scheduler 里注入的 UserService 是同一个对象。

@Service
public class UserService {
    // Spring 只会创建一个 UserService 对象
    // 所有注入它的地方拿到的都是同一个
}

如果你确实需要每次注入都拿到一个新对象,可以改成 prototype 作用域,但大多数情况下单例就够了。

Spring 管理 Bean 的整个生命周期。 创建、初始化、依赖注入、销毁,都是 Spring 来做。你可以在这些节点插入自己的逻辑:

@Service
public class CacheService {
    
    @PostConstruct
    public void init() {
        // Bean 创建完、依赖注入完之后执行
        // 比如在这里加载缓存
        loadCache();
    }
    
    @PreDestroy
    public void cleanup() {
        // 应用关闭前执行
        // 比如在这里清理连接
        closeConnections();
    }
}

不是所有类都该变成 Bean。 这个很多初学者搞不清楚。Bean 适合那些"提供服务"的对象——Service、Dao、Controller、配置类。不适合那些"承载数据"的对象——实体类(User、Order)、DTO、VO。你不会把 User 注册成 Bean,因为系统里有成千上万个 User,每个数据不同,不应该共享。

一个简单的判断标准:这个对象是"做事的"还是"存数据的"?做事的交给 Spring 管,存数据的自己 new。


为什么不直接 new,非要搞个 Bean

初学的时候很多人会疑惑:我 new 一个也能用,为什么要绕这么一圈让 Spring 管?

核心原因是解耦。

假设你的 OrderService 直接 new 了一个 MySQLUserDao:

public class OrderService {
    private UserDao userDao = new MySQLUserDao();
}

有一天要把数据库从 MySQL 换成 PostgreSQL,你就得改代码,把 new MySQLUserDao() 改成 new PostgreSQLUserDao()。如果十个 Service 都用了 UserDao,就得改十个地方。

用 Bean 的话:

@Service
public class OrderService {
    @Autowired
    private UserDao userDao;  // 我只知道我需要一个 UserDao,具体是哪个实现我不管
}

UserDao 是接口,具体用 MySQLUserDao 还是 PostgreSQLUserDao,在配置里改一下就行,OrderService 的代码一行都不用动。所有用了 UserDao 的地方自动切换。

这就是依赖注入的价值——用什么不再由使用者决定,而是由 Spring 容器统一管理。改一处,处处生效。


总结一下

Bean 就是 Spring 帮你创建和管理的对象。你告诉 Spring 哪些类需要管理(加 @Component/@Service/@Repository 等注解),Spring 启动时帮你创建好实例,哪里需要就自动注入进去。

不用想得太复杂。写到后面你就会觉得这是理所当然的事情——你写一个 Service,加个注解,在需要的地方 @Autowired 一下就能用,根本不用关心对象是谁创建的、什么时候创建的、创建了几个。这些 Spring 全替你操心了。

上次编辑于: