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

References

Download

You may also like...