Spring Context

Complete guide to Spring Context: learn about ApplicationContext, its implementations, and how to access and manage application context in Spring applications. Master high-level container features and enterprise capabilities.

Table of Contents

1. What is ApplicationContext?

The ApplicationContext is Spring's advanced IoC container that extends the basic BeanFactory interface. It provides enterprise-level features and is the primary interface for accessing Spring's container functionality in most applications.

The following diagram illustrates the ApplicationContext hierarchy and its relationship to BeanFactory:

graph TB subgraph "Container Hierarchy" A[BeanFactory - Basic Container] B[ApplicationContext - Advanced Container] B -.->|extends| A end subgraph "ApplicationContext Implementations" C[ClassPathXmlApplicationContext] D[FileSystemXmlApplicationContext] E[AnnotationConfigApplicationContext] F[WebApplicationContext] G[GenericApplicationContext] end subgraph "Enterprise Features" H[MessageSource - Internationalization] I[ApplicationEventPublisher - Events] J[ResourceLoader - Resource Access] K[Environment - Profiles & Properties] L[BeanPostProcessor - Auto Registration] end B --> C B --> D B --> E B --> F B --> G B --> H B --> I B --> J B --> K B --> L style A fill:#fff4e1,stroke:#f57c00,stroke-width:2px style B fill:#e1f5ff,stroke:#0273bd,stroke-width:3px style C fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px style D fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px style E fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px style F fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px style G fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px

ApplicationContext provides:

  • Bean factory capabilities (creating and managing beans)
  • Automatic registration of BeanFactoryPostProcessor and BeanPostProcessor
  • Internationalization (i18n) support through MessageSource
  • Application event publishing and listening
  • Resource loading capabilities
  • Environment abstraction for profiles and properties
  • Eager bean initialization (singleton beans are created at startup)

Unlike BeanFactory, ApplicationContext eagerly initializes singleton beans at startup, which helps detect configuration errors early but may increase startup time.

2. ApplicationContext vs BeanFactory

Understanding the differences between ApplicationContext and BeanFactory helps you choose the right container for your needs.

2.1 Key Differences

Feature BeanFactory ApplicationContext
Bean Initialization Lazy (on demand) Eager (at startup)
Internationalization No Yes (MessageSource)
Application Events No Yes
Post Processors Manual registration Automatic registration
Resource Loading Basic Advanced (ResourceLoader)
Use Case Memory-constrained environments Most applications (recommended)

2.2 When to Use Each

Use BeanFactory when:

  • Memory is constrained
  • You need lazy initialization
  • You're building a lightweight application

Use ApplicationContext when:

  • Building standard Spring applications (recommended)
  • You need internationalization
  • You want application events
  • You need automatic post-processor registration
  • You want early error detection

3. ApplicationContext Implementations

Spring provides several ApplicationContext implementations for different use cases.

3.1 ClassPathXmlApplicationContext

Loads XML configuration files from the classpath. Most common for standalone applications.

ApplicationContext context = new ClassPathXmlApplicationContext(
    "applicationContext.xml",
    "services.xml",
    "daos.xml"
);

// Or using array
ApplicationContext context = new ClassPathXmlApplicationContext(
    new String[]{"applicationContext.xml", "services.xml"}
);

// Access beans
UserService userService = context.getBean(UserService.class);
UserService userServiceByName = (UserService) context.getBean("userService");

3.2 FileSystemXmlApplicationContext

Loads XML configuration files from the file system or URL.

// Load from file system
ApplicationContext context = new FileSystemXmlApplicationContext(
    "/path/to/applicationContext.xml"
);

// Load from relative path
ApplicationContext context = new FileSystemXmlApplicationContext(
    "conf/applicationContext.xml"
);

// Load from URL
ApplicationContext context = new FileSystemXmlApplicationContext(
    "file:///path/to/applicationContext.xml"
);

3.3 AnnotationConfigApplicationContext

Uses Java-based configuration with @Configuration classes. Modern approach, no XML needed.

// Single configuration class
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

// Multiple configuration classes
ApplicationContext context = new AnnotationConfigApplicationContext(
    AppConfig.class,
    DatabaseConfig.class,
    SecurityConfig.class
);

// Component scanning
AnnotationConfigApplicationContext context = 
    new AnnotationConfigApplicationContext();
context.scan("com.example");
context.refresh();

// Or using constructor
ApplicationContext context = new AnnotationConfigApplicationContext("com.example");

3.4 WebApplicationContext

Specialized ApplicationContext for web applications. Provides access to ServletContext.

// In web.xml (traditional)
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

// Access in servlet
WebApplicationContext context = 
    WebApplicationContextUtils.getWebApplicationContext(servletContext);

// In Spring Boot (automatic)
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

4. Context Features

4.1 Internationalization (i18n)

ApplicationContext provides built-in support for internationalization through the MessageSource interface.

// Configuration
@Configuration
public class AppConfig {
    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = 
            new ResourceBundleMessageSource();
        messageSource.setBasename("messages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }
}

// messages.properties (default)
greeting=Hello
welcome=Welcome

// messages_fr.properties (French)
greeting=Bonjour
welcome=Bienvenue

// messages_es.properties (Spanish)
greeting=Hola
welcome=Bienvenido

// Usage
@Service
public class GreetingService {
    private final MessageSource messageSource;
    
    public GreetingService(MessageSource messageSource) {
        this.messageSource = messageSource;
    }
    
    public String getGreeting(Locale locale) {
        return messageSource.getMessage(
            "greeting",
            null,
            locale
        );
    }
    
    public String getWelcome(String name, Locale locale) {
        return messageSource.getMessage(
            "welcome",
            new Object[]{name},
            locale
        );
    }
}

// In controller
@RestController
public class GreetingController {
    private final GreetingService greetingService;
    
    public GreetingController(GreetingService greetingService) {
        this.greetingService = greetingService;
    }
    
    @GetMapping("/greeting")
    public String greeting(@RequestHeader("Accept-Language") String lang) {
        Locale locale = Locale.forLanguageTag(lang);
        return greetingService.getGreeting(locale);
    }
}

4.2 Application Events

ApplicationContext supports a publish-subscribe event mechanism for loose coupling between components.

The event flow is illustrated below:

graph LR subgraph "Event Publishing" A[Service Component] -->|publishes| B[ApplicationEventPublisher] B -->|creates| C[ApplicationEvent] end subgraph "Event Distribution" C --> D[ApplicationContext] D -->|distributes| E[Event Listeners] end subgraph "Event Listeners" E --> F[EventListener Method] E --> G[Async Listener] E --> H[Conditional Listener] E --> I[Ordered Listener] end subgraph "Listener Execution" F --> J[Process Event] G --> K[Async Processing] H --> L[Conditional Processing] I --> M[Ordered Processing] end style A fill:#e1f5ff,stroke:#0273bd,stroke-width:2px style B fill:#fff4e1,stroke:#f57c00,stroke-width:2px style C fill:#fff4e1,stroke:#f57c00,stroke-width:2px style D fill:#e1f5ff,stroke:#0273bd,stroke-width:3px style E fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px style J fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px style K fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px style L fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px style M fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
// Define custom event
public class OrderCreatedEvent extends ApplicationEvent {
    private final Order order;
    
    public OrderCreatedEvent(Object source, Order order) {
        super(source);
        this.order = order;
    }
    
    public Order getOrder() {
        return order;
    }
}

// Publish event
@Service
public class OrderService {
    private final ApplicationEventPublisher eventPublisher;
    
    public OrderService(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }
    
    public void createOrder(Order order) {
        // Save order
        // ...
        
        // Publish event
        eventPublisher.publishEvent(new OrderCreatedEvent(this, order));
    }
}

// Listen to events
@Component
public class OrderEventListener {
    @EventListener
    public void handleOrderCreated(OrderCreatedEvent event) {
        Order order = event.getOrder();
        // Send notification, update inventory, etc.
        System.out.println("Order created: " + order.getId());
    }
    
    @EventListener
    @Async
    public void sendEmail(OrderCreatedEvent event) {
        // Async email sending
    }
    
    @EventListener(condition = "#event.order.total > 1000")
    public void handleLargeOrder(OrderCreatedEvent event) {
        // Handle large orders differently
    }
}

// Multiple events
@Component
public class GenericEventListener {
    @EventListener({OrderCreatedEvent.class, OrderCancelledEvent.class})
    public void handleOrderEvents(ApplicationEvent event) {
        // Handle multiple event types
    }
}

4.3 Resource Loading

ApplicationContext implements ResourceLoader, providing unified resource access.

@Service
public class ResourceService {
    private final ResourceLoader resourceLoader;
    
    public ResourceService(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
    
    public void loadResources() throws IOException {
        // Load from classpath
        Resource classpathResource = 
            resourceLoader.getResource("classpath:config.properties");
        
        // Load from file system
        Resource fileResource = 
            resourceLoader.getResource("file:/path/to/file.txt");
        
        // Load from URL
        Resource urlResource = 
            resourceLoader.getResource("https://example.com/data.json");
        
        // Load with prefix
        Resource resource = resourceLoader.getResource("classpath:data/input.txt");
        
        // Check if resource exists
        if (resource.exists()) {
            InputStream inputStream = resource.getInputStream();
            // Process resource
        }
        
        // Get file
        File file = resource.getFile();
        
        // Get URL
        URL url = resource.getURL();
    }
}

4.4 Environment Abstraction

ApplicationContext provides Environment abstraction for managing profiles and properties.

@Service
public class ConfigurationService {
    private final Environment environment;
    
    public ConfigurationService(Environment environment) {
        this.environment = environment;
    }
    
    public void checkProfiles() {
        // Check active profiles
        String[] activeProfiles = environment.getActiveProfiles();
        
        // Check if profile is active
        if (environment.acceptsProfiles(Profiles.of("dev"))) {
            // Dev-specific logic
        }
        
        // Get property
        String dbUrl = environment.getProperty("database.url");
        
        // Get property with default
        int port = environment.getProperty("server.port", Integer.class, 8080);
        
        // Get required property (throws exception if missing)
        String apiKey = environment.getRequiredProperty("api.key");
        
        // Check property existence
        if (environment.containsProperty("feature.enabled")) {
            boolean enabled = environment.getProperty("feature.enabled", Boolean.class);
        }
    }
}

// Access Environment in @Configuration
@Configuration
public class DatabaseConfig {
    @Autowired
    private Environment environment;
    
    @Bean
    public DataSource dataSource() {
        String url = environment.getProperty("database.url");
        String username = environment.getProperty("database.username");
        String password = environment.getProperty("database.password");
        
        // Create DataSource
        return new HikariDataSource(/* ... */);
    }
}

5. Accessing ApplicationContext

There are several ways to access ApplicationContext in your application.

5.1 Dependency Injection (Recommended)

The best way is to inject ApplicationContext or ApplicationContextAware.

@Service
public class UserService {
    private final ApplicationContext context;
    
    public UserService(ApplicationContext context) {
        this.context = context;
    }
    
    public void doSomething() {
        // Access beans programmatically
        UserRepository repo = context.getBean(UserRepository.class);
    }
}

// Or implement ApplicationContextAware
@Service
public class UserService implements ApplicationContextAware {
    private ApplicationContext context;
    
    @Override
    public void setApplicationContext(ApplicationContext context) {
        this.context = context;
    }
}

5.2 Static Access (Not Recommended)

You can access context statically, but this creates tight coupling.

public class ContextHolder {
    private static ApplicationContext context;
    
    public static void setContext(ApplicationContext context) {
        ContextHolder.context = context;
    }
    
    public static ApplicationContext getContext() {
        return context;
    }
    
    public static <T> T getBean(Class<T> clazz) {
        return context.getBean(clazz);
    }
}

// Initialize in configuration
@Configuration
public class AppConfig implements ApplicationContextAware {
    @Override
    public void setApplicationContext(ApplicationContext context) {
        ContextHolder.setContext(context);
    }
}

5.3 WebApplicationContextUtils (Web Only)

In web applications, use WebApplicationContextUtils.

public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
        ServletContext servletContext = request.getServletContext();
        WebApplicationContext context = 
            WebApplicationContextUtils.getWebApplicationContext(servletContext);
        
        UserService userService = context.getBean(UserService.class);
    }
}

6. Real-World Examples

6.1 Example 1: Multi-Context Application

// Parent context
ApplicationContext parentContext = new ClassPathXmlApplicationContext("parent-context.xml");

// Child context (inherits from parent)
ClassPathXmlApplicationContext childContext = 
    new ClassPathXmlApplicationContext();
childContext.setConfigLocation("child-context.xml");
childContext.setParent(parentContext);
childContext.refresh();

// Child can access parent beans, but not vice versa
Service parentService = childContext.getBean(Service.class); // From parent
ChildService childService = childContext.getBean(ChildService.class); // From child

6.2 Example 2: Programmatic Context Creation

@Configuration
public class DynamicContextExample {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = 
            new AnnotationConfigApplicationContext();
        
        // Register configuration classes
        context.register(AppConfig.class, DatabaseConfig.class);
        
        // Register beans programmatically
        context.registerBean("customService", CustomService.class, 
            () -> new CustomService("custom-value"));
        
        // Refresh to initialize
        context.refresh();
        
        // Use context
        CustomService service = context.getBean(CustomService.class);
        
        // Close when done
        context.close();
    }
}

6.3 Example 3: Event-Driven Architecture

// Event hierarchy
public abstract class DomainEvent extends ApplicationEvent {
    private final LocalDateTime occurredOn;
    
    protected DomainEvent(Object source) {
        super(source);
        this.occurredOn = LocalDateTime.now();
    }
    
    public LocalDateTime getOccurredOn() {
        return occurredOn;
    }
}

public class UserRegisteredEvent extends DomainEvent {
    private final User user;
    
    public UserRegisteredEvent(Object source, User user) {
        super(source);
        this.user = user;
    }
    
    public User getUser() {
        return user;
    }
}

// Event publisher
@Service
public class UserService {
    private final ApplicationEventPublisher eventPublisher;
    private final UserRepository userRepository;
    
    public UserService(ApplicationEventPublisher eventPublisher,
                      UserRepository userRepository) {
        this.eventPublisher = eventPublisher;
        this.userRepository = userRepository;
    }
    
    public void registerUser(User user) {
        userRepository.save(user);
        eventPublisher.publishEvent(new UserRegisteredEvent(this, user));
    }
}

// Event listeners
@Component
public class UserEventHandlers {
    @EventListener
    @Order(1)
    public void sendWelcomeEmail(UserRegisteredEvent event) {
        // Send welcome email
    }
    
    @EventListener
    @Order(2)
    @Async
    public void updateAnalytics(UserRegisteredEvent event) {
        // Update analytics asynchronously
    }
    
    @EventListener
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void syncWithExternalSystem(UserRegisteredEvent event) {
        // Sync only after transaction commits
    }
}

6.4 Example 4: Internationalized Messages

@Configuration
public class I18nConfig {
    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = 
            new ReloadableResourceBundleMessageSource();
        messageSource.setBasename("classpath:messages");
        messageSource.setCacheSeconds(3600); // Cache for 1 hour
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }
    
    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver resolver = new SessionLocaleResolver();
        resolver.setDefaultLocale(Locale.ENGLISH);
        return resolver;
    }
}

// Usage in service
@Service
public class MessageService {
    private final MessageSource messageSource;
    
    public MessageService(MessageSource messageSource) {
        this.messageSource = messageSource;
    }
    
    public String getMessage(String code, Object[] args, Locale locale) {
        return messageSource.getMessage(code, args, locale);
    }
    
    public String getMessage(String code, Locale locale) {
        return messageSource.getMessage(code, null, locale);
    }
}

// Usage in controller
@RestController
public class MessageController {
    private final MessageService messageService;
    
    public MessageController(MessageService messageService) {
        this.messageService = messageService;
    }
    
