Spring的原型Bean(Prototype)声明和注入方式
目录
一、了解单例和原型Bean
1.1 什么是单例Bean?什么是原型Bean?
- 单例Bean,相信各位朋友都不陌生,Spring当中的Bean默认就是单例的,也就是无论从什么地方去使用@Autowired或者@Resource等方式去进行注入,拿到的都是同一个对象,这个对象维护在Spring容器当中,每次使用都是直接从Spring容器当中直接进行获取。
- 原型Bean,也就是说你每次使用到该Bean,都是Spring框架它去重新帮你去进行创建的,也就是说你任意的两次获取该Bean,永远不可能获取到相同的对象。
1.2 如何去定义一个原型Bean
使用@Component、@Bean、@Configuration等注解往容器中注册的Bean,都是单例Bean,要想实现原型Bean,可以通过@Scope注解等方式去配置为Bean的作用域为prototype
比如:
@Component
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
public class User {
private int id;
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
二、注入原型Bean的方法
因为使用@Autowire注入时,只会在初始化时注入一次,所以每次请求获取的user都是同一个实例。
@Configuration
class UserConfiguration {
// 声明原型bean
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
@Bean
fun user(): UserDO {
return UserDO(0, "root", Date(), Date())
}
}
@RestController
class UserController {
@Autowired
lateinit var user: UserDO
@GetMapping("/user")
fun getUser(): ResultDTO<UserDO> {
// 拿到的user都是同一个实例
println(System.identityHashCode(user))
return ResultDTO.ok(user)
}
}
2.1 使用ApplicationContext的getBean每次进行获取
@RestController
class UserController {
@Autowired
lateinit var applicationContext: ApplicationContext
@GetMapping("/user")
fun getUser(): ResultDTO<UserDO> {
val user = applicationContext.getBean(UserDO::class.java)
println(System.identityHashCode(user))
return ResultDTO.ok(user)
}
}
2.2 使用@Lookup注解
@Lookup注解需要配置在返回类型为具体类型的方法上,spring会实现或覆盖该方法,改为从Ioc容器中获取对象。该方法需要满足以下语法要求
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
- public|protected要求方法必须是可以被子类重写和调用的
- abstract可选,如果是抽象方法,CGLIB的动态代理类就会实现这个方法,如果不是抽象方法,就会覆盖这个方法
- return-type是非单例的类型
- no-arguments不允许有参数
修改后:
@RestController
class UserController {
@GetMapping("/user")
fun getUser(): ResultDTO<UserDO> {
val user = findUser()
println(System.identityHashCode(user))
return ResultDTO.ok(user)
}
@Lookup
fun findUser(): UserDO? {
return null
}
}
- 如果不配置@Lookup注解的value属性,那么默认是按照方法的返回类型去返回对象
- 如果配置了@Lookup注解的value属性,那么将会按照beanName去返回对象
- Spring会通过CGLIB创建代理对象,然后实现或覆盖@Lookup标注的方法,因此@Lookup标注的方法内部是什么逻辑不重要,直接 return null 就行
三、使用场景
原型bean适用于每次需要一个新对象的时候。比如说通过builder模式创建很多对象,但每个对象又有很多相同的配置,那么可以把builder声明为原型bean。每个对象创建时,通过使用注入的builder进行创建,这样既保留了公共的配置,又能进行自定义配置
spring的JacksonAutoConfiguration里就使用了该方式创建ObjectMapper
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
static class JacksonObjectMapperConfiguration {
@Bean
@Primary
@ConditionalOnMissingBean
ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
return builder.createXmlMapper(false).build();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
static class JacksonObjectMapperBuilderConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(ApplicationContext applicationContext,
List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.applicationContext(applicationContext);
customize(builder, customizers);
return builder;
}
private void customize(Jackson2ObjectMapperBuilder builder,
List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
for (Jackson2ObjectMapperBuilderCustomizer customizer : customizers) {
customizer.customize(builder);
}
}
}
通过Jackson2ObjectMapperBuilder,可以创建多个ObjectMapper,这些ObjectMapper可以通过builder做相同的配置
四、参考
https://www.jianshu.com/p/b894edef2966
https://www.cnblogs.com/XiaoZhengYu/p/15732023.html