spring-projects-experimental / spring-fu

Configuration DSLs for Spring Boot
Apache License 2.0
1.67k stars 139 forks source link

I would like to contribute code that supports R2DBC Repositories. #361

Open wickedev opened 3 years ago

wickedev commented 3 years ago

After several weeks of digging, I found the code that worked. I would like to contribute based on this code. originally, I was use the registerBean method, but the compiler could not find a proper overriding method because the compile time type of beanClass was not clear, so I copied the ClassDerivedBeanDefinition code and called registerBeanDefinition. there anything I need to know?

val app = reactiveWebApplication {
    dataR2dbc {
        enableR2dbcRepositories()
    }
    reactiveMongodb {
        enableMongoRepositories()
    }
}

fun DataR2dbcDsl.enableR2dbcRepositories() {
    context.registerBean(R2dbcRepositoryFactory::class.java, Supplier {
        val operations = context.getBean(R2dbcEntityOperations::class.java)
        R2dbcRepositoryFactory(operations)
    })

    registerReactiveRepositoryFactoryTo(context)
}

fun ReactiveMongoDsl.enableMongoRepositories() {
    context.registerBean(ReactiveMongoRepositoryFactory::class.java, Supplier {
        val operations = context.getBean(ReactiveMongoOperations::class.java)
        ReactiveMongoRepositoryFactory(operations)
    })

    registerReactiveRepositoryFactoryTo(context)
}

class RepositoryBeanInfo(
    val beanName: String,
    val beanClass: Class<*>
)

fun registerReactiveRepositoryFactoryTo(context: GenericApplicationContext) {
    context.addBeanFactoryPostProcessor { beanFactory ->
        val repoBeans = mutableListOf<RepositoryBeanInfo>()

        beanFactory.beanNamesIterator.forEach { beanName ->
            val type = beanFactory.getType(beanName)
            if (type != null && ReactiveCrudRepository::class.java.isAssignableFrom(type)) {
                val beanClass = beanFactory.getType(beanName, true)
                beanClass?.let { repoBeans.add(RepositoryBeanInfo(beanName, it)) }
            }
        }

        repoBeans.forEach { info ->
            val beanName = info.beanName
            val beanClass = info.beanClass
            val prevBD = context.getBeanDefinition(beanName)
            val beanDefinition = ClassDerivedBeanDefinition(beanClass).apply {
                instanceSupplier = Supplier {
                    val factory = context.getBean(ReactiveRepositoryFactorySupport::class.java)
                    factory.getRepository(beanClass)
                }

                BeanDefinitionCustomizer { bd ->
                    bd.scope = prevBD.scope
                    bd.isLazyInit = prevBD.isLazyInit
                    bd.isPrimary = prevBD.isPrimary
                    bd.isAutowireCandidate = prevBD.isAutowireCandidate
                    bd.initMethodName = prevBD.initMethodName
                    bd.destroyMethodName = prevBD.destroyMethodName
                    bd.description = prevBD.description
                    bd.role = prevBD.role
                }.customize(this)
            }

            context.removeBeanDefinition(beanName)
            context.registerBeanDefinition(beanName, beanDefinition)
        }
    }
}

class ClassDerivedBeanDefinition : RootBeanDefinition {
    constructor(beanClass: Class<*>?) : super(beanClass)
    constructor(original: ClassDerivedBeanDefinition) : super(original)

    override fun getPreferredConstructors(): Array<Constructor<*>>? {
        val clazz = beanClass
        val primaryCtor = BeanUtils.findPrimaryConstructor(clazz)
        if (primaryCtor != null) {
            return arrayOf(primaryCtor)
        }
        val publicCtors = clazz.constructors
        return if (publicCtors.isNotEmpty()) {
            publicCtors
        } else null
    }

    override fun cloneBeanDefinition(): RootBeanDefinition {
        return ClassDerivedBeanDefinition(this)
    }
}