Spring 的 Bean 明明设置了 Scope 为 Prototype,为什么还是只能获取到单例对象?

@Component源码级解析:Spring IoC容器启动的核心机制 @Component是Spring实现控制反转(IoC)最基础的元数据标记,它本身不执行逻辑,而是触发ClassPathBeanDefinitionScanner在运行期扫描字节码、生成BeanDefinition并注册到BeanFactory中。其本质是将对象创建权从硬编码new解耦为容器统一调度,支撑依赖注入、AOP切面定位与生命周期管理等核心能力。在微服务与多模块架构中,合理配置@ComponentScan路径、作用域(Singleton/Prototype)及环境条件,直接决定应用启动性能、内存开销 阅读详情

Spring​ 作为当下最火热的Java​ 框架,相信很多小伙伴都在使用,对于 Spring​ 中的 Bean​ 我们都知道默认是单例的,意思是说在整个 Spring 容器里面只存在一个实例,在需要的地方直接通过依赖注入或者从容器中直接获取,就可以直接使用。

测试原型

对于有些场景,我们可能需要对应的 Bean​ 是原型的,所谓原型就是希望每次在使用的时候获取到的是一个新的对象实例,而不是单例的,这种情况下很多小伙伴肯定会说,那还不简单,只要在对应的类上面加上 @scope​ 注解,将 value​ 设置成 Prototype 不就行了。如下所示:

HelloService.java

package com.example.demo.service;

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

/**
 * <br>
 * <b>Function:</b><br>
 * <b>Author:</b>@author ziyou<br>
 * <b>Date:</b>2022-07-17 21:20<br>
 * <b>Desc:</b>无<br>
 */
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class HelloService {

  public String sayHello() {
    return "hello: " + this.hashCode();
  }
}

HelloController.java 代码如下:

package com.example.demo.controller;

import com.example.demo.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Lookup;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * <br>
 * <b>Function:</b><br>
 * <b>Author:</b>@author ziyou<br>
 * <b>Date:</b>2022-07-17 15:43<br>
 * <b>Desc:</b>无<br>
 */
@RestController
public class HelloController {

  @Autowired
  private HelloService service;

  @GetMapping(value = "/hello")
  public String hello() {
    return service.sayHello();
  }
}

简单描述一下上面的代码,其中 HelloService​ 类我们使用了注解 Scope​,并将值设置为 SCOPE_PROTOTYPE​,表示是原型类,在 HelloController​ 类中我们调用 HelloService​ 的 sayHello​ 方式,其中返回了当前实例的 hashcode。

我们通过访问 http://127.0.0.1:8080/hello 来获取返回值,如果说每次获取到的值都不一样,那就说明我们上面的代码是没有问题的,每次在获取的时候都会使用一个新的 HelloService 实例。

然而在阿粉的电脑上,无论刷新浏览器多少次,最后的结果却没有发生任何变化,换句话说这里引用到的 HelloService 始终就是一个,并没有原型的效果。

那么问题来了,我们明明给 HelloService 类增加了原型注解,为什么这里没有效果呢?

原因分析

我们这样思考一下,首先我们通过浏览器访问接口的时候,访问到的是 HelloController​ 类中的方法,那么 HelloController​ 由于我们没有增加 Scope​ 的原型注解,所以肯定是单例的,那么单例的 HelloController​ 中的 HelloService 属性是什么怎么赋值的呢?

那自然是 Spring​ 在 HelloController​ 初始化的时候,通过依赖注入帮我们赋值的。Spring​ 注入依赖的赋值逻辑简单来说就是创建 Bean​ 的时候如果发现有依赖注入,则会在容器中获取或者创建一个依赖 Bean​,此时对应属性的 Bean​ 是单例的,则容器中只会创建一个,如果对应的 Bean​ 是原型,那么每次都会创建一个新的 Bean​,然后将创建的 Bean 赋值给对应的属性。

在我们这里 HelloService​ 类是原型的,所以在创建 HelloController Bean​ 的时候,会创建一个 HelloService​ 的 Bean​ 赋值到 service​ 属性上;到这里都没有问题,但是因为我们 HelloController Bean​ 是单例的,初始化的动作在整个生命周期中只会发生一次,所以即使 HelloService 类是原因的,也只会被依赖注入一次,因此我们上面的这种写入是达不到我们需要的效果的。

