billydh
billydh

Reputation: 1035

Spring Dependency Injection via @Configuration, @Bean and @Component

I have been struggling to understand fully how the dependency injection works via @Configuration, @Bean and @Component annotations.

My Code is as follows.

1) Route.kt

/* Route.kt */

package com.example.service

import com.example.service.ports.AutoComplete
import com.example.service.ports.Validation
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType.APPLICATION_JSON
import org.springframework.web.reactive.function.server.router

@Configuration
@Suppress("unused")
class Routes(private val autoComplete: AutoComplete,
             private val validation: Validation) {

    @Bean
    fun route() = router {
        ("/service-lookup" and accept(APPLICATION_JSON)).nest {
            GET("/auto-complete/{service}", autoComplete::autoComplete)
            GET("/validation/{service}", validation::validation)
        }
    }
}

2) ServiceImpl.kt

package com.example.service

import com.example.service.ports.AutoComplete
import com.example.service.ports.Validation
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import reactor.core.publisher.Mono

@Component
@Suppress("unused")
class ServiceImpl: AutoComplete, Validation {
    override fun autoComplete(request: ServerRequest): Mono<ServerResponse> {
        TODO("not implemented autoComplete") //To change body of created functions use File | Settings | File Templates.
    }

    override fun validation(request: ServerRequest): Mono<ServerResponse> {
        TODO("not implemented validation") //To change body of created functions use File | Settings | File Templates.
    }
}

3) ports/AutoComplete.kt

package com.example.service.ports

import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import reactor.core.publisher.Mono

interface AutoComplete {

    fun autoComplete(request: ServerRequest): Mono<ServerResponse>

}

4) ports/Validation.kt

package com.example.service.ports

import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import reactor.core.publisher.Mono

interface Validation {

    fun validation(request: ServerRequest): Mono<ServerResponse>

}

My question is, how does the route bean created from Route.kt know that the autoComplete and validation should use the ServiceImpl class from ServiceImpl.kt?

Upvotes: 2

Views: 2075

Answers (2)

Alexander Petrov
Alexander Petrov

Reputation: 9492

I will try to clarify:

@Component - It is tighty bound to the spring auto configuration and component scanning. Everything marked with Component will be picked up as long as you have it in the path of the component scanning defined by @ComponentScanned annotation. The @Components come into three flavours:

A) Repositories - used for persistence

B) Controllers for example RestController

C) Service - a service without state . F.ex a Facade.

This annotation is used to automate the construction of the Application context,as well as to define a stereotype for the bean bound to the context.

@Bean - @Bean and @Component have the same goal, but @Bean is not a @Component. They both build the application context, but they do it in a very different way. While @Component defines a stereotype of the class and tells spring to pick it up. The Bean has full responsibility of manually configuring the instance of what you are creating. The implementation and the configuration are fully separated and you receive a higher degree of control on how exactly the Bean is generated.

@Configuration is used in conjunction with @Bean. @Bean in contrast to @Component is a method level annotation therefore the usual case is that a class is marked with @Configuration and then one ro more methods annotated with @Bean are following.

In your particular example You have create a @Bean router. The router was created based on the autoComplete and validation which were injected in the Routes. Spring was able to figure out what to inject based on the best matching candidate. Since you have a single implementing bean instance of the two interfaces AutoComplete, Validation it injects it. In your case autoComplete and validation will point to the same instance.

Upvotes: 1

Rene
Rene

Reputation: 6148

The following description of the Spring mechanism is simplified for you example:

At startup Spring Boot scans the classpath for all classes annotated with @Configuration, @Component, etc and builds a bean definition list. In your example it finds the Routes and ServiceImpl classes.

After this Spring scans all methods of every class in the bean definition list for further @Bean annotations and adds the method (especially the return type) to the bean definition list. In your example it finds the route method.

After the first scan Spring knows which bean types are present and which bean class implements which interfaces. And Spring knows which constructor parameters, Inject targets or method parameters are required to create an instance of the bean. With this information Spring instantiates the beans in the correct order. In your example Spring knows that Router requires AutoComplete and Validation and that ServiceImpl implements these interfaces. Therefore it has to instantiate ServiceImpl first and Router later.

If more then one bean implements the same interface Spring throws an exception and you have to further qualify the bean.

Upvotes: 1

Related Questions