如何在Spring中自定义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" 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项目。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 浅谈spring中scope作用域

    今天研究了一下scope的作用域.默认是单例模式,即scope="singleton".另外scope还有prototype.request.session.global session作用域.scope="prototype"多例.再配置bean的作用域时,它的头文件形式如下: 如何使用spring的作用域: <bean id="role" class="spring.chapter2.maryGame.Role" s

  • spring boot补习系列之几种scope详解

    目标 了解HTTP 请求/响应头及常见的属性: 了解如何使用SpringBoot处理头信息 : 了解如何使用SpringBoot处理Cookie : 学会如何对 Session 进行读写: 了解如何在不同请求间传递 flash参数 一.Http 头信息 HTTP 头(Header)是一种附加内容,独立于请求内容和响应内容. HTTP 协议中的大量特性都通过Header信息交互来实现,比如内容编解码.缓存.连接保活等等. 如下面的一个请求响应: Request Accept: text/html,

  • 如何在Spring中自定义scope的方法示例

    大家对于 Spring 的 scope 应该都不会默认.所谓 scope,字面理解就是"作用域"."范围",如果一个 bean 的 scope 配置为 singleton,则从容器中获取 bean 返回的对象都是相同的:如果 scope 配置为prototype,则每次返回的对象都不同. 一般情况下,Spring 提供的 scope 都能满足日常应用的场景.但如果你的需求极其特殊,则本文所介绍自定义 scope 合适你. Spring 内置的 scope 默认时,所

  • 如何在Django中设置定时任务的方法示例

    Django 作为后端Web开发框架,有时候我们需要用到定时任务来或者固定频次的任务来执行某段代码,这时我们就要用到Celery了.Django中有一个中间件:Django-celery 环境: Python 3.6 Django为小于1.8版本 Celery为3.1版本 第一步安装:django-celery pip install django-celery 第二步:配置celery和任务 创建测试django环境: django-admin.py createproject test dj

  • 简单了解如何在spring中使用RabbitMQ

    这篇文章主要介绍了简单了解如何在spring中使用RabbitMQ,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 常见的消息中间件产品: (1)ActiveMQ ActiveMQ 是Apache出品,最流行的,能力强劲的开源消息总线.ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现. (2)RabbitMQ AMQP协议的领导实现,支持多种场景.淘宝的MySQL集群内部有使用它进行通讯,Open

  • Spring中自定义数据类型转换的方法详解

    目录 类型转换服务 实现Converter接口 实现ConverterFactory接口 实现GenericConverter接口 环境:Spring5.3.12.RELEASE. Spring 3引入了一个core.onvert包,提供一个通用类型转换系统.系统定义了一个SPI来实现类型转换逻辑,以及一个API来在运行时执行类型转换.在Spring容器中,可以使用这个系统作为PropertyEditor实现的替代,将外部化的bean属性值字符串转换为所需的属性类型.还可以在应用程序中需要类型转

  • 详解如何在SpringBoot中自定义参数解析器

    目录 前言 1.自定义参数解析器 2.PrincipalMethodArgumentResolver 3.RequestParamMapMethodArgumentResolver 4.小结 前言 在一个 Web 请求中,参数我们无非就是放在地址栏或者请求体中,个别请求可能放在请求头中. 放在地址栏中,我们可以通过如下方式获取参数: String javaboy = request.getParameter("name "); 放在请求体中,如果是 key/value 形式,我们可以通

  • 使用Spring中的scope配置和@scope注解

    目录 Spring的scope配置和@scope注解 1. Spring的作用域在装配Bean 2. 基于注解开发时 Spring中的scope详解 1. scope概论 2. scope历史及分类 3. 单个scope详解 4. scope配置 Spring的scope配置和@scope注解 Scope,也称作用域,在 Spring IoC 容器是指其创建的 Bean 对象相对于其他 Bean 对象的请求可见范围. 在 Spring IoC 容器中具有以下几种作用域: 基本作用域(single

  • Spring中自定义拦截器的使用

    1.创建自定义拦截器类(UserTokenInterceptor)并实现HandlerInterceptor 接口,再重写方法,代码如下: public class UserTokenInterceptor implements HandlerInterceptor { /** * @description 访问Controller之前执行 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletR

  • spring缓存自定义resolver的方法

    目录 一.概述 二.缓存的读取和失效 三.自定义缓存resolver 四.自定义resolver的实现 五.总结 本文介绍spring中自定义缓存resolver,通过自定义resolver,可以在spring的cache注解中增加附加处理. 一.概述 cache-aside模式是常用的缓存使用模式. 使用流程如下图: 当更新数据库中的数据后,对缓存做失效处理,后续就能读取到数据库中最新的数据,使得缓存数据与数据库数据保持一致. 在spring中通过cache注解进行缓存的处理,一般会把缓存处理

  • jquery遍历标签中自定义的属性方法

    在开发中我们有时会对html标签添加属性,如何遍历处理? <ul> <li name="li1" sortid="nav_1">aaaaaaa</li> <li name="li1" sortid="nav_2">bbbbbbb</li> <li name="li1" sortid="nav_3">cccccccc&

  • SpringBoot 中使用JSP的方法示例

    本文介绍了SpringBoot 中使用JSP的方法示例,分享给大家,具体如下: 依赖: <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> <relativePath/> <!-- l

随机推荐