解法

解法一

写到这里有的小伙伴就会想到,那如果我把 HelloController​ 类也设置成原型呢?这样不就可以了么。给 HelloController​ 增加上注解 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)​ 重启过后我们重新访问 http://127.0.0.1:8080/hello ,发现确实是可以的。也很好理解,因为此时 HelloController​ 是原型的,所以每次访问都会创建一个新的实例,初始化的过程中会被依赖注入新的 HelloService 实例。

但是不得不说,这种解法很不优雅,把 Controller 类设置成原型,并不友好,所以这里我们不推荐这种解法。

解法二

除了将 HelloController​ 设置成原型,我们还有其他的解法,上面我们提到 HelloController​ 在初始化的时候会依赖注入 HelloService​,那我们是不是可以换一个方式,让 HelloController​ 创建的时候不依赖注入 HelloService,而是在真正需要的时候再从容器中获取。如下所示:

package com.example.demo.controller;

import com.example.demo.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * <br>
 * <b>Function:</b><br>
 * <b>Author:</b>@author ziyou<br>
 * <b>Date:</b>2022-07-17 15:43<br>
 * <b>Desc:</b>无<br>
 */
@RestController
public class HelloController {

  @Autowired
  private ApplicationContext applicationContext;

  @GetMapping(value = "/hello")
  public String hello() {
    HelloService service = getService();
    return service.sayHello();
  }

  public HelloService getService() {
    return applicationContext.getBean(HelloService.class);
  }
}

通过测试这种方式也是可以的,每次从容器中重新获取的时候都是重新创建一个新的实例。

解法三

上面解法二还是比较常规的,除了解法二之外还有一个解法,那就是使用 Lookup​ 注解,根据 Spring 的官方文档,我们可以看到下面的内容。

简单来说就是通过使用 Lookup​ 注解的方法,可以被容器覆盖,然后通过 BeanFactory 返回指定类型的一个类实例,可以在单例类中使用获取到一个原型类,示例如下:

package com.example.demo.controller;

import com.example.demo.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Lookup;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * <br>
 * <b>Function:</b><br>
 * <b>Author:</b>@author ziyou<br>
 * <b>Date:</b>2022-07-17 15:43<br>
 * <b>Desc:</b>无<br>
 */
@RestController
public class HelloController {

  @GetMapping(value = "/hello")
  public String hello() {
    HelloService service = getService();
    return service.sayHello();
  }

  @Lookup
  public HelloService getService() {
    return null;
  }
}

写法跟我们解法二比较相似,只不过不是我们显示的通过容器中获取一个原型 Bean​ 实例,而是通过 Lookup​ 的注解,让容器来帮我们覆盖对应的方法,返回一个原型实例对象。这里我们的 getService​ 方法里面可以直接返回一个 null,因为这里面的代码是不会被执行到的。

我们打个断点调试,会发现通过 Lookup​ 注解的方法最终后走到org.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy.LookupOverrideMethodInterceptor#intercept 这里。

这里我们可以看到,动态从容器中获取实例。不过需要注意一点,那就是我们通过 Lookup​ 注解的方法是有要求的,因为是需要被重写,所以针对这个方法我们只能使用下面的这种定时定义,必须是 public​ 或者 protected,可以是抽象方法,而且方法不能有参数。

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

总结

今天阿粉通过几个例子,给大家介绍了一下如何在单例类中获取原型类的实例,提供了三种解法,其中解法一不推荐,解法二和解法三异曲同工,感兴趣的小伙伴可以自己尝试一下。

Spring @Configuration 原理深度解析:代理机制、条件化配置与性能优化 @Configuration 是 Spring 基于 Java 的声明式配置核心机制,其本质是通过 CGLIB 动态代理实现 Bean 方法的复用与调用拦截,区别于 @Component 的被动注册模式。它支撑了类型安全、编译期校验、运行时条件判断(如 @ConditionalOnProperty、@ConditionalOnClass)等关键能力,成为 Spring Boot 自动配置(@EnableAutoConfiguration)和模块化配置(@Import)的技术基石。在多环境部署、微服务配置 阅读详情

相关推荐

基于Java+Spring+SSH的CRM客户关系管理系统设计与实现解析

Java Web开发中,SSH(Struts2+Spring+Hibernate)是经典的分层架构组合,体现了MVC模式、依赖注入与ORM映射等核心思想。对于客户关系管理系统这类典型业务场景,SSH框架能够清晰地将请求控制、业务逻辑与数据持久化分离,帮助开发者快速搭建可维护的企业级应用。文章从分层架构原理切入,梳理了客户管理、联系人跟进、权限控制、分页查询等核心功能的实现思路,并给出了动态HQL拼接、文件上传、Excel导出及Tomcat部署等工程实践技巧。理解和掌握SSH技术栈,不仅有助于完成基于Jav

weixin_32325225的博客 348

Spring注解

我们可以自定义初始化和销毁方法通过@Bean注解指定init-method和destroy-method实现和接口,重写里面的destroy和afterPropertiesSet方法使用和注解在bean创建完成并且属性赋值完成后进行初始化PreDestroy在容器销毁bean之前执行接口,bean后置处理器,在bean初始化前后进行一些处理在初始化之前执行在初始化之后执行BeanPostProcessor执行过程。.........

liangjiayy的博客 663

@Scope注解

1. 作用 ConfigurableBeanFactory.SCOPE_PROTOTYPEprototype——多实,IOC容器启动不会调用方法创建对象,在获取时才会创建对象 ConfigurableBeanFactory.SCOPE_SINGLETON:singleton——(默认值),IOC启动就会调用方法将对象放入IOC容器中 org.springframework.web.context.WebApplicationContext.SCOPE_REQUEST:request——同一次请求

学而不思则忘 837

Spring的@Scope注解prototype

因为A是,所以注入的属性B相当于也是了。即:A中的B是固定不变的了,每次调用a.printB,输出b都是一样的。那么如果想要每次调用a.printB,输出b都不一样,就需要使用代理proxyMode这样每次调用b.toString()的时候就会通过代理new一个b。

liaonanfeng88的CSDN博客 1264

Spring注解驱动开发】使用@Scope注解设置组件的作用域(1)

