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

posted @ 2023-05-17 21:19  wusanga  阅读(1384)  评论(0编辑  收藏  举报