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.