Spring Boot annotations #2

Sandeep
11 min readOct 22, 2023

--

1. @Lazy

By default, spring creates all singleton beans eagerly at the startup of the application.

The @Lazy annotation indicates that the bean is lazily initialized. It can be used on classes that are annotated with @Component and on methods that are annotated with @Bean.

This annotation can be used on class, field, constructor and method.

If @Lazy is present on @Component or @Bean and set to true then the @Component and @Bean would not be initialized until referenced by another bean or explicitly retrieved from bean factory. It can take true and false values.

package com.spring.annotations.lazy;

import org.springframework.stereotype.Component;

@Component
public class CarEngine {

private String modelNo;

public CarEngine() {
System.out.println("CarEngine constructor invoked");
}

public String getModelNo() {
return modelNo;
}

public void setModelNo(String modelNo) {
this.modelNo = modelNo;
}

@Override
public String toString() {
return "Engine [modelNo=" + modelNo + "]";
}

}

From the screenshot below, it can be seen that constructor of CarEngine class is invoked.

1. @Lazy on @Component class

The @Lazy annotation is placed on CarEngine class.

package com.spring.annotations.lazy;

import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

@Component
@Lazy
public class CarEngine {

private String modelNo;

public CarEngine() {
System.out.println("CarEngine constructor invoked");
}

public String getModelNo() {
return modelNo;
}

public void setModelNo(String modelNo) {
this.modelNo = modelNo;
}

@Override
public String toString() {
return "Engine [modelNo=" + modelNo + "]";
}

}

In the screenshot below, the constructor of CarEngine is not invoked.

2. @Lazy on @Bean method

package com.spring.annotations.lazy;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;

@Configuration
public class AppConfig {

@Bean
public Engine getCarEngine() {
System.out.println("getCarEngine() - invoked");
return new CarEngine();
}

@Bean
@Lazy
public Engine getBikeEngine() {
System.out.println("getBikeEngine() - invoked");
return new BikeEngine();
}

}

The @Lazy is used on getBikeEngine() @Bean method, the object of BikeEngine is not created.

The @Lazy can also be used on classes annotated with @Configuration. If present on @Configuration class then all @Bean methods within @Configuration class are initialized lazily. If @Lazy is present on a @Bean method(value as false) within a @Lazy annotated @Configuration class, this indicates overriding the ‘default lazy’ behavior and that the bean should be eagerly initialized.

In the class below, @Lazy is placed on the @Configuration class.

package com.spring.annotations.lazy;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;

@Configuration
@Lazy
public class AppConfig {

@Bean
public Engine getCarEngine() {
System.out.println("getCarEngine() - invoked");
return new CarEngine();
}

@Bean
public Engine getBikeEngine() {
System.out.println("getBikeEngine() - invoked");
return new BikeEngine();
}

}

The methods annotated with @Bean are not invoked by IOC container.

This annotation can also be placed on fields annotated with @Autowired. If a field is annotated with @Lazy then dependency is not injected at the time of bean creation.

package com.spring.annotations.lazy;

public interface Engine {

public String getModelNo();

public void setModelNo(String modelNo);
}

The @Lazy is placed on CarEngine class along with @Component.

package com.spring.annotations.lazy;

import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

@Component
@Lazy
public class CarEngine implements Engine {

private String modelNo;

public CarEngine() {
this.modelNo = "Car123"+hashCode();
System.out.println("CarEngine bean created");
}

public String getModelNo() {
return modelNo;
}

public void setModelNo(String modelNo) {
this.modelNo = modelNo;
}

@Override
public String toString() {
return "Engine [modelNo=" + modelNo + "]";
}

}
package com.spring.annotations.lazy;

public interface Vehicle {

void increaseSpeed(int speed);

}

The Engine dependency is injected in car. The @Lazy is used along with @Autowired to inject engine dependency lazily.

package com.spring.annotations.lazy;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;

public class Car implements Vehicle {

@Autowired
@Lazy
private Engine engine;

private int speed;

public Car() {
System.out.println("Car bean created");
System.out.println(toString());
}

@Override
public void increaseSpeed(int changeInSpeed) {
this.speed = this.speed + changeInSpeed;
}

public void setEngine(CarEngine engine) {
this.engine = engine;
}

public Engine getEngine() {
return engine;
}

@Override
public String toString() {
return "Car [engine=" + engine + ", speed=" + speed + "]";
}
}

The @Configuration class declared a car bean that is created at the time of app startup.

package com.spring.annotations.lazy;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

@Bean
public Car getCar() {
return new Car();
}
}

The car object is taken from context and stored in a reference variable.

package com.spring.annotations;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import com.spring.annotations.lazy.Car;

@SpringBootApplication
public class SpringBootAnotationsApplication {

public static void main(String[] args) {
ConfigurableApplicationContext ctx =
SpringApplication.run(SpringBootAnotationsApplication.class, args);
Car car = ctx.getBean(Car.class);
System.out.println("Context object created");
}

}

The car constructor is invoked but engine dependency is not injected.

When getter method in car is invoked to get car engine. The car engine bean is created.

2. @Scope

