夯实spring(六):Scope--bean的作用域详解

我会带着你远行 2024-03-23 17:19 139阅读 0赞
  • 详解五种bean的sope及使用注意点
  • 自定义作用域的实现

有时候我们需要一个对象在整个应用中只有一个,有些对象希望每次使用的时候都重新创建一个,spring对我们这种需求也提供了支持,在spring中这个叫做bean的作用域,xml中定义bean的时候,可以通过scope属性指定bean的作用域

  1. <bean id="" class="" scope="作用域" />

scope的值常见的有5种:

  • singleton
  • prototype
  • request
  • session
  • application

一,作用域

1,singleton

当scope的值设置为singleton的时候,整个spring容器中只会存在一个bean实例,通过容器多次查找bean的时候(调用BeanFactory的getBean方法或者bean之间注入依赖的bean对象的时候),返回的都是同一个bean对象,singleton是scope的默认值,所以spring容器中默认创建的bean对象是单例的,通常spring容器在启动的时候,会将scope为singleton的bean创建好放在容器中(有个特殊的情况,当bean的lazy被设置为true的时候,表示懒加载,那么使用的时候才会创建),用的时候直接返回。

Car类:

  1. public class Car {
  2. private String brand;
  3. private Double price;
  4. public Car() {
  5. System.out.println("我是Car的无参构造方法");
  6. }
  7. }

bean.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
  6. <!-- 单例bean,scope设置为singleton -->
  7. <bean id="singletonCarBean" class="com.chen.Car" scope="singleton">
  8. </bean>
  9. </beans>

测试输出:

  1. public class Main {
  2. public static void main(String[] args) {
  3. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:bean.xml");
  4. System.out.println(context.getBean("singletonCarBean"));
  5. System.out.println(context.getBean("singletonCarBean"));
  6. System.out.println(context.getBean("singletonCarBean"));
  7. }
  8. }

输出,可以看到都是同一个对象:

  1. <!--构造方法是在容器启动过程中调用的,说明这个bean实例在容器启动过程中就创建好了,放在容器中缓存着-->
  2. 我是Car的无参构造方法
  3. com.chen.Car@ff5b51f
  4. com.chen.Car@ff5b51f
  5. com.chen.Car@ff5b51f

单例bean使用注意
单例bean是整个应用共享的,所以需要考虑到线程安全问题,springmvc中controller默认是单例的,在controller中创建了一些变量,那么这些变量实际上就变成共享的了,controller可能会被很多线程同时访问,这些线程并发去修改controller中的共享变量,可能会出现数据错乱的问题。

1,prototype

scope设置为prototype类型的,表示这个bean是多例的,通过容器每次获取的bean都是不同的实例,每次获取都会重新创建一个bean实例对象。

bean.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
  6. <!-- 多例bean,scope设置为prototype-->
  7. <bean id="prototypeCarBean" class="com.chen.Car" scope="prototype">
  8. </bean>
  9. </beans>

测试输出:

  1. public class Main {
  2. public static void main(String[] args) {
  3. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:bean.xml");
  4. System.out.println(context.getBean("prototypeCarBean"));
  5. System.out.println(context.getBean("prototypeCarBean"));
  6. System.out.println(context.getBean("prototypeCarBean"));
  7. }
  8. }

输出,可以看到每次使用都是新建一个新的

  1. 我是Car的无参构造方法
  2. com.chen.Car@6e06451e
  3. 我是Car的无参构造方法
  4. com.chen.Car@59494225
  5. 我是Car的无参构造方法
  6. com.chen.Car@6e1567f1

容器启动过程中不会去创建Car对象,都是使用的时候才会创建,3次获取prototypeCarBean得到的都是不同的实例,每次获取的时候才会去调用构造方法创建bean实例。如果这个bean比较复杂,创建时间比较长,会影响系统的性能,

request、session、application都是在spring web容器环境中才会有的。

3,request

当一个bean的作用域为request,表示在一次http请求中,一个bean对应一个实例;对每个http请求都会创建一个bean实例,request结束的时候,这个bean也就结束了,request作用域用在spring容器的web环境中。

  1. <bean id="" class="" scope="request" />

4,session

这个和request类似,也是用在web环境中,session级别共享的bean,每个会话会对应一个bean实例,不同的session对应不同的bean实例。

  1. <bean id="" class="" scope="session" />

5,application

全局web应用级别的作用域,也是在web环境中使用的,一个web应用程序对应一个bean实例,通常情况下和singleton效果类似的,不过也有不一样的地方,singleton是每个spring容器中只有一个bean实例,一般我们的程序只有一个spring容器,但是,一个应用程序中可以创建多个spring容器,不同的容器中可以存在同名的bean,但是sope=aplication的时候,不管应用中有多少个spring容器,这个应用中同名的bean只有一个。

  1. <bean id="" class="" scope="application" />

二,自定义作用域

步骤:

  1. 实现Scope接口

    package org.springframework.beans.factory.config;
    import org.springframework.beans.factory.ObjectFactory;
    import org.springframework.lang.Nullable;
    public interface Scope {

    1. /**
    2. * 返回当前作用域中name对应的bean对象
    3. * name:需要检索的bean的名称
    4. * objectFactory:如果name对应的bean在当前作用域中没有找到,那么可以调用这个ObjectFactory来创建这个对象
    5. **/
    6. Object get(String name, ObjectFactory<?> objectFactory);
    7. /**
    8. * 将name对应的bean从当前作用域中移除
    9. **/
    10. @Nullable
    11. Object remove(String name);
    12. /**
    13. * 用于注册销毁回调,如果想要销毁相应的对象,则由Spring容器注册相应的销毁回调,而由自定义作用域选择是不是要销毁相应的对象
    14. */
    15. void registerDestructionCallback(String name, Runnable callback);
    16. /**
    17. * 用于解析相应的上下文数据,比如request作用域将返回request中的属性。
    18. */
    19. @Nullable
    20. Object resolveContextualObject(String key);
    21. /**
    22. * 作用域的会话标识,比如session作用域将是sessionId
    23. */
    24. @Nullable
    25. String getConversationId();

    }

  2. 将自定义的scope注册到容器

  3. 使用自定义的作用域

案例

实现一个线程级别的bean作用域,同一个线程中同名的bean是同一个实例,不同的线程中的bean是不同的实例。

1,创建ThreadScope类并实现Scope接口
  1. package com.chen;
  2. import org.springframework.beans.factory.ObjectFactory;
  3. import org.springframework.beans.factory.config.Scope;
  4. import java.util.HashMap;
  5. import java.util.Map;
  6. import java.util.Objects;
  7. /**
  8. * @Author @Chenxc
  9. * @Date 2023.08.03 9:30
  10. */
  11. public class ThreadScope implements Scope {
  12. public static final String THREAD_SCOPE = "thread";//@1
  13. private ThreadLocal<Map<String, Object>> beanMap = ThreadLocal.withInitial(()->{
  14. return new HashMap<>();});
  15. @Override
  16. public Object get(String s, ObjectFactory<?> objectFactory) {
  17. Object bean = beanMap.get().get(s);
  18. if (Objects.isNull(bean)) {
  19. bean = objectFactory.getObject();
  20. beanMap.get().put(s, bean);
  21. }
  22. return bean;
  23. }
  24. @Override
  25. public Object remove(String s) {
  26. return this.beanMap.get().remove(s);
  27. }
  28. @Override
  29. public void registerDestructionCallback(String s, Runnable runnable) {
  30. //bean作用域范围结束的时候调用的方法,用于bean清理
  31. System.out.println(s);
  32. }
  33. @Override
  34. public Object resolveContextualObject(String s) {
  35. return null;
  36. }
  37. @Override
  38. public String getConversationId() {
  39. return null;
  40. }
  41. }

2,将自定义的scope注册到容器

  1. public class Main {
  2. public static void main(String[] args) throws Exception {
  3. String beanXml = "classpath:bean.xml";
  4. //手动创建容器
  5. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(){
  6. @Override
  7. protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
  8. //向容器中注册自定义的scope
  9. beanFactory.registerScope(ThreadScope.THREAD_SCOPE, new ThreadScope());//向容器中注册了自定义的ThreadScope
  10. super.postProcessBeanFactory(beanFactory);
  11. }
  12. };
  13. //设置配置文件位置
  14. context.setConfigLocation(beanXml);
  15. //启动容器
  16. context.refresh();
  17. //使用容器获取bean
  18. for (int i = 0; i < 2; i++) {
  19. //创建了2个线程,然后在每个线程中去获取同样的bean 2次
  20. new Thread(() -> {
  21. System.out.println(Thread.currentThread() + "," + context.getBean("threadCarBean"));
  22. System.out.println(Thread.currentThread() + "," + context.getBean("threadCarBean"));
  23. }).start();
  24. TimeUnit.SECONDS.sleep(1);
  25. }
  26. }

注意:

  1. //向容器中注册自定义的scope
  2. beanFactory.registerScope(ThreadScope.THREAD_SCOPE, new ThreadScope());//向容器中注册了自定义的ThreadScope

3,在bean.xml中使用自定义Scope

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
  6. <!-- 自定义Scope,scope设置为thread-->
  7. <bean id="threadCarBean" class="com.chen.Car" scope="thread"></bean>
  8. </beans>

运行上面输出:

  1. 我是Car的无参构造方法
  2. Thread[Thread-1,5,main],com.chen.Car@e9db358
  3. Thread[Thread-1,5,main],com.chen.Car@e9db358
  4. 我是Car的无参构造方法
  5. Thread[Thread-2,5,main],com.chen.Car@2a0e29d5
  6. Thread[Thread-2,5,main],com.chen.Car@2a0e29d5

可以看到,bean在同样的线程中获取到的是同一个bean的实例,不同的线程中bean的实例是不同的。

总结:

  • spring容器自带的有2种作用域,分别是singleton和prototype;还有3种分别是spring
    web容器环境中才支持的request、session、application
  • singleton是spring容器默认的作用域,一个spring容器中同名的bean实例只有一个,多次获取得到的是同一个bean;单例的bean需要考虑线程安全问题
  • prototype是多例的,每次从容器中获取同名的bean,都会重新创建一个;多例bean使用的时候需要考虑创建bean对性能的影响, 一个应用中可以有多个spring容器
  • 自定义scope 3个步骤,实现Scope接口,将实现类注册到spring容器,使用自定义的sope

发表评论

表情:
评论列表 (有 0 条评论,139人围观)

还没有评论,来说两句吧...

相关阅读