自定义Scope主要分为三个步骤,如下所示。(1)实现Scope接口我们先来看下Scope接口的定义,如下所示。/**返回当前作用域中name对应的bean对象name:需要检索的bean的名称objectFactory:如果name对应的bean在当前作用域中没有找到,那么可以调用这个ObjectFactory来创建这个对象**//**将name对应的bean从当前作用域中移除**/@Nullable/**

2301_82242844的博客 561

解决@Scope(“prototype“)不生效的问题

(34和35是两个线程的ID,每次运行都可能不同,但是两个请求使用的线程的ID肯定不一样,可以用来区分两个请求。Spring管理的Bean默认是的(即Spring创建好Bean,需要时就拿来用,而不是每次用到时都去new,又快性能又好),但有时候并不满足要求(比如Bean中不全是方法,有成员,使用会有线程安全问题,可以搜索线程安全与线程不安全的相关文章),你上网可以很容易找到解决办法,即使用@Scope("prototype")注解,可以通知Spring把被注解的Bean变成多

百创科技 1409

spring 问题。

spring 中属性scopeprototype(有状态)和singleton(无状态)     scope="prototype">         scope="prototype"没写的问题,项目中对一个表的增删该操作是用一个action,这个action有add,update,delete,save这些方法,  添加和修改是共用一个页面,当页

牛栏札记 540

Spring 为啥默认把bean设计成的?这篇讲的明明白白的

程序员的成长之路互联网/程序员/技术/资料共享关注阅读本文大概需要 2.6 分钟。来自:juejin.im/post/5cab7ebf518825177637b2f9熟悉Spring开...

良月柒 654

Bean的六种作用域

其实真实的情况是Spring的IoC容器有两种作用域模式,一种是IoC默认的singleton(作用域),一种是可手动修改的prototype(原型作用域 / 多作用域),在默认情况下,IoC容器采用singleton,也就是所有人获取到的都是同一个Bean对象,这样效率会高很多.每次对该作用域下的Bean的请求都会创建新的实,获取Bean(指使用ApplicationContext.getBean()等方法获取)和装配Bean(即使用@Autowired注入),获取到的都是新的对象.

qq_62592809的博客 4535

什么场景要用@bean_Spring 为啥默认把bean设计成的?这篇给你讲的明明白白的...

熟悉Spring开发的朋友都知道Spring提供了5种scope分别是singleton、prototype、request、session、global session。如下图是官方文档上的截图,感兴趣的朋友可以进去看看这五种分别有什么不同。今天要介绍的是这五种中的前两种,也是Spring最初提供的bean scope singleton 和 prototypeSpring官方文档介绍如下图:...

weixin_39569051的博客 547

Spring 配置 @Scope(value =prototype“,proxyMode = ScopedProxyMode.TARGET_CLASS) 执行过程分析

总结一下 createScopedProxy() 方法把原始的 BeanDefinition 的 **autowireCandidate 属性修改成了 false**,因为改了这个值,所以在 @Autowired 属性注入的时候,就不会考虑到这个原生的 BeanDefinition 定义了 这里自己给造了一份新的这个 beanBeanDefinition,可以理解为这个 bean 的代理 BeanDefinition,此时容器中已经存在两份 beanBeanDefinition,只是原生的 .

龚小帅的博客 3569

深入解析Java Bean生命周期:从核心原理到Spring实战应用

Java企业级开发中,控制反转(IoC)和依赖注入(DI)是构建松耦合、可测试应用的核心设计模式。其原理在于将对象的创建、组装与管理权交由容器负责,从而解耦组件间的依赖关系。这一机制的技术价值在于提升了代码的模块化、可维护性和可扩展性,是Spring等主流框架的基石。在实际应用场景中,开发者通过定义Bean并利用容器管理的生命周期钩子,如初始化与销毁回调,来实现资源管理、配置加载等关键任务。本文聚焦于Java Bean生命周期的完整流程,深入探讨其实化、属性填充、初始化及销毁等核心阶段,并结合@Post

weixin_30617695的博客 335

Lesson3:Bean作用域和生命周期

介绍了Bean的六种作用域,作用域和原型作用域是Spring核心作用域,request、session、application、websocket是Spring MVC作用域。使用@Scope设置Bean的作用域。最后介绍了Spring Core的执行流程和Bean的生命周期。

weixin_44258092的博客 184

手把手带你吃透Spring Bean生命周期!从实化到销毁全流程解析

作为Spring框架的核心机制之一,Bean的生命周期贯穿了从对象创建到资源释放的完整过程。无论是面试还是实际开发中,理解这一过程都能帮你解决90%以上的依赖注入、资源管理问题。

weixin_42148384的博客 1200

ssh框架下 一个action的值会传到接下来响应另外一个action的jsp界面值 明明代码没有获取上一个action的值

就是我在ssh框架下搭建的web网站 执行完update()操作后,紧接着通过另外一个action跳入增加界面的jsp,这个jsp界面的某些属性就会获取到update的值 导致出现错误 后来发现原来是spring bean出了问题。 scope="prototype" 会在该类型的对象被请求时创建一个新的action对象。如果没有配置scope=prototype则添加的时候不会新建一个act

Emotionameng的博客 833

C语言作用域与生存期:从变量丢失到内存管理的核心原理

在编程中,变量的作用域和生存期是理解内存管理与程序执行流程的基础概念。作用域决定了变量在代码中的可见范围,而生存期则定义了变量内存的分配与释放时机。掌握这两者对于避免内存泄漏、编写可调试的健壮代码至关重要,尤其在C语言这类系统级编程中,它们直接关系到指针操作、函数调用和数据共享的安全性。从自动变量的栈内存管理到静态变量的持久化存储,这些原理不仅是理解闭包、模式等高级特性的基石,也直接关联到现代框架如Spring Bean作用域的设计思想。本文通过典型实验案,深入解析作用域与生存期的交织关系,帮助你从底

weixin_30911451的博客 362

工作中使用到的词(软件开发)

1.エイリアス   alias 别名 powershell使用 get-help 命令时,会得到与此命令 相关(别名)的命令 2.プロキシ proxy 代 理 3.プロモート   promote 促进,提升   开发的系统新功能上线发布,有时会使用到这个词,比如 STプロモート 还有使用リリース这个词的。比如STリリース。个人认为两者含义相同。 4.見逃し み................................................................

sun0322 6万+
上一篇: 大厂的门只进不出,来看我的Java面试资料大全!免费送
下一篇: 查日志只有ES好使?那是你没这样用Clickhouse……
程序员职业指南
博客等级 码龄4年 2102粉丝 960原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值