Spring 的 Bean 明明设置了 Scope 为 Prototype,为什么还是只能获取到单例对象?
source link: http://www.justdojava.com/2022/07/17/lookup/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Spring 的 Bean 明明设置了 Scope 为 Prototype,为什么还是只能获取到单例对象?
2022-07-17
| Java
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);
今天阿粉通过几个例子,给大家介绍了一下如何在单例类中获取原型类的实例,提供了三种解法,其中解法一不推荐,解法二和解法三异曲同工,感兴趣的小伙伴可以自己尝试一下。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK