Example of how to customize scope in Spring

  • 2021-06-28 12:50:35
  • OfStack

Everyone should not default to scope of Spring.The so-called scope literally means "Scope", "Scope". If an scope of bean is configured as singleton, the objects returned by the bean retrieved from the container are the same;If scope is configured as prototype, the objects returned will be different each time.

1 In general, scope provided by Spring can satisfy everyday scenarios.However, if your needs are extremely special, the custom scope described in this article is appropriate for you.

Spring Built-in scope

By default, all Spring bean are singletons, meaning that there is only one instance of bean in the entire Spring application.You can modify this default value by adding the scope attribute to bean.The values available for the scope property are as follows:

范围 描述
singleton 每个 Spring 容器1个实例(默认值)
prototype 允许 bean 可以被多次实例化(使用1次就创建1个实例)
request 定义 bean 的 scope 是 HTTP 请求。每个 HTTP 请求都有自己的实例。只有在使用有 Web 能力的 Spring 上下文时才有效
session 定义 bean 的 scope 是 HTTP 会话。只有在使用有 Web 能力的 Spring ApplicationContext 才有效
application 定义了每个 ServletContext 1个实例
websocket 定义了每个 WebSocket 1个实例。只有在使用有 Web 能力的 Spring ApplicationContext 才有效

If the above scope still does not meet your needs, Spring also reserves an interface that allows you to customize scope.

Scope interface

org.springframework.beans.factory.config.Scope Interfaces are used to define the behavior of scope:


package org.springframework.beans.factory.config;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.lang.Nullable;

public interface Scope {

 Object get(String name, ObjectFactory<?> objectFactory);

 @Nullable
 Object remove(String name);

 void registerDestructionCallback(String name, Runnable callback);

 @Nullable
 Object resolveContextualObject(String key);

 @Nullable
 String getConversationId();

}

Generally speaking, only the get and remove methods need to be re-established.

scope in Custom Thread Scope

Now we are in the real world.We want to customize an scope that Spring does not have, which limits the scope of bean to threads.That is, the bean within the same thread is the same object, while the cross-thread is a different object.

1. Define scope

To customize an scope of Spring, simply implement the org.springframework.beans.factory.config.Scope interface.The code is as follows:


package com.waylau.spring.scope;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;

/**
 * Thread Scope.
 * 
 * @since 1.0.0 2019 year 2 month 13 day 
 * @author <a href="https://waylau.com" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >Way Lau</a>
 */
public class ThreadScope implements Scope {
 
 private final ThreadLocal<Map<String, Object>> threadLoacal = new ThreadLocal<Map<String, Object>>() {
 @Override
 protected Map<String, Object> initialValue() {
  return new HashMap<String, Object>();
 }
 };

 public Object get(String name, ObjectFactory<?> objectFactory) {
 Map<String, Object> scope = threadLoacal.get();
 Object obj = scope.get(name);

 //  Place if not present ThreadLocal
 if (obj == null) {
  obj = objectFactory.getObject();
  scope.put(name, obj);
  
  System.out.println("Not exists " + name + "; hashCode: " + obj.hashCode());
 } else {
  System.out.println("Exists " + name + "; hashCode: " + obj.hashCode());
 }

 return obj;
 }

 public Object remove(String name) {
 Map<String, Object> scope = threadLoacal.get();
 return scope.remove(name);
 }

 public String getConversationId() {
 return null;
 }

 public void registerDestructionCallback(String arg0, Runnable arg1) {
 }

 public Object resolveContextualObject(String arg0) {
 return null;
 }

}

In the above code, threadLoacal is used to isolate data between threads.In other words, threadLoacal implements that bean with the same thread name is the same object;bean is a different object with the same name for different threads.

At the same time, we print out the hashCode of the object.If they are the same object, hashCode is the same.

2. Register scope

Define an AppConfig configuration class to register the custom scope into the container.The code is as follows:


package com.waylau.spring.scope;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.config.CustomScopeConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * App Config.
 *
 * @since 1.0.0 2019 year 2 month 13 day 
 * @author <a href="https://waylau.com" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >Way Lau</a>
 */
@Configuration
@ComponentScan
public class AppConfig {

 @Bean
 public static CustomScopeConfigurer customScopeConfigurer() {
 CustomScopeConfigurer customScopeConfigurer = new CustomScopeConfigurer();

 Map<String, Object> map = new HashMap<String, Object>();
 map.put("threadScope", new ThreadScope());

 //  To configure scope
 customScopeConfigurer.setScopes(map);
 return customScopeConfigurer;
 }
}

"threadScope" is the name of the custom ThreadScope.

3. Use scope

The next step is to use a custom scope based on the general usage of scope.The code is as follows:


package com.waylau.spring.scope.service;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

/**
 * Message Service Impl.
 * 
 * @since 1.0.0 2019 year 2 month 13 day 
 * @author <a href="https://waylau.com" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >Way Lau</a>
 */
@Scope("threadScope")
@Service
public class MessageServiceImpl implements MessageService {
 
 public String getMessage() {
 return "Hello World!";
 }

}

"threadScope" in @Scope ("threadScope") is the name of the custom ThreadScope.

4. Define application entry

Define the Spring application entry:


package com.waylau.spring.scope;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.waylau.spring.scope.service.MessageService;

/**
 * Application Main.
 * 
 * @since 1.0.0 2019 year 2 month 13 day 
 * @author <a href="https://waylau.com" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >Way Lau</a>
 */
public class Application {

 public static void main(String[] args) {
 @SuppressWarnings("resource")
 ApplicationContext context = 
      new AnnotationConfigApplicationContext(AppConfig.class);

 MessageService messageService = context.getBean(MessageService.class);
 messageService.getMessage();
 
 MessageService messageService2 = context.getBean(MessageService.class);
 messageService2.getMessage();
 
 }

}

The output of the Run Application Observation Console is as follows:

Not exists messageServiceImpl; hashCode: 2146338580
Exists messageServiceImpl; hashCode: 2146338580

The output also verifies that ThreadScope "bean with the same thread name is the same object".

If you want to continue validating ThreadScope that "bean with the same name for different threads is a different object", you only need to convert Application to multithreaded.


package com.waylau.spring.scope;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.waylau.spring.scope.service.MessageService;

/**
 * Application Main.
 * 
 * @since 1.0.0 2019 year 2 month 13 day 
 * @author <a href="https://waylau.com" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >Way Lau</a>
 */
public class Application {

 public static void main(String[] args) throws InterruptedException, ExecutionException {
 @SuppressWarnings("resource")
 ApplicationContext context = 
  new AnnotationConfigApplicationContext(AppConfig.class);

 CompletableFuture<String> task1 = CompletableFuture.supplyAsync(()->{
      // Simulate execution of time-consuming tasks 
      MessageService messageService = context.getBean(MessageService.class);
  messageService.getMessage();
 
  MessageService messageService2 = context.getBean(MessageService.class);
  messageService2.getMessage();
      // Return results 
      return "result";
    });
 
 CompletableFuture<String> task2 = CompletableFuture.supplyAsync(()->{
      // Simulate execution of time-consuming tasks 
      MessageService messageService = context.getBean(MessageService.class);
  messageService.getMessage();
 
  MessageService messageService2 = context.getBean(MessageService.class);
  messageService2.getMessage();
      // Return results 
      return "result";
    });
 
 task1.get();
 task2.get(); 
 
 }
}

Observe the output;

Not exists messageServiceImpl; hashCode: 1057328090
Not exists messageServiceImpl; hashCode: 784932540
Exists messageServiceImpl; hashCode: 1057328090
Exists messageServiceImpl; hashCode: 784932540

The above results validate that ThreadScope "the same thread with the same name bean is the same object;bean is a different object with the same name for different threads"

Source code

See s5-ch02-custom-scope-annotation project https://github.com/waylau/spring-5-book.


Related articles: