Spring Boot Topic
Spring Beans
1. What is a Spring Bean?
1.1 Definition
A Spring Bean is an object that is instantiated, assembled, and managed by the Spring IoC (Inversion of Control) container. Beans are the backbone of any Spring application.
1.2 Key Characteristics
-
Managed Lifecycle: Spring controls creation, initialization, and destruction
-
Dependency Injection: Dependencies are injected by Spring container
-
Configuration Externalization: Configuration is separate from business logic
-
AOP Support: Can be enhanced with cross-cutting concerns
1.3 Simple Example
// A simple Java class
public class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User findUser(Long id) {
return userRepository.findById(id);
}
}
// This class becomes a Spring Bean when registered with Spring container
2. Bean Configuration Methods
2.1 XML Configuration (Traditional)
applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Simple bean definition -->
<bean id="userRepository" class="com.example.UserRepositoryImpl"/>
<!-- Bean with constructor injection -->
<bean id="userService" class="com.example.UserService">
<constructor-arg ref="userRepository"/>
</bean>
<!-- Bean with property injection -->
<bean id="dataSource" class="com.example.BasicDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="admin"/>
<property name="password" value="secret"/>
</bean>
<!-- Bean with init and destroy methods -->
<bean id="cacheManager" class="com.example.CacheManager"
init-method="initialize" destroy-method="cleanup"/>
</beans>
2.2 Java Configuration (@Configuration + @Bean)
@Configuration
public class AppConfig {
@Bean
public UserRepository userRepository() {
return new UserRepositoryImpl();
}
@Bean
public UserService userService(UserRepository userRepository) {
return new UserService(userRepository);
}
@Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
dataSource.setUsername("admin");
dataSource.setPassword("secret");
return dataSource;
}
@Bean(initMethod = "initialize", destroyMethod = "cleanup")
public CacheManager cacheManager() {
return new CacheManager();
}
}
2.3 Annotation-Based Configuration (@Component)
@Component
public class UserRepositoryImpl implements UserRepository {
// Spring automatically detects and registers this as a bean
}
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
3. Bean Lifecycle - Complete Details
3.1 Bean Lifecycle Phases
Instantiation → Population → Aware Interfaces → Post Processors → Initialization → Ready → Destruction
3.2 Detailed Lifecycle Steps
1. Instantiation
-
Bean instance is created using constructor
-
If no constructor defined, default constructor is used
2. Population of Properties
-
Dependencies are injected via setters or fields
-
Property values are set
3. Aware Interfaces Callbacks
-
Spring calls specific Aware interfaces
4. Bean Post Processors - Pre Initialization
-
BeanPostProcessor.postProcessBeforeInitialization()
5. Initialization Methods
-
@PostConstructannotated methods -
InitializingBean.afterPropertiesSet() -
Custom init method
6. Bean Post Processors - Post Initialization
-
BeanPostProcessor.postProcessAfterInitialization()
7. Bean is Ready
-
Bean is fully initialized and ready for use
8. Destruction Phase
-
@PreDestroyannotated methods -
DisposableBean.destroy() -
Custom destroy method
3.3 Lifecycle Implementation Example
@Component
public class LifecycleDemoBean implements
BeanNameAware, BeanFactoryAware, ApplicationContextAware,
InitializingBean, DisposableBean {
private String data;
public LifecycleDemoBean() {
System.out.println("1. Constructor - Bean Instantiated");
}
@Autowired
public void setDependency(SomeDependency dependency) {
System.out.println("2. Dependency Injection - Properties Populated");
}
// BeanNameAware
@Override
public void setBeanName(String name) {
System.out.println("3. BeanNameAware - Bean name: " + name);
}
// BeanFactoryAware
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("4. BeanFactoryAware - BeanFactory injected");
}
// ApplicationContextAware
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("5. ApplicationContextAware - ApplicationContext injected");
}
// @PostConstruct
@PostConstruct
public void postConstruct() {
System.out.println("6. @PostConstruct - Custom initialization");
}
// InitializingBean
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("7. InitializingBean.afterPropertiesSet()");
}
// Custom init method
@Bean(initMethod = "customInit")
public void customInit() {
System.out.println("8. Custom init method");
}
// Business method
public void businessMethod() {
System.out.println("9. Business method executed");
}
// @PreDestroy
@PreDestroy
public void preDestroy() {
System.out.println("10. @PreDestroy - Preparing for destruction");
}
// DisposableBean
@Override
public void destroy() throws Exception {
System.out.println("11. DisposableBean.destroy()");
}
// Custom destroy method
public void customDestroy() {
System.out.println("12. Custom destroy method");
}
}
3.4 Custom Bean Post Processor
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Before Initialization: " + beanName);
return bean; // you can return a proxy here
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("After Initialization: " + beanName);
return bean;
}
}
4. Spring Components - Detailed Explanation
4.1 @Component - The Base Stereotype
@Component
public class GenericComponent {
// This class will be automatically detected and registered as a bean
// during component scanning
}
// Equivalent XML configuration:
// <bean id="genericComponent" class="com.example.GenericComponent"/>
4.2 Specialized Stereotypes
@Repository - Data Access Layer
@Repository
public class UserRepositoryImpl implements UserRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public User findById(Long id) {
return entityManager.find(User.class, id);
}
// Spring automatically translates persistence exceptions
// DataAccessException hierarchy
}
Benefits:
-
Exception translation (PersistenceException → DataAccessException)
-
Clear architectural layer identification
-
AOP advice for persistence operations
@Service - Business Service Layer
@Service
@Transactional
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
@Autowired
public UserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
public User createUser(User user) {
User savedUser = userRepository.save(user);
emailService.sendWelcomeEmail(savedUser);
return savedUser;
}
}
Benefits:
-
Transaction boundary definition
-
Business logic organization
-
Service facade pattern implementation
@Controller - Web Presentation Layer
@Controller
@RequestMapping("/users")
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public String listUsers(Model model) {
model.addAttribute("users", userService.findAll());
return "users/list";
}
@PostMapping
public String createUser(@ModelAttribute User user) {
userService.createUser(user);
return "redirect:/users";
}
}
@RestController - REST API Layer
@RestController
@RequestMapping("/api/users")
public class UserApiController {
private final UserService userService;
public UserApiController(UserService userService) {
this.userService = userService;
}
@GetMapping
public List<User> getUsers() {
return userService.findAll();
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody @Valid User user) {
User created = userService.createUser(user);
return ResponseEntity.created(URI.create("/api/users/" + created.getId()))
.body(created);
}
}
4.3 Component Scanning Configuration
@Configuration
@ComponentScan(
basePackages = "com.example",
includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Service.class),
excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Test.*")
)
public class AppConfig {
// Configuration class
}
// Or in Spring Boot main class
@SpringBootApplication(
scanBasePackages = "com.example",
exclude = {DataSourceAutoConfiguration.class}
)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
5. Dependency Injection (IoC) - Detailed Explanation
5.1 Inversion of Control (IoC) Principle
Traditional Approach:
// Application controls dependencies
public class UserService {
private UserRepository userRepository;
public UserService() {
this.userRepository = new UserRepositoryImpl(); // Direct control
}
}
IoC Approach:
// Framework controls dependencies
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) { // Injected by Spring
this.userRepository = userRepository;
}
}
5.2 Injection Methods
Constructor Injection (Recommended)
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
private final AuditService auditService;
@Autowired
public UserService(UserRepository userRepository,
EmailService emailService,
AuditService auditService) {
this.userRepository = userRepository;
this.emailService = emailService;
this.auditService = auditService;
}
}
Setter Injection
java
@Service
public class UserService {
private UserRepository userRepository;
private EmailService emailService;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Autowired
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}
Field Injection
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private EmailService emailService;
}
5.3 @Autowired Variations
@Service
public class ComplexService {
// Required dependency (default)
@Autowired
private UserRepository userRepository;
// Optional dependency
@Autowired(required = false)
private Optional<CacheService> cacheService;
// Multiple beans of same type
@Autowired
@Qualifier("primaryDataSource")
private DataSource dataSource;
// All beans of type
@Autowired
private List<Validator> validators;
// Map of beans with qualifier as key
@Autowired
private Map<String, DataSource> dataSources;
}
6. Introduction to Bean Scopes
What are Bean Scopes?
Bean scopes define the lifecycle and visibility of Spring beans - determining how many instances are created and how long they live in the application context.
Key Concepts:
-
Instance Management: Controls how many instances of a bean are created
-
Lifecycle Duration: Determines how long bean instances live
-
Visibility: Defines which parts of application can access the bean
-
Thread Safety: Impacts whether beans need to be thread-safe
6.1 Singleton Scope (Default)
Singleton is the default scope in Spring. Only one instance of the bean is created per Spring IoC container, and this single instance is shared and reused by all requests.
Use Cases:
-
Stateless services
-
Configuration classes
-
Utility classes
-
Cache managers
-
Database connection pools
@Component
@Scope("singleton") // or @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class SingletonBean {
private int counter = 0;
public void increment() {
counter++;
}
public int getCounter() {
return counter;
}
}
// Usage: All injections get the SAME instance
6.2 Prototype Scope
Prototype scope creates a new instance every time the bean is requested from the container. Each injection or getBean() call returns a brand new object.
Use Cases:
-
Stateful components
-
Objects carrying request-specific data
-
When you need independent instances
-
Processing objects with mutable state
@Component
@Scope("prototype") // or @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeBean {
private int counter = 0;
public void increment() {
counter++;
}
public int getCounter() {
return counter;
}
}
// Usage: Each injection gets a NEW instance
6.3 Web-Aware Scopes
Request Scope
Request scope creates a new bean instance for each HTTP request. The instance lives only for the duration of a single HTTP request and is destroyed when the request completes.
Use Cases:
-
Request-specific data holders
-
User context information
-
Request processing tracking
-
Form data processing
@Component
@RequestScope // or @Scope(WebApplicationContext.SCOPE_REQUEST)
public class RequestScopedBean {
private final String requestId = UUID.randomUUID().toString();
public String getRequestId() {
return requestId;
}
}
// New instance for each HTTP request
Session Scope
Session scope creates a new bean instance for each HTTP session. The bean lives for the entire user session and is shared across all requests from the same user.
Use Cases:
-
User authentication information
-
Shopping cart functionality
-
User preferences
-
Multi-step form data
-
User-specific caching
@Component
@SessionScope // or @Scope(WebApplicationContext.SCOPE_SESSION)
public class UserSession {
private User currentUser;
private Locale userLocale;
private List<String> recentActions = new ArrayList<>();
public void setCurrentUser(User user) {
this.currentUser = user;
}
public User getCurrentUser() {
return currentUser;
}
}
// Same instance for entire user session
Application Scope
Application scope creates a single bean instance per ServletContext (web application). It's similar to singleton but scoped at ServletContext level rather than Spring ApplicationContext.
Use Cases:
-
Application-wide caching
-
Global configuration
-
Shared resource managers
-
Application statistics
@Component
@ApplicationScope // or @Scope(WebApplicationContext.SCOPE_APPLICATION)
public class ApplicationCache {
private final Map<String, Object> cache = new ConcurrentHashMap<>();
public void put(String key, Object value) {
cache.put(key, value);
}
public Object get(String key) {
return cache.get(key);
}
}
// Same instance for entire web application
6.4 WebSocket Scope
WebSocket scope creates a bean instance for each WebSocket session. The bean lives for the duration of a WebSocket connection.
Use Cases:
-
WebSocket session management
-
Real-time game state
-
Chat session management
-
Live collaboration tools
@Component
@Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class WebSocketSessionHandler {
private String sessionId;
private String userId;
private final List<String> messageHistory = new ArrayList<>();
private Instant connectionTime;
@PostConstruct
public void init() {
this.connectionTime = Instant.now();
this.sessionId = UUID.randomUUID().toString();
System.out.println("WebSocket session created: " + sessionId);
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserId() {
return userId;
}
public String getSessionId() {
return sessionId;
}
public void addMessage(String message) {
messageHistory.add(message);
// Keep only last 100 messages
if (messageHistory.size() > 100) {
messageHistory.remove(0);
}
}
public List<String> getMessageHistory() {
return new ArrayList<>(messageHistory);
}
public long getConnectionDuration() {
return Duration.between(connectionTime, Instant.now()).getSeconds();
}
@PreDestroy
public void cleanup() {
System.out.println("WebSocket session destroyed: " + sessionId +
", user: " + userId + ", duration: " + getConnectionDuration() + "s");
messageHistory.clear();
}
}
6.5 Custom Scope
@Component
@Scope("thread") // Custom thread scope
public class ThreadScopedBean {
private final String threadName = Thread.currentThread().getName();
public String getThreadName() {
return threadName;
}
}
// Register custom scope
@Configuration
public class CustomScopeConfig implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.registerScope("thread", new SimpleThreadScope());
}
}
7. Spring Environment - Detailed Explanation
7.1 Environment Abstraction
The Environment interface provides abstraction for property resolution and profile management.
7.2 Accessing Environment
Injection via @Autowired
@Service
public class EnvironmentService {
@Autowired
private Environment environment;
public void printEnvironmentInfo() {
// Check active profiles
String[] activeProfiles = environment.getActiveProfiles();
String[] defaultProfiles = environment.getDefaultProfiles();
// Get properties
String appName = environment.getProperty("app.name");
Integer serverPort = environment.getProperty("server.port", Integer.class);
Boolean debugMode = environment.getProperty("app.debug", Boolean.class, false);
// Check profile availability
boolean isDevProfile = environment.acceptsProfiles(Profiles.of("dev"));
boolean isProdProfile = environment.acceptsProfiles(Profiles.of("prod"));
}
}
Using @ConfigurationProperties with Environment
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String name;
private String version;
private Database database = new Database();
// Nested class
public static class Database {
private String url;
private String username;
private String password;
// getters and setters
}
// getters and setters
}
7.3 Programmatic Environment Access
@Configuration
public class EnvironmentConfig {
@Bean
public DataSource dataSource(Environment environment) {
HikariDataSource dataSource = new HikariDataSource();
// Get properties with fallbacks
dataSource.setJdbcUrl(environment.getProperty("app.datasource.url",
"jdbc:h2:mem:defaultdb"));
dataSource.setUsername(environment.getProperty("app.datasource.username", "sa"));
dataSource.setPassword(environment.getProperty("app.datasource.password", ""));
// Get required property (throws exception if missing)
dataSource.setDriverClassName(environment.getRequiredProperty("app.datasource.driver"));
return dataSource;
}
@Bean
@Profile("dev") // Only created in dev profile
public DevelopmentService devService() {
return new DevelopmentService();
}
@Bean
@Profile("prod") // Only created in prod profile
public ProductionService prodService() {
return new ProductionService();
}
}
7.4 Property Source Configuration
@Configuration
@PropertySource("classpath:application.properties")
@PropertySource("classpath:database.properties")
@PropertySource(value = "file:/etc/myapp/config.properties", ignoreResourceNotFound = true)
public class PropertySourceConfig {
@Autowired
private Environment environment;
@Bean
public static PropertySourcesPlaceholderConfigurer propertyConfig() {
return new PropertySourcesPlaceholderConfigurer();
}
}
7.5 Conditional Bean Registration
@Configuration
public class ConditionalConfig {
@Bean
@ConditionalOnProperty(name = "app.cache.enabled", havingValue = "true")
public CacheManager cacheManager() {
return new RedisCacheManager();
}
@Bean
@ConditionalOnClass(name = "com.redis.RedisClient")
public RedisTemplate redisTemplate() {
return new RedisTemplate();
}
@Bean
@ConditionalOnMissingBean(DataSource.class)
public DataSource embeddedDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
@Bean
@ConditionalOnExpression("${app.feature.advanced:false} and ${app.mode:dev} == 'dev'")
public AdvancedFeature advancedFeature() {
return new AdvancedFeature();
}
}
8. Advanced Bean Concepts
8.1 Factory Beans
@Component
public class UserServiceFactory implements FactoryBean<UserService> {
@Autowired
private Environment environment;
@Override
public UserService getObject() throws Exception {
if (environment.acceptsProfiles(Profiles.of("dev"))) {
return new MockUserService();
} else {
return new RealUserService();
}
}
@Override
public Class<?> getObjectType() {
return UserService.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
8.2 Bean Definition Inheritance
<!-- XML Configuration -->
<bean id="abstractDataSource" abstract="true">
<property name="maxPoolSize" value="10"/>
<property name="minPoolSize" value="2"/>
</bean>
<bean id="mysqlDataSource" parent="abstractDataSource" class="com.example.MySQLDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
</bean>
8.3 Lazy Initialization
@Component
@Lazy
public class HeavyResourceService {
// This bean will be created only when first requested
public HeavyResourceService() {
System.out.println("HeavyResourceService initialized - this might be expensive");
}
}
@Configuration
public class LazyConfig {
@Bean
@Lazy
public ExpensiveBean expensiveBean() {
return new ExpensiveBean(); // Created only when needed
}
}
This comprehensive guide covers all aspects of Spring Beans from basic concepts to advanced features, providing a solid foundation for understanding Spring's core container functionality.
Comments (0)
No comments yet. Be the first to comment!