Skip to main content

ApplicationContext

The related code for this article (from official source code spring-test module) can be found at spring-framework under the org.springframework.mylearntest package.

Unified Resource Loading Strategy

Spring proposes a set of resource abstraction and loading strategies based on org.springframework.core.io.Resource and org.springframework.core.io.ResourceLoader interfaces.

Resource: The interface can provide corresponding concrete implementations based on different types of resources or different scenarios where resources are located. It can help us query resource status, access resource content, and even create new relative resources based on current resources. We can inherit the org.springframework.core.io.AbstractResource abstract class.

ResourceLoader: But how to find and locate these resources should be the responsibility of ResourceLoader. The org.springframework.core.io.ResourceLoader interface is a unified abstraction of resource finding and locating strategies, and specific resource finding and locating strategies are provided by corresponding ResourceLoader implementation classes.

DefaultResourceLoader: ResourceLoader has a default implementation class, org.springframework.core.io.DefaultResourceLoader, whose default resource finding processing logic is as follows.

  • First check if the resource path starts with the classpath: prefix. If so, try to construct a ClassPathResource type resource and return it.
  • Otherwise, (a) try to locate the resource through URL based on the resource path. If no MalformedURLException is thrown, construct a UrlResource type resource and return it; (b) if still unable to locate the specified resource based on the resource path, delegate to the getResourceByPath(String) method for location. The default implementation logic of DefaultResourceLoader's getResourceByPath(String) method is to construct a ClassPathResource type resource and return it.

Four Loading Methods

Using ApplicationContext as ResourceLoader

ResourceLoader resourceLoader = new ClassPathXmlApplicationContext("configuration file path");

ResourceLoader Type Injection

  1. Depending on ResourceLoader
resourceloader.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">
<bean id="resourceLoader" class="org.springframework.core.io.DefaultResourceLoader">
</bean>

<bean id="fooBar" class="org.springframework.mylearntest.ioc.resourceloader.FooBar">
<property name="resourceLoader" ref="resourceLoader"/>
</bean>
</beans>
  1. Instance classes that implement ResourceLoaderAware or ApplicationContextAware interfaces
resourceloader4ContextBoo.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">

<bean id="fooBar" class="org.springframework.mylearntest.ioc.resourceloader.FooBarImplApplicationContextAware">
</bean>
</beans>
  1. Resource Type Injection
xmailer.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">

<bean id="xMailer" class="org.springframework.mylearntest.ioc.resourceloader.XMailer">
<property name="template" value="resourceloader/resources.default_template.vm"/>
</bean>
</beans>
  1. ApplicationContext's Resource Loading Behavior When ClassPathXmlApplicationContext is instantiated, even without specifying prefixes like classpath: or classpath*:, it will load bean definition configuration files from the classpath by default, while FileSystemXmlApplicationContext is somewhat similar. If we specify conf/appContext.xml as in the following code, it will try to load bean definition files from the file system

Internationalization Support (i18n MessageSource)

Internationalization support provided by Java SE

  • Locale

Different Locales represent different countries and regions. Each country and region has corresponding abbreviation codes in Locale, including language codes and country codes, which are ISO standard codes. For example, Locale.CHINA represents China.

  • ResourceBundle

ResourceBundle is used to save information specific to a certain Locale (can be String type information or any type of object). Usually ResourceBundle manages a set of information sequences, all information sequences have a unified basename, and then information for specific Locales can be distinguished by appending language or region codes after the basename. For example, we can use a set of properties files to save information for different countries and regions, and name the corresponding properties files as follows:

messages.properties
messages_zh.properties
messages_zh_CN.properties
messages_en.properties
messages_en_US.properties

Among them, the messages part in the filename is called the basename of the resource that ResourceBundle will load, and resources for other languages or regions append Locale-specific codes based on the basename.

If a business object needs internationalization information support, the simplest way is to let it implement the MessageSourceAware interface and then register it with the ApplicationContext container. However, this makes the business object too dependent on the ApplicationContext container, making the container quite intrusive. In fact, if a business object really needs to depend on MessageSource, it can directly declare the dependency through constructor injection or setter method injection.

Container Internal Event Publishing

  1. Custom Event Publishing Provide custom event types (define your own event object). To distinguish specific event types for specific scenarios, we need to provide our own event type definitions. The usual practice is to extend the java.util.EventObject class to implement custom event types.
Define Event Type
package org.springframework.mylearntest.ioc.eventpublication.applicationevent;

import org.springframework.context.ApplicationEvent;
import org.springframework.mylearntest.ioc.eventpublication.event.MethodExecutionStatus;

public class MethodExecutionEvent extends ApplicationEvent {
private static final long serialVersionUID = -71960369269303337L;
private String methodName;
private MethodExecutionStatus methodExecutionStatus;

public MethodExecutionEvent(Object source) {
super(source);
}

public MethodExecutionEvent(Object source, String methodName, MethodExecutionStatus methodExecutionStatus) {
super(source);
this.methodName = methodName;
this.methodExecutionStatus = methodExecutionStatus;
}

public String getMethodName() {
return methodName;
}

public void setMethodName(String methodName) {
this.methodName = methodName;
}

public MethodExecutionStatus getMethodExecutionStatus() {
return methodExecutionStatus;
}

public void setMethodExecutionStatus(MethodExecutionStatus methodExecutionStatus) {
this.methodExecutionStatus = methodExecutionStatus;
}
}
Define Event Listener Interface
package org.springframework.mylearntest.ioc.eventpublication.event;


import java.util.EventListener;

/**
* Custom event listener
*/
public interface MethodExecutionEventListener extends EventListener {
/**
* Handle MethodExecutionEvent published when method starts executing
*/
void onMethodBegin(MethodExecutionEvent evt);
/**
* Handle MethodExecutionEvent published when method execution is about to end
*/
void onMethodEnd(MethodExecutionEvent evt);
}

Custom Event Listener Implementation
package org.springframework.mylearntest.ioc.eventpublication.event;

/**
* Custom event listener implementation
*/
public class SimpleMethodExecutionEventListener implements MethodExecutionEventListener {

public void onMethodBegin(MethodExecutionEvent evt) {
String methodName = evt.getMethodName();
System.out.println("start to execute the method[" + methodName + "].");
}

public void onMethodEnd(MethodExecutionEvent evt) {
String methodName = evt.getMethodName();
System.out.println("finished to execute the method[" + methodName + "].");
}
}
Define Event Status Enum and Event Publisher
package org.springframework.mylearntest.ioc.eventpublication.event;

public enum MethodExecutionStatus {
BEGIN,END
}
Event Publishing Class
package org.springframework.mylearntest.ioc.eventpublication.event;

import java.util.ArrayList;
import java.util.List;

public class MethodExecutionEventPublisher {
private List<MethodExecutionEventListener> listeners = new ArrayList<>();

public void methodToMonitor() {
MethodExecutionEvent event2Publish = new MethodExecutionEvent(this, "methodToMonitor");
publishEvent(MethodExecutionStatus.BEGIN, event2Publish);
// Execute actual method logic
// ...
publishEvent(MethodExecutionStatus.END, event2Publish);
}

// To avoid registration or removal operations of event listeners during event processing affecting the process, we make a safe copy of the listener list at the time of event publishing
protected void publishEvent(MethodExecutionStatus status, MethodExecutionEvent methodExecutionEvent) {
List<MethodExecutionEventListener> copyListeners = new ArrayList<>(listeners);
for (MethodExecutionEventListener listener : copyListeners) {
if (MethodExecutionStatus.BEGIN.equals(status)) {
listener.onMethodBegin(methodExecutionEvent);
} else {
listener.onMethodEnd(methodExecutionEvent);
}
}
}

public void addMethodExecutionEventListener(MethodExecutionEventListener listener) {
this.listeners.add(listener);
}

public void removeListener(MethodExecutionEventListener listener) {
this.listeners.remove(listener);
}

public void removeAllListeners() {
this.listeners.clear();
}

}
Test Class
package org.springframework.mylearntest.ioc.eventpublication.event;

public class Test4Event {
public static void main(String[] args) {
MethodExecutionEventPublisher eventPublisher = new MethodExecutionEventPublisher();
eventPublisher.addMethodExecutionEventListener(new SimpleMethodExecutionEventListener());
eventPublisher.methodToMonitor();
eventPublisher.removeAllListeners();
}
}

In the implementation, it should be noted that to avoid registration or removal operations of event listeners during event processing affecting the process, we make a safe copy of the listener list at the time of event publishing. Additionally, event publishing is executed sequentially, so to avoid affecting processing performance, the processing logic of event listeners should be as brief as possible.

Spring's Container Internal Event Publishing Class Structure Analysis

Spring's ApplicationContext container internally allows publishing events in the form of org.springframework.context.ApplicationEvent. Bean definitions of org.springframework.context.ApplicationListener type registered in the container will be automatically recognized by the ApplicationContext container, and they are responsible for listening to all ApplicationEvent type events published within the container.

ApplicationEvent: Spring container's custom event type, inheriting from java.util.EventObject. It is an abstract class that needs to provide corresponding subclasses according to different situations. By default, Spring provides three implementations.

  • ContextClosedEvent: Event type published when ApplicationContext container is about to close.
  • ContextRefreshedEvent: Event type published when ApplicationContext container is initialized or refreshed.
  • RequestHandledEvent: Event published after web request processing, with a subclass ServletRequestHandledEvent providing Java EE Servlet-specific events.

ApplicationListener: Custom event listener interface definition used within ApplicationContext container, inheriting from java.util.EventListener.

ApplicationContext: When the container starts, it will automatically recognize and load EventListener type bean definitions. Once there are events published within the container, these EventListeners registered with the container will be notified.

MethodExecutionEventListener
package org.springframework.mylearntest.eventpublication.applicationevent;


import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

@SuppressWarnings("rawtypes")
public class MethodExecutionEventListener implements ApplicationListener {
public void onApplicationEvent(ApplicationEvent evt) {
if (evt instanceof MethodExecutionEvent) {
// Execute processing logic
}
}
}

ApplicationContext: Remember the definition of ApplicationContext? Besides the previous ResourceLoader and MessageSource, the ApplicationContext interface definition also inherits the ApplicationEventPublisher interface, which provides the void publishEvent(ApplicationEvent event) method definition. It's not hard to see that the ApplicationContext container now plays the role of event publisher. The specific implementation classes of ApplicationContext container do not handle event publishing and event listener registration personally, but delegate these tasks to an interface called org.springframework.context.event.ApplicationEventMulticaster.

MethodExecutionEventPublisher
package org.springframework.mylearntest.eventpublication.applicationevent;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.mylearntest.eventpublication.event.MethodExecutionStatus;

public class MethodExecutionEventPublisher implements ApplicationEventPublisherAware {
private ApplicationEventPublisher eventPublisher;

public void methodToMonitor() {
MethodExecutionEvent beginEvt = new
MethodExecutionEvent(this, "methodToMonitor", MethodExecutionStatus.BEGIN);
this.eventPublisher.publishEvent(beginEvt);
// Execute actual method logic
// ...
MethodExecutionEvent endEvt = new
MethodExecutionEvent(this, "methodToMonitor", MethodExecutionStatus.END);
this.eventPublisher.publishEvent(endEvt);
}

public void setApplicationEventPublisher(ApplicationEventPublisher appCtx) {
this.eventPublisher = appCtx;
}
}
applicationevent.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">

<bean id="methodExecListener" class="org.springframework.mylearntest.eventpublication.applicationevent.MethodExecutionEventListener">
</bean>
<bean id="evtPublisher" class="org.springframework.mylearntest.eventpublication.applicationevent.MethodExecutionEventPublisher">
</bean>

</beans>
Test4AppEvent
package org.springframework.mylearntest.eventpublication.applicationevent;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test4AppEvent {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("eventpublication/applicationevent.xml");
MethodExecutionEventPublisher evtPublisher = (MethodExecutionEventPublisher) context.getBean("evtPublisher");
evtPublisher.methodToMonitor();
}
}

ApplicationEventMulticaster has an abstract implementation class—org.springframework.context.event.AbstractApplicationEventMulticaster, which implements event listener management functionality. Event publishing functionality is delegated to its subclass org.springframework.context.event.SimpleApplicationEventMulticaster. It uses SyncTaskExecutor by default for event publishing. To avoid potential performance issues with this approach, we can provide other types of TaskExecutor implementation classes for it.

When the container starts, it checks whether there is an ApplicationEventMulticaster object instance named applicationEventMulticaster in the container. If there is, it uses the provided implementation; if not, it initializes a SimpleApplicationEventMulticaster by default as the ApplicationEventMulticaster to be used.

Looking at dependency injection related information, half is scattered in Java source code (@Autowired annotated information), and half still remains in XML configuration files, with many bean tags still existing. When using @Autowired annotation can find two or more object instances of the same type simultaneously, you can use @Qualifier to further qualify the conditions for dependency injection, specifying which specific id.

XML Method
<beans>
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
<bean id="newsProvider" class="..FXNewsProvider"/>
<bean id="djNewsListener" class="..DowJonesNewsListener"/>
<bean id="reutersNewsListner" class="..ReutersNewsListener"/>
<bean id="djNewsPersister" class="..DowJonesNewsPersister"/>
</beans>
@Qualifier on Properties
public class FXNewsProvider {
@Autowired
@Qualifier("reutersNewsListner")// At this time inject id=reutersNewsListner
private IFXNewsListener newsListener;
@Autowired
private IFXNewsPersister newPersistener;
//...
}
@Qualifier Annotation on Parameters
// @Qualifier annotation on parameters
public class FXNewsProvider{
// ...
@Autowired
public void setUp(@Qualifier("reutersNewsListner") IFXNewsListener newsListener,IFXNewsPersister newPersistener) {
this.newsListener = newsListener;
this.newPersistener = newPersistener;
}
// ...
}

@Resource is different from @Autowired. It follows the byName automatic binding behavior principle, which means the IoC container will search for instances in the container with beanName corresponding to the name specified by @Resource, and then inject the found object instance into the object annotated by @Resource.

@PostConstruct and @PreDestroy are not for dependency injection. They are mainly used to annotate methods related to object lifecycle management, which is similar to Spring's InitializingBean and DisposableBean interfaces, as well as init-method and destroy-method in configuration items.

Just like @Autowired needs AutowiredAnnotationBeanPostProcessor to bridge it with the IoC container, JSR250 annotations also need a BeanPostProcessor to help them realize their value. This BeanPostProcessor is org.springframework.context.annotation.CommonAnnotationBeanPostProcessor. Only by adding CommonAnnotationBeanPostProcessor to the container can JSR250 related annotations take effect.

XML Configuration
<beans>
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>
<bean id="newsProvider" class="..FXNewsProvider"/>
<bean id="djNewsListener" class="..DowJonesNewsListener"/>
<bean id="djNewsPersister" class="..DowJonesNewsPersister"/>
</beans>

<context:annotation-config> not only helps us register AutowiredAnnotationBeanPostProcessor and CommonAnnotationBeanPostProcessor to the container, but also registers PersistenceAnnotationBeanPostProcessor and RequiredAnnotationBeanPostProcessor together, achieving four benefits at once!

After annotating the relevant classes that make up the application with corresponding annotations, the classpath-scanning functionality can start scanning from a certain top-level package (base package). When it scans that a class is annotated with corresponding annotations, it will extract the relevant information of that class, build the corresponding BeanDefinition, and then register the built BeanDefinition to the container.

XML Configuration
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="org.spring21"/>
</beans>

References

  1. Book Title: Spring Revealed Author: Wang Fuqiang