1 Spring Bean

1.1 Spring Bean 概念

Spring Bean 就是被 Spring IOC 容器初始化、装配和管理的对象。这些 beans 通过容器中配置的元数据创建。
Spring Bean 的定义包含了容器所需要的元数据,包括如何创建一个 bean,生命周期,依赖等等。

1.2 Spring Bean 配置方式/提供配置元数据

XML 配置文件、注解的配置、Java 的配置。

1.3 Spring 配置文件包含了哪些信息

Spring 配置文件是个 XML 文件,这个文件包含了类信息,描述了如何配置它们,以及如何相互调用。

1.4 Spring Bean 作用域

  1. singleton:单例模式,默认作用域,整个 Spring 容器生命周期内只创建这一个 Bean。
  2. prototype:原型模式(多例模式),Spring 容器初始化时不创建 Bean 的实例,而在每次通过 getBean 获取 Bean 时会创建一个新的 Bean 实例。
  3. request:请求模式,用于 Web 开发,在一次 HTTP 请求中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Request 内有效。
  4. session:会话模式,用于 Web 开发,在一次 HTTP Session 中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP session,会返回不同的实例,该作用域仅在当前 HTTP Session 内有效。

我们可以通过 XML <bean> 元素中的 scope 属性或者 @Scope 注解来设置 Bean 的作用域,例如:

  • XML 中设置作用域:<bean id="" class="" scope="prototype" />
  • 注解设置作用域: @Scope("prototype") 或者 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

在上述五种作用域中,实际开发中 singleton 和 prototype 是最常用的两种。

// 基于 XML
// <bean id="user" class="com.thr.pojo.User" scope="prototype"/>
 
// 基于注解
@ComponentScan(basePackages = "com.thr.pojo")
@Configuration
public class PojoConfig {
	 public PojoConfig() { System.out.println("PojoConfig容器初始化成功..."); }
	 @Bean(name = "user")
	 @Scope(value = "singleton")
	 public User user(){  return new User();  }
}

1.5 Spring Bean 是否有线程安全/线程并发问题,如何解决

线程安全问题主要是:多个线程操作同一个对象的时候是存在资源竞争,这主要发生在该对象是有状态的情况下。其中,对象有状态是指对象存储了数据,无状态是指对象没有存储数据。

原型模式,由于每次创建新对象,因此不会出现线程安全问题。
单例模式,Spring 框架没有对单例 Bean 进行多线程的封装处理。因此对象无状态(dao service)时,不存在线程安全问题;对象有状态(view model)时,存在线程安全问题。

线程安全问题解决:
一是,作用域更改为原型模式,但是会创建和销毁很多对象。
二是,在 bean 中尽量只定义不变的成员变量。
三是,synchronized 关键字,使用时间换空间的方式,使用加锁保证访问不冲突。
四是,在类中定义一个 ThreadLocal 类的变量,将可变变量存在 ThreadLocal 中。推荐!
ThreadLocal 涉及到 Java 线程,这个类可以让每个线程都有自己的专属本地变量。

Spring 中使用 ThreadLocal 方式来处理线程并发问题,如 RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder 等。

1.6 bean 的生命周期

详细可见:Spring-32 Bean 生命周期

Bean 生命周期主要包含四个部分:实例化、填充属性、初始化、销毁。
实例化,实例化一个 bean 对象。
填充属性,为 bean 设置相关属性和依赖。
初始化,包含五个步骤。第一步,若实现了 Aware 接口,则调用相应方法;第二步,执行 BeanPostProcessor 的 postProcessBeforeInitialization 方法;第三步,若实现了
InitializingBean 接口,则执行 afterPropertiesSet 方法;第四步,若包含 init-method 属性,则执行指定方法;第五步,执行 BeanPostProcessor 的 postProcessAfterInitialization 方法,与第二步相对应。
销毁,包含两个步骤。第一步,若实现了 DisposableBean 接口,执行 destroy 方法;第二部,若包含 destroy-method 属性,则执行指定方法。

实例化:

  • Bean 容器找到配置文件中 Spring Bean 的定义。
  • Bean 容器利用 Java Reflection API 创建一个 Bean 的实例。
    填充属性:
  • 如果涉及到一些属性值利用 set() 方法设置一些属性值。
    初始化:
  • 如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName() 方法,传入 Bean 的名字。
  • 如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader() 方法,传入 ClassLoader 对象的实例。
  • 如果 Bean 实现了 BeanFactoryAware 接口,调用 setBeanFactory() 方法,传入 BeanFactory 对象的实例。
  • 与上面的类似,如果实现了其他 *.Aware 接口,就调用相应的方法。
  • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行 postProcessBeforeInitialization() 方法
  • 如果 Bean 实现了 InitializingBean 接口,执行 afterPropertiesSet() 方法。
  • 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
  • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行 postProcessAfterInitialization() 方法
    销毁:
  • 当要销毁 Bean 的时候,如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
  • 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。

其他参考:Spring Bean 的生命周期(非常详细) - Chandler Qian - 博客园

1.7 哪些是重要的 bean 生命周期方法? 你能重载它们吗?

有两个重要的 bean 生命周期方法,第一个是 setup ,它是在容器加载 bean 的时候被调用。第二个方法是 teardown ,它是在容器卸载类的时候被调用。

bean 标签有两个重要的属性( init-methoddestroy-method )。用它们你可以自己定制初始化和注销方法。它们也有相应的注解( @PostConstruct@PreDestroy )。

1.8 Spring 内部 Bean 概念(Spring Inner Bean)

当一个 bean 仅被用作另一个 bean 的属性时,它能被声明为一个内部 bean。
内部 bean 可以用 setter 注入和构造方法注入的方式来实现;通常是匿名的;作用域一般是 prototype。

1.9 将一个类声明为 bean 的注解有哪些?

我们一般使用 @Autowired 注解自动装配 bean,要想把类标识成可用于 @Autowired 注解自动装配的 bean 的类, 采用以下注解可实现:

  • @Component :通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用 @Component 注解标注。
  • @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。
  • @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
  • @Controller : 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。

1.10 Spring 中可以注入一个 null 和一个空字符串?

可以。

1.11 Spring 如何解决出现同名的 bean

  • 同一个配置文件内同名的 Bean,以最上面定义的为准。
  • 不同配置文件中存在同名 Bean,后解析的配置文件会覆盖先解析的配置文件。
  • 同文件中 ComponentScan 和 @Bean 出现同名 Bean。同文件下 @Bean 的会生效,@ComponentScan 扫描进来不会生效。通过@ComponentScan 扫描进来的优先级是最低的,原因就是它扫描进来的 Bean 定义是最先被注册的。

1.12 Spring 如何解决循环依赖问题

  • 构造器的循环依赖: 这种依赖 spring 是处理不了的,直接抛出 BeanCurrentlyIncreationException 异常。
  • 单例模式下的 setter 循环依赖: 通过“三级缓存”处理循环依赖。
  • 非单例循环依赖: 无法处理。