如何在Spring中自定义scope的方法示例
作者:waylau 发布时间:2022-12-23 02:17:41
大家对于 Spring 的 scope 应该都不会默认。所谓 scope,字面理解就是“作用域”、“范围”,如果一个 bean 的 scope 配置为 singleton,则从容器中获取 bean 返回的对象都是相同的;如果 scope 配置为prototype,则每次返回的对象都不同。
一般情况下,Spring 提供的 scope 都能满足日常应用的场景。但如果你的需求极其特殊,则本文所介绍自定义 scope 合适你。
Spring 内置的 scope
默认时,所有 Spring bean 都是的单例的,意思是在整个 Spring 应用中,bean的实例只有一个。可以在 bean 中添加 scope 属性来修改这个默认值。scope 属性可用的值如下:
范围 | 描述 |
---|---|
singleton | 每个 Spring 容器一个实例(默认值) |
prototype | 允许 bean 可以被多次实例化(使用一次就创建一个实例) |
request | 定义 bean 的 scope 是 HTTP 请求。每个 HTTP 请求都有自己的实例。只有在使用有 Web 能力的 Spring 上下文时才有效 |
session | 定义 bean 的 scope 是 HTTP 会话。只有在使用有 Web 能力的 Spring ApplicationContext 才有效 |
application | 定义了每个 ServletContext 一个实例 |
websocket | 定义了每个 WebSocket 一个实例。只有在使用有 Web 能力的 Spring ApplicationContext 才有效 |
如果上述 scope 仍然不能满足你的需求,Spring 还预留了接口,允许你自定义 scope。
Scope 接口
org.springframework.beans.factory.config.Scope
接口用于定义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();
}
一般来说,只需要重新 get 和 remove 方法即可。
自定义线程范围内的scope
现在进入实战环节。我们要自定义一个Spring没有的scope,该scope将bean的作用范围限制在了线程内。即,相同线程内的bean是同个对象,跨线程则是不同的对象。
1. 定义scope
要自定义一个Spring的scope,只需实现 org.springframework.beans.factory.config.Scope接口。代码如下:
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年2月13日
* @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);
// 不存在则放入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;
}
}
在上述代码中,threadLoacal用于做线程之间的数据隔离。换言之,threadLoacal实现了相同的线程相同名字的bean是同一个对象;不同的线程的相同名字的bean是不同的对象。
同时,我们将对象的hashCode打印了出来。如果他们是相同的对象,则hashCode是相同的。
2. 注册scope
定义一个AppConfig配置类,将自定义的scope注册到容器中去。代码如下:
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年2月13日
* @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());
// 配置scope
customScopeConfigurer.setScopes(map);
return customScopeConfigurer;
}
}
“threadScope”就是自定义ThreadScope的名称。
3. 使用scope
接下来就根据一般的scope的用法,来使用自定义的scope了。代码如下:
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年2月13日
* @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!";
}
}
其中@Scope("threadScope")中的“threadScope”就是自定义ThreadScope的名称。
4. 定义应用入口
定义Spring应用入口:
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年2月13日
* @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();
}
}
运行应用观察控制台输出如下:
Not exists messageServiceImpl; hashCode: 2146338580
Exists messageServiceImpl; hashCode: 2146338580
输出的结果也就验证了ThreadScope“相同的线程相同名字的bean是同一个对象”。
如果想继续验证ThreadScope“不同的线程的相同名字的bean是不同的对象”,则只需要将Application改造为多线程即可。
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年2月13日
* @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(()->{
//模拟执行耗时任务
MessageService messageService = context.getBean(MessageService.class);
messageService.getMessage();
MessageService messageService2 = context.getBean(MessageService.class);
messageService2.getMessage();
//返回结果
return "result";
});
CompletableFuture<String> task2 = CompletableFuture.supplyAsync(()->{
//模拟执行耗时任务
MessageService messageService = context.getBean(MessageService.class);
messageService.getMessage();
MessageService messageService2 = context.getBean(MessageService.class);
messageService2.getMessage();
//返回结果
return "result";
});
task1.get();
task2.get();
}
}
观察输出结果;
Not exists messageServiceImpl; hashCode: 1057328090
Not exists messageServiceImpl; hashCode: 784932540
Exists messageServiceImpl; hashCode: 1057328090
Exists messageServiceImpl; hashCode: 784932540
上述结果验证ThreadScope“相同的线程相同名字的bean是同一个对象;不同的线程的相同名字的bean是不同的对象”
源码
见https://github.com/waylau/spring-5-book 的 s5-ch02-custom-scope-annotation项目。
来源:https://my.oschina.net/waylau/blog/3009991


