Spring框架中@Scope原型、单例实现原理和使用细节

目录

一、@Scope原型、单例实现原理

1.@Scope配置单例

2.@Scope配置原型

3.实现原理

二、使用细节

1.原型失效示例

1.1 Controller

1.2 Service

2.原型成功示例

2.1 第一种生效方式

2.2 第二种生效方式

三、总结


一、@Scope原型、单例实现原理

在Spring框架中可以使用@Scope注解声明需要创建的bean是原型或者是单例类型的,如果是原型则每次调用getBean获取到的对象都是不同的;如果是单例则调用getBean方法获取的对象每个都是一样的。

1.@Scope配置单例

有两种配置方式,一种是不指定@Scope,直接使用@Component相关注解即可,一般都是使用这种方式指定单例对象:

@Service
public class XXXX {
    ...
}

另一种是指定@Scope注解,这种方式只要懂的@Component的默认bean属性基本上不会使用这种方式:

@Scope("singleton")
@Service
public class XXXX {
    ...
}

2.@Scope配置原型

如下:

@Scope("prototype")
@Service
public class XXXX {
    ...
}

或者:

@Configuration
public class SystemConfig {
    @Scope("prototype")
    @Bean
    public TaskExecutor taskExecutor() {
        ...
    }
}

3.实现原理

接下来简要介绍一下在getBean方法调用链中在哪个地方使用了原型和单例的配置属性。

其关键部分源码如下:

public abstract class AbstractBeanFactory 
        extends FactoryBeanRegistrySupport 
        implements ConfigurableBeanFactory {
    // 单例模式创建的bean都会被缓存在该集合中
    private final Map<String, Object> singletonObjects = 
            new ConcurrentHashMap<>(256);
    @Override
    public Object getBean(String name) throws BeansException {
       // 其它的getBean略过,最终都会调用doGetBean方法
       return doGetBean(name, null, null, false);
    }
    protected <T> T doGetBean(final String name, 
            @Nullable final Class<T> requiredType,
            @Nullable final Object[] args, boolean typeCheckOnly) 
            throws BeansException {
        // getSingleton方法实际上会调用singletonObjects等一系列的单例对象
        // 缓存集合来提前获取一次,因此在单例对象第一次实例化之后后续获取的
        // 就是缓存集合中的对象了
        Object sharedInstance = getSingleton(beanName);
        if (sharedInstance != null && args == null) {
            // 如果是FactoryBean类型的将会在该方法中被处理,并调用getObject
            // 方法获取bean对象,因此能够保证FactoryBean是单例的且getObject
            // 方法返回的一直是同一个对象才能保证getBean返回的对象是同一个
            bean = getObjectForBeanInstance(sharedInstance, name, 
                    beanName, null);
        } else {
            ...
            if (mbd.isSingleton()) {
                // 跑到这里则说明前面的getSingleton方法从缓存中获取到单例对象
                // 意味着Spring工厂中还未创建该对象,因此该方法中最终会调用
                // createBean方法创建bean对象并缓存到单例集合中,方便前面的
                // getSingleton方法直接获取
                sharedInstance = getSingleton(beanName, () -> {
                   try {
                      return createBean(beanName, mbd, args);
                   }
                   catch (BeansException ex) {
                     
                      destroySingleton(beanName);
                      throw ex;
                   }
                });
                // 处理被FactoryBean封装的baen类型
                bean = getObjectForBeanInstance(sharedInstance, name, 
                        beanName, mbd);
            } else if (mbd.isPrototype()) {
                Object prototypeInstance = null;
                try {
                   // 调用createBean方法前设置prototypesCurrentlyInCreation
                   // 相当于为创建该原型Bean对象上锁
                   beforePrototypeCreation(beanName);
                   // 调用createBean方法去创建bean,并且不会缓存
                   prototypeInstance = createBean(beanName, mbd, args);
                }
                finally {
                   // 释放prototypesCurrentlyInCreation,相当于释放锁
                   afterPrototypeCreation(beanName);
                }
                // 处理被FactoryBean封装的baen类型
                bean = getObjectForBeanInstance(prototypeInstance, name, 
                        beanName, mbd);
            } else {
                // 处理其它自定义Scope范围对象
                ...
            }
            ...
        } 
        ...
    }
}

二、使用细节

虽然使用@Scope声明了原型,这个bean在Spring工厂中也确实是原型,但还是有一些使用的小细节需要注意一下。

1.原型失效示例

当使用下面这种方式使用原型bean时,原型bean的声明在使用层面将会失效。

1.1 Controller

示例如下:

@Controller
public class TestController implements InitializingBean {
    @Autowired
    private TestService testService;
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("testController Initializing");
    }
}

1.2 Service

示例如下:

@Scope("prototype")
@Service
public class TestService implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("testService Initializing");
    }
}

失效原因:如果使用这种配置方式,当调用controller的方法时testService对象将永远会是同一个,因为Controller是个单例,其只会在项目启动时被加载一次,自动注入Service时也只是注入一次,注入完这一次后将不再调用getBean设置该Controller的Service。因此对于Controller而言,这里面的Service就是一个单例对象(当然,如果其它的Controller自动注入了这个Service,两个Service确实是不一样的)。

2.原型成功示例

有两种实现方式:

  1. 一种是保证持有原型Service的Controller也是原型bean,这样每次请求进来的Controller和Service都是不一样的;
  2. 一种是手动调用getBean方法获取新的bean对象。

2.1 第一种生效方式

Controller实例如下:

@Controller
public class TestController implements InitializingBean, BeanFactoryAware {
    private BeanFactory beanfactory;
    @Override
    public void setBeanFactory(BeanFactory beanFactory) 
            throws BeansException {
       this.beanfactory = beanfactory ;
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("testController Initializing");
    }
    @GetMapping("/test.json")
    public XXX request() {
        TestService testService = beanFactory.getBean(TestService.class);
        testService.XXXX();
        return new XXX();
    }
}

这种方式为手动获取bean工厂对象,每次需要调用原型bean时则使用getBean方法从Bean工厂中获取对应的新对象,进而完成调用。

2.2 第二种生效方式

Controller示例如下:

@Scope("prototype")
@Controller
public class TestController implements InitializingBean {
    @Autowired
    private TestService testService;
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("testController Initializing");
    }
}

Service不变,这样将能实现每次有新的请求进来时Controller和Service每次都是新的bean对象。

那么为什么可以实现这样的效果呢?原因便在SpringMVC的HandlerMethod类中,关键源码如下:

public class HandlerMethod {
    // 可以是具体的bean对象,也可以是这个bean对象的beanName
    private final Object bean;
    // 设置进来的bean工厂
    private final BeanFactory beanFactory;
    public HandlerMethod createWithResolvedBean() {
       // 当在DispatcherServlet中获取HandlerExecutionChain对象时,最终会调用
       // 到这个方法中
       Object handler = this.bean;
       // 如果是String类型,则需要进入调用getBean方法获取bean对象
       // 如果是单例这个bean则是对应的Controller对象,如果是原型则
       // 是String字符串,是字符串每次都会调用getBean方法获取Controller对象
       if (this.bean instanceof String) {
          String beanName = (String) this.bean;
          handler = this.beanFactory.getBean(beanName);
       }
       return new HandlerMethod(this, handler);
    }
}

可以看到实际上Spring框架也是每次有新的请求都调用了一次getBean方法,在获取Controller原型时又会使用自动注入再注入进一个原型Service,让Spring框架帮我们完成了每次调用都是新的对象这个操作。

三、总结

从上面的例子可以总结出一下几点:

  1. 对于单例对象而言,在里面自动注入原型bean对象,对于单例对象而言,这个原型bean就是单例的,每次调用的都是同一个bean对象;
  2. 要达到每次使用都是新对象,必须得使用getBean方法重新获取新的对象;
  3. 可以将Controller也变成原型对象,使用Spring框架的特性来帮我们调用getBean方法获取原型对象,确保每次调用都是不同的对象。

 

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: @Scope("prototype")是Spring框架的一个注解,用于指定Bean的作用域为原型(Prototype)作用域。 在Spring,Bean的作用域有多种类型,包括单例(Singleton)、原型(Prototype)、会话(Session)、请求(Request)等。其原型作用域表示每次请求该Bean时都会创建一个新的实例。与之相对的是单例作用域,它表示在整个应用程序只创建一个Bean实例。 例如,以下代码演示了如何使用@Scope("prototype")注解来定义一个原型作用域的Bean: ``` @Component @Scope("prototype") public class MyPrototypeBean { // ... } ``` 使用时,只需要在需要使用该Bean的地方注入即可: ``` @Service public class MyService { @Autowired private MyPrototypeBean myPrototypeBean; public void doSomething() { MyPrototypeBean bean1 = myPrototypeBean; MyPrototypeBean bean2 = myPrototypeBean; // bean1和bean2是不同的实例 } } ``` 在上面的例子,每次调用doSomething方法时,都会创建一个新的MyPrototypeBean实例,并且该实例只在当前方法有效。 ### 回答2: @Scope("prototype")是一个Spring框架的注解。它可以用于将Spring容器的bean的作用域设置为原型(prototype)。 在Spring,默认情况下,bean的作用域是单例(Singleton),也就是说容器只会创建一个该类型的对象实例,并且在每次请求该bean时都返回同一个实例。而使用@Scope("prototype")注解后,每次请求该bean时都会创建一个新的实例。 @Scope("prototype")的使用场景包括: 1. 当某个bean的状态会随着请求的不同而变化时,适合使用原型作用域。每次请求都创建一个新的实例,可以确保对象的状态不会受到其他请求的影响。 2. 当某个bean需要使用到其他原型作用域的bean时,可以注入一个原型作用域的bean引用,以保证每次使用时都获取新的实例。 需要注意的是,使用原型作用域的bean在容器的生命周期管理上与单例作用域的bean是不同的。原型作用域的bean不会由容器来管理销毁,也就是说当使用完毕后,需要手动释放资源。 总结来说,@Scope("prototype")是一个用于设置Spring容器bean作用域的注解,它使得每次请求该bean时都会创建一个新的实例。在使用时需要注意手动释放资源的问题。 ### 回答3: @Scope("prototype")是Spring框架的一个注解,用于指定Bean的作用域为原型(prototype)。 在Spring,Bean的作用域有多种,包括单例(Singleton)、原型(Prototype)、会话(Session)、请求(Request)等。@Scope("prototype")就是用来设置Bean的作用域为原型原型作用域的Bean在每次被请求时都会创建一个新的实例。与单例作用域不同,原型作用域的Bean每次注入或获取时都会创建一个新的实例,因此每个实例之间是相互独立的。 使用@Scope("prototype")将Bean的作用域设置为原型可以在某些场景下提供更灵活的实例管理。比如,对于某些状态不可共享的Bean,或者需要多个相同类型的实例时,可以使用原型作用域。同时,原型作用域也可以避免一些潜在的线程安全问题,因为每个实例都是独立的,不会被多线程共享。 需要注意的是,在使用原型作用域的Bean时,Spring框架不会负责管理其生命周期,也不会在每次使用后自动销毁实例。因此,需要手动管理Bean的生命周期,确保在使用完毕后及时销毁。 总结起来,@Scope("prototype")可以用于将Bean的作用域设置为原型,在每次请求时都创建一个新的实例,提供更灵活的实例管理,并避免一些潜在的线程安全问题。需要手动管理Bean的生命周期。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值