Spring筆記--@ConditionalOnBean坑


@ConditionalOnBean 巨坑

場景:SpringBoot 引入 redis-starter , 加載 RabbitAutoConfiguration ,進而存在 StringRedisTemplate 。也可能排除掉 RabbitAutoConfiguration 。 自動義Bean,依賴 StringRedisTemplate。

字面理解太單純,實際執行太復雜。
實際是: 執行到該注解時,如果已經存在某個類型的Bean,才創建當前Bean。否則不創建當前Bean
問題是: 在執行到該注解時,依賴的Bean還沒有創建(它實際是要創建的,只是我們無法控制它的順序), 它的順序無法提前,無法被感知。

核心解決的問題: 1. 手動創建Bean。 2. 選擇合適的時機創建Bean。

手動創建Bean

val registry: BeanDefinitionRegistry
	get() {
		if (registryField == null) {
			throw RuntimeException("需要@Import(SpringUtil::class)")
		}
		return registryField!!
	}
/**
* 動態注冊Bean
*/
@JvmStatic
inline fun <reified T> registerBeanDefinition(
	name: String,
	instance: T,
	callback: ((BeanDefinitionBuilder) -> Unit) = {}
) {
	registry.registerBeanDefinition(name, getGenericBeanDefinition(instance, callback));
}

/**
 * 動態創建Bean
 */
inline fun <reified T> getGenericBeanDefinition(
	instance: T,
	callback: ((BeanDefinitionBuilder) -> Unit) = {}
): GenericBeanDefinition {
	val builder = BeanDefinitionBuilder.genericBeanDefinition(T::class.java);
	callback(builder);

	val definition = builder.rawBeanDefinition as GenericBeanDefinition;
	definition.autowireMode = GenericBeanDefinition.AUTOWIRE_BY_TYPE
	definition.instanceSupplier = Supplier { instance }
	return definition;
}

時機1,利用 BeanPostProcessor

在初始化 StringRedisTemplate 時注冊。

@Component
@Import(SpringUtil::class)
class StringRedisTemplateBeanProcessor : BeanPostProcessor {
    override fun postProcessAfterInitialization(bean: Any, beanName: String): Any? {
        if (bean.javaClass == StringRedisTemplate::class.java) {
            var stringRedisTemplate = bean as StringRedisTemplate
            stringRedisTemplate.hashValueSerializer = RedisSerializer.json()


            SpringUtil.registerBeanDefinition("redisCacheDbDynamicService", RedisCacheDbDynamicService())
            SpringUtil.registerBeanDefinition("redisRenewalDynamicService", RedisRenewalDynamicService())
        }
        return super.postProcessAfterInitialization(bean, beanName)
    }
}

時機2,利用 ApplicationPreparedEvent 事件

類似邏輯,在所有Bean准備完之后,判斷是否有依賴的Bean,再注冊。

@Component
@Import(SpringUtil::class)
@ConditionalOnClass(MysqlDataSource::class)
class MySqlDataSourceConfig {
    companion object {
        @JvmStatic
        val hasSlave: Boolean
            get() {
                return SpringUtil.containsBean("slave", DataSource::class.java);
            }
    }


    @EventListener
    fun prepared(ev: ApplicationPreparedEvent) {
        if (SpringUtil.context.environment.getProperty("spring.datasource.url").isNullOrEmpty() &&
            SpringUtil.context.environment.getProperty("spring.datasource.hikari.jdbc-url").isNullOrEmpty()
        ) {
            return;
        }
        if (SpringUtil.containsBean(DataSourceAutoConfiguration::class.java) == false) {
            return;
        }


        SpringUtil.beanFactory.getBeanDefinition("dataSource").isPrimary = true;
        SpringUtil.beanFactory.getBeanDefinition("jdbcTemplate").isPrimary = true;


        var slaveDataProperties =
            SpringUtil.binder.bindOrCreate("spring.datasource-slave", DataSourceProperties::class.java);
        if (slaveDataProperties.url.HasValue) {
            var dataSourceSlave = slaveDataProperties.getDataSource()
            SpringUtil.registerBeanDefinition("slaveDataSource", dataSourceSlave)
            SpringUtil.registerBeanDefinition("slaveJdbcTemplate", JdbcTemplate(dataSourceSlave, true))
        }
    }


    private fun DataSourceProperties.getDataSource(): HikariDataSource {
        return this.initializeDataSourceBuilder().type(HikariDataSource::class.java)
            .build() as HikariDataSource
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM