Spring Custom Scope – Create and Implement ThreadScope
In spring you can use scopes like singleton or prototype. In my experience these scopes are sufficient for most application needs. But when you have specific requirements for creating your own custom scope, you can. Spring allows you to implement the Scope
interface which allows you to add, remove and specify a special callback method to manage your scope lifecycle.
Creating a Custom Scope
In this example we create a ThreadScope
The idea behind this is that we create a short lived scope which we can manage and manually clear the scope using an additional clear()
method. We must return a unique conversation id for every scope. The registerDestructionCallback()
allows you to execute some code when the scope is destroyed, the resolveContextualObject()
resolves the contextual object for the given key. Both these methods are optional.
package com.memorynotfound.spring.core.scope;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.core.NamedThreadLocal;
import java.util.HashMap;
import java.util.Map;
public class ThreadScope implements Scope {
private final ThreadLocal<Map<String, Object>> threadScope =
new NamedThreadLocal<Map<String, Object>>(ThreadScope.class.getName()) {
@Override
protected Map<String, Object> initialValue() {
return new HashMap<String, Object>();
}
};
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String, Object> scope = this.threadScope.get();
Object object = scope.get(name);
if (object == null) {
object = objectFactory.getObject();
scope.put(name, object);
}
return object;
}
@Override
public Object remove(String name) {
Map<String, Object> scope = this.threadScope.get();
return scope.remove(name);
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return Thread.currentThread().getName();
}
public void clear(){
Map<String, Object> scope = this.threadScope.get();
scope.clear();
}
}
Register.java
package com.memorynotfound.spring.core.scope;
public class Register {
public Register() {
System.out.println("- - - Register initialized");
}
}
Configuring Custom Scope
You need to make the spring container aware of your new scope. You can either add the scope programatically by calling the registerScope(name, scope)
method of the BeanFactory
bean, or like we do in this example, register the new scope using CustomScopeConfigurer
.
First we create a bean definition of our custom scope bean and assign it with an id. Next we configure the CustomScopeConfigurer
by setting the scopes
property and initialize it with a map containing a reference to our custom ThreadScope
bean together with a key. This key is used to register the custom scope. Finally we configure the Register
bean with our custom thread scope.
<?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="thread" class="com.memorynotfound.spring.core.scope.ThreadScope"/>
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread" value-ref="thread"/>
</map>
</property>
</bean>
<bean class="com.memorynotfound.spring.core.scope.Register" scope="thread"/>
</beans>
Using the Custom Scope
This application will evaluate if the retrieved bean is the same as the subsequent. We also use our custom clear()
method to purge the custom ThreadScope
scope.
package com.memorynotfound.spring.core.scope;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Run {
public static void main(String... args) throws InterruptedException {
ApplicationContext context = new ClassPathXmlApplicationContext("app-config.xml");
System.out.println("ApplicationContext initialized");
System.out.println("Retrieve 'Register'");
Register register1 = context.getBean(Register.class);
System.out.println("Retrieve 'Register' again");
Register register2 = context.getBean(Register.class);
System.out.println("Register1 == Register2: " + (register1 == register2));
System.out.println("Clear thread scope");
ThreadScope threadScope = context.getBean(ThreadScope.class);
threadScope.clear();
System.out.println("Retrieve 'Register'");
Register register3 = context.getBean(Register.class);
System.out.println("Retrieve 'Register' again");
Register register4 = context.getBean(Register.class);
System.out.println("Register3 == Register4: " + (register3 == register4));
}
}
Output
The instance of Register
is created only when the instance does not exist in the ThreadScope
bean.
ApplicationContext initialized
Retrieve 'Register'
- - - Register initialized
Retrieve 'Register' again
Register1 == Register2: true
Clear thread scope
Retrieve 'Register'
- - - Register initialized
Retrieve 'Register' again
Register3 == Register4: true