Spring Framework Basics
Spring Bean Lifecycle
The lifecycle of a Spring bean is managed by the Spring container. The main stages are:
Spring Bean Lifecycle
- Instantiation: The container creates an instance of the bean.
- Populate Properties: Dependencies are injected into the bean.
- Set Bean Name: If the bean implements
BeanNameAware,setBeanName()is called. - Set Bean Factory: If the bean implements
BeanFactoryAware,setBeanFactory()is called. - Pre-Initialization:
BeanPostProcessorsare applied before initialization. - Initialization: The container calls
afterPropertiesSet()(ifInitializingBeanis implemented) or a custom init method. - Post-Initialization:
BeanPostProcessorsare applied after initialization. - Ready for Use: The bean is now available for use.
- Destruction: The container calls
destroy()(ifDisposableBeanis implemented) or a custom destroy method.
Base Classes for Examples
The following base classes are used across all examples in this document. Refer to these classes for context when reviewing the examples.
Example
public class Processor {
private String brand;
private String model;
public Processor() {}
public Processor(String brand, String model) {
this.brand = brand;
this.model = model;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String getDetails() {
return brand + " " + model;
}
}
public class Memory {
private String type;
private int size;
public Memory() {}
public Memory(String type, int size) {
this.type = type;
this.size = size;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public String getDetails() {
return type + " " + size + "GB";
}
}
public class Computer {
private Processor processor;
private Memory memory;
public Computer() {}
public Computer(Processor processor, Memory memory) {
this.processor = processor;
this.memory = memory;
}
public Processor getProcessor() {
return processor;
}
public void setProcessor(Processor processor) {
this.processor = processor;
}
public Memory getMemory() {
return memory;
}
public void setMemory(Memory memory) {
this.memory = memory;
}
public void displaySpecs() {
System.out.println("Computer Specs:");
System.out.println("Processor: " + processor.getDetails());
System.out.println("Memory: " + memory.getDetails());
}
}
Spring Configuration Methods
Spring provides multiple ways to configure applications:
Spring Configuration
- XML configuration is the traditional way of configuring Spring applications.
- It uses an XML file to define beans and their dependencies.
- XML files are usually placed in the
src/main/resourcesdirectory. - The XML file is loaded into the Spring context using
ClassPathXmlApplicationContext.
- Java-based configuration uses Java classes to define beans and their dependencies.
- It is more type-safe and allows for better refactoring.
- Java configuration classes are annotated with
@Configurationand use@Beanto define beans.
- Annotation-based configuration uses annotations to define beans and their dependencies.
- It is more concise and easier to read compared to XML configuration.
- Common annotations include
@Component,@Autowired, and@Configuration.
Load the Spring Context
Spring context can be loaded in different ways:
Spring Context
Scope of Beans
Spring supports different bean scopes:
- Singleton: Default scope. A single instance is created and shared across the application.
- Prototype: A new instance is created each time the bean is requested.
- Request: A new instance is created for each HTTP request (web applications).
- Session: A new instance is created for each HTTP session (web applications).
- Global Session: A new instance is created for each global HTTP session (web applications).
- Application: A single instance is created for the lifecycle of the application context.
- WebSocket: A new instance is created for each WebSocket session (web applications).
Spring Bean Scopes
Dependency Injection
Spring supports multiple ways to inject dependencies into beans. The most common methods are:
Setter Injection
Setter injection uses setter methods to inject dependencies into a bean. It is flexible and allows changing dependencies at runtime. Must have setter methods defined in the class.
Example
<bean id="processor" class="com.example.Processor">
<property name="brand" value="Intel"/>
<property name="model" value="Core i7"/>
</bean>
<bean id="memory" class="com.example.Memory">
<property name="type" value="DDR4"/>
<property name="size" value="16"/>
</bean>
<bean id="computer" class="com.example.Computer">
<property name="processor" ref="processor"/>
<property name="memory" ref="memory"/>
</bean>
@Configuration
public class AppConfig {
@Bean
public Processor processor() {
Processor processor = new Processor();
processor.setBrand("Intel");
processor.setModel("Core i7");
return processor;
}
@Bean
public Memory memory() {
Memory memory = new Memory();
memory.setType("DDR4");
memory.setSize(16);
return memory;
}
@Bean
public Computer computer() {
Computer computer = new Computer();
computer.setProcessor(processor());
computer.setMemory(memory());
return computer;
}
}
@Component
public class Computer {
private Processor processor;
private Memory memory;
@Autowired
public void setProcessor(Processor processor) {
this.processor = processor;
}
@Autowired
public void setMemory(Memory memory) {
this.memory = memory;
}
public void displaySpecs() {
System.out.println("Computer Specs:");
System.out.println("Processor: " + processor.getDetails());
System.out.println("Memory: " + memory.getDetails());
}
}
Constructor Injection
Constructor injection uses constructor parameters to inject dependencies into a bean. It is useful when dependencies are mandatory and cannot be changed at runtime.
Example
<bean id="processor" class="com.example.Processor">
<constructor-arg value="Intel"/>
<constructor-arg value="Core i7"/>
</bean>
<bean id="memory" class="com.example.Memory">
<constructor-arg value="DDR4"/>
<constructor-arg value="16"/>
</bean>
<bean id="computer" class="com.example.Computer">
<constructor-arg ref="processor"/>
<constructor-arg ref="memory"/>
</bean>
@Component
public class Computer {
private final Processor processor;
private final Memory memory;
@Autowired
public Computer(Processor processor, Memory memory) {
this.processor = processor;
this.memory = memory;
}
public void displaySpecs() {
System.out.println("Processor: " + processor.getDetails());
System.out.println("Memory: " + memory.getDetails());
}
}
Field Injection
Field injection uses annotations to inject dependencies directly into the fields of a bean. It is less verbose but can make testing and refactoring more difficult. It is generally not recommended for production code.
Example
Autowiring
Autowiring automatically resolves and injects dependencies into a bean. It reduces the need for explicit configuration. There are several modes of autowiring:
- No Autowiring: Default mode. No autowiring is performed.
- By Type: The container looks for a bean of the same type and injects it.
- By Name: The container looks for a bean with the same name as the property and injects it.
- Constructor: The container looks for a constructor with matching types and injects the dependencies.
- By Type with Qualifier: The container looks for a bean of the same type and uses the
@Qualifierannotation to resolve ambiguity. - By Name with Qualifier: The container looks for a bean with the same name as the property and uses the
@Qualifierannotation to resolve ambiguity. - By Type with Primary: The container looks for a bean of the same type and uses the
@Primaryannotation to resolve ambiguity. - By Name with Primary: The container looks for a bean with the same name as the property and uses the
@Primaryannotation to resolve ambiguity. - By Type with Factory Method: The container looks for a factory method that returns a bean of the same type and injects it.
- By Name with Factory Method: The container looks for a factory method that returns a bean with the same name as the property and injects it.
- By Type with Factory Bean: The container looks for a factory bean that returns a bean of the same type and injects it.
Example
<bean id="processor" class="com.example.Processor">
<property name="brand" value="Intel"/>
<property name="model" value="Core i7"/>
</bean>
<bean id="memory" class="com.example.Memory">
<property name="type" value="DDR4"/>
<property name="size" value="16"/>
</bean>
<bean id="computer" class="com.example.Computer" autowire="byName"/>
Using Primary to Resolve Bean Ambiguity
When multiple beans of the same type are eligible for injection you can explicitly use the bean name, in java based config Use Qualifier or use the @Primary annotation or the primary attribute in XML can be used to mark one bean as the default choice.
Example
@Component
public class Computer {
@Autowired
@Qualifier("amdProcessor") // specify the bean name to be injected
private Processor processor;
@Autowired
@Qualifier("memory") // specify the bean name to be injected
private Memory memory;
public void displaySpecs() {
System.out.println("Processor: " + processor.getDetails());
}
}
Inner Beans
Inner beans are beans defined within the <property> or <constructor-arg> tags of another bean. They are used when a bean is only relevant as a dependency of another bean and does not need to be reused elsewhere.
Example
<bean id="computer" class="com.example.Computer">
<constructor-arg>
<bean class="com.example.Processor">
<constructor-arg value="Intel"/>
<constructor-arg value="Core i7"/>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="com.example.Memory">
<constructor-arg value="DDR4"/>
<constructor-arg value="16"/>
</bean>
</constructor-arg>
</bean>
@Component
public class Computer {
private Processor processor;
private Memory memory;
@Autowired
public void setProcessor(Processor processor) {
this.processor = processor;
}
@Autowired
public void setMemory(Memory memory) {
this.memory = memory;
}
public void displaySpecs() {
System.out.println("Computer Specs:");
System.out.println("Processor: " + processor.getDetails());
System.out.println("Memory: " + memory.getDetails());
}
}