文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-12-01 18:21

关注

测试原型

对于有些场景,我们可能需要对应的 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;


@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;


@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;


@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;


@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);

总结

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

来源:Java极客技术内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