The @Scope annotation defines the life cycle and the visibility of the bean in the container. Spring framework defines 6 types of scopes.
1. Singleton
2. Prototype
3. Request
4. Session
5. Application
6. Websocket

The last four scopes, request, session, application, websocket are only available for web applications.

The @Scope annotation is used on concrete classes(@Component classes) and factory methods(@Bean methods).

@Singleton When the scope of the bean is defined as singleton, only one instance of the bean is created and managed by the IOC container. All requests for beans with an id or ids matching that bean definition will result in a specific bean instance being returned by the Spring container.

The singleton is the default scope when no scope is defined.

package com.spring.annotations.scope;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("singleton")
public class Car {

public Car() {
System.out.println("Car bean created");
}

@Override
public String toString() {
return "Car [hashCode()=" + hashCode() + "]";
}

}

The car object is requested twice from the container. Each time, same object is returned. The hashcode of both the objects is same.

package com.spring.annotations;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import com.spring.annotations.scope.Car;

@SpringBootApplication
public class SpringBootAnotationsApplication {

public static void main(String[] args) {
ConfigurableApplicationContext ctx =
SpringApplication.run(SpringBootAnotationsApplication.class, args);
Car car = ctx.getBean(Car.class);
System.out.println("Car : "+car);

car = ctx.getBean(Car.class);
System.out.println("Car : "+car);
}

}

@Prototype When the scope of the bean is defined as prototype, a new bean object is returned by the container on every request.

package com.spring.annotations.scope;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class Car {

public Car() {
System.out.println("Car bean created");
}

@Override
public String toString() {
return "Car [hashCode()=" + hashCode() + "]";
}

}

The car bean requested twice. Two different beans are returned by the container. The hashcode of both beans is different.

package com.spring.annotations;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import com.spring.annotations.scope.Car;

@SpringBootApplication
public class SpringBootAnotationsApplication {

public static void main(String[] args) {
ConfigurableApplicationContext ctx =
SpringApplication.run(SpringBootAnotationsApplication.class, args);
Car car = ctx.getBean(Car.class);
System.out.println("Car : "+car);

car = ctx.getBean(Car.class);
System.out.println("Car : "+car);
}

}

Prototype injection bean in a singleton bean problem

When a prototype bean is injected in a singleton bean, the prototype dependency behaves as a singleton bean. Lets try to understand this from code snippets below.

The scope of Car bean is prototype, it means that the object of Car bean should be created by container on each request.

package com.spring.annotations.scope;

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class CarEngine {

private String modelNo;

public CarEngine() {
this.modelNo = "Car123" + hashCode();
System.out.println("CarEngine bean created");
}

public String getModelNo() {
return modelNo;
}

public void setModelNo(String modelNo) {
this.modelNo = modelNo;
}

@Override
public String toString() {
return "Engine [modelNo=" + modelNo + "]";
}

}

The scope of car bean is singleton and The cope of CarEngine bean is prototype that is injected in the Car.

package com.spring.annotations.scope;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON)
public class Car {

@Autowired
private CarEngine engine;

public Car() {
System.out.println("Car bean created");
}

@Override
public String toString() {
return "Car [engine=" + engine + ", hashCode()=" + hashCode() + "]";
}

}

The container has been requested twice for car bean.

package com.spring.annotations;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import com.spring.annotations.scope.Car;

@SpringBootApplication
public class SpringBootAnotationsApplication {

public static void main(String[] args) {
ConfigurableApplicationContext ctx =
SpringApplication.run(SpringBootAnotationsApplication.class, args);
Car car = ctx.getBean(Car.class);
System.out.println("Car : "+car);

car = ctx.getBean(Car.class);
System.out.println("Car : "+car);
}

}

From the screenshot below, it can be seen that car engine bean is created only once. The scope of bean is prototype so two objects should be created.

The object factory can be used to get car engine dependency.

package com.spring.annotations.scope;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON)
public class Car {

@Autowired
private ObjectFactory<CarEngine> objectFactory;

public Car() {
System.out.println("Car bean created");
}

public CarEngine getEngine() {
return objectFactory.getObject();
}

@Override
public String toString() {
return "Car [engine=" + objectFactory.getObject() +
", hashCode()=" + hashCode() + "]";
}

}

From the screenshot below, it can be seen that two objects of CarEngine are created.

3. @Value

The @Value is used to inject values to the fields in the beans that are managed by IOC container. This annotation can be used at the field or method/constructor parameter level.

The @Value annotation is used to assign default values to variables and method arguments.

Two properties are defined in the property file.

@Value on the field

This annotation can be used on the field. In the class below, “app.mq.input.name” property value is injected.

package com.spring.annotations.value;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class OrderBooking {

@Value("${app.mq.input.name}")
private String inputQueueName;

@Override
public String toString() {
return "OrderBooking [inputQueueName=" + inputQueueName + "]";
}

}

The bean for OrderBooking class is retrived from the container.

package com.spring.annotations;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import com.spring.annotations.value.OrderBooking;

