Spring Scope作用域及源码解析

文章目录

        • 作用域范围说明
        • singleton & prototype 举例说明
        • prototype Bean销毁示例
        • request & session & application
        • Scope源码分析
          • @RequestScope & @SessionScope
        • Scope扩展

写在前面,这里使用的spring-context版本 5.1.3.RELEASE;SpringBoot版本2.2.4.RELEASE

作用域范围说明

来源 说明
singleton 默认Spring Bean的作用域,一个BeanFactory有且仅有一个实例
prototype 原型作用域,每次依赖查找和依赖注入生成新Bean对象
request Web环境下,将Spring Bean存储在ServletRequest 上下文中
session Web环境下 将Spring Bean存储在HttpSession中
application Web环境下 将Spring Bean存储在ServletContext中

singleton & prototype 举例说明

先来看一下singleton、prototype这两种作用域

public class BeanScopeDemo {

    @Autowired
    @Qualifier("singletonDTO")
    private BeanScopeDTO singletonDTO;

    @Autowired
    @Qualifier("singletonDTO")
    private BeanScopeDTO singletonDTO2;

    @Autowired
    @Qualifier("prototypeDTO")
    private BeanScopeDTO prototypeDTO;

    @Autowired
    @Qualifier("prototypeDTO")
    private BeanScopeDTO prototypeDTO2;

    @Autowired
    private List<BeanScopeDTO> dtoList;

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
      
        context.register(BeanScopeDemo.class);

        context.refresh();
				//依赖查找
        scopedBeansByLookup(context);
        //依赖注入
        scopedBeansByInjection(context);

        BeanScopeDemo beanScopeDemo = context.getBean(BeanScopeDemo.class);

        System.out.println("beanScopeDemo.dtoList = " + beanScopeDemo.dtoList);

        context.close();
    }

    private static void scopedBeansByLookup(AnnotationConfigApplicationContext applicationContext) {

        System.out.println("===========依赖查找=============");
        for (int i = 0; i < 3; i++) {
            // singletonDTO 是共享 Bean 对象
            BeanScopeDTO singletonDTO = applicationContext.getBean("singletonDTO", BeanScopeDTO.class);
            System.out.println("singletonDTO = " + singletonDTO);
            // prototypeDTO 是每次依赖查找均生成了新的 Bean 对象
            BeanScopeDTO prototypeDTO = applicationContext.getBean("prototypeDTO", BeanScopeDTO.class);
            System.out.println("prototypeDTO = " + prototypeDTO);
        }
    }

    private static void scopedBeansByInjection(AnnotationConfigApplicationContext applicationContext) {

        System.out.println("===========依赖注入=============");

        BeanScopeDemo beanScopeDemo = applicationContext.getBean(BeanScopeDemo.class);

        System.out.println("beanScopeDemo.singletonDTO = " + beanScopeDemo.singletonDTO);
        System.out.println("beanScopeDemo.singletonDTO2 = " + beanScopeDemo.singletonDTO2);

        System.out.println("beanScopeDemo.prototypeDTO = " + beanScopeDemo.prototypeDTO);

        System.out.println("beanScopeDemo.prototypeDTO2 = " + beanScopeDemo.prototypeDTO2);
        System.out.println("===========依赖注入=============");

    }


    @Bean
    public BeanScopeDTO singletonDTO() {
        return new BeanScopeDTO();
    }

    @Bean
    @Scope("prototype")
    public BeanScopeDTO prototypeDTO() {
        return new BeanScopeDTO();
    }

    public static class BeanScopeDTO implements BeanNameAware, InitializingBean, DisposableBean {

        private String beanName;

        @Override
        public void setBeanName(String name) {
            this.beanName = name;
        }

        @Override
        public void destroy() throws Exception {
            System.out.println(beanName + "正在执行销毁操作");
        }

        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println(beanName + "正在执行初始化操作");
        }
    }

}

上面的例子中声明了一个对象,并将其分别以不同的作用域注入到IoC容器中,来观察下在依赖注入与依赖查找下对象的特性

输出结果:

singletonDTO正在执行初始化操作
prototypeDTO正在执行初始化操作
prototypeDTO正在执行初始化操作
prototypeDTO正在执行初始化操作
===========依赖查找=============
singletonDTO = com.wojiushiwo.scope.BeanScopeDTO@4a87761d
prototypeDTO正在执行初始化操作
prototypeDTO = com.wojiushiwo.scope.BeanScopeDTO@4c762604
singletonDTO = com.wojiushiwo.scope.BeanScopeDTO@4a87761d
prototypeDTO正在执行初始化操作
prototypeDTO = com.wojiushiwo.scope.BeanScopeDTO@2641e737
singletonDTO = com.wojiushiwo.scope.BeanScopeDTO@4a87761d
prototypeDTO正在执行初始化操作
prototypeDTO = com.wojiushiwo.scope.BeanScopeDTO@727803de
===========依赖注入=============
beanScopeDemo.singletonDTO = com.wojiushiwo.scope.BeanScopeDTO@4a87761d
beanScopeDemo.singletonDTO2 = com.wojiushiwo.scope.BeanScopeDTO@4a87761d
beanScopeDemo.prototypeDTO = com.wojiushiwo.scope.BeanScopeDTO@2f0a87b3
beanScopeDemo.prototypeDTO2 = com.wojiushiwo.scope.BeanScopeDTO@319b92f3
===========依赖注入=============
beanScopeDemo.dtoList = [com.wojiushiwo.scope.BeanScopeDTO@4a87761d, com.wojiushiwo.scope.BeanScopeDTO@66d1af89]
singletonDTO正在执行销毁操作

结论:

从Bean生命周期角度看,单例Bean拥有完整的生命周期,而prototype的Bean则没有完整的生命周期,只进行了初始化操作,而未进行destroy操作。

Singleton Bean 无论依赖查找还是依赖注入,均为同一个对象

Prototype Bean 无论依赖查找还是依赖注入,均为新生成的对象

如果依赖注入集合类型的对象,Singleton Bean 和 Prototype Bean 均会存在一个

因此我们说,Spring 不会维护原型Bean的整个生命周期的,在使用原型的bean时,要注意原型bean的销毁工作,使用不当甚至会导致OOM

prototype Bean销毁示例

那么原型Bean要怎么进行销毁呢?可以参考下面这种方式

在当前配置类中实现DisposableBean,手动执行注入Bean的销毁操作

public class BeanScopeDemo implements DisposableBean {

    @Autowired
    @Qualifier("singletonDTO")
    private BeanScopeDTO singletonDTO;

    @Autowired
    @Qualifier("singletonDTO")
    private BeanScopeDTO singletonDTO2;

    @Autowired
    @Qualifier("prototypeDTO")
    private BeanScopeDTO prototypeDTO;

    @Autowired
    @Qualifier("prototypeDTO")
    private BeanScopeDTO prototypeDTO2;

    @Autowired
    private List<BeanScopeDTO> dtoList;

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(BeanScopeDemo.class);

        context.refresh();

        scopedBeansByLookup(context);
        scopedBeansByInjection(context);

        BeanScopeDemo beanScopeDemo = context.getBean(BeanScopeDemo.class);

        System.out.println("beanScopeDemo.dtoList = " + beanScopeDemo.dtoList);

        context.close();
    }

    private static void scopedBeansByLookup(AnnotationConfigApplicationContext applicationContext) {

        System.out.println("===========依赖查找=============");
        for (int i = 0; i < 3; i++) {
            // singletonDTO 是共享 Bean 对象
            BeanScopeDTO singletonDTO = applicationContext.getBean("singletonDTO", BeanScopeDTO.class);
            System.out.println("singletonDTO = " + singletonDTO);
            // prototypeDTO 是每次依赖查找均生成了新的 Bean 对象
            BeanScopeDTO prototypeDTO = applicationContext.getBean("prototypeDTO", BeanScopeDTO.class);
            System.out.println("prototypeDTO = " + prototypeDTO);
        }
    }

    private static void scopedBeansByInjection(AnnotationConfigApplicationContext applicationContext) {

        System.out.println("===========依赖注入=============");

        BeanScopeDemo beanScopeDemo = applicationContext.getBean(BeanScopeDemo.class);

        System.out.println("beanScopeDemo.singletonDTO = " + beanScopeDemo.singletonDTO);
        System.out.println("beanScopeDemo.singletonDTO2 = " + beanScopeDemo.singletonDTO2);

        System.out.println("beanScopeDemo.prototypeDTO = " + beanScopeDemo.prototypeDTO);

        System.out.println("beanScopeDemo.prototypeDTO2 = " + beanScopeDemo.prototypeDTO2);
        System.out.println("===========依赖注入=============");
    }


    @Bean
    public BeanScopeDTO singletonDTO() {
        return new BeanScopeDTO();
    }

    @Bean
    @Scope("prototype")
    public BeanScopeDTO prototypeDTO() {
        return new BeanScopeDTO();
    }

  //BeanScopeDTO与之前一样 这里省略了
	
    @Override
    public void destroy() throws Exception {
        System.out.println("当前 BeanScopeDemo Bean 正在销毁中...");

        this.singletonDTO.destroy();
        this.singletonDTO2.destroy();
        this.prototypeDTO.destroy();
        this.prototypeDTO2.destroy();
        // 获取 BeanDefinition
        for (BeanScopeDTO beanScopeDTO : this.dtoList) {
          beanScopeDTO.destroy();
        }

        System.out.println("当前 BeanScopeDemo Bean 销毁完成");
    }

    
}

