如何在 Spring 中自定义 scope

大家对于 Spring 的 scope 应该都不会默认。所谓 scope,字面理解就是“作用域”、“范围”,如果一个 bean 的 scope 配置为 singleton,则从容器中获取 bean 返回的对象都是相同的;如果 scope 配置为prototype,则每次返回的对象都不同。

一般情况下,Spring 提供的 scope 都能满足日常应用的场景。但如果你的需求极其特殊,则本文所介绍自定义 scope 合适你。

Spring 内置的 scope

默认时,所有 Spring bean 都是的单例的,意思是在整个 Spring 应用中,bean的实例只有一个。可以在 bean 中添加 scope 属性来修改这个默认值。scope 属性可用的值如下:

如果上述 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">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">Way Lau</a>
 */@Configuration@ComponentScanpublic 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">Way Lau</a>
     */@Scope("threadScope")@Servicepublic 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">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: 2146338580Exists 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">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: 
1057328090Not exists messageServiceImpl;
 hashCode: 784932540Exists messageServiceImpl; 
 hashCode: 1057328090Exists messageServiceImpl; hashCode: 784932540

上述结果验证ThreadScope“相同的线程相同名字的bean是同一个对象;不同的线程的相同名字的bean是不同的对象”

源码
见https://github.com/waylau/spring-5-book 的 s5-ch02-custom-scope-annotation项目。

欢迎工作一到五年的Java工程师朋友们加入Java架构开发: 957734884,群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值