Spring Annotations: Part 1

Janani Subbiah
5 min readNov 10, 2020
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

Class level annotation indicating to Spring’s inversion of control container that the class carrying that annotation is a source of beans (dependencies required by other classes in the project).

@Bean

Method/Function level annotation indicating that the method/function creates and returns a bean that can be used as a dependency by other classes in the project. There are two types of dependency injection that Spring supports: by type (Default) and by name. By default Spring will create and expose beans for dependency injection based on their type. For example: If there exists an interface A and a class that implements A called AImpl, consider the following configuration file:

@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

This can be a field level or a constructor level annotation that the consumer uses to indicate the dependencies it requires. Resusing the example from the previous section, lets say there is another class B that requires class A

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

Class level annotation that indicates to Spring’s inversion of control container that the class that carries this annotation will need to be exposed as a bean and injected into consumers that have indicated they need it. In the above example if we wanted to create an instance of class C for dependency injection, then we would annotate it with the @Component annotation:

@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

Class level annotation that indicates to Spring’s inversion of control container that the class that carries this annotation will need to be exposed as a bean and injected into consumers that have indicated they need it (similar to @Component in functionality but the @Service annotation is used for classes that act as service classes containing business logic while the @Component is more useful for other generic beans).

@RestController/@Controller

Class level annotation that indicates to Spring’s inversion of control container that the class that carries this annotation will need to be exposed as a controller bean and injected into consumers that have indicated they need it. Controllers that carry this annotation provide support for specifying controller specific things like the path, the HTTP verb, request body, query parameter, path parameter, accepted content types etc. Here is a sample 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!

--

--

Janani Subbiah

Product Architect | Ice cream lover | Newbie gardener