从乐高积木到乐队指挥,用最通俗易懂的方式带你玩转 Spring Boot Bean!

在这里插入图片描述


🌟如果喜欢作者的讲解方式,关注作者不迷路,同时也可以看看我的其他文章! 感谢!!!
🌟 JDK动态代理 vs CGLIB:一场经纪人之战,谁才是你的最佳选择?

咱今儿个就来好好聊聊 Spring Boot 的 Bean,保证你听得懂,记得住,还能笑出声!🤣

一、啥是 Spring Boot 的 Bean?

简单来说,Bean 就是 Spring 容器管理的对象。你可以把它想象成一个乐高积木,Spring 容器就是你的乐高底板,你可以把各种各样的积木(Bean)放到上面,然后 Spring 帮你把它们组装起来,让它们协同工作。就像一个乐队,每个乐器(Bean)都有自己的作用,Spring 就是指挥,让它们一起演奏出美妙的音乐!🎵

更专业一点说,Bean 是一个被 Spring IoC 容器实例化、组装和管理的对象。Spring 容器负责 Bean 的生命周期,包括创建、初始化、使用和销毁。就像一个保姆,照顾 Bean 的一生!👶

举个栗子🌰:

假设你有一个 UserService 类,负责处理用户相关的业务逻辑:

package com.example.demo;

import org.springframework.stereotype.Service;

@Service // 告诉 Spring,这是一个 Bean
public class UserService {

    public String getUserName(Long userId) {
        // 模拟从数据库获取用户名
        return "用户" + userId;
    }
}

上面的 @Service 注解就是告诉 Spring,UserService 类是一个 Bean,需要被 Spring 容器管理。Spring 会自动创建 UserService 的实例,并把它放到容器里。就像给 UserService 贴了个标签,告诉 Spring:“嘿,我是个 Bean,照顾我一下!” 🏷️

二、Bean 的作用域 (Scope)

Bean 的作用域决定了 Spring 容器每次返回 Bean 实例的方式。就像你买东西,有的东西是独一份(单例),有的东西每次买都是新的(原型)。就像你去餐厅吃饭,有的菜是限量供应(单例),有的菜是随便点(原型)。 🍽️

Spring 提供了几种常用的 Bean 作用域:

  1. singleton (单例): 这是默认的作用域。在整个 Spring 容器中,只有一个该 Bean 的实例。每次获取 Bean,都返回同一个实例。就像皇帝只有一个,大家都拜他。👑 只有一个,省资源!💰

    @Service // 默认就是 singleton
    public class UserService {
        // ...
    }
    
  2. prototype (原型): 每次获取 Bean,都会创建一个新的实例。就像你每次去买冰淇淋,都是一个新的。🍦 每次都要新的,不共享! 🙅‍♀️

    @Component
    @Scope("prototype")
    public class User {
        // ...
    }
    
  3. request (请求): 每次 HTTP 请求都会创建一个新的 Bean 实例。这个作用域只在 Web 应用中使用。就像你每次点外卖,都是一个新的订单。 🍕 每个请求都隔离,互不影响! 🛡️

    @Component
    @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
    public class RequestInfo {
        // ...
    }
    
  4. session (会话): 每次 HTTP 会话都会创建一个新的 Bean 实例。这个作用域也只在 Web 应用中使用。就像你每次登录网站,都会创建一个新的会话。 🍪 保持用户状态,记住你! 🧠

    @Component
    @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
    public class SessionInfo {
        // ...
    }
    
  5. application (应用): 在整个 Web 应用中,只有一个 Bean 实例。这个作用域也只在 Web 应用中使用。就像你的网站只有一个 logo。 🖼️ 全局共享,整个应用都用它! 🌍

    @Component
    @Scope(value = "application", proxyMode = ScopedProxyMode.TARGET_CLASS)
    public class AppConfig {
        // ...
    }
    
  6. websocket (WebSocket): 每次 WebSocket 会话都会创建一个新的 Bean 实例。

注意: requestsessionapplicationwebsocket 作用域只能在 Web 应用中使用,并且需要配置 ScopedProxyMode 来解决依赖注入的问题。就像你玩游戏,不同的房间有不同的规则! 🎮

三、单例 Bean 线程安全吗?单例 Bean 一定不安全吗?

这是一个非常重要的问题!🤔 就像你只有一个碗,很多人都用它吃饭,会不会出问题? 🥣

单例 Bean 本身并不是线程安全的。 线程安全指的是多个线程并发访问同一个对象时,对象的状态不会被破坏。就像很多人同时修改一个文件,可能会导致文件内容混乱! 📝

如果单例 Bean 中有可变的实例变量,并且多个线程同时修改这些变量,就可能导致线程安全问题。

举个栗子:

@Service
public class CounterService {

    private int count = 0; // 可变的实例变量

    public void increment() {
        count++; // 多个线程同时执行,可能导致 count 的值不正确
    }

    public int getCount() {
        return count;
    }
}

在上面的例子中,CounterService 是一个单例 Bean,count 是一个可变的实例变量。如果多个线程同时调用 increment() 方法,就可能导致 count 的值不正确。就像很多人同时往一个计数器上加数,最后的结果可能不对! 🔢

但是,单例 Bean 并不一定不安全!

如果单例 Bean 是无状态的,也就是说,它没有任何可变的实例变量,那么它就是线程安全的。就像一个计算器,你输入什么,它就输出什么,不会记住任何东西! 🧮

举个栗子:

@Service
public class StringUtil {

    public String toUpperCase(String str) {
        return str.toUpperCase(); // 没有可变的实例变量
    }
}

在上面的例子中,StringUtil 是一个单例 Bean,它没有任何可变的实例变量,所以它是线程安全的。就像一个翻译器,你给它什么,它就翻译什么,不会改变任何东西! 🗣️

总结一下:

  • 单例 Bean 本身不是线程安全的。
  • 如果单例 Bean 有可变的实例变量,并且多个线程同时修改这些变量,就可能导致线程安全问题。
  • 如果单例 Bean 是无状态的,那么它就是线程安全的。

如何保证单例 Bean 的线程安全?

  • 避免使用可变的实例变量。 尽量让 Bean 保持无状态! 🧘
  • 如果必须使用可变的实例变量,可以使用线程安全的集合类(例如 ConcurrentHashMap)或者使用锁(例如 synchronized 关键字或 ReentrantLock)。 就像给碗加个盖子,防止别人乱动! 🔒
  • 使用 ThreadLocal 来为每个线程创建一个变量副本。 就像每个人都有自己的碗,互不干扰! 🥣

四、Bean 的注入方式

Bean 的注入是指将一个 Bean 注入到另一个 Bean 中,让它们可以互相协作。Spring 提供了几种常用的 Bean 注入方式:就像搭积木,把不同的积木拼在一起! 🧱

  1. 构造器注入 (Constructor Injection): 通过构造器来注入依赖。这是推荐的注入方式,因为它强制依赖必须存在,并且可以保证 Bean 在创建时就拥有所有需要的依赖。就像盖房子,地基必须先打好! 🏗️

    @Service
    public class OrderService {
    
        private final UserService userService;
    
        @Autowired // 可以省略,如果只有一个构造器
        public OrderService(UserService userService) {
            this.userService = userService;
        }
    
        public void createOrder(Long userId) {
            String userName = userService.getUserName(userId);
            // ...
        }
    }
    

    在上面的例子中,OrderService 通过构造器注入了 UserService@Autowired 注解告诉 Spring,需要将 UserService 的实例注入到 OrderService 的构造器中。就像告诉 Spring:“嘿,OrderService 需要 UserService 才能工作,帮我搞定!” 🤝

  2. Setter 注入 (Setter Injection): 通过 Setter 方法来注入依赖。这种方式比较灵活,但是依赖不是强制的,可能会导致 Bean 在使用时缺少依赖。就像装修房子,可以先住进去,再慢慢添置家具! 🛋️

    @Service
    public class ProductService {
    
        private CategoryService categoryService;
    
        @Autowired
        public void setCategoryService(CategoryService categoryService) {
            this.categoryService = categoryService;
        }
    
        public void getProductByCategory(Long categoryId) {
            String categoryName = categoryService.getCategoryName(categoryId);
            // ...
        }
    }
    

    在上面的例子中,ProductService 通过 Setter 方法注入了 CategoryService@Autowired 注解告诉 Spring,需要将 CategoryService 的实例注入到 ProductServicesetCategoryService() 方法中。就像告诉 Spring:“ProductService 可能需要 CategoryService,如果需要就帮我注入一下!” 🤔

  3. 字段注入 (Field Injection): 直接在字段上使用 @Autowired 注解来注入依赖。这种方式最简单,但是不推荐使用,因为它破坏了封装性,并且难以进行单元测试。就像直接把电线接到电器上,不安全! ⚡

    @Service
    public class PaymentService {
    
        @Autowired
        private PaymentGateway paymentGateway;
    
        public void processPayment(Double amount) {
            paymentGateway.charge(amount);
            // ...
        }
    }
    

    在上面的例子中,PaymentService 通过字段注入了 PaymentGateway@Autowired 注解告诉 Spring,需要将 PaymentGateway 的实例注入到 PaymentServicepaymentGateway 字段中。就像告诉 Spring:“PaymentService 需要 PaymentGateway,直接给它就行了!” 🤷‍♀️

总结一下:

  • 构造器注入: 推荐使用,强制依赖,保证 Bean 在创建时就拥有所有需要的依赖。就像盖房子,地基必须先打好! 🏗️
  • Setter 注入: 比较灵活,但是依赖不是强制的。就像装修房子,可以先住进去,再慢慢添置家具! 🛋️
  • 字段注入: 最简单,但是不推荐使用,因为它破坏了封装性,并且难以进行单元测试。就像直接把电线接到电器上,不安全! ⚡

最佳实践:

  • 优先使用构造器注入。 就像盖房子,地基必须先打好! 🏗️
  • 如果依赖是可选的,可以使用 Setter 注入。 就像装修房子,可以先住进去,再慢慢添置家具! 🛋️
  • 避免使用字段注入。 就像直接把电线接到电器上,不安全! ⚡

五、总结

Bean 是 Spring Boot 的核心概念,理解 Bean 的作用域和注入方式对于开发 Spring Boot 应用至关重要。希望这篇文章能够帮助你更好地理解 Spring Boot 的 Bean,让你在开发过程中更加得心应手!🚀

记住,编程就像玩乐高,把各种积木(Bean)组装起来,创造出你想要的东西!🎉 祝你编程愉快! 💻 😊

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值