《系列二》-- 2、bean 的作用域: Scope 有哪些

阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。

写在开始前的话:

阅读spring 源码实在是一件庞大的工作,不说全部内容,单就最基本核心部分包含的东西就需要很长时间去消化了:

  • beans
  • core
  • context

实际上我在博客里贴出来的还只是一部分内容,更多的内容,我放在了个人,fork自 spring 官方源码仓了; 而且对源码的学习,必须是要跟着实际代码层层递进的,不然只是干巴巴的文字味同嚼蜡。

https://gitee.com/bokerr/spring-framework-5.0.x-study

这个仓设置的公共仓,可以直接拉取。



Spring源码阅读系列--全局目录.md



作用域 Scope 特性概述

Spring 容器支持以下6种 scopes

常规作用域

在常规的 spring IOC 场景下使用

  • prototype: 原型模式
    跟单例模式相反,每次使用beanName 向spring 容器申请bean的时候,必须重新new一个bean对象。

如下案例,向spring 容器中注入一个 "原型模式" 作用域的bean, 每次当需要注入 WorkerService 时,都会创建一个全新的bean 对象。

@Component
@Scope("prototype")
public class WorkerService {
    public void handler(String arg) {
      this.workerDo();
    }
    public void workerDo() {
        // xx 业务逻辑
    }
}
  • singleton:单例模式<Spring默认的bean作用域>
    当用户通过getBean(beanName) 向容器主动申请bean时、或者由 spring 容器自身进行依赖注入操作时;必须保证被当作依赖,注入的单例bean全局唯一。

如下案例:通过注解的方式向容器中注入一个单例bean, spring 容器在第一次创建 UserService 这个bean后会将其缓存下来。
后续通过同样的beanName 来获取这个bean的时候,Spring 容器会将之前缓存的bean引用返回,从而保证 UserService 的bean 全局唯一。

@Component
@Scope("singleton")
public class UserService {
    @Autowired
    private WorkerService workerService;
    
    public void handler(String arg) {
      workerService.handler(arg);
      System.out.println("workerService实例对象内存地址:" + workerService);
    }
}

web 场景作用域

  • request: 该作用域的bean,在每次HTTP 请求发生时被创建,它生命周期被,该HTTP请求的生命周期包含。(只有一个bean实例)

  • session: session作用域的bean,在session的生命周期内有效。(只有一个bean实例)

  • application: 该作用域的bean 在 ServletContext 的生命周期内有效。(只有一个bean实例)

  • webSocket: 在webSocket生命周期内有效。(只有一个bean实例)

实际上当下 Spring Web MVC 已经显现颓势,大势已经转向了前后端分离,所以 Web 场景的 bean 作用域的使用场景已经越来越少,本文也不再详细展开。

经典问题

单例作用域的 UserService 中注入,原型作用域的 WorkerService;必须保证从 spring 容器中多次获取 UserService 时,其中注入的 WorkerService 每次都必须不一样。

模拟场景

结合上边讲解单例和原型作用域时,给的伪代码,已知:

  • UserService 是单例作用域

    • WorkerService 将作为依赖注入到 UserService 中
  • WorkerService 是原型作用域

看如下的程序1和程序2,如果我们从不同的地方,多次使用 UserService.handler(arg), 你猜猜打印出来的 WorkerService 对象地址会是怎样的?

程序1:

@Controller
public class BusinessOld {
    @Autowired
    private UserService userService;
    
    public void execute(String arg) {
      userService.handler("arg");
    }
}

程序2:

@Controller
public class BusinessNew {
    @Autowired
    private UserService userService;
    
    public void execute(String arg) {
      userService.handler("arg");
    }
}

实际上运行上述的两段代码,输出的 workerService 对象内存地址将会一样. 前边也讲过了spring 对单例bean的管理模式:

  • 单例bean 在第一次被加载后,会被直接缓存在容器中,后续再向容器请求这个单例 bean时,会将之前加载好的bean 直接返回

  • 再说直白点,在UserService 类的单例bean 在第一次被初始化时,其上注入的 WorkerService 类对象就已经注入成功了,由于在UserService 是通过单例的形式注入容器的。
    所以它只会被初始化一次,所以 WorkerService 也只会被注入 UserService 中一次。自然就会打印一模一样的 WorkerService 地址了

所以,上述的 程序1 和 程序2 将会输出同样的 WorkerService 对象地址。

解决办法

方法一

借助前边讲过的 "lookup-method"实现

img.png

简单回归下这个标签的作用,lookup-method.name 属性配置一个方法名, lookup-method.bean 配置一个beanId;
当从容器中加载 id="lookUpTest" 的bean 时,就会通过 lookup-method.name 配置的方法,动态的返回一个 userBean。

这里我们就不用xml的方式书写案例了,基于上述的原理,我们可以简单改一下 UserService 类的代码:

这里在 UserService.handler() 方法上加了一个注解:@Lookup 这代表着,UserService.handler() 方法每次执行时都会重新向容器获取 WorkerService 类的bean。

因为其以原型模式作用域注入容器,故此可以保证每次都能获取到一个新new出来的 WorkerService 类对象。

@Component
@Scope("singleton")
public class UserService {
    @Autowired
    private WorkerService workerService;
    
    @Lookup("workerService")
    public void handler(String arg) {
      workerService.handler(arg);
      System.out.println("workerService实例对象内存地址:" + workerService);
    }
}

方法二 实现接口 BeanFactoryAware

我们首先看看这个接口的结构:

  • 很简单,这里只有一个简单的Setter 方法,从这里可以接收到的参数是 BeanFactory 对象,也就是spring容器工厂本身,意思就是说我把整个车床斗给你了,你还车不出来一个珠子?

BeanFactoryAware

既然都拿到工厂了,你要什么bean 还不是随你喜欢,还要啥自行车。下边直接看修改好的代码:

@Component
@Scope("singleton")
public class UserService implements BeanFactoryAware {
    private BeanFactory beanFactory; // 接收Setter 注入的容器对象工厂
    @Override
    public void setBeanFactory(Beanfactory beanFactory) {
        this.beanFactory = beanFactory;
    }
    
    // @Autowired
    // private WorkerService workerService;
    
    @Lookup("workerService")
    public void handler(String arg) {
      // workerService.handler(arg);
      WorkerService workerService = beanFactory.getBean("workerService");
      workerService.handler(arg);
      
      System.out.println("workerService实例对象内存地址:" + workerService);
    }
}

这里相当于我们会在运行时动态从 BeanFactory 中获取bean: "workerService"
由于我们在bean 定义时把它的作用域定义为了 "原型模式", 故此这里当然可以从容器中捞出单例的 WorkerService

posted @ 2023-06-24 10:03  bokerr  阅读(41)  评论(0编辑  收藏  举报