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

  • @PostConstruct annotated 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

  • @PreDestroy annotated 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!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?