request & session & application

由于request、session、application作用域与Web有关,这里以SpringBoot Web举例演示

@SpringBootApplication
public class BeanScopeApplication {
    public static void main(String[] args) {
        SpringApplication.run(BeanScopeApplication.class, args);
    }
    @Bean
    @RequestScope
    public User requestUser() {
        return new User(3, "requestUser");
    }

    @Bean
    @SessionScope
    public User sessionUser() {
        return new User(4, "sessionUser");
    }

    @Bean
    @ApplicationScope
    public User applicationUser() {
        return new User(4, "applicationUser");
    }

}
@RestController
@RequestMapping("/api/bean/scope")
public class BeanScopeController {

    @Autowired
    private User requestUser;

    @Autowired
    private User sessionUser;

    @Autowired
    private User applicationUser;

    @GetMapping
    public void beanScopePrint(){
        System.out.println("requestUser==>" + requestUser);
        System.out.println("sessionUser==>" + sessionUser);
        System.out.println("applicationUser==>" + applicationUser);

        System.out.println("====================");
    }

}

User类是普通的对象,只是实现了初始化、销毁以及BeanNameAware,这里不再贴出来了

输出结果:

scopedTarget.requestUser正在 初始化
requestUser==>com.wojiushwo.scope.domain.User@69920699
scopedTarget.sessionUser正在 初始化
sessionUser==>com.wojiushwo.scope.domain.User@4d50ec2
scopedTarget.applicationUser正在 初始化
applicationUser==>com.wojiushwo.scope.domain.User@678f3c22
====================
scopedTarget.requestUser正在 销毁
scopedTarget.requestUser正在 初始化
requestUser==>com.wojiushwo.scope.domain.User@4168adbd
scopedTarget.sessionUser正在 初始化
sessionUser==>com.wojiushwo.scope.domain.User@4faf4be0
applicationUser==>com.wojiushwo.scope.domain.User@678f3c22
====================
scopedTarget.requestUser正在 销毁
2022-01-26 14:35:26.065  INFO 39763 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'
singletonUser正在 销毁
scopedTarget.sessionUser正在 销毁
scopedTarget.sessionUser正在 销毁

通过多次请求,发现一些特点:

  • request作用域 每次请求都是一个新对象
  • session作用域 每一个会话 是一个对象
  • application作用于 则是整个应用是一个对象,其实这相当于singleton了
  • 这三种作用域对应的BeanName与普通BeanName 也有所不同,变成了scopedTarget.+原始BeanName

Scope源码分析

透过源码AbstractBeanFactory#doGetBean我们可以看出,在getBean时会根据不同scope进行不同处理,截取部分代码看下

//如果是单例
if (mbd.isSingleton()) {
          //这时会运用单例对象的缓存,先去缓存中找,找不到再创建 最后再放缓存一份
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							destroySingleton(beanName);
							throw ex;
						}
					});
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}
				//如果是prototype
				else if (mbd.isPrototype()) {
					// It's a prototype -> create a new instance.
					Object prototypeInstance = null;
					try {
						beforePrototypeCreation(beanName);
            //从这里看出 每次getBean 均是新创建的一个对象
						prototypeInstance = createBean(beanName, mbd, args);
					}
					finally {
						afterPrototypeCreation(beanName);
					}
					bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
				}
				//这个else分支 就是处理request、session、application这些作用域啦
				else {
					String scopeName = mbd.getScope();
					if (!StringUtils.hasLength(scopeName)) {
						throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
					}
					Scope scope = this.scopes.get(scopeName);
					if (scope == null) {
						throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
					}
					try {
            //这里Scope是个接口,每种作用域对应一个类,如@RequestScope对应RequestScope
            //@SessionScope对应SessionScope等等
						Object scopedInstance = scope.get(beanName, () -> {
							beforePrototypeCreation(beanName);
							try {
                //当从scope域中拿不到缓存Bean时,调用这个lambda方法去创建Bean
								return createBean(beanName, mbd, args);
							}
							finally {
								afterPrototypeCreation(beanName);
							}
						});
						bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
					}
					catch (IllegalStateException ex) {
						throw new BeanCreationException(beanName,
								"Scope '" + scopeName + "' is not active for the current thread; consider " +
								"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
								ex);
					}
				}
			}

我们着重看一下Scope接口及其实现,弄清楚request、session、application三个作用域在实现上的不同

public interface Scope {

	
  //这是Scope接口的核心操作,根据name在指定Scope下获取Bean
	Object get(String name, ObjectFactory<?> objectFactory);

	//在指定Scope下 移除名称为name的Bean 在destroyScopeBean时会被用到,如AbstractBeanFactory#在destroyScopeBean时会被用到
	@Nullable
	Object remove(String name);

	//为指定name的Bean注册销毁时的回调
	void registerDestructionCallback(String name, Runnable callback);

	//使用key解析上下文对象
	@Nullable
	Object resolveContextualObject(String key);

	//创建会话id
	@Nullable
	String getConversationId();

}

Spring Scope作用域及源码解析_第1张图片

@RequestScope & @SessionScope

我们以@RequestScope、@SessionScope来分析下源码

public abstract class AbstractRequestAttributesScope implements Scope {

	@Override
	public Object get(String name, ObjectFactory<?> objectFactory) {
    // 这里获取到的是绑定到当前线程上的RequestAttributes
		RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
    //从请求域中尝试获取指定beanName,指定scope的Bean
		Object scopedObject = attributes.getAttribute(name, getScope());
    //若获取不到指定Bean
		if (scopedObject == null) {
      //则调用objectFactory.getObject() 去createBean 并将创建的Bean 放到请求域中缓存起来
			scopedObject = objectFactory.getObject();
			attributes.setAttribute(name, scopedObject, getScope());
			Object retrievedObject = attributes.getAttribute(name, getScope());
			if (retrievedObject != null) {
				// Only proceed with retrieved object if still present (the expected case).
				// If it disappeared concurrently, we return our locally created instance.
				scopedObject = retrievedObject;
			}
		}
		return scopedObject;
	}

	//...
  //留给子类去实现
	protected abstract int getScope();

}

attributes.setAttribute(name, scopedObject, getScope());这句话是关键,它会根据你的scope,而选择将bean保存到request还是session中

 //ServletRequestAttributes
@Override
	public void setAttribute(String name, Object value, int scope) {
		if (scope == SCOPE_REQUEST) {
			if (!isRequestActive()) {
				throw new IllegalStateException(
						"Cannot set request attribute - request is not active anymore!");
			}
			this.request.setAttribute(name, value);
		}
		else {
			HttpSession session = obtainSession();
			this.sessionAttributesToUpdate.remove(name);
			session.setAttribute(name, value);
		}
	}
public class RequestScope extends AbstractRequestAttributesScope {
	
	@Override
	protected int getScope() {
		return RequestAttributes.SCOPE_REQUEST;
	}
	@Override
	@Nullable
	public String getConversationId() {
		return null;
	}

}

RequestScope类的实现就很简单了,只是配合重写了getScope方法。

public class SessionScope extends AbstractRequestAttributesScope {

	@Override
	protected int getScope() {
		return RequestAttributes.SCOPE_SESSION;
	}
	//获取会话Id
	@Override
	public String getConversationId() {
		return RequestContextHolder.currentRequestAttributes().getSessionId();
	}

	@Override
	public Object get(String name, ObjectFactory<?> objectFactory) {
		Object mutex = RequestContextHolder.currentRequestAttributes().getSessionMutex();
		//这里获取互斥信号量,同一个session,只创建一次指定Bean,随后就从session缓存中取
    synchronized (mutex) {
			return super.get(name, objectFactory);
		}
	}

	@Override
	@Nullable
	public Object remove(String name) {
    //获取互斥信号量时,会生成或复用一个session,该session会与ServletRequestAttribues关联,在后续获取Bean时会用到
    //同一个session下,mutex也会复用,详见WebUtils#getSessionMutex
		Object mutex = RequestContextHolder.currentRequestAttributes().getSessionMutex();
		synchronized (mutex) {
			return super.remove(name);
		}
	}

}

上面简要地了解了RequestScopeSessionScope的实现,现在我们已经清楚了,其实request域下的bean 是存放在HttpServletRequest中的,而session域下的bean是存放在HttpSession中的。

由于每一个请求其HttpServletRequest对象均不同,所以@RequestScope 每次都是新对象。但是同一个会话下Session是共享的(浏览器下每一个tab页代表一个会话,JSESSIONID是不同的),因此@SessionScope同一个会话下多次请求得到的依然是同一个对象。

Scope扩展

其实Scope作用域也有很多用途的,也有不少框架选择扩展它,比如RefreshScope,它可以实现动态刷新配置属性。

我们也来尝试扩展一个线程级别的Scope,用途是同一个线程在IoC容器中获得的Bean是相同的。

public class ThreadLocalScope implements Scope {

    public static final String SCOPE_NAME = "thread-local";

    private final NamedThreadLocal<Map<String, Object>> threadLocal = new NamedThreadLocal("thread-local-scope") {
        @Override
        protected Object initialValue() {
            return new HashMap<>();
        }
    };

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        Map<String, Object> context = getContext();
        Object object = context.get(name);
        if (object == null) {
            object = objectFactory.getObject();
            context.put(name, object);
        }
        return object;
    }

    private Map<String, Object> getContext() {
        return threadLocal.get();
    }

    @Override
    public Object remove(String name) {
        return getContext().remove(name);
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {

    }

    @Override
    public Object resolveContextualObject(String key) {
        return getContext().get(key);
    }

    @Override
    public String getConversationId() {
        return String.valueOf(Thread.currentThread().getId());
    }
}
public class ThreadLocalScopeDemo {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(ThreadLocalScopeDemo.class);
        context.addBeanFactoryPostProcessor((beanFactory -> {
            beanFactory.registerScope(ThreadLocalScope.SCOPE_NAME, new ThreadLocalScope());
        }));

        context.refresh();

        scopedBeansByLookup(context);

        context.close();


    }

    private static void scopedBeansByLookup(AnnotationConfigApplicationContext applicationContext) {

        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(() -> {
                // user 是共享 Bean 对象
                User user = applicationContext.getBean("user", User.class);
                System.out.printf("[Thread id :%d] user = %s%n", Thread.currentThread().getId(), user);
                 user = applicationContext.getBean("user", User.class);
                System.out.printf("[Thread id :%d] user = %s%n", Thread.currentThread().getId(), user);
            });

            // 启动线程
            thread.start();
            // 强制线程执行完成
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
		//声明一个自定义Scope的Bean
    @Bean
    @Scope(ThreadLocalScope.SCOPE_NAME)
    public User user() {
        return User.createUser();
    }

}

输出结果:

user初始化
user初始化
[Thread id :12] user = com.wojiushiwo.ioc.overview.domain.User@707095f1
[Thread id :12] user = com.wojiushiwo.ioc.overview.domain.User@707095f1
user初始化
[Thread id :13] user = com.wojiushiwo.ioc.overview.domain.User@5ee96074
[Thread id :13] user = com.wojiushiwo.ioc.overview.domain.User@5ee96074
user初始化
[Thread id :14] user = com.wojiushiwo.ioc.overview.domain.User@3f226d64
[Thread id :14] user = com.wojiushiwo.ioc.overview.domain.User@3f226d64

可以看出 同一个线程 得到的Bean是相同的。

你可能感兴趣的:(Spring,Boot,spring,scope,RequestScope,prototype)