@SpringBootApplication
public class SpringBootAnotationsApplication {

public static void main(String[] args) {
ConfigurableApplicationContext ctx =
SpringApplication.run(SpringBootAnotationsApplication.class, args);

OrderBooking orders = ctx.getBean(OrderBooking.class);
System.out.println("Order booking object : "+orders);

}

}

@Value on the constructor

The properties from the resources can also be injected through constructor.

The car’s models no is injected from property file in the constructor.

package com.spring.annotations.scope;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class CarEngine {

private String modelNo;

public CarEngine(@Value("${car.model.no}") String modelNo) {
this.modelNo = modelNo;
System.out.println("CarEngine bean created");
}

public String getModelNo() {
return modelNo;
}

@Override
public String toString() {
return "Engine [modelNo=" + modelNo + "]";
}

}

The car engine dependency is injected in the Car.

package com.spring.annotations.scope;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Car {

@Autowired
private CarEngine engine;

public Car() {
System.out.println("Car bean created");
}

public CarEngine getEngine() {
return engine;
}

@Override
public String toString() {
return "Car [engine=" + engine + "]";
}

}

@Value on the setter

The @Value annotation can be used on the methods. In the class below, @Value is used on the setModelNo() method.

package com.spring.annotations.scope;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class CarEngine {

private String modelNo;

public CarEngine() {
System.out.println("Car engine object is created");
}

@Autowired
public void setModelNo(@Value("${car.model.no}") String modelNo) {
this.modelNo = modelNo;
}

public String getModelNo() {
return modelNo;
}

@Override
public String toString() {
return "Engine [modelNo=" + modelNo + "]";
}

}

@Value with map and list

package com.spring.annotations.value;

import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class Test {

@Value("#{${mapValues}}")
private Map<String, String> map;

@Value("#{${mapValues}.key1}")
private String value;

@Value("#{'${list}'.split(',')}")
private List<Integer> list;

@Override
public String toString() {
return "Test [map=" + map + ", value=" + value + ", list=" + list + "]";
}

}

4. @PropertySource and @PropertySources

The @PropertySource annotation is used to provide property sources to the Spring environment. It is used in conjunction with @Configuration annotation. The properties are accessible through @Value and spring environment variable of type Environment. Multiple @PropertySource annotations can be used on a configuration class.

To load environment specific properties, environment name can be used as a placeholder in @PropertySource value.

package com.spring.annotations.propertySources;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@PropertySource("classpath:db-connection-${spring.profile.active}.properties")
public class AppConfig {

}

In the class below, properties are accessible through the fields annotated with @Value annotation and Environment class.

package com.spring.annotations.propertySources;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

@Component
public class ReadResources {

@Value("${db.name}")
private String dbName;

@Value("${db.password}")
private String dbPassword;

@Autowired
private Environment env;

public String getDbName() {
return env.getProperty("db.name");
}

public String getDbPassword() {
return env.getProperty("db.password");
}

@Override
public String toString() {
return "ReadResources [dbName=" + dbName +
", dbPassword=" + dbPassword + "]";
}

}
package com.spring.annotations;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import com.spring.annotations.propertySources.ReadResources;

@SpringBootApplication
public class SpringBootAnotationsApplication {

public static void main(String[] args) {
System.setProperty("spring.profile.active","qa");
ConfigurableApplicationContext ctx =
SpringApplication.run(SpringBootAnotationsApplication.class, args);
ReadResources bean = ctx.getBean(ReadResources.class);
System.out.println("using @Value - "+bean);

System.out.println("using environment class");
System.out.println("DB Name - "+bean.getDbName());
System.out.println("DB Password : "+bean.getDbPassword());
}
}

@PropertySources

If we have more than one property sources, the @PropertySources annotation can be used. It accepts an array of @PropertySource.

Multiple @PropertySource can used on configuration class.

package com.spring.annotations.propertySources;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@PropertySource("classpath:db-connection-${spring.profile.active}.properties")
@PropertySource("classpath:queue-names-${spring.profile.active}.properties")
public class AppConfig {

}
package com.spring.annotations.propertySources;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.PropertySources;

@Configuration
@PropertySources({
@PropertySource("classpath:db-connection-${spring.profile.active}.properties"),
@PropertySource("classpath:queue-names-${spring.profile.active}.properties")
})
public class AppConfig {

}

The class below accesses properties from two different files.

package com.spring.annotations.propertySources;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

@Component
public class ReadResources {

@Value("${db.name}")
private String dbName;

@Value("${db.password}")
private String dbPassword;

@Value("${mq.source.queue.name}")
private String sourceQueueName;

@Value("${mq.destination.queue.name}")
private String destinationQueueName;

@Autowired
private Environment env;

public String getDbName() {
return env.getProperty("db.name");
}

public String getDbPassword() {
return env.getProperty("db.password");
}

public String getSourceQueueName() {
return env.getProperty("mq.source.queue.name");
}

public String getDestinationQueueName() {
return env.getProperty("mq.destination.queue.name");
}

@Override
public String toString() {
return "ReadResources [dbName=" + dbName +
", dbPassword=" + dbPassword +
", sourceQueueName=" + sourceQueueName
+ ", destinationQueueName=" + destinationQueueName + "]";
}



}

--

--