Spring Annotations: Part 1

Photo by Kimberly Farmer on Unsplash

Spring uses a good chunk of annotations for the purposes of dependency injection. For someone who is new to the Spring eco system, it can get quickly overwhelming because not only are there a good number of annotations to understand, but also several ways to achieve the same thing by using different annotation or a different combination of annotations! So this post is aimed at going over a few basic annotations that are sure to come in handy as you get started with your first Spring application. There are several more annotations that are created and leveraged by the Spring framework, but this post only covers the very common ones (in Kotlin!).

@Configuration

@Bean

@Configuration
class SampleConfiguration {
@Bean
fun A(): A = AImpl()
}

Here we have defined a function A() annotated with the @Bean annotation that is responsible for creating and returning a new instance of AImpl (lets also assume AImple has a default constructor). One thing to note here is that the name of the function does not follow convention in that it does not start with a verb, instead it is called A. The reason for this is to ensure that the bean created by this method will be exposed under the name A. Spring, by default uses the name of the method as the name of the bean the method creates and returns. Lets say we have a scenario where we would like to expose two implementations of the same interface as beans. Lets continue to call our interface A and let us call our implementations AImpl1 and AImpl2.

@Configuration
class SampleConfiguration {
@Bean
fun A(): A = AImpl1()

@Bean
fun A(): A = AImpl2()
}

The above code will throw an error because we are trying to create two beans of the same type A and if the consumer of A needs A to be injected in, Spring has no way of knowing whether to inject AImpl1 or AImpl2. A workaround is to specify different bean names for these implementations so that Spring can inject these dependencies by name and not by type. But this should not be done unless the application actually requires multiple beans of the same type to be created by Spring. This can be achieved by the following code:

@Configuration
class SampleConfiguration {

@Bean(name = "AImpl1")
fun A(): A = AImpl1()

@Bean(name = "AImpl2")
fun A(): A = AImpl2()
}

The above code will ensure that both the implementations of interface A are created and exposed as beans for dependency injection. Spring also provides bean scopes out of the box.

@Autowired

class B {    @Autowired
private lateinit var a: A
}

and if wanted to autowire a dependency by name we would do the following:

class B {    @Autowired
@Qualifier("AImpl1")
private lateinit var a: A
}

The lateinit key word comes from Kotlin to indicate that the field that carries that annotation will be initialized at a later time and not when the class is initialized. The Autowired annotation in the above example indicated that class B requires an instance of A. The Autowired annotation is used to inject dependencies using setter injection and it is a good idea to avoid setter injection in favor of constructor injection mostly because constructor injection forces users of a class to provide all dependencies required by the class at the time of creation and it also prevents accidental changes to fields by another classes. So if we were to use constructor injection our updated code would look as follows:

class B (private val a: A) {    // Use field a as required.
}

Another scenario where we use the Autowired annotation is to indicate which constructor of a given class is to be used for dependency injection when there are multiple constructors present. For example:

class C {    private lateinit var a: A
private lateinit var b: B
@Autowired
constructor(a: A, b: B) {
this.a = a
this.b = b
}
constructor(a: A) {
this.a = a
}
}

In the above example the Autowired annotation on the first constructor indicates to Spring that that is the constructor that should be used to create an instance of class C for dependency injection (which actually indicates that beans of type A and B should be available to be able to create C).

@Component

@Component
class C {
private lateinit var a: A
private lateinit var b: B
@Autowired
constructor(a: A, b: B) {
this.a = a
this.b = b
}
constructor(a: A) {
this.a = a
}
}

@Service

@RestController/@Controller

class SampleController {    @GetMapping("/hello")
fun hello() = "Hello"
}

And that’s it! I hope this information come in handy as you start your journey with Spring. As mentioned earlier there are several other annotations that are provided by Spring which make development SO much more easy and fun including annotations to convert user provided properties into easy-to-work-with objects, conditional bean creation etc which have not been covered here but I hope to have a new post about them soon!

Product Architect | Ice cream lover | Newbie gardener