English 中文(简体)
How to resolve multi-module classpath beans in Quarkus?
原标题:
  • 时间:2020-12-18 07:29:57
  •  标签:
  • cdi
  • quarkus

In Spring it s possible to define bean dependencies in separate modules, which are then resolved via the classpath at runtime. Is it possible to do something similar in Quarkus?

For example, a multi-module setup that looks like this:

- service
- service-test
- service-artifact

In Spring it s possible to define @Configuration in the service module, that resolves concrete dependencies at runtime via the classpath of its current context, either service-test or service-artifact, allowing injection of dummy or test dependencies when under test, and real ones in the production artifact.

For example, a class in service requires an instance of SomeInterface. The implementation of SomeInterface is defined in either the -test or -artifact module. The service module has no direct dependency on either the -test or -artifact modules.

Some code:

In the service module:

@ApplicationScoped
class OrderService(private val repository: OrderRepository) {
    fun process(order: Order) {
        repository.save(order)
    }
}

interface OrderRepository {
    fun save(order: Order)
}

In the service-test module:

class InMemoryOrderRepository : OrderRepository {
    val orders = mutableListOf<Order>()
    override fun save(order: Order) {
        orders.add(order)
    }
}

class OrderServiceTestConfig {
    @ApplicationScoped
    fun orderRepository(): OrderRepository {
        return InMemoryOrderRepository()
    }
}


@QuarkusTest
class OrderServiceTest {

    @Inject
    private lateinit var service: OrderService

    @Test
    fun `injected order service with resolved repository dependency`() {
        // This builds and runs OK            
        service.process(Order("some_test_order"))
    }
}

Where I have tried to replicate a Spring-style setup as above in Quarkus, ArC validation is failing with UnsatisfiedResolutionException on the build of the service module, even though everywhere it is actually consumed provides the correct dependencies; a test successfully resolves the dependency and passes.

How do I achieve the separation of dependency interface from the implementation, and keep ArC validation happy, with Quarkus?

(Note: this behaviour occurs with Java and Maven also.)

I have included a maven example here. Note that ./mvnw install fails with the UnsatisfiedResolutionException but that it s possible to build and run the test successfully using ./mvnw test.

Build files:

root project build.gradle.kts:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

    plugins {
        kotlin("jvm") version "1.3.72"
        kotlin("plugin.allopen") version "1.3.72"
    }

allprojects {

    group = "my-group"
    version = "1.0.0-SNAPSHOT"

    repositories {
        mavenLocal()
        mavenCentral()
    }
}

subprojects {

    apply {
        plugin("kotlin")
        plugin("kotlin-allopen")
    }

    java {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    
    allOpen {
        annotation("javax.ws.rs.Path")
        annotation("javax.enterprise.context.ApplicationScoped")
        annotation("io.quarkus.test.junit.QuarkusTest")
    }

    apply {
        plugin("kotlin")
    }

    dependencies {
        implementation("org.jetbrains.kotlin:kotlin-reflect")
        implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    }

    tasks.withType<KotlinCompile> {
        kotlinOptions.jvmTarget = JavaVersion.VERSION_11.toString()
        kotlinOptions.javaParameters = true
    }
}

build.gradle.kts for service:

import io.quarkus.gradle.tasks.QuarkusDev

plugins {
    id("io.quarkus") version "1.9.1.Final"
}

apply {
    plugin("io.quarkus")
}

dependencies {
    implementation(project(":common:model"))
    implementation(enforcedPlatform("io.quarkus:quarkus-universe-bom:1.9.1.Final"))
    implementation("io.quarkus:quarkus-kotlin")
}

build.gradle.kts for service-test:

import io.quarkus.gradle.tasks.QuarkusDev

plugins {
    id("io.quarkus") version "1.9.1.Final"
}

apply {
    plugin("io.quarkus")
}

dependencies {
    implementation(project(":service"))
    implementation(enforcedPlatform("io.quarkus:quarkus-universe-bom:1.9.1.Final"))
    implementation("io.quarkus:quarkus-kotlin")
    testImplementation("io.quarkus:quarkus-junit5")
}
问题回答

Try to use instance injection (java example):

import javax.enterprise.inject.Instance;
...
@Inject
Instance<MyBeanClass> bean;
...
bean.get(); // for a single bean
bean.stream(); // for a collection

Unfortunately, Quarkus has a bit different way of creating and injecting beans as in Spring.

It s using "simplified bean discovery", and that means that the beans are scanned on the classpath during the build time, but only those that have annotations considered as "discovery mode", are taken into the account.

Those would be: @ApplicationScoped, @SessionScoped, @ConversationScoped and @RequestScoped, @Interceptor and @Decorator more described here

In addition to that, beans must not have the visibility boundaries.

In you d like to use beans from other modules, create a configuration class within that module. The configuration class should not have any annotation. In that config class, create beans with @Producer annotation and one of the above scope beans. Example in Kotlin:

class QuarkusConfig {
    @Producer
    @ApplicationScope
    fun myClass(myClassDependency: DependencyClass): MyClass {
        return MyClass(myClassDependency)
    }
}

But notice, despite that, some beans are treated by Quarkus in a special way (ex. all beans that have @Path annotation) and those should be annotated preferably with @ApplicationScope, using either constructor, or field injection. Produced by @Producer methods won t allow for all the magic that Quarkus is doing.

If you d like some more quarkus dependant beans, ex. the bean that bounds a configuration (using @ConfigMapping annotated beans), in addition you need to have either beans.xml in your META-INF directory, or which seems easier to add the jandex index to your build system:

plugins {
  id("io.quarkus") version "2.14.1.Final"
  id("org.kordamp.gradle.jandex") version "1.0.0"
}

Summary: don t use configuration beans as in spring, only the constructor/field injection, and to have beans discovered from different modules, add the jandex index file using plugin.

Add the descriptor file META-INF/beans.xml to the resources folder of any module where beans should be discovered.

enter image description here

Ref: https://quarkus.io/guides/cdi-reference#bean_discovery





相关问题
EJB 玻璃鱼网应用中的注射

我用“EJB”通知,在我的ejb.jar档案中用远距离提及EJBs。 结果是不一致的。 在1起案件中,我有一位听众在网上获得......。

Google Guice vs. JSR-299 CDI / Weld

Weld, the JSR-299 Contexts and Dependency Injection reference implementation, considers itself as a kind of successor of Spring and Guice. CDI was influenced by a number of existing Java ...

Java EE without Application Server

Since EJB 3 we have embeddable EJB containers, JPA implementations can be used without an application server, there is Weld for contexts and dependency injection and so on. Since on many systems is ...

How to test a DAO with JPA implementation?

I came from the Spring camp , I don t want to use Spring , and am migrating to JavaEE6 , But I have problem testing DAO + JPA , here is my simplified sample : public interface PersonDao { public ...

How to inject a Session Bean into a Message Driven Bean?

I m reasonably new to Java EE, so this might be stupid.. bear with me pls :D I would like to inject a stateless session bean into a message-driven bean. Basically, the MDB gets a JMS message, then ...

how to instantiate more than one CDI/Weld bean for one class?

In Spring it was possible to instantiate any class by defining the corresponding bean in xml conf. It was also possible to instantiate more then one bean for the same class with different parameters.....

热门标签