    @GetMapping("/messages")
    public String getMessage(
            @RequestParam String code,
            @RequestHeader(value = "Accept-Language", defaultValue = "en") String lang) {
        Locale locale = Locale.forLanguageTag(lang);
        return messageService.getMessage(code, locale);
    }
}

7. Best Practices

7.1 Prefer Dependency Injection

Always inject ApplicationContext or specific beans rather than accessing context statically.

7.2 Use Appropriate Context Implementation

Choose the right ApplicationContext implementation for your use case (AnnotationConfigApplicationContext for Java config, ClassPathXmlApplicationContext for XML).

7.3 Leverage Application Events

Use events for loose coupling between components instead of direct dependencies.

7.4 Use Environment for Configuration

Use Environment abstraction for accessing properties and managing profiles.

7.5 Close Context Properly

Always close ApplicationContext in standalone applications to release resources.

try (AnnotationConfigApplicationContext context = 
         new AnnotationConfigApplicationContext(AppConfig.class)) {
    // Use context
} // Automatically closed

8. Testing

Spring provides excellent support for testing with ApplicationContext.

8.1 Integration Testing

@SpringJUnitConfig(AppConfig.class)
class UserServiceIntegrationTest {
    @Autowired
    private ApplicationContext context;
    
    @Autowired
    private UserService userService;
    
    @Test
    void testContextLoads() {
        assertNotNull(context);
        assertTrue(context.containsBean("userService"));
    }
    
    @Test
    void testBeanRetrieval() {
        UserService service = context.getBean(UserService.class);
        assertNotNull(service);
        assertSame(userService, service); // Singleton
    }
}

8.2 Testing with Profiles

@SpringJUnitConfig(AppConfig.class)
@ActiveProfiles("test")
class ProfileTest {
    @Autowired
    private Environment environment;
    
    @Test
    void testProfileActive() {
        assertTrue(environment.acceptsProfiles(Profiles.of("test")));
    }
}

8.3 Testing Events

@SpringJUnitConfig(AppConfig.class)
class EventTest {
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    @Autowired
    private TestEventListener testListener;
    
    @Test
    void testEventPublishing() {
        OrderCreatedEvent event = new OrderCreatedEvent(this, new Order());
        eventPublisher.publishEvent(event);
        
        assertTrue(testListener.isEventReceived());
    }
}

9. Advanced Concepts

9.1 Hierarchical Contexts

Spring supports parent-child context relationships for modular applications.

// Parent context
ApplicationContext parent = new ClassPathXmlApplicationContext("parent.xml");

// Child context
ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext();
child.setConfigLocation("child.xml");
child.setParent(parent);
child.refresh();

// Child can access parent beans
ParentBean parentBean = child.getBean(ParentBean.class);
ChildBean childBean = child.getBean(ChildBean.class);

9.2 Refreshable Context

Some ApplicationContext implementations support refreshing to reload configuration.

ConfigurableApplicationContext context = 
    new ClassPathXmlApplicationContext("config.xml");

// Use context
// ...

// Refresh to reload configuration
context.refresh();

// Close when done
context.close();

9.3 Custom ApplicationContext

You can create custom ApplicationContext implementations for special requirements.

public class CustomApplicationContext extends GenericApplicationContext {
    @Override
    protected void refreshBeanFactory() throws IllegalStateException {
        // Custom initialization logic
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        // Configure bean factory
        setBeanFactory(beanFactory);
    }
    
    @Override
    protected void closeBeanFactory() {
        // Custom cleanup logic
        super.closeBeanFactory();
    }
}

10. Conclusion

ApplicationContext is Spring's advanced IoC container that provides enterprise-level features beyond basic bean management. It offers internationalization, application events, resource loading, and environment abstraction, making it the recommended choice for most Spring applications.

Understanding ApplicationContext implementations, features, and best practices enables you to build robust, maintainable, and feature-rich Spring applications. The context serves as the central hub for accessing Spring's capabilities and managing your application's components.

Whether you're building standalone applications, web applications, or enterprise systems, ApplicationContext provides the foundation for leveraging Spring's powerful dependency injection and enterprise features.

Post a Comment

0 Comments