猜你喜欢
- 命令行编译java文件import java.util.*;public class shuchu{ public
- 一、前言目前 java 垃圾回收主流算法是虚拟机采用 GC Roots Tracing 算法。算法的基本思路是:通过一系列的名为 GC Ro
- 直接看代码,注释都写清楚了public class MainActivity extends Activity { private
- 本文的目的是用springboot整合mybatis实现一个简单的一对多查询。(查询一个用户有多少件衣服)第一步:数据库中,可以直接在nav
- 一、快捷键添加代码块:++快速生成属性等:++导包:+++自动创建变量名:++查找源代码:++按条件查找替换:++快速查看当前类的所有方法:
- 本文实例为大家分享了JAVA NIO实现简单聊天室功能的具体代码,供大家参考,具体内容如下服务端初始化一个ServerSocketChann
- 1.查询(get)-调用的时候记得开线程GET一般用于获取/查询资源信息val sb = StringBuffer() try { &nbs
- java 中链表的定义与使用方法Java实现链表主要依靠引用传递,引用可以理解为地址,链表的遍历多使用递归,这里我存在一个疑问同一个类的不同
- 前言回调的核心就是回调方将本身即this传递给调用方,这样调用方就可以在调用完毕之后告诉回调方它想要知道的信息。1、什么是回调软件模块之间总
- 本文实例讲述了Java基本数据类型与类型转换。分享给大家供大家参考,具体如下:相关内容:基本数据类型整型浮点型字符型布尔型数据类型转换数组首
- 一、项目简述功能:用户的邮箱注册、验证码验证以及用户登录。 不需要注册账号,也可以上传满足条件的临时文件,但是只4小时内有效。 文件的管理,
- 前言 对于服务端,达到高性能、高扩展离不开异步。对于客户端,函数执行时间是1毫秒还是100毫秒
- 每天上下楼都是乘坐电梯的,就想电梯的工作原理是什么呢?于是自己写了个控制台程序来模拟一下电梯的工作原理!采用面向对象的编程思想!将电梯拆解为
- 在后端数据接口项目开发中,经常遇到返回的数据中有null值,导致前端需要进行判断处理,否则容易出现undefined的情况,如何便捷的将nu
- Java当中的类和对象1. 类和对象的初步认知java 是一门面向对象的语言,所谓面向对象有别于面向过程,面向对象是只需对象之间的交互即可完
- 完整代码:https://github.com/iyuanyb/Downloader多线程下载及断点续传的实现是使用 HTTP/1.1 引入
- 常量是固定值,程序执行期间不会改变。常量可以是任何基本数据类型,比如整数常量、浮点常量、字符常量或者字符串常量,还有枚举常量。常量可以被当作
- 虽然Android给我们提供了众多组件,但是使用起来都不是很方便,我们开发的APK都有自己的风格,如果使用了系统自带的组件,总是觉得和应用的
- 理论基础so的加载是一种解析式装载,这与dex有一定区别,dex是先加载进行优化验证生成odex,再去解析odex文件,而so更像边解析边装
- 业务场景近年来B2C、O2O等商业概念的提出和移动端的发展,使得分布式系统流行了起来。分布式系统相对于单系统,解决了流量大、系统高可用和高容