Spring中的Bean是线程安全的吗?

2022-07-01  本文已影响0人  程就人生

这是一道迷幻题,既不能回答是,也不能回答不是。
先回忆一下在Spring中是如何创建一个bean的?通过添加@Controller、@Service、@Repository 、@Component等注解在一个类上,添加@Bean在一个方法上...还有呢?
创建一个单例、多例的Bean呢?
默认就是单例的bean,要声明多例的通过@Scope注解进行配置。
如果是一个多例Bean,我们还需要考虑线程安全吗?
当然不需要。如果是一个单例Bean,我们一定要考虑线程安全吗?这,,,不一定吧!
再来看看@Controller注解的源码:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {

  /**
   * The value may indicate a suggestion for a logical component name,
   * to be turned into a Spring bean in case of an autodetected component.
   * @return the suggested component name, if any (or empty String otherwise)
   */
  @AliasFor(annotation = Component.class)
  String value() default "";

}

@Service的源码:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {

  /**
   * The value may indicate a suggestion for a logical component name,
   * to be turned into a Spring bean in case of an autodetected component.
   * @return the suggested component name, if any (or empty String otherwise)
   */
  @AliasFor(annotation = Component.class)
  String value() default "";

}

Repository 的源码:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {

  /**
   * The value may indicate a suggestion for a logical component name,
   * to be turned into a Spring bean in case of an autodetected component.
   * @return the suggested component name, if any (or empty String otherwise)
   */
  @AliasFor(annotation = Component.class)
  String value() default "";

}

@Component的源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {

  /**
   * The value may indicate a suggestion for a logical component name,
   * to be turned into a Spring bean in case of an autodetected component.
   * @return the suggested component name, if any (or empty String otherwise)
   */
  String value() default "";

}

@Controller和@Service、@Repository最终都指向了@Component。@Component注解中有没有关于线程安全的?没有,它们几个并没有什么不同。接下来看看@Bean的源码:

/**
 * bean注解
 * 作用在方法上 
 */
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
  
  /**
   * bean的value,别名name
   * @return
   */
  @AliasFor("name")
  String[] value() default {};  


  /**
   * bean的name,别名value
   * @return
   */
  @AliasFor("value")
  String[] name() default {};
  
  /**
   * 是否自动装配默认false,已过期忽略不计
   * @return
   */
  @Deprecated
  Autowire autowire() default Autowire.NO;
  
  /**
   * 自动装配到其他bean的候选,默认true
   * @return
   */
  boolean autowireCandidate() default true;
  
  /**
   * 初始化方法,默认空
   * @return
   */
  String initMethod() default "";
  
  /**
   * 销毁方法,默认 AbstractBeanDefinition.INFER_METHOD
   * @return
   */
  String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;

}

@Bean可用于方法上,它的name是value,value既是name。@Bean("xxx") 声明一个名为xxx的bean。initMethod是bean初始化时,所要执行的方法。同样也没有关于线程安全的。@Bean和以上几个注解的不同之处就是@Bean是作用在方法上的。而@Controller、@Service、@Repository、@Compoment是作用在类上的。再看下@Scope的源码:

/**
 * Scope注解
 * 作用在类/接口/枚举、方法上
 * @Date
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {

  /**
   * 等同于 scopeName
   * @see #scopeName
   */
  @AliasFor("scopeName")
  String value() default "";

  /**
   * 默认单例
   * {@link ConfigurableBeanFactory#SCOPE_SINGLETON SCOPE_SINGLETON}.
   * @since 4.2
   * @see ConfigurableBeanFactory#SCOPE_PROTOTYPE  原型,每次创建一个新对象
   * @see ConfigurableBeanFactory#SCOPE_SINGLETON  单例,默认作用域
   * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST  每次http请求创建一个对象
   * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION  同一个会话共享一个实例
   * @see #value
   */
  @AliasFor("value")
  String scopeName() default "";

  /**
   * 代理模式,默认不需要
   * @see ScopedProxyMode
   */
  ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;

}

@Scope,默认单例(一个spring容器中只创建一个实例),可设置原型(每次创建一个实例)、http请求(一个http请求一个实例)、会话(一个会话一个实例)。这个注解只是声明在Spring容器中实例化的数量,并没有关于线程安全的任何代码。多个实例,每个线程中的实例互不影响,因此多实例的bean是不需要考虑线程安全的。单例Bean又分为两种情况,无状态的bean和有状态的bean。无状态的bean也不需要考虑线程安全,比如service、controller、dao这些都是无状态的bean。有状态的bean,Spring官方提供的bean,通过ThreadLocal去解决线程安全的方法,比如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等,可以看看它们的源码。因此在我们的项目里,如果涉及到有状态的单例Bean,单例bean中有成员变量的,可考虑使用ThreadLocal来保存单例中的成员变量,使线程之间的状态隔离,互不影响,从而达到线程安全的目的。

最后总结Spring容器中的Bean是否线程安全,其实和Sping容器是没有什么关系的。Spring容器只负责创建和管理Bean,并未提供Bean的线程安全策略。因此,Spring容器中的Bean并不具备线程安全的特性。Spring中的Bean又可分为多例bean和单例bean,单例bean又可分为无状态的bean和有状态的bean。多例bean和无状态的bean是不需要考虑线程安全的。只有有状态的单例bean是需要考虑线程安全的,可使用ThreadLocal来管理成员变量,使之线程隔离,以达到线程安全的目的。

上一篇下一篇

猜你喜欢

热点阅读