首页 > 其他分享 >在 Spring 中自定义 scope

在 Spring 中自定义 scope

时间:2023-06-04 12:05:18浏览次数:44  
标签:自定义 Spring springframework bean context org import scope


大家对于 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">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
@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">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">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">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-books5-ch02-custom-scope-annotation项目。

标签:自定义,Spring,springframework,bean,context,org,import,scope
From: https://blog.51cto.com/u_9427273/6410308

相关文章

  • Spring 的狭义与广义
    Java开发者对于Spring应该不会陌生。Spring可以说是JavaEE开发事实上的标准。无论是Web开发,还是分布式应用,Spring都致力于简化开发者创建应用的复杂性。本文讨论Spring在狭义上以及广义上,所承载的不同的概念。Spring有广义与狭义之说。狭义上的Spring——SpringFram......
  • Spring RestTemplate 调用天气预报接口乱码的解决
    SpringRestTemplate调用天气预报接口可能遇到中文乱码的问题,解决思路如下。问题出现我们在网上找了一个免费的天气预报接口http://wthrcdn.etouch.cn/weather_mini?citykey=101280601。我们希望调用该接口,并将返回的数据解析为JSON格式。核心业务逻辑如下:privateWeatherRespo......
  • Flex/AS3/flash player支持屏蔽右键菜单,自定义菜单,并设置相应的菜单事件(示例,图解)..
    播放器版本11.2以后支持右键菜单屏蔽及自定义菜单1.更新播放器,11.2以上版本http://download.macromedia.com/get/flashplayer/updaters/11/playerglobal11_3.swchttp://download.macromedia.com/get/flashplayer/updaters/11/playerglobal11_4.swchttp://download.macro......
  • 踩坑|以为是Redis缓存没想到却是Spring事务!
    前言  最近碰到了一个Bug,折腾了我好几天。并且这个Bug不是必现的,出现的概率比较低。一开始我以为是旧数据的问题,就让测试重新生成了一下数据,重新测试。由于后面几轮测试均未出现,我也就没太在意。  可惜好景不长,测试反馈上次的问题又出现了。于是我立马着手排查,根据日志的表......
  • 组件的自定义事件
      专门用来看自定义事件的:  ......
  • 聊聊Spring Cloud Gateway
    网关概述整体来看,网关有点类似于门面,所有的外部请求都会先经过网关这一层。网关不仅只是做一个请求的转发及服务的整合,有了网关这个统一的入口之后,它还能提供以下功能。针对所有请求进行统一鉴权、限流、熔断、日志。协议转化。针对后端多种不同的协议,在网关层统一处理后以HT......
  • svgicon 实现自定义 svg icon
      对于后台管理框架,经常要用到自定义的svg来当做路由的icon图标。https://mmf-fe.github.io/svgicon,这是这款插件的地址。这里总结下在vue3+vite中使用改插件的方式。安装yarnadd@yzfe/svgicon@yzfe/vue3-svgiconyarnaddvite-plugin-svgicon--dev配置vit......
  • 二、JMX自定义MBean
    一、自定义MBeanpublicinterfaceHelloMBean{StringgetName();voidsetName(Stringname);Stringprint();}HelloMBean必须以MBean结尾。@Slf4jpublicclassHelloimplementsHelloMBean{privateStringname;@OverridepublicSt......
  • (五)Spring源码解析:ApplicationContext源码解析
    一、概述1.1>整体概览在前面的内容中,我们针对BeanFactory进行了深度的分析。那么,下面我们将针对BeanFactory的功能扩展类ApplicationContext进行深度的分析。ApplicationConext与BeanFactory的功能相似,都是用于向IOC中加载Bean的。由于ApplicationConext的功能是大于BeanFactory的......
  • 《面试1v1》Spring循环依赖
    我是javapub,一名Markdown程序员从......