Spring Annotations: Core Framework Annotations

Introduction

The Spring Framework is a very robust framework, released in 2002. Its core features can be applied to plain Java applications or extended to complex, modern web applications.

As it's constantly being updated and is following new architectural and programming paradigms, it offers support for many other frameworks that work hand-in-hand with it.

With such a vast array of functionalities, it's only normal that it introduces us to some new annotations, which are a key part of developing Spring applications.

Spring's configuration is fully customizable, which was originally done through XML configuration files. However, this approach has become outdated, and most people nowadays resort to annotation configuration.

That being said, this series of articles aims to unravel the options you as a developer have to configure and use the Spring framework:

Note: This article assumes you're familiar with the Spring Inversion of Control principle.

Core Annotations

Let's take a look at the core annotations that make up almost all Spring applications:

@Bean

A @Bean is a backbone object in the Spring Framework. It all comes down back to JavaBeans - classes that encapsulate objects into a single one. They are a type of POJO (Plain Old Java Object).

All JavaBeans must be Serializable, all fields should be private, all fields must have setters and getters, there should be a no-arg constructor, and fields are accessed exclusively by the constructor or the getter/setter methods:

public class Developer implements java.io.Serializable {
   private int id;
   private String name;

   public Developer() {}
   public void setId(int id) {this.id = id;}
   public int getId() {return id;}
   public void setName(String name) {this.name = name;}
   public String getName() {return name;}
}

In terms of Spring - beans are instantiated and managed by the Spring IoC Container. They are simply object instances managed by Spring.

In order to let Spring know which object instances it should manage, we simply mark the methods in which we instantiate them with the @Bean annotation.

When this method is encountered, it'll be executed and the returned value will be saved within a BeanFactory:

@Configuration
public class ConfigurationClass {
    @Bean
    public Developer developer() {
        return new Developer();  
    }
}

This is the same as using the old XML approach to register a bean:

<beans>
    <bean name="developer" class="com.stackabuse.Developer"/>
</beans>

Now, to inject this bean as a dependency into another bean, we simply have another bean call the developer bean's method:

@Configuration
public class ConfigurationClass() {
    
    @Bean
    public Manager manager() {
        return new Manager(developer());
    }

    @Bean
    public Developer developer() {
        return new Developer();  
    }
}

@Required

The @Required annotation is used on setter methods and constructors. As the name suggests, it tells Spring that these fields are required in order for the bean to initialize properly.

If the fields aren't populated at the time of configuration, the bean will fail to initialize, resulting in an exception and the application will fail to build:

public class Developer implements java.io.Serializable {
    private int id;
    private String name;

    public Developer() {}
   
    @Required
    public void setId(int id) {
        this.id = id;
    }
   
    public int getId() {
        return id;
    }
   
    @Required
    public void setName(String name) {
        this.name = name;
    }
   
    public String getName() {
        return name;
    }
}

To populate a field on configuration time like this, we assign the property names through XML:

<bean class="com.stackabuse.Develope>
    <property name="name" value="David"/>
</bean>

@Autowired

The @Autowired annotation is used for further control over dependency injection. It's used to wire a bean to another one without instantiating the former one.

Again, instead of wiring dependencies through XML, which was cumbersome, we simply mark our dependencies as @Autowired. Based on our base class, where all of our components are located, Spring does all of the wiring for us.

To declare the base package of our components, we can simply add a tag to our application context file:

<context:component-scan base-package="com.stackabuse.basePackage"/>

All @Component tagged classes (including the derivatives such as @Service, @Controller, and @Repository) will be registered as beans are eligible for autowiring.

@Autowired on Properties

Instead of the explicit, imperative instantiation:

public class ProductController {
    private ProductService productService = new ProductService();

    public void someMethod() {
        List<Product> productList = productService.getProductList();
    }
}

We use a declarative approach:

public class ProductController {

    @Autowired
    private ProductService productService;

    public void someMethod() {
        List<Product> productList = productService.getProductList();
    }
}

In this implementation, we never really instantiate the ProductService class, decoupling it from the ProductController if we wish to test it.

Of course, to autowire a field, it needs to be registered as a bean in the Spring IoC Container. In our case, it's a @Service annotated bean, but more on that later.

There are also other use-cases for the @Autowired annotation.

@Autowired on Setters

Very similar to the @Required annotation, we can also use @Autowired on setters:

public class ProductController {

    private ProductService productService;

    @Autowired
    public void setProductService(ProductService productService) {
        this.productService = productService;
    }
}

By autowiring a setter like this, there's no need to populate it through XML.

This is the so-called setter-based dependency injection.

@Autowired on Constructors

The @Autowired annotation can also be used on constructors:

public class ProductService {

    private ProductDao productDao;
    
    @Autowired
    public ProductService(ProductDao productDao) {
        this.productDao = productDao;
    }
}

This is the so-called constructor-based dependency injection.

The required Flag

By marking a bean as @Autowired, Spring expects it to be available when constructing the other dependencies. If not, we'll be greeted with an exception and a failed build.

If you can't guarantee that the bean will be available, or if it's not always needed, you can use the required flag to mark it as optional:

public class ProductController {

    @Autowired(required = false)
    private ProductService productService;
}

This way, if the product service bean isn't available, everything will run smoothly.

@Qualifier

The @Qualifier annotation is used to clear up cases where we'd like to autowire more than one bean of the same type.

For an example, in a company, we'll most likely have more than one employee, and each employee has their respective position - developer, lead developer, manager, CEO, etc...

@Component
public class Developer implements Employee {}

@Component
public class Manager implements Employee {}

If we were to autowire an employee, it would be ambiguous as to which bean we want to autowire:

@Controller
public class CompanyController {
    @Autowired
    private Employee employee;
}

We'd be greeted with an error:

org.springframework.beans.factory.NoSuchBeanDefinitionException: 
    No unique bean of type [com.stackabuse.employee] is defined: 
        expected single matching bean but found 2: [developer, manager]

To avoid such a situation, we add qualifiers:

@Component
@Qualifier("developer")
public class Developer implements Employee {}

@Component
@Qualifier("manager")
public class Manager implements Employee {}

And when autowiring:

@Controller
public class CompanyController {
    @Autowired
    @Qualifier("developer")
    private Employee employee;
}

This clears up as to which bean we'd like to autowire and the code runs just fine.

@ComponentScan

A crucial annotation for Spring is the @ComponentScan annotation. It specifies which packages contain classes that are annotated. That way, Spring knows which classes it needs to manage and it's always used alongside the @Configuration annotation.

For example, we have a com.stackabuse.controller package that contains all of our controllers where each class is annotated with @Controller. In order for Spring to know that this package contains components that need management, we use the @ComponentScan annotation and add the package.

Otherwise, we'd have to register every single bean individually, which would be cumbersome and impossible to scale.

In a lot of cases, we simply define a single basePackage that contains all of our components, such as com.stackabuse. Though in some cases we'd want to include multiple basePackages or basePackageClasses:

@Configuration
@ComponentScan(basePackage = "com.stackabuse")
public class SomeApplication {
    // some code
}

If we'd like to define multiple base packages:

@Configuration
@ComponentScan(basePackage = {"com.package1", "com.package2})
public class SomeApplication {
    // some code
}
Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

A type-safe alternative for basePackages is basePackageClasses:

@Configuration
@ComponentScan(basePackageClasses =  Developer.class) 
public class SomeApplication {
    // some code
}

Note: If no base package is defined at all, the package that the class is located in will be used as the base package.

@Lazy

By default, beans and components get initialized eagerly. If we'd like to change that behavior, we're free to do so using the @Lazy annotation.

It can either be used on a class-level that's annotated as a @Component or on a method-level that's annotated as a @Bean.

If annotated, the component/bean will not be initialized until another bean explicitly references it and it's needed for the application to run smoothly:

@Lazy
@Bean
class SomeResource {}

We could also mark a @Configuration class as @Lazy:

@Lazy
@Configuration
public class AppConfig {
    // some code
}

In this case, all of the beans defined within AppConfig will also be lazily initialized.

@Configuration

The @Configuration annotation is on a class-level and tells Spring that this class contains one or more @Bean methods and may be processed by the Spring container to generate bean definitions.

This is one of the reasons why developers were able to stop using XML-based configuration and the simplicity of the annotation makes Java-based configuration preferable.

@Configuration
public class AppConfig {
     @Bean
     public SomeBean someBean() {
         // Instantiation, configuration, returning the bean
}

@Value

The @Value annotation has quite a few use-cases in Spring and warrants an article for itself. I'll try to be brief and cover the most common and obvious use cases in this one.

It can be used for:

  • Assigning default values to fields
  • Reading environment variables
  • Using Spring Expression Language (SpEL) Expressions
  • Default values for parameters if used within a method/constructor

That being said, let's go over all of these use-cases one by one.

Default Field Values

If you'd like to assign a default value to a field, the @Value annotation is pretty straightforward:

@Value("Hello World!")
private String helloString;

Even though we didn't instantiate this String nor assign it a value explicitly, we've done so through the annotation.

The @Value annotation is meant to be used with Strings. If you try to apply it to another type, it'll work only if Spring can easily convert between the two - such as booleans and ints:

@Value("true")
private boolean accepted;

@Value("53")
private int userId;

Reading Environment Properties

Let's say that amongst other properties, our application.properties file contains some environment variables:

sa.website_name = Stack Abuse

For an example, let's read this property and assign it to a String in our configuration class. To do this, we need to define the property source as well:

@PropertySource("classpath:application.properties")
@Configuration
public class AppConfig {
    @Value("${sa.website_name}")
    private String websiteName;
}

Generally speaking, the ${...} is used as a property placeholder in Spring. You're probably already familiar with this if you've dabbled in Spring technologies.

If the property isn't available or defined, we could run into a problem. In this case, we can define default values for placeholders in case they're not properly defined:

@PropertySource("classpath:application.properties")
@Configuration
public class AppConfig {
    @Value("${sa.website_name}:Backup Value")
    private String websiteName;
}

This way, if sa.website_name doesn't exist, the value assigned to the String will be Backup Value.

Using SpEL

Similar to the placeholder syntax, Spring Expression Language (SpEL) uses the #{...} syntax for storing expressions:

@Value("#{systemProperties['java.home']}")
private String someValue;

If we decide to throw in some properties that might not be available, then again we'd be in a problem. To avoid such cases, we can also define default "backup" values for SpELs:

@Value("#{systemProperties['unknownproperty'] ?: 'Backup Value'}")
private String someValue;

Default Parameter Values

If applied to a method, the @Value annotation will assign the default value to all parameters of the method:

@Value("Hello")
public String hello(String str1, String str2) {
    return str1 + str2;
}

This method would print:

HelloHello

On the other hand, if we apply the @Value method to both a method and a parameter, the parameter will be assigned the new value:

@Value("Hello")
public String hello(String str1, @Value("World") String str2) {
    return str1 + str2;
}

The output in this case would be:

HelloWorld

@DependsOn

If a bean depends on some other beans for correct instantiation, Spring can guarantee that all the beans it depends on will be created before it. However, we need to specify which ones using the @DependsOn annotation.

The annotation accepts an array of Strings which correspond to the names of the beans in question. This means that you can pass any valid bean name as the argument, as long as it's properly annotated with a @Component or @Bean annotation.

@Configuration
public class AppConfig {
    @Bean("firstBean")
    @DependsOn(value = {"secondBean", "thirdBean"})
    public FirstBean firstBean() {
        return new FirstBean();
    }
    
    @Bean("secondBean")
    public SecondBean secondBean() {
        return new SecondBean();
    }
    
    @Bean("thirdBean")
    public ThirdBean thirdBean() {
        return new ThirdBean();
    }
}

Even though FirstBean is located before the second and third one, we've annotated that it depends on the creation of the SecondBean and ThirdBean to work properly. By doing this, Spring will first define those two and then FirstBean.

@Primary

The @Primary annotation is often used alongside the Qualifier annotation. It's used to define the "default" bean for autowiring when no further information is available.

It gives precedence to the annotated bean, if there are more than one beans of the same type, as the name implies:

@Component
@Qualifier("developer")
@Primary
public class Developer implements Employee {}

@Component
@Qualifier("manager")
public class Manager implements Employee {}

This is the same problem we encountered in the earlier part of the article where we defined a qualifier to allow the @Autowired annotation to choose between the qualified beans.

However, this time, we don't need to add the @Qualifier annotation to the @Autowired annotation as the primary/default bean has been declared:

@Controller
public class CompanyController {
    @Autowired
    private Employee employee;
}

This will instantiate a Developer bean.

@Scope

The @Scope annotation is applied on bean-level and defines its visibility/life cycle. If applied alongside the @Component annotation, it defines the scope for the instances of the annotated type. If used on a @Bean method, the scope applies to the returned instance.

There are two basic scopes, with another four for web-aware applications:

  • singleton
  • prototype
  • request
  • session
  • application
  • websocket

Singleton Scope

If no other scope name is used, the default value is singleton. A singleton scope warrants only one instance of the annotated method's returned instances. The object will be saved in the Spring container and cached allowing it to be used anywhere from the application:

@Bean
@Scope("singleton")
public CompanyCEO companyCEO() {
    return new CompanyCEO();
}

Prototype

The opposite of the singleton scope, applying the prototype scope warrants a new instance of the annotated bean every single time we request it.

@Bean
@Scope("prototype")
public Developer developer() {
    return new Developer();  
}

Request

The request scope warrants the instantiation of a single bean for each HTTP request:

// This method will be called on every HTTP request
@Bean
@Scope("request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public SetupClass someSetup() {
    // Run some setup on each http request
}

An alternative would be to use the 4.3 annotation @RequestScope which includes the proxy by default.

Session

Very similar to the request scope, the session scope will instantiate the annotated bean with a lifecycle-dependent of the HTTP session.

// This method will be called on every HTTP session
@Bean
@Scope("session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public SetupClass someSetup() {
    // Run some setup on each http session
}

Application

The application scope works similarly to the singleton scope. An application scoped bean's life cycle depends on the application, or rather, the ServletContext.

The main difference between those two is the fact that application has a wider scope in the sense that it can expand to other applications running on the same ServletContext.

@Scope("application")
@Component
public class Application {}

Again, as of 4.3, you can replace this annotation with @ApplicationScope.

WebSocket

If we use the websocket scope, we tie our bean's life cycle to the life cycle of the WebSocket's session.

The first time it's called, the bean is instantiated and stored for further use within the same session:

// This method will be called on every websocket session
@Bean
@Scope("websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public SetupClass someSetup() {
    // Run some setup on each websocket session
}

Conclusion

The Spring framework is a powerful and robust framework which really changed the game when it comes to developing web-applications. Amongst its myriad of projects, it's a good idea to start with the core framework and build upon that.

The core framework introduces us to various annotations that make our lives easier and more productive. Handling these annotations is a must for every Java/Spring developer.

Last Updated: August 22nd, 2023
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

David LandupAuthor

Entrepreneur, Software and Machine Learning Engineer, with a deep fascination towards the application of Computation and Deep Learning in Life Sciences (Bioinformatics, Drug Discovery, Genomics), Neuroscience (Computational Neuroscience), robotics and BCIs.

Great passion for accessible education and promotion of reason, science, humanism, and progress.

Make Clarity from Data - Quickly Learn Data Visualization with Python

Learn the landscape of Data Visualization tools in Python - work with Seaborn, Plotly, and Bokeh, and excel in Matplotlib!

From simple plot types to ridge plots, surface plots and spectrograms - understand your data and learn to draw conclusions from it.

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms