Autowire all the implementations of an interface in Springboot

Vinay Mahamuni
3 min readJun 21, 2020

--

Dependency Injection has eased developer’s life. Earlier, we use to write factory methods to get objects of services and repositories. Now With help of Spring boot and Autowired annotation, we can inject dependency in any classes easily.

So, we had a requirement where we were taking customer data from external system and validate the fields before using the data further.

Here is the customer data class which we need to validate

data class Customer(
val fullName: String,
val dateOfBirth: String,
val pan: String,
val passport: String
)

One way to validate is write validate method containing all the validations on customer field

fun validate(customer: Customer): Boolean {
if (customer.dateOfBirth.isEmpty()) {
throw Exception("Date Of Birth is invalid")
}
if (customer.fullName.isEmpty()) {
throw Exception("Name is empty")
}
if (customer.pan.length != 10) {
throw Exception("pan invalid. length should be exact 10")
}

return true
}

Of course above example is the easy one. In actual there were more complex validations for the fields and also number of fields were almost 8 to 10.

this is bad code!!

I have written all the validations in one method. It makes the code hard to test, the code is hard to understand in one go, method is long/bulky and the code is not modular.

How to make it better?

Well, you can extract out field specific validations into different classes with common interface as CustomerValidator

interface CustomerValidator {
fun validate(customer: Customer): Boolean
}
-------------------------------------------------------------------class DOBValidator : CustomerValidator {
override fun validate(customer: Customer): Boolean {
if (customer.dateOfBirth.isEmpty()) {
throw Exception("Date Of Birth is invalid")
}
return true
}
}
-------------------------------------------------------------------class NameValidator : CustomerValidator {
override fun validate(customer: Customer): Boolean {
if (customer.fullName.isEmpty()) {
throw Exception("Name is empty")
}
return true
}
}
-------------------------------------------------------------------class PanValidator : CustomerValidator {
override fun validate(customer: Customer): Boolean {
if (customer.pan.length != 10) {
throw Exception("pan invalid. length should be exact 10")
}
return true
}
}

Now create list of objects of this validators and use it.

@Component
class ValidateCustomerService() {
val validators: List<CustomerValidator> = listOf(
DOBValidator(),
PanValidator(),
NameValidator()
)

fun validateCustomer(customer: Customer): Boolean {
validators.forEach { it.validate(customer)

return true
}
}

Now you have achieved modularity. Your validations are in separate classes. you can test each class separately. But still you have to write a code to create object of the validator class. Plus you can’t have perfect unit tests for validateCustomer method, as you are using actual objects of validator. Why would you want to test validators functionality again here when you already have tested it separately for each class, right?

So how to get rid of it???

This is where @Autowired get into the picture. If you are using this annotation to inject list of validators, you no longer need to create objects of validators, Springboot will do it for you.

@Component
class ValidateCustomerService(
@Autowired val validators: List<CustomerValidator>
) {

fun validateCustomer(customer: Customer): Boolean {
validators.forEach { it.validate(customer) }

return true
}

}

Also, you will have to add @Component annotation on each CustomerValidator implementation class.

This is very powerful. You don’t even need to write a bean to provide object for this injection. It automatically detects all the implementations of CustomerValidator interface and injects it in the service.

Above code is easy to read, small and testable. Best thing is that you don’t even need to make changes in service to add new validation. Just extend CustomerValidator and its done.

Here is the github link to check whole code and tests

--

--