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:
- Spring Framework Annotations: @RequestMapping and its Variants
- Spring Annotations: Core Annotations
- Spring Annotations: Spring Cloud Annotations
- Spring Annotations: Testing Annotations
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
}
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 boolean
s and int
s:
@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.