# Spring IOC 基础篇

## 1. IOC依赖查找和依赖注入

### 1.1 依赖查找：byName和byType

```java
public static void main(String[] args) throws Exception {
    BeanFactory factory = new ClassPathXmlApplicationContext("basic_dl/quickstart-byname.xml");
    // byName
  	Person person = (Person) factory.getBean("person");
    System.out.println(person);
  	// byType
  	Cat cat = factory.getBean(Cat.class);
    System.out.println(cat);
  	// 根据接口类型找到实现类，如果有多个实现类，需要使用到ApplicationContext.getBeansOfType()
  	DemoDao demoDao = factory.getBean(DemoDao.class);
    System.out.println(demoDao.findAll());
}
```

### 1.2 依赖注入：在xml中是用ref属性

```xml
<bean class="com.linkedbear.spring.basic_dl.b_bytype.dao.impl.DemoDaoImpl"/>

<bean id="person" class="com.linkedbear.spring.basic_di.a_quickstart_set.bean.Person">
    <property name="name" value="test-person-byset"/>
    <property name="age" value="18"/>
</bean>
<bean id="cat" class="com.linkedbear.spring.basic_di.a_quickstart_set.bean.Cat">
    <property name="name" value="test-cat"/>
    <!-- ref引用上面的person对象 -->
    <property name="master" ref="person"/>
</bean>
```

### 1.3 依赖查找中的ofType

```java
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="demoMySQLDao" class="com.linkedbear.spring.basic_dl.c_oftype.dao.impl.DemoMySQLDao"/>
    <bean id="demoOracleDao" class="com.linkedbear.spring.basic_dl.c_oftype.dao.impl.DemoOracleDao"/>
    <bean id="demoPostgreDao" class="com.linkedbear.spring.basic_dl.c_oftype.dao.impl.DemoPostgresDao"/>
</beans>
public class OfTypeApplication {
    
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("basic_dl/quickstart-oftype.xml");
        Map<String, DemoDao> beans = ctx.getBeansOfType(DemoDao.class);
        beans.forEach((beanName, bean) -> {
            System.out.println(beanName + " : " + bean.toString());
        });
    }
}

// demoMySQLDao : com.linkedbear.spring.basic_dl.c_oftype.dao.impl.DemoMySQLDao@4883b407
// demoOracleDao : com.linkedbear.spring.basic_dl.c_oftype.dao.impl.DemoOracleDao@7d9d1a19
// demoPostgreDao : com.linkedbear.spring.basic_dl.c_oftype.dao.impl.DemoPostgresDao@39c0f4a
```

**这样就实现了传入一个接口 / 抽象类，返回容器中所有的实现类 / 子类。**

### 1.4 withAnnotation

IOC 容器除了可以根据一个父类 / 接口来找实现类，还可以**根据类上标注的注解来查找对应的 Bean** 。下面咱来测试包含注解的 Bean 如何被查找。

```java
public class WithAnnoApplication {
    
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("basic_dl/quickstart-withanno.xml");
        Map<String, Object> beans = ctx.getBeansWithAnnotation(Color.class);
        beans.forEach((beanName, bean) -> {
            System.out.println(beanName + " : " + bean.toString());
        });
    }
}
```

### 1.5 获取IOC容器中的所有Bean：getBeanDefinitionNames

```java
public class BeannamesApplication {
    
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("basic_dl/quickstart-withanno.xml");
        String[] beanNames = ctx.getBeanDefinitionNames();
        // 利用jdk8的Stream快速编写打印方法
        Stream.of(beanNames).forEach(System.out::println);
    }
}
```

### 1.6 延迟查找：**ObjectProvider**

```java
public class LazyLookupApplication {
    
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("basic_dl/quickstart-lazylookup.xml");
        Cat cat = ctx.getBean(Cat.class);
        System.out.println(cat);
        // 下面的代码会报Bean没有定义 NoSuchBeanDefinitionException
        // Dog dog = ctx.getBean(Dog.class);
    
        // 这一行代码不会报错
        ObjectProvider<Dog> dogProvider = ctx.getBeanProvider(Dog.class);
      	// 只有调用 dogProvider 的 getObject ，真正要取包装里面的 Bean 时，才会报异常
      	dogProvider.getObject()
    }
}
```

除了ObjectProvider，还有一个方法：getIfAvailable，它可以在**找不到 Bean 时返回 null 而不抛出异常**。

```java
Dog dog = dogProvider.getIfAvailable();
if (dog == null) {
    dog = new Dog();
}
```

ObjectProvider在jdk8中进行了升级，支持了函数式编程。

```java
Dog dog = dogProvider.getIfAvailable(() -> new Dog());
// 或者
Dog dog = dogProvider.getIfAvailable(Dog::new);
// 或者
dogProvider.ifAvailable(dog -> System.out.println(dog)); // 或者使用方法引用
```

## 2. BeanFactory与ApplicationContext

**BeanFactory**接口提供了一种**高级配置机制**，能够管理任何类型的对象。**ApplicationContext** 是 BeanFactory 的子接口。它增加了：

* 与 SpringFramework 的 AOP 功能轻松集成
* 消息资源处理（用于国际化）
* 事件驱动机制
* 消息与国际化
* 应用层特定的上下文，例如 Web 应用程序中使用的 WebApplicationContext

ApplicationContext是BeanFactory的子接口，ApplicationContext包含了BeanFactory的所有功能，并且还扩展了很多特性。（子类只做扩展，不改变父类的功能）

## 3. 注解驱动IOC

在jdk5之前使用xml进行配置bean，在jdk5支持注解之后就支持使用注解配置bean了，对应的就用AnnotationConfigApplicationContext

### 3.1 @Configuration

对比于 xml 文件作为驱动，注解驱动需要的是**配置类**。一个配置类就可以类似的理解为一个 xml 。配置类没有特殊的限制，只需要在类上标注一个 @Configuration 注解即可。

```java
@Configuration
public class QuickstartConfiguration {

}
```

在 xml 中，声明 Bean 是通过 标签。

```xml
<bean id="person" class="com.linkedbear.spring.basic_dl.a_quickstart_byname.bean.Person"/>
```

在配置类中就是：

```java
@Bean
public Person person() {
    return new Person();
}
```

**向 IOC 容器注册一个类型为 Person ，id 为 person 的 Bean** 。**方法的返回值代表注册的类型，方法名代表 Bean 的 id** 。当然，也可以直接在 @Bean 注解上显式的声明 Bean 的 id ，只不过在注解驱动的范畴里，它不叫 id 而是叫 **name**。

```java
@Bean(name = "aaa") // 4.3.3之后可以直接写value
public Person person() {
    return new Person();
}
```

**@Configuration也是bean，会被注册到IOC容器中。**

### 3.2 启动类初始化注解IOC容器

```java
public class AnnotationConfigApplication {
    
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(QuickstartConfiguration.class);
        Person person = ctx.getBean(Person.class);
        System.out.println(person);
    }
}
```

### 3.3 依赖注入

```java
@Bean
public Person person() {
    Person person = new Person();
    person.setName("person");
    person.setAge(123);
    return person;
}

@Bean
public Cat cat() {
    Cat cat = new Cat();
    cat.setName("test-cat-anno");
    // 直接拿上面的person()方法作为返回值即可，相当于ref
    cat.setMaster(person());
    return cat;
}
```

相当于xml中的：

```xml
<bean id="person" class="com.linkedbear.spring.basic_di.a_quickstart_set.bean.Person">
    <property name="name" value="test-person-byset"/>
    <property name="age" value="18"/>
</bean>

<bean id="cat" class="com.linkedbear.spring.basic_di.a_quickstart_set.bean.Cat">
    <property name="name" value="test-cat"/>
    <property name="master" ref="person"/>
</bean>
```

### 3.4 组件注册：@Component

SpringFramework 整了几个注解出来，可以帮咱快速注册需要的组件，这些注解被成为**模式注解 ( stereotype annotation )**。

在类上标注 @Component 注解，即代表该类会被注册到 IOC 容器中作为一个 Bean 。

```java
@Component
public class Person {
    
}
```

相当于 xml 中的：

```xml
<bean class="com.linkedbear.spring.basic_dl.a_quickstart_byname.bean.Person"/>
```

如果想指定 Bean 的名称，可以直接在 @Component 中声明 **value** 属性即可：**一般不会这么操作的吧**

```java
@Component("aaa")
public class Person { }
```

如果不指定 Bean 的名称，它的默认规则是 **“类名的首字母小写”**（例如 Person 的默认名称是 person ，DepartmentServiceImpl 的默认名称是 departmentServiceImpl ）。

SpringFramework 为了迎合咱在进行 Web 开发时的三层架构，它额外提供了三个注解：**@Controller** **、\*\*\*\*@Service** **、\*\*\*\*@Repository** ，分别代表表现层、业务层、持久层。这三个注解的作用与 @Component 完全一致，其实它们的底层也就是 @Component。

### 3.5 组件扫描：@ComponentScan

只声明了组件，咱在写配置类时如果还是只写 @Configuration 注解，随后启动 IOC 容器，那它是感知不到有 @Component 存在的，一定会报 NoSuchBeanDefinitionException 。

在配置类上额外标注一个 @ComponentScan ，并指定要扫描的路径，它就可以**扫描指定路径包及子包下的所有** **@Component** **组件**：

```java
@Configuration
@ComponentScan("com.linkedbear.spring.annotation.c_scan.bean")
public class ComponentScanConfiguration {
    
}
```

如果不指定扫描路径，则**默认扫描本类所在包及子包下的所有** **@Component** **组件**。

另外，如果不写 @ComponentScan ，也是可以做到组件扫描的。在 AnnotationConfigApplicationContext 的构造方法中有一个类型为 String 可变参数的构造方法：

```java
ApplicationContext ctx = new AnnotationConfigApplicationContext("com.linkedbear.spring.annotation.c_scan.bean");
```

组件扫描可不是注解驱动 IOC 的专利，**对于 xml 驱动的 IOC 同样可以启用组件扫描**，它只需要在 xml 中声明一个标签即可：

```xml
<context:component-scan base-package="com.linkedbear.spring.annotation.c_scan.bean"/>
<!-- 注意标签是package，不是packages，代表一个标签只能声明一个根包 -->
```

### 3.6 注解驱动和xml驱动互通

如果一个应用中，既有注解配置，又有 xml 配置，这个时候就需要由一方引入另一方了。

#### 3.6.1 xml引入注解

在 xml 中要引入注解配置，需要开启注解配置，同时注册对应的配置类：

```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd 
        http://www.springframework.org/schema/context 
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 开启注解配置 -->
    <context:annotation-config />
    <bean class="com.linkedbear.spring.annotation.d_importxml.config.AnnotationConfigConfiguration"/>
</beans>
```

#### 3.6.2 注解引入xml

在注解配置中引入 xml ，需要在配置类上标注 @ImportResource 注解，并声明配置文件的路径：

```java
@Configuration
@ImportResource("classpath:annotation/beans.xml")
public class ImportXmlAnnotationConfiguration {
    
}
```

## 4. 依赖注入

### 4.1 属性注入

#### 4.1.1 setter属性注入

**xml方式的setter注入:**

```xml
<bean id="person" class="com.linkedbear.spring.basic_di.a_quickstart_set.bean.Person">
    <property name="name" value="test-person-byset"/>
    <property name="age" value="18"/>
</bean>
```

**注解方式的setter注入:**

```java
@Bean
public Person person() {
    Person person = new Person();
    person.setName("test-person-anno-byset");
    person.setAge(18);
    return person;
}
```

#### 4.1.2 构造器注入

有一些 bean 的属性依赖，需要在调用构造器（构造方法）时就设置好；或者另一种情况，有一些 bean 本身没有无参构造器，这个时候就必须使用**构造器注入**了。

**xml方式的构造器注入:**

在 标签的内部，可以声明一个子标签：constructor-arg ，顾名思义，它是指构造器参数，由它可以指定构造器中的属性，来进行属性注入。

```xml
<bean id="person" class="com.linkedbear.spring.basic_di.b_constructor.bean.Person">
    <constructor-arg index="0" value="test-person-byconstructor"/>
    <constructor-arg index="1" value="18"/>
</bean>
```

**注解式构造器属性注入：**

```java
@Bean
public Person person() {
    return new Person("test-person-anno-byconstructor", 18);
}
```

#### 4.1.3 注解式属性注入

**@Component下的属性注入：**

实现注解式属性注入，可以直接在要注入的字段上标注 **@Value** 注解：

```java
@Value("black-value-anno")
private String name;

@Value("0")
private Integer order;
```

**外部配置文件引入：@PropertySource**

在工程的 resources 目录下新建一个 red.properties ，用于存放 Red 的属性的配置：

```properties
red.name=red-value-byproperties
red.order=1
```

使用时，只需要将 **@PropertySource** 注解标注在配置类上，并声明 properties 文件的位置，即可导入外部的配置文件：

```java
@Configuration
// 顺便加上包扫描
@ComponentScan("com.linkedbear.spring.basic_di.c_value_spel.bean")
@PropertySource("classpath:basic_di/value/red.properties")
public class InjectValueConfiguration {
    
}
```

Red类的属性注入:

```java
@Value("${red.name}")
private String name;

@Value("${red.order}")
private Integer order;
```

对于 xml 中，占位符的使用方式与 @Value 是一模一样的：

```xml
<bean class="com.linkedbear.spring.basic_di.c_value_spel.bean.Red">
    <property name="name" value="${red.name}"/>
    <property name="order" value="${red.order}"/>
</bean>
```

作为一个 **properties 文件**，它加载到 SpringFramework 的 IOC 容器后，**会转换成 Map 的形式来保存**这些配置，而 SpringFramework 中本身在初始化时就有一些配置项，这些配置项也都放在这个 Map 中。**占位符的取值就是从这些配置项中取**。

### 4.2 SpEL表达式

SpEL 全称 Spring Expression Language ，它从 SpringFramework 3.0 开始被支持，它本身可以算 SpringFramework 的组成部分，但又可以被独立使用。它可以支持**调用属性值、属性参数以及方法调用、数组存储、逻辑计算**等功能。

**SpEL 的语法统一用** **#{}** **表示，花括号内部编写表达式语言。**

#### 4.2.1 SpEL属性注入

使用 @Value 配合 SpEL 完成字面量的属性注入，需要额外在花括号内部加单引号：

```java
@Component
public class Blue {
    
    @Value("#{'blue-value-byspel'}")
    private String name;
    
    @Value("#{2}")
    private Integer order;
}
```

修改启动类，从 IOC 容器中取 Blue 并打印，可以发现字面量被成功注入：

```java
Blue{name='blue-value-byspel', order=2}
```

#### 4.2.2 Bean属性引用

SpEL 可以取 IOC 容器中其它 Bean 的属性。

```java
@Component
public class Green {
    
    @Value("#{'copy of ' + blue.name}")
    private String name;
    
    @Value("#{blue.order + 1}")
    private Integer order;
}
```

修改启动类，测试运行，发现 Blue 的属性已经成功被取出了：

```java
use spel bean property : Green{name='copy of blue-value-byspel', order=3}
```

xml的使用方式：

```xml
<bean class="com.linkedbear.spring.basic_di.c_value_spel.bean.Green">
    <property name="name" value="#{'copy of ' + blue.name}"/>
    <property name="order" value="#{blue.order + 1}"/>
</bean>
```

#### 4.2.3 方法调用

SpEL 表达式不仅可以引用对象的属性，还可以**直接引用类常量，以及调用对象的方法**等。

```java
@Component
public class White {
    
    @Value("#{blue.name.substring(0, 3)}")
    private String name;
    
    @Value("#{T(java.lang.Integer).MAX_VALUE}")
    private Integer order;
}
```

注意，**直接引用类的属性，需要在类的全限定名外面使用** `**T()**` **包围。**

xml 的方式，同样都是使用 value 属性：

```xml
<bean class="com.linkedbear.spring.basic_di.c_value_spel.bean.White">
    <property name="name" value="#{blue.name.substring(0, 3)}"/>
    <property name="order" value="#{T(java.lang.Integer).MAX_VALUE}"/>
</bean>
```

### 4.3 自动注入

#### 4.3.1 @Autowired

在 Bean 中直接在 **属性 / setter 方法** 上标注 @Autowired 注解，IOC 容器会**按照属性对应的类型，从容器中找对应类型的 Bean 赋值到对应的属性**上，实现自动注入。

**首先，创建几个Bean用于演示:**

```java
@Component
public class Person {
    private String name = "administrator";
    // setter
}
@Component
public class Dog {
    
    @Value("dogdog")
    private String name;
    
    private Person person;
    // toString() ......
}
```

**给Dog注入Person的三种方式:**

对于 @Autowired 的使用，只需要在属性上标注即可：

```java
@Component
public class Dog {
    // ......
    @Autowired
    private Person person;
}
```

也可以使用**构造器**注入方式：

```java
@Component
public class Dog {
    // ......
    private Person person;
    
    @Autowired
    public Dog(Person person) {
        this.person = person;
    }
}
```

亦可以使用 **setter 方法注入**：

```java
@Component
public class Dog {
    // ......
    private Person person;
    
    @Autowired
    public void setPerson(Person person) {
        this.person = person;
    }
}
```

#### 4.3.2 注入的Bean不存在

如果IOC容器中没有Person，那么正常情况下程序会抛出异常：

```
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.linkedbear.spring.basic_di.d_autowired.bean.Person' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {} 
```

如果出现这种情况下又不想让程序抛异常，就需要在 @Autowired 注解上加一个属性：**required = false** 。

```java
@Autowired(required = false)
private Person person;
```

再次运行启动类，可以发现控制台打印 person=null ，但没有抛出异常：

```java
Dog{name='dogdog', person=null}
```

#### 4.3.3 @Autowired在配置类的使用

@Autowired 不仅可以用在普通 Bean 的属性上，在配置类中，注册 @Bean 时也可以标注：

```java
@Configuration
@ComponentScan("com.linkedbear.spring.basic_di.d_complexfield.bean")
public class InjectComplexFieldConfiguration {

    @Bean
    @Autowired // 高版本可不标注
    public Cat cat(Person person) {
        Cat cat = new Cat();
        cat.setName("mimi");
        cat.setPerson(person);
        return cat;
    }
}
```

由于配置类的上下文中没有 Person 的注册了（使用了 @Component 模式注解），自然也就没有 person() 方法给咱调，那就可以使用 @Autowired 注解来进行自动注入了。（其实不用标，SpringFramework 也知道自己得注入了）

#### 4.3.4 多个相同类型Bean的自动注入：@Qualifier，@Primary

假设此时，两个 person （type是一样的），但是一个叫 master ，一个叫 administrator 。

下面咱直接运行测试启动类，可以发现控制台会报这样一个错误：

```java
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.linkedbear.spring.basic_di.d_complexfield.bean.Person' available: expected single matching bean but found 2: administrator,master
```

@Qualifier 注解的使用目标是要注入的 Bean ，它配合 @Autowired 使用，可以显式的指定要注入哪一个 Bean ：

```java
@Autowired
@Qualifier("administrator")
private Person person;
```

@Primary 注解的使用目标是被注入的 Bean ，在一个应用中，一个类型的 Bean 注册只能有一个，它配合 @Bean 使用，可以指定**默认注入的 Bean** ：

```java
@Bean
@Primary
public Person master() {
    Person master = new Person();
    master.setName("master");
    return master;
}
```

**@Qualifier** **不受** **@Primary** **的干扰。**

在 xml 中可以指定 标签中的 primary 属性为 true ，跟上面标注 @Primary 注解是一样的。

**另外可以修改注入对象的变量名：**

```java
@Autowired
private Person administrator;
```

#### 4.3.5 @Autowired注入的原理逻辑

**先拿属性对应的类型，去 IOC 容器中找 Bean ，如果找到了一个，直接返回；如果找到多个类型一样的 Bean ， 把属性名拿过去，跟这些 Bean 的 id 逐个对比，如果有一个相同的，直接返回；如果没有任何相同的 id 与要注入的属性名相同，则会抛出** **NoUniqueBeanDefinitionException** **异常。**

**优先级：类型 -> id**

#### 4.3.6 多个相同类型Bean的全部注入

**注入一个用单个对象接收，注入一组对象就用集合来接收**。

```java
@Component
public class Dog {
    // ......
    
    @Autowired
    private List<Person> persons;
}
```

如上就可以实现一次性把所有的 Person 都注入进来。

#### 4.3.7 自动注入的规范：JSR250-@Resource

JSR 全程 **Java Specification Requests** ，它定义了很多 Java 语言开发的规范，有专门的一个组织叫 JCP ( Java Community Process ) 来参与定制。

@Resource 也是用来属性注入的注解，它与 @Autowired 的不同之处在于：**@Autowired** **是按照类型注入，\*\*\*\*@Resource** **是直接按照属性名 / Bean的名称注入**。

**@Resource** **注解相当于标注** **@Autowired** **和** **@Qualifier。**

```java
@Component
public class Bird {
    
    @Resource(name = "master")
    private Person person;
}
```

#### 4.3.8 JSR330-@Inject

JSR330 也提出了跟 @Autowired 一样的策略，它也是**按照类型注入**。

```xml
<!-- jsr330 -->
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>
```

使用方式就跟 SpringFramework 原生的 @Autowired + @Qualifier 一样了：

```java
@Component
public class Cat {
    
    @Inject // 等同于@Autowired
    @Named("admin") // 等同于@Qualifier
    private Person master;
}
```

#### 4.3.9 @Autowired与@Inject对比

@Inject的包名是`import javax.inject.Inject;` ，@Autowired的包名是`import org.springframework.beans.factory.annotation.Autowired;`

也就是说，如果万一项目中没有 SpringFramework 了，那么 @Autowired 注解将失效，但 @Inject 属于 **JSR 规范，不会因为一个框架失效而失去它的意义**，只要导入其它支持 JSR330 的 IOC 框架，它依然能起作用。

### 4.4 【面试题】依赖注入的注入方式

| 注入方式     | 被注入成员是否可变 | 是否依赖IOC框架的API                       | 使用场景                     |
| -------- | --------- | ----------------------------------- | ------------------------ |
| 构造器注入    | 不可变       | 否（xml、编程式注入不依赖）                     | 不可变的固定注入                 |
| 参数注入     | 不可变       | 否（高版本中注解配置类中的 `@Bean` 方法参数注入可不标注注解） | 注解配置类中 `@Bean` 方法注册 bean |
| 属性注入     | 不可变       | 是（只能通过标注注解来侵入式注入）                   | 通常用于不可变的固定注入             |
| setter注入 | 可变        | 否（xml、编程式注入不依赖）                     | 可选属性的注入                  |

其实应该划分为两种形式——基于XML注入和基于注解注入，然后再细分为下面的形式：

![img](https://image.ldbmcs.com/ZQ6xzS.jpg)

基于XML注入的方式略过，直接看基于注解注入的方式。

**通过构造方法注入：**

```java
@Service("userService")
public class UserServiceImpl implements UserService {
    
    private UserDao userDao;
    
    @Autowired
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }
    /**继承自UserService的方法**/
}
```

根据开发文档的说法，这种只有一个构造方法的情况，自Spring4.3以后，就不再需要添加 @Autowired标注，也可以。但是，如果有多个构造方法时，是必须要对其中一个方法标注 @Autowired,不然Spring会报出异常。

**通过setter方法注入：**

```java
@Service("userService")
public class UserServiceImpl implements UserService {

    private UserDao userDao;

    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    /**继承自UserService的方法**/
}
```

**通过字段注入：**

Fields are injected right after construction of a bean, before any config methods are invoked. Such a config field does not have to be public.

```java
@Service("userService")
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;
    
    /**继承自UserService的方法**/
}
```

### 4.5 【面试题】自动注入的注解对比

| 注解                                   | 注入方式   | 是否支持[@Primary](spring-ioc-ji-chu-pian) | 来源                   | Bean不存在时处理               |
| ------------------------------------ | ------ | -------------------------------------- | -------------------- | ------------------------ |
| [@Autowired](spring-ioc-ji-chu-pian) | 根据类型注入 | 是                                      | SpringFramework原生注解  | 可指定required=false来避免注入失败 |
| [@Resource](spring-ioc-ji-chu-pian)  | 根据名称注入 | 是                                      | JSR250规范             | 容器中不存在指定Bean会抛出异常        |
| [@Inject](spring-ioc-ji-chu-pian)    | 根据类型注入 | 是                                      | JSR330规范 ( 需要导jar包 ) | 容器中不存在指定Bean会抛出异常        |

### 4.6 复杂类型注入

创建一个复杂的对象：

```java
public class Person {

    private String[] names;
    private List<String> tels;
    private Set<Cat> cats;
    private Map<String, Object> events;
    private Properties props;
    // setter
}
```

#### 4.6.1 xml复杂注入

首先，创建一个简单的bean：

```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="com.linkedbear.spring.basic_di.g_complexfield.bean.Person"></bean>
</beans>
```

**数组注入：**

```xml
<property name="names">
    <array>
        <value>张三</value>
        <value>三三来迟</value>
    </array>
</property>
```

**list注入：**

```xml
<property name="tels">
    <list>
        <value>13888</value>
        <value>15999</value>
    </list>
</property>
```

**Set注入：**

```xml
<!-- 已经提前声明好的Cat -->
<bean id="mimi" class="com.linkedbear.spring.basic_di.g_complexfield.bean.Cat"/>
---

<property name="cats">
    <set>
        <bean class="com.linkedbear.spring.basic_di.g_complexfield.bean.Cat"/>
        <ref bean="mimi"/>
    </set>
</property>
```

**Map注入：**

```xml
<property name="events">
    <map>
        <entry key="8:00" value="起床"/>
        <!-- 撸猫 -->
        <entry key="9:00" value-ref="mimi"/>
        <!-- 买猫 -->
        <entry key="14:00">
            <bean class="com.linkedbear.spring.basic_di.g_complexfield.bean.Cat"/>
        </entry>
        <entry key="18:00" value="睡觉"/>
    </map>
</property>
```

**Properties注入:**

```xml
<property name="props">
    <props>
        <prop key="sex">男</prop>
        <prop key="age">18</prop>
    </props>
</property>
```

#### 4.6.2 注解复杂注入

```java
@Component("miaomiao")
public class Cat {
    private String name = "cat";
}
@Component
public class Person2 {
    
    @Value("#{new String[] {'张三', '张仨'}}")
    private String[] names;
    
    @Value("#{{'333333', '3333', '33'}}")
    private List<String> tels;
    
    // 引用现有的Bean，以及创建新的Bean
    @Value("#{{@miaomiao, new com.linkedbear.spring.basic_di.g_complexfield.bean.Cat()}}")
    private Set<Cat> cats;
    
    @Value("#{{'喵喵': @miaomiao.name, '猫猫': new com.linkedbear.spring.basic_di.g_complexfield.bean.Cat().name}}")
    private Map<String, Object> events;
    
    @Value("#{{'123': '牵着手', '456': '抬起头', '789': '我们私奔到月球'}}")
    private Properties props;
}
```

### 4.7 回调注入：**Aware**

```java
public interface Aware {

}
```

![img](https://cdn.nlark.com/yuque/0/2022/png/229542/1647959135491-743a569a-f9a5-44e7-9444-5165329704fc.png)

**常用的几个回调接口：**

| 接口名                            | 用途                                         |
| ------------------------------ | ------------------------------------------ |
| BeanFactoryAware               | 回调注入 BeanFactory                           |
| ApplicationContextAware        | 回调注入 ApplicationContext（与上面不同，后续 IOC 高级讲解） |
| EnvironmentAware               | 回调注入 Environment（后续IOC高级讲解）                |
| ApplicationEventPublisherAware | 回调注入事件发布器                                  |
| ResourceLoaderAware            | 回调注入资源加载器（xml驱动可用）                         |
| BeanClassLoaderAware           | 回调注入加载当前 Bean 的 ClassLoader                |
| BeanNameAware                  | 回调注入当前 Bean 的名称                            |

这里面大部分接口，其实在当下的 SpringFramework 5 版本中，借助 @Autowired 注解就可以实现注入了，根本不需要这些接口，只有最后面两个，是因 Bean 而异的，还是需要 **Aware** 接口来帮忙注入。

#### 4.7.1 ApplicationContextAware

```java
public class AwaredTestBean implements ApplicationContextAware {
    
    private ApplicationContext ctx;
    
    public void printBeanNames() {
        Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
    }
    
    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        this.ctx = ctx;
    }
}
```

这样就相当于，当这个 AwaredTestBean 被初始化好的时候，就会把 ApplicationContext 传给它，之后它就可以干自己想干的事了。

#### 4.7.2 BeanNameAware

如果当前的 bean 需要依赖它本身的 name ，使用 @Autowired 就不好使了，这个时候就得使用 BeanNameAware 接口来辅助注入当前 bean 的 name 了。

```java
public class AwaredTestBean implements ApplicationContextAware, BeanNameAware {
    
    private String beanName;
    private ApplicationContext ctx;
    
    public String getName() {
        return beanName;
    }
    
    public void printBeanNames() {
        Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
    }
    
    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        this.ctx = ctx;
    }
    
    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }
}
```

#### 4.7.3 NamedBean

其实，BeanNameAware 还有一个可选的搭配接口：**NamedBean** ，它专门提供了一个 getBeanName 方法，用于获取 bean 的 name 。

所以说，如果给上面的 AwaredTestBean 再实现 NamedBean 接口，那就不需要自己定义 getName 或者 getBeanName 方法，直接实现 NamedBean 定义好的 getBeanName 方法即可。

### 4.8 延迟注入：ObjectProvider

#### 4.8.1 setter的延迟注入

```java
@Component
public class Dog {
    
    private Person person;
    
    @Autowired
    public void setPerson(ObjectProvider<Person> person) {
        // 有Bean才取出，注入
        this.person = person.getIfAvailable();
    }
}
```

#### 4.8.2 构造器的延迟注入

```java
@Component
public class Dog {
    
    private Person person;
    
    @Autowired
    public Dog(ObjectProvider<Person> person) {
        // 如果没有Bean，则采用缺省策略创建
        this.person = person.getIfAvailable(Person::new);
    }
}
```

构造器的延迟注入效果跟 setter 是一样的，只不过 setter 的注入时机是创建对象**后**，而构造器的注入时机是创建对象**时**。

#### 4.8.3 属性字段的延迟注入

属性直接注入是不能直接注入 Bean 的，只能注入 ObjectProvider ，通常也不会这么干，因为这样注入了之后，每次要用这个 Bean 的时候都得判断一次：

```java
@Autowired
private ObjectProvider<Person> person;

@Override
public String toString() {
    // 每用一次都要getIfAvailable一次
    return "Dog{" + "person=" + person.getIfAvailable(Person::new) + '}';
}
```

#### 4.8.4 【面试题】依赖注入的注入方式-扩展

| 注入方式     | 被注入成员是否可变 | 是否依赖IOC框架的API     | 注入时机  | 使用场景         | 支持延迟注入 |
| -------- | --------- | ----------------- | ----- | ------------ | ------ |
| 构造器注入    | 不可变       | 否（xml、编程式注入不依赖）   | 对象创建时 | 不可变的固定注入     | 是      |
| 参数注入     | 不可变       | 是（只能通过标注注解来侵入式注入） | 对象创建后 | 通常用于不可变的固定注入 | 否      |
| setter注入 | 可变        | 否（xml、编程式注入不依赖）   | 对象创建后 | 可选属性的注入      | 是      |

### 4.9【面试题】依赖注入4连问

#### 4.9.1 依赖注入的目的和优点？

首先，依赖注入作为 IOC 的实现方式之一，目的就是**解耦**，我们不再需要直接去 new 那些依赖的类对象（直接依赖会导致对象的创建机制、初始化过程难以统一控制）；而且如果组件存在多级依赖，依赖注入可以将这些依赖的关系简化，开发者只需要定义好谁依赖谁即可。

除此之外，依赖注入的另一个特点是依赖对象的**可配置**：通过 xml 或者注解声明，可以指定和调整组件注入的对象，借助 Java 的多态特性，可以不需要大批量的修改就完成依赖注入的对象替换（面向接口编程与依赖注入配合近乎完美）。

#### 4.9.2 谁把什么注入给谁了？

**IOC 容器把需要依赖的对象注入给待注入的组件**。

#### 4.9.3 依赖注入具体是如何注入的？

#### 4.9.4 使用setter注入还是构造器注入？

* SpringFramework **4.0.2** 及之前是推荐 setter 注入，理由是**一个 Bean 有多个依赖时，构造器的参数列表会很长**；而且如果 **Bean 中依赖的属性不都是必需的话，注入会变得更麻烦**；
* **4.0.3** 及以后官方推荐构造器注入，理由是**构造器注入的依赖是不可变的、完全初始化好的，且可以保证不为 null** ；
* 当然 **4.0.3** 及以后的官方文档中也说了，如果**真的出现构造器参数列表过长的情况，可能是这个 Bean 承担的责任太多，应该考虑组件的责任拆解**。

## 5. Bean

### 5.1 FactoryBean

Bean的类型：**普通 Bean 、工厂 Bean** ，普通Bean就不多说了，常见的都是普通Bean。

SpringFramework 考虑到一些特殊的设计：\*\*Bean 的创建需要指定一些策略，或者依赖特殊的场景来分别创建，也或者一个对象的创建过程太复杂，使用 xml 或者注解声明也比较复杂。\*\*这种情况下，如果还是使用普通的创建 Bean 方式，以咱现有的认知就搞不定了。于是，SpringFramework 在一开始就帮我们想了办法，可以借助 **FactoryBean** 来使用工厂方法创建对象。

#### FactoryBean是什么

FactoryBean 本身是一个接口，它本身就是一个创建对象的工厂。

如果 Bean 实现了 FactoryBean 接口，则它本身将不再是一个普通的 Bean ，不会在实际的业务逻辑中起作用，而是由创建的对象来起作用。

```java
public interface FactoryBean<T> {
    // 返回创建的对象
    @Nullable
    T getObject() throws Exception;

    // 返回创建的对象的类型（即泛型类型）
    @Nullable
    Class<?> getObjectType();

    // 创建的对象是单实例Bean还是原型Bean，默认单实例
    default boolean isSingleton() {
        return true;
    }
}
```

#### FactoryBean的使用

场景：小孩子要买玩具，由一个玩具生产工厂来给这个小孩子造玩具。

```java
public class Child {
    // 当前的小孩子想玩球
    private String wantToy = "ball";
    
    public String getWantToy() {
        return wantToy;
    }
```

创建几个玩具，包括一个抽象类和两个实现类：

```java
public abstract class Toy {
    
    private String name;
    
    public Toy(String name) {
        this.name = name;
    }
}

public class Ball extends Toy { // 球
    
    public Ball(String name) {
        super(name);
    }
}

public class Car extends Toy { // 玩具汽车
    
    public Car(String name) {
        super(name);
    }
}
```

创建一个 ToyFactoryBean 玩具工厂，让它实现 FactoryBean 接口：

```java
public class ToyFactoryBean implements FactoryBean<Toy> {
    
    @Override
    public Toy getObject() throws Exception {
        return null;
    }
    
    @Override
    public Class<Toy> getObjectType() {
        return Toy.class;
    }
}
```

希望能让它根据小孩子想要玩的玩具来决定生产哪种玩具，那咱就得在这里面注入 Child 。由于咱这里面使用的不是注解式自动注入，那咱就用 setter 注入吧：

```java
public class ToyFactoryBean implements FactoryBean<Toy> {
    
    private Child child;
    
    @Override
    public Toy getObject() throws Exception {
        // 根据 Child 中的 wantToy 属性，来决定创建哪个玩具：
        switch (child.getWantToy()) {
            case "ball":
                return new Ball("ball");
            case "car":
                return new Car("car");
            default:
                // SpringFramework2.0开始允许返回null
                // 之前的1.x版本是不允许的
                return null;
        }
    }
    
    @Override
    public Class<Toy> getObjectType() {
        return Toy.class;
    }
    
    public void setChild(Child child) {
        this.child = child;
    }
}
```

然后注入工厂类和小孩子的类：

```java
@Bean
public Child child() {
    return new Child();
}

@Bean
public ToyFactoryBean toyFactory() {
    ToyFactoryBean toyFactory = new ToyFactoryBean();
    toyFactory.setChild(child());
    return toyFactory;
}
```

或者xml的方式：

```xml
<bean id="child" class="com.linkedbear.spring.bean.a_type.bean.Child"/>

<bean id="toyFactory" class="com.linkedbear.spring.bean.a_type.bean.ToyFactoryBean">
  <property name="child" ref="child"/>
</bean>
```

#### FactoryBean创建Bean的时机

**FactoryBean** **本身的加载是伴随 IOC 容器的初始化时机一起的**。

**FactoryBean** **生产 Bean 的机制是延迟生产**。

**FactoryBean** **默认生成的 Bean 是单实例的**。

#### BeanFactory 与 FactoryBean 的区别是什么？

1. BeanFactory ：SpringFramework 中实现 IOC 的**最底层容器**（此处的回答可以从两种角度出发：从类的继承结构上看，它是最顶级的接口，也就是最顶层的容器实现；从类的组合结构上看，它则是最深层次的容器，ApplicationContext 在最底层组合了 BeanFactory ）
2. FactoryBean ：**创建对象的工厂 Bean** ，可以使用它来直接创建一些初始化流程比较复杂的对象。

### 5.2 Bean的作用域

#### 5.2.1 内置的作用域

| 作用域类型       | 概述                            |
| ----------- | ----------------------------- |
| singleton   | 一个 IOC 容器中只有一个【默认值】           |
| prototype   | 每次获取创建一个                      |
| request     | 一次请求创建一个（仅Web应用可用）            |
| session     | 一个会话创建一个（仅Web应用可用）            |
| application | 一个 Web 应用创建一个（仅Web应用可用）       |
| websocket   | 一个 WebSocket 会话创建一个（仅Web应用可用） |

#### 5.2.2 singleton：单实例Bean

![img](https://cdn.nlark.com/yuque/0/2022/png/229542/1649554575038-3ebeefe4-ab89-4192-b368-c18fd5b37d13.png) SpringFramework 中默认所有的 Bean 都是单实例的，即：一个 IOC 容器中只有一个。

#### 5.2.3 prototype：原型Bean

**每次对原型 Bean 提出请求(任何依赖查找、依赖注入的动作)时，都会创建一个新的 Bean 实例。**

如果连续 getBean() 两次，那就应该创建两个不同的 Bean 实例；向两个不同的 Bean 中注入两次，也应该注入两个不同的 Bean 实例。

![img](https://cdn.nlark.com/yuque/0/2022/png/229542/1649555152669-762480e5-5103-4d0b-9752-c99184fe9eb8.png)

其实对于**原型**这个概念，在设计模式中也是有对应的：**原型模式**。原型模式实质上是使用对象**深克隆**，乍看上去跟 SpringFramework 的原型 Bean 没什么区别，但咱仔细想，每一次生成的原型 Bean 本质上都还是一样的，只是可能带一些特殊的状态等等，这个可能理解起来比较抽象，可以跟下面的 request 域结合着理解。

可以通过`**@Scope("prototype")**`声明为原型类型。也可以通过常量`**ConfigurableBeanFactory.**SCOPE_PROTOTYPE`**定义。**

**原型Bean的创建时机：**

单实例 Bean 的创建咱已经知道，是在 ApplicationContext 被初始化时就已经创建好了，那这些原型 Bean 又是什么时候被创建的呢？其实也不难想出，它都是什么时候需要，什么时候创建(**类似懒加载，饿汉式**)。

#### 5.2.4 Web应用的作用域们

* **request** ：请求Bean，每次客户端向 Web 应用服务器发起一次请求，Web 服务器接收到请求后，由 SpringFramework 生成一个 Bean ，直到请求结束。
* **session** ：会话Bean，每个客户端在与 Web 应用服务器发起会话后，SpringFramework 会为之生成一个 Bean ，直到会话过期。
* **application** ：应用Bean，每个 Web 应用在启动时，SpringFramework 会生成一个 Bean ，直到应用停止（有的也叫 global-session ）
* **websocket** ：WebSocket Bean ，每个客户端在与 Web 应用服务器建立 WebSocket 长连接时，SpringFramework 会为之生成一个 Bean ，直到断开连接。

### 5.3 Bean的实例化方式

**实例化指调用构造方法，创建新的对象**，**初始化指创建好新的对象后的属性赋值、组件注入等后续动作。**

#### 5.3.1 普通Bean实例化

普通 Bean 的对象，它们默认是单实例的，在 IOC 容器初始化时就已经被初始化了。

#### 5.3.2 FactoryBean创建Bean

注册 Bean 时，只需要注入 FactoryBean ，IOC 容器会自动识别，并默认在第一次获取时创建对应的 Bean 并缓存（针对默认的单实例 FactoryBean ）。

#### 5.3.3 静态工厂创建Bean

CarStaticFactory 类，加上静态方法：

```java
public class CarStaticFactory {
    
    public static Car getCar() {
        return new Car();
    }
}
```

静态工厂的使用通常运用于 xml 方式比较多，创建一个 bean-instantiate.xml 文件，在这里面编写关于静态工厂的使用方法：

```xml
<bean id="car1" class="com.linkedbear.spring.bean.c_instantiate.bean.Car"/>
<bean id="car2" class="com.linkedbear.spring.bean.c_instantiate.bean.CarStaticFactory" factory-method="getCar"/>
```

上面的注册方式是普通的 Bean 注册方式，下面的方式会直接引用**静态工厂**，并声明要创建对象的工厂方法 factory-method 即可。SpringFramework 会依照这个 xml 的方式，解析出规则并调用静态工厂的方法来创建实际的 Bean 对象。

**静态工厂本身不会被注册到 IOC 容器中**。

如果使用注解方式，由于 SpringFramework 中并没有提供关于静态工厂相关的注解，所以只能使用**注解配置类+编程式**使用静态工厂了，而这个使用方式相当的简单：

```java
@Bean
public Car car2() {
    return CarStaticFactory.getCar();
}
```

#### 5.3.4 实例工厂创建Bean

创建一个 CarInstanceFactory 代表实例工厂：

```java
public class CarInstanceFactory {
    
    public Car getCar() {
        return new Car();
    }
}
```

对于实例工厂，要想调用对象的方法，那自然得先把对象实例化才行了，所以咱就需要**先在 xml 中注册实例工厂**，随后才能创建真正的目标 Bean ：

```xml
<bean id="carInstanceFactory" class="com.linkedbear.spring.bean.c_instantiate.bean.CarInstanceFactory"/>
<bean id="car3" factory-bean="carInstanceFactory" factory-method="getCar"/>
```

可以发现， 标签可以不传入 class 属性，用 factory-bean 和 factory-method 属性也可以完成 Bean 的创建。

**编程式使用实例工厂的方式：**

```java
@Bean
public Car car3(CarInstanceFactory carInstanceFactory) {
    return carInstanceFactory.getCar();
}
```

### 5.4 Bean的生命周期

![img](https://cdn.nlark.com/yuque/0/2022/png/229542/1649556298915-a6a62e7f-0533-4f7e-8cb3-7512df633e8e.png)

一个对象从被创建，到被垃圾回收，可以宏观的划分为 5 个阶段：

* **创建 / 实例化阶段**：调用类的构造方法，产生一个新的对象。
* **初始化阶段**：对象已经创建好，但还没有被正式使用，可能这里面需要做一些额外的操作（如预初始化数据库的连接池）。
* **运行使用期**：此时对象已经完全初始化好，程序正常运行，对象被使用。
* **销毁阶段**：此时对象准备被销毁，已不再使用，需要预先的把自身占用的资源等处理好（如关闭、释放数据库连接）。
* **回收阶段**：此时对象已经完全没有被引用了，被垃圾回收器回收。

#### 5.4.1 Spring能干预的生命周期阶段：**初始化和销毁两个阶段**

#### 5.4.2 初始化阶段：init-method和销毁阶段：destroy-method

为了方便演示 xml 与注解的方式，咱创建两个类分别演示，这样咱搞一个 Cat 和一个 Dog 。

```java
public class Cat {
    
    private String name;
    
    public void setName(String name) {
        this.name = name;
    }
    
    public void init() {
        System.out.println(name + "被初始化了。。。");
    }
    public void destroy() {
        System.out.println(name + "被销毁了。。。");
    }
}
public class Dog {
    
    private String name;
    
    public void setName(String name) {
        this.name = name;
    }
    
    public void init() {
        System.out.println(name + "被初始化了。。。");
    }
    public void destroy() {
        System.out.println(name + "被销毁了。。。");
    }
}
```

**xml配置：**

在 标签中，有两个属性，就是上面的 init-method 和 destroy-method 。

```xml
<bean class="com.linkedbear.spring.lifecycle.a_initmethod.bean.Cat"
      init-method="init" destroy-method="destroy">
  <property name="name" value="mimi"/>
</bean>
```

**注解配置：**

```java
@Bean(initMethod = "init", destroyMethod = "destroy")
public Dog dog() {
    Dog dog = new Dog();
    dog.setName("wangwang");
    return dog;
}
```

#### 5.4.3 初始化销毁方法的要求特征

* 方法访问权限无限制要求（ SpringFramework 底层会反射调用的）。
* 方法**无参数**（如果真的设置了参数，SpringFramework 也不知道传什么进去）。
* 方法**无返回值**（返回给 SpringFramework 也没有意义）。
* 可以抛出异常（异常不由自己处理，交予 SpringFramework 可以打断 Bean 的初始化 / 销毁步骤）。

#### 5.4.4 测试

这里接收的类型不再用 ApplicationContext ，而是用实现类本身，目的是为了调用 close 方法对容器进行关闭，以触发 Bean 的销毁动作。

```java
public class InitMethodXmlApplication {
    
    public static void main(String[] args) throws Exception {
        System.out.println("准备初始化IOC容器。。。");
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("lifecycle/bean-initmethod.xml");
        System.out.println("IOC容器初始化完成。。。");
        
        System.out.println();
        
        System.out.println("准备销毁IOC容器。。。");
        ctx.close();
        System.out.println("IOC容器销毁完成。。。");
    }
}
```

运行 main 方法，控制台会打印如下内容：

```java
准备初始化IOC容器。。。
mimi被初始化了。。。
IOC容器初始化完成。。。

准备销毁IOC容器。。。
mimi被销毁了。。。
IOC容器销毁完成。。。
```

**说明在 IOC 容器初始化之前，默认情况下 Bean 已经创建好了，而且完成了初始化动作；容器调用销毁动作时，先销毁所有 Bean ，最后 IOC 容器全部销毁完成。**

#### 5.4.5 Bean的初始化流程顺序探究

上面的编码中，只能看出来 Bean 在 IOC 容器初始化阶段就创建并初始化好，那每个 Bean 的初始化动作又是如何呢？

```java
public class Cat {
    
    private String name;
    
    public Cat() {
        System.out.println("Cat 构造方法执行了。。。");
    }
    
    public void setName(String name) {
        System.out.println("setName方法执行了。。。");
        this.name = name;
    }
}
```

重新运行启动类的 main 方法，控制台会打印如下内容：（省略销毁部分）

```java
准备初始化IOC容器。。。
Cat 构造方法执行了。。。
setName方法执行了。。。
mimi被初始化了。。。
IOC容器初始化完成。。。
```

由此可以得出结论：**Bean 的生命周期中，是先对属性赋值，后执行** **init-method** **标记的方法**。

#### 5.4.6 【重要】JSR250规范

上面的方法，都是咱手动声明注册的 Bean ，对于那些**使用模式注解**的 Bean ，这种方式就不好使了，因为**没有可以让你声明** **init-method** **和** **destroy-method** **的地方了，\*\*\*\*@Component** **注解上也只有一个** **value** **属性而已**。这个时候咱就需要学习一种新的方式，这种方式专门配合注解式注册 Bean 以完成全注解驱动开发，那就是如标题所说的 **JSR250 规范**。

JSR250 规范中除了有 @Resource 这样的自动注入注解，还有负责生命周期的注解，包括 **@PostConstruct** 、**@PreDestroy** 两个注解，分别对应 init-method 和 destroy-method 。

这次咱模拟另一种场景：钢笔与墨水，刚买来的动作代表实例化，加墨水的动作代表初始化，倒掉所有墨水的动作代表销毁，于是这个 Pen 可以这样设计：

```java
@Component
public class Pen {
    
    private Integer ink;
    
    public void addInk() {
        this.ink = 100;
    }
    
    public void outwellInk() {
        this.ink = 0;
    }
    
    @Override
    public String toString() {
        return "Pen{" + "ink=" + ink + '}';
    }
    
    @PostConstruct
    public void addInk() {
        System.out.println("钢笔中已加满墨水。。。");
        this.ink = 100;
    }
    
    @PreDestroy
    public void outwellInk() {
        System.out.println("钢笔中的墨水都放干净了。。。");
        this.ink = 0;
    }
}
```

对于 JSR250 规范的这两个注解的使用，直接标注在 Bean 的方法上即可：

**被** **@PostConstruct** **和** **@PreDestroy** **注解标注的方法，与** **init-method** **/** **destroy-method** **方法的声明要求是一样的，访问修饰符也可以是 private 。**

#### 5.4.7 JSR250规范与init-method共存

如果不使用 @Component 注解来注册 Bean 而转用 / @Bean 的方式，那 @PostConstruct 与 @PreDestroy 注解是可以与 init-method / destroy-method 共存的。

新建 Pen2 ：

```java
public void open() {
    System.out.println("init-method - 打开钢笔。。。");
}

public void close() {
    System.out.println("destroy-method - 合上钢笔。。。");
}

@PostConstruct
public void addInk() {
    System.out.println("@PostConstruct - 钢笔中已加满墨水。。。");
    this.ink = 100;
}

@PreDestroy
public void outwellInk() {
    System.out.println("@PreDestroy - 钢笔中的墨水都放干净了。。。");
    this.ink = 0;
}
```

之后使用注解式注册这个 Pen2 ：

```java
@Configuration
public class JSR250Configuration {

    @Bean(initMethod = "open", destroyMethod = "close")
    public Pen2 pen() {
        return new Pen2();
    }
}
```

之后修改启动类，驱动这个配置类，观察控制台的打印：

```java
准备初始化IOC容器。。。
@PostConstruct - 钢笔中已加满墨水。。。
init-method - 打开钢笔。。。
IOC容器初始化完成。。。

准备销毁IOC容器。。。
@PreDestroy - 钢笔中的墨水都放干净了。。。
destroy-method - 合上钢笔。。。
IOC容器销毁完成。。。
```

可以得出结论：**@PostConstruct** **→** **init-method** 。

#### 5.4.8 【重要】InitializingBean\&DisposableBean

**InitializingBean\&DisposableBean**实际上是两个接口，而且是 SpringFramework 内部预先定义好的两个关于生命周期的接口。他们的触发时机与上面的 init-method / destroy-method 以及 JSR250 规范的两个注解一样，都是在 Bean 的初始化和销毁阶段要回调的。

依然使用 Pen 作为演示对象，这次咱让 Pen 实现这两个接口：

```java
@Component
public class Pen implements InitializingBean, DisposableBean {
    
    private Integer ink;
    
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("钢笔中已加满墨水。。。");
        this.ink = 100;
    }
    
    @Override
    public void destroy() throws Exception {
        System.out.println("钢笔中的墨水都放干净了。。。");
        this.ink = 0;
    }
    
    @Override
    public String toString() {
        return "Pen{" + "ink=" + ink + '}';
    }
}
```

直接使用注解驱动 IOC 容器扫描这个 Pen ，其余编写内容与上面一致：

```java
public class InitializingDisposableAnnoApplication {
    
    public static void main(String[] args) throws Exception {
        System.out.println("准备初始化IOC容器。。。");
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
                "com.linkedbear.spring.lifecycle.c_initializingbean.bean");
        System.out.println("IOC容器初始化完成。。。");
        System.out.println();
        System.out.println("准备销毁IOC容器。。。");
        ctx.close();
        System.out.println("IOC容器销毁完成。。。");
    }
}
```

运行 main 方法，控制台同样打印了触发生命周期的内容：

```java
准备初始化IOC容器。。。
钢笔中已加满墨水。。。
IOC容器初始化完成。。。

准备销毁IOC容器。。。
钢笔中的墨水都放干净了。。。
IOC容器销毁完成。。。
```

#### 5.4.9 三种生命周期并存

当一个 Bean 同时使用这三种生命周期共同控制时，执行顺序是怎样的。

```java
public void open() {
    System.out.println("init-method - 打开钢笔。。。");
}

public void close() {
    System.out.println("destroy-method - 合上钢笔。。。");
}

@PostConstruct
public void addInk() {
    System.out.println("@PostConstruct - 钢笔中已加满墨水。。。");
    this.ink = 100;
}

@PreDestroy
public void outwellInk() {
    System.out.println("@PreDestroy - 钢笔中的墨水都放干净了。。。");
    this.ink = 0;
}

@Override
public void afterPropertiesSet() throws Exception {
    System.out.println("InitializingBean - 准备写字。。。");
}

@Override
public void destroy() throws Exception {
    System.out.println("DisposableBean - 写完字了。。。");
}
```

之后使用注解驱动方式注册这个 Pen ：

```java
@Bean(initMethod = "open", destroyMethod = "close")
public Pen pen() {
    return new Pen3();
}
```

之后让注解 IOC 容器驱动这个配置类，运行 main 方法，观察控制台的打印：

```java
准备初始化IOC容器。。。
@PostConstruct - 钢笔中已加满墨水。。。
InitializingBean - 准备写字。。。
init-method - 打开钢笔。。。
IOC容器初始化完成。。。

准备销毁IOC容器。。。
@PreDestroy - 钢笔中的墨水都放干净了。。。
DisposableBean - 写完字了。。。
destroy-method - 合上钢笔。。。
IOC容器销毁完成。。。
```

这个顺序又有点怪怪的，咱不要关注那些，总结执行顺序才是最关键的：**@PostConstruct** **→** **InitializingBean** **→** **init-method** 。

#### 5.4.10 【重要】原型Bean的生命周期

对于原型 Bean 的生命周期，使用的方式跟上面是完全一致的，只是它的触发时机就不像单实例 Bean 那样了。

**单实例 Bean 的生命周期是陪着 IOC 容器一起的，容器初始化，单实例 Bean 也跟着初始化（当然不绝对，后面会介绍延迟 Bean ）；容器销毁，单实例 Bean 也跟着销毁。原型 Bean 由于每次都是取的时候才产生一个，所以它的生命周期与 IOC 容器无关。**

```java
@Configuration
public class PrototypeLifecycleConfiguration {
    
    @Bean(initMethod = "open", destroyMethod = "close")
    @Scope(ConfigurableBeanFactroy.SCOPE_PROTOTYPE)
    public Pen pen() {
        return new Pen();
    }
}
```

编写启动类，只初始化 IOC 容器：

```java
public class PrototypeLifecycleApplication {
    
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
                PrototypeLifecycleConfiguration.class);
        System.out.println("IOC容器初始化完成。。。");
    }
}
```

运行 main 方法，控制台只打印了 IOC容器初始化完成。。。 这一句话，证明**原型 Bean 的创建不随 IOC 的初始化而创建**。

在 main 方法中添加如下几行代码，来获取一次 Pen 实例：

```java
public static void main(String[] args) throws Exception {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
            PrototypeLifecycleConfiguration.class);
    System.out.println("准备获取一个Pen。。。");
    Pen pen = ctx.getBean(Pen.class);
    System.out.println("已经取到了Pen。。。");
}
```

运行，控制台打印 Pen 的初始化动作：

```java
准备获取一个Pen。。。
@PostConstruct - 钢笔中已加满墨水。。。
InitializingBean - 准备写字。。。
init-method - 打开钢笔。。。
已经取到了Pen。。。
```

三种初始化的动作都执行了，证明**原型Bean的初始化动作与单实例Bean完全一致**。

在 main 方法中再添加几行代码，将这个 Pen 销毁掉：

```java
public static void main(String[] args) throws Exception {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
            PrototypeLifecycleConfiguration.class);
    System.out.println("准备获取一个Pen。。。");
    Pen pen = ctx.getBean(Pen.class);
    System.out.println("已经取到了Pen。。。");
    System.out.println("用完Pen了，准备销毁。。。");
    ctx.getBeanFactroy().destroyBean(pen);
    System.out.println("Pen销毁完成。。。");
}
```

再次运行 main 方法，发现控制台中只打印了 @PreDestroy 注解和 DisposableBean 接口的执行，没有触发 destroy-method 的执行：

```java
用完Pen了，准备销毁。。。
@PreDestroy - 钢笔中的墨水都放干净了。。。
DisposableBean - 写完字了。。。
Pen销毁完成。。。
```

得出结论：**原型 Bean 在销毁时不处理** **destroy-method** **标注的方法**。

#### 5.4.11【面试题】SpringFramework中控制Bean生命周期的三种方式

以下的对比维度不是最终最全的，不过在现在的阶段已经是足够了：

|         | init-method & destroy-method | @PostConstruct & @PreDestroy | InitializingBean & DisposableBean |
| ------- | ---------------------------- | ---------------------------- | --------------------------------- |
| 执行顺序    | 最后                           | 最先                           | 中间                                |
| 组件耦合度   | 无侵入（只在 和 @Bean 中使用）          | 与 JSR 规范耦合                   | 与 SpringFramework 耦合              |
| 容器支持    | xml 、注解原生支持                  | 注解原生支持，xml需开启注解驱动            | xml 、注解原生支持                       |
| 单实例Bean | √                            | √                            | √                                 |
| 原型Bean  | 只支持 init-method              | √                            | √                                 |

到这里，Bean 的基本生命周期就讲解完毕了（恩，仅仅是基本的），更复杂的生命周期知识咱放到后面的 IOC 高级中讲解。
