# Ioc应用


<!--more-->

# Spring IOC应用

## 一、IOC基础

### 1.  BeanFactory与ApplicationContext区别
BeanFactory是Spring框架中IoC容器的顶层接⼝,它只是⽤来定义⼀些基础功能,定义⼀些基础规范,⽽ApplicationContext是它的⼀个⼦接⼝，所以ApplicationContext是具备BeanFactory提供的全部功能的。通常，我们称BeanFactory为SpringIOC的基础容器，ApplicationContext是容器的⾼级接⼝，⽐BeanFactory要拥有更多的功能，⽐如说国际化⽀持和资源访问（xml，java配置类）等等  
启动ioc容器的方式：
- Java环境下启动
  - ClassPathXmlApplicationContext：从类的根路径下加载配置⽂件（推荐使⽤）
  - FileSystemXmlApplicationContext：从磁盘路径上加载配置⽂件
  - AnnotationConfigApplicationContext：纯注解模式下启动Spring容器
- web环境下启动
  - 从xml启动容器
    ```xml
    <!DOCTYPE web-app PUBLIC
            "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
            "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app>
        <display-name>Archetype Created Web Application</display-name>
    
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </context-param>
    
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
    </web-app>
    ```
  - 从配置类启动容器
    ```xml
    <web-app>
        <display-name>Archetype Created Web Application</display-name>
    
        <!--告诉ContextloaderListener知道我们使用注解的方式启动ioc容器-->
        <context-param>
            <param-name>contextClass</param-name>
            <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
        </context-param>
        <!--配置启动类的全限定类名-->
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.example.demo.SpringConfig</param-value>
        </context-param>
    
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
    </web-app>
    ```
### 2. 纯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"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  https://www.springframework.org/schema/beans/spring-beans.xsd">
  ```
  
- **实例化bean的三种方式**
  - 使用无参构造  
    在默认情况下，它会通过反射调⽤⽆参构造函数来创建对象。如果类中没有⽆参构造函数，将创建
    失败
    ```xml
    <bean id="connectionUtils" class="com.example.demo.utils.ConnectionUtils"></bean>
    ```
  - 使用静态方法  
    在实际开发中，我们使⽤的对象有些时候并不是直接通过构造函数就可以创建出来的，它可能在创建的过程 中会做很多额外的操作。此时会提供⼀个创建对象的⽅法，恰好这个⽅法是static修饰的⽅法，即是此种情况。  
    例如，我们在做Jdbc操作时，会⽤到`java.sql.Connection`接⼝的实现类，如果是mysql数据库，那么⽤的就 是`JDBC4Connection`，但是我们不会去写`JDBC4Connection connection = new JDBC4Connection() `，因 为我们要注册驱动，还要提供URL和凭证信息,⽤`DriverManager.getConnection` ⽅法来获取连接。  
    在实际开发中，尤其早期的项⽬没有使⽤Spring框架来管理对象的创建，但是在设计时使⽤了⼯⼚模式 解耦，那么当接⼊spring之后，⼯⼚类创建对象就具有和上述例⼦相同特征，即可采⽤此种⽅式配置
    ```xml
    <!--使⽤静态⽅法创建对象的配置⽅式-->
    <bean id="connectionUtils" class="com.example.demo.factory.CreateBeanFactory" factory-method="getInstanceStatic"></bean>
    ```
  - 使用实例方法  
    此种⽅式和上⾯静态⽅法创建其实类似，区别是⽤于获取对象的⽅法不再是static修饰的了，⽽是类中的⼀ 个普通⽅法。此种⽅式⽐静态⽅法创建的使⽤⼏率要⾼⼀些。在早期开发的项⽬中，⼯⼚类中的⽅法有可能是静态的，也有可能是⾮静态⽅法，当是⾮静态⽅法时，即可 采⽤下⾯的配置⽅式：
    ```xml
    <bean id="createBeanFactory" class="com.example.demo.factory.CreateBeanFactory"></bean>
    <bean id="connectionUtils" factory-bean="createBeanFactory" factory-method="getInstance"></bean>
    ```
  
- **Bean的作用域及生命周期**
  - 作用域
    singleton、prototype、request、session、application、websocket,通过`<bean>`标签的scop属性配置
  - 生命周期
    > **单例模式：singleton**  
    > 对象出⽣：当创建容器时，对象就被创建了。  
    > 对象活着：只要容器在，对象⼀直活着。  
    > 对象死亡：当销毁容器时，对象就被销毁了。  
    > ⼀句话总结：单例模式的bean对象⽣命周期与容器相同。  
    >
    > **多例模式：prototype**  
    > 对象出⽣：当使⽤对象时，创建新的对象实例。  
    > 对象活着：只要对象在使⽤中，就⼀直活着。  
    > 对象死亡：当对象⻓时间不⽤时，被java的垃圾回收器回收了。  
    > ⼀句话总结：多例模式的bean对象，spring框架只负责创建，不负责销毁  

- **Bean标签属性**  
  在基于xml的IoC配置中，bean标签是最基础的标签。它表示了IoC容器中的⼀个对象。换句话说，如果⼀个对象想让spring管理，在XML的配置中都需要使⽤此标签配置，Bean标签的属性如下：
  - id属性： ⽤于给bean提供⼀个唯⼀标识。在⼀个标签内部，标识必须唯⼀。
  - class属性：⽤于指定创建Bean对象的全限定类名。
  - name属性：⽤于给bean提供⼀个或多个名称。多个名称⽤空格分隔。
  - factory-bean属性：⽤于指定创建当前bean对象的⼯⼚bean的唯⼀标识。当指定了此属性之后，class属性失效。
  - factory-method属性：⽤于指定创建当前bean对象的⼯⼚⽅法，如配合factory-bean属性使⽤，
  - 则class属性失效。如配合class属性使⽤，则⽅法必须是static的。
  - scope属性：⽤于指定bean对象的作⽤范围。通常情况下就是singleton。当要⽤到多例模式时，可以配置为prototype。
  - init-method属性：⽤于指定bean对象的初始化⽅法，此⽅法会在bean对象装配后调⽤。必须是⼀个⽆参⽅法。
  - destory-method属性：⽤于指定bean对象的销毁⽅法，此⽅法会在bean对象销毁前执⾏。它只能为scope是singleton时起作⽤。

- **DI依赖注入的xml配置**
  - 注入分类
    >**按注入方式**  
    > 1 构造函数注入：利用带参构造实现对成员的数据赋值    
    > 2 setter方法注入：通过类成员的set方法实现数据注入-使用最多   
    > 
    >**按注入数据类型**  
    > 1 基本类型和String：基本数据类型   
    > 2 其他Bean类型：对象    
    > 3 复杂类型：Array,List,Set,Map,Properties等  

  - 依赖注⼊的配置实现之构造函数注⼊ 
    就是利⽤构造函数实现对类成员的赋值。它的使⽤要求是，类中提供的构造函数参数个数必须和配置的参数个数⼀致，且数据类型匹配。同时需要注意的是，当没有⽆参构造时，则必须提供构造函数参数的注⼊，否则Spring框架会报错，在使⽤构造函数注⼊时，涉及的标签是`constructor-arg` ，该标签有如下属性：
    > 1 name：⽤于给构造函数中指定名称的参数赋值。  
    > 2 index：⽤于给构造函数中指定索引位置的参数赋值。  
    > 3 value：⽤于指定基本类型或者String类型的数据。  
    > 4 ref：⽤于指定其他Bean类型的数据。写的是其他bean的唯⼀标识。  

  - 依赖注⼊的配置实现之set⽅法注⼊
    就是利⽤字段的set⽅法实现赋值的注⼊⽅式。此种⽅式在实际开发中是使⽤最多的注⼊⽅式,在使⽤set⽅法注⼊时，需要使⽤`property` 标签，该标签属性如下：
    > 1 name：指定注⼊时调⽤的set⽅法名称。（注：不包含set这三个字⺟,druid连接池指定属性名称）  
    > 2 value：指定注⼊的数据。它⽀持基本类型和String类型。  
    > 3 ref：指定注⼊的数据。它⽀持其他bean类型。写的是其他bean的唯⼀标识。  

  - 复杂数据类型注⼊
    以set方式示例
    ```xml
    <property name="myArray">
      <array>
        <value>array1</value>
        <value>array2</value>
        <value>array3</value>
      </array>
    </property>
    
    <property name="myMap">
      <map>
        <entry key="key1" value="value1"/>
        <entry key="key2" value="value2"/>
      </map>
    </property>
    
    <property name="mySet">
      <set>
        <value>set1</value>
        <value>set2</value>
      </set>
    </property>
    
    <property name="myProperties">
      <props>
        <prop key="prop1">value1</prop>
        <prop key="prop2">value2</prop>
      </props>
    </property>
    ```
    > 1 在**List**结构的集合数据注⼊时， array , list , set 这三个标签通⽤，另外注值的value 标签内部可以直接写值，也可以使⽤bean 标签配置⼀个对象，或者⽤ref 标签引⽤⼀个已经配合的bean的唯⼀标识    
    > 2 在**Map**结构的集合数据注⼊时， map 标签使⽤entry ⼦标签实现数据注⼊， entry 标签可以使⽤key和value属性指定存⼊map中的数据。使⽤value-ref属性指定已经配置好的bean的引⽤。同时entry 标签中也可以使⽤ref 标签，但是不能使⽤bean 标签    
    > 3 **property** 标签中不能使⽤ref 或者bean 标签引⽤对象  


### 3. xml与注解相结合

第三⽅jar中的bean定义在xml，⽐如德鲁伊数据库连接池

⾃⼰开发的bean定义使⽤注解

- 标签与注解对应
  | xml形式                  | 对应注解形式                                                 |
  | ------------------------ | ------------------------------------------------------------ |
  | <bean>                   | @Component("accountDao")，注解加在类上; <br> bean的id属性内容直接配置在注解后⾯如果不配置，默认定义个这个bean的id为类的类名⾸字⺟⼩写;<br>针对分层代码开发提供了@Componenet的三种别名@Controller、@Service、@Repository分别⽤于控制层类、服务层类、dao层类的bean定义 |
  | 标签的scope属性          | @Scope("prototype")，默认单例，注解加在类上                  |
  | 标签的init-method属性    | @PostConstruct，注解加在⽅法上，该⽅法就是初始化后调⽤的⽅法 |
  | 标签的destroy-method属性 | @PreDestory，注解加在⽅法上，该⽅法就是销毁前调⽤的⽅法      |

- DI依赖注入实现方式
  @Autowired（按照类型注入）
  > 如果有多个类型，则配合@Qualifier使用，例：@Qualifier(name="jdbcAccountDaoImpl")

  @Resource
  > 由J2EE提供，需要导入包javax.annotation.Resurce,默认按照ByName注入，例：@Resource(name="manDao",type="ManDao")
  >
  > 在jdk11中已经去掉，如需使用，需添加依赖
  >
  > ```xml
  > <dependency>
  >   <groupId>javax.annotation</groupId>
  >   <artifactId>javax.annotation-api</artifactId>
  >   <version>1.3.2</version>
  > </dependency>
  > ```

### 4. 纯注解模式

从Java配置类启动，对应注解  
- @Configuration 注解，表名当前类是⼀个配置类
- @ComponentScan 注解，替代 context:component-scan
- @PropertySource，引⼊外部属性配置⽂件
- @Import 引⼊其他配置类
- @Value 对变量赋值，可以直接赋值，也可以使⽤ ${} 读取资源配置⽂件中的信息
- @Bean 将⽅法返回对象加⼊ SpringIOC 容器



## 二、IOC高级特性

### 1. lazy-init 延迟加载

ApplicationContext 容器的默认⾏为是在启动服务器时将所有 singleton bean 提前进⾏实例化。提前实例化意味着作为初始化过程的⼀部分，ApplicationContext 实例会创建并配置所有的singleton bean， 如：

```xml
<bean id="testBean" calss="cn.example.demo.LazyBean" lazy-init="false" />
```

`lazy-init="false"`: false立即加载，true延迟加载(第⼀次向容器通过 getBean 索取 bean 时实例化)

>如果⼀个设置了⽴即加载的 bean1，引⽤了⼀个延迟加载的 bean2 ，那么 bean1 在容器启动时被实例化，⽽ bean2 由于被 bean1 引⽤，所以也被实例化，这种情况也符合延时加载的 bean 在第⼀次调⽤时才被实例化的规则

也可以在容器层次上进行控制，如：

```xml
<beans default-lazy-init="true">
<!-- no beans will be eagerly pre-instantiated... -->
</beans>
```

> 如果⼀个 bean 的 scope 属性为 scope="pototype" 时，即使设置了 lazy-init="false"，容器启动时也不会实例化bean，⽽是调⽤ getBean ⽅法实例化的

### 2. FactoryBean 和 BeanFactory

**BeanFactory**: 是容器的顶级接⼝，定义了容器的⼀些基础⾏为，负责⽣产和管理Bean的⼀个⼯⼚，具体使⽤它下⾯的⼦接⼝类型，⽐如ApplicationContext

**FactoryBean**: 工厂bean,可以⽣成某⼀个类型的Bean实例（返回给我们），也就是说我们可以借助于它⾃定义Bean的创建过程

> Bean创建的三种⽅式中的静态⽅法和实例化⽅法和FactoryBean作⽤类似，FactoryBean使⽤较多，尤其在Spring框架⼀些组件中会使⽤，还有其他框架和Spring框架整合时使⽤

```java
// 可以让我们⾃定义Bean的创建过程（完成复杂Bean的定义）
public interface FactoryBean<T> {
  // 返回FactoryBean创建的Bean实例，如果isSingleton返回true，则该实例会放到Spring容器的单例对象缓存池中Map
  @Nullable
  T getObject() throws Exception;
  
  // 返回FactoryBean创建的Bean类型
  @Nullable
  Class<?> getObjectType();
  
  // 返回作⽤域是否单例
  default boolean isSingleton() {
  	return true;
  }
}
```

例：

```java
/**
 * user 实体
 * @author zhaojianqiang
 */
public class User {
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private String name;

    @Override
    public String toString() {
        return "User{" +
            "name='" + name + '\'' +
            '}';
    }
}

/**
 * @author zhaojianqiang
 */
public class UserFactoryBean implements FactoryBean<User> {
    private String username;

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public User getObject() throws Exception {
        User user = new User();
        user.setName(username);
        return user;
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }
}

//配置类
@Configuration 
@ComponentScan({"com.example.demo"})
public class SpringConfig {

    @Bean("userFactoryBean")
    public UserFactoryBean creatUserFactoryBean() {
        UserFactoryBean userFactoryBean = new UserFactoryBean();
        userFactoryBean.setUsername("赵小二");
        return userFactoryBean;
    }
}

/**
 * @author zhaojianqiang
 */
public class TestClass {

    /**
     * factoryBean 测试
     */
    @Test
    public void test1() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        User bean = (User) context.getBean("userFactoryBean");
        System.out.println(bean);

        //注意，要获取factoryBean，需要前面加 & 符号
        UserFactoryBean bean1 = (UserFactoryBean) context.getBean("&userFactoryBean");
        System.out.println(bean1);
    }
}

```

### 3. 后置处理器

⼯⼚初始化（BeanFactory）—> Bean对象

#### 3.1 BeanFactoryPostProcessor

在BeanFactory初始化之后，进⾏后置处理做⼀些事情

BeanFactory级别的处理，是针对整个Bean的⼯⼚进⾏处理，典型应⽤:PropertyPlaceholderConfigurer

![img](https://img.zhaojq.top/20260727184928519.png "img")

此接⼝只提供了⼀个⽅法，⽅法参数为ConfigurableListableBeanFactory，该参数类型定义了⼀些⽅法

![img](https://img.zhaojq.top/20260727184928741.png "img")

其中有个⽅法名为getBeanDefinition的⽅法，我们可以根据此⽅法，找到我们定义bean 的BeanDefinition对象，然后我们可以对定义的属性进⾏修改，以下是BeanDefinition中的⽅法

![img](https://img.zhaojq.top/20260727184928960.png "img")

> ⽅法名字类似我们bean标签的属性，setBeanClassName对应bean标签中的class属性，所以当我们拿到BeanDefinition对象时，我们可以⼿动修改bean标签中所定义的属性值。
>
> **BeanDefinition**对象：我们在 XML 中定义的 bean标签，Spring 解析 bean 标签成为⼀个 JavaBean，这个JavaBean 就是 BeanDefinition
>
> 调⽤ BeanFactoryPostProcessor ⽅法时，这时候bean还没有实例化，此时 bean 刚被解析BeanDefinition对象

#### 3.2 BeanPostProcessor

在Bean对象实例化（并不是Bean的整个⽣命周期完成）之后，进⾏后置处理做⼀些事情

![img](https://img.zhaojq.top/20260727184929161.png "img")

该接⼝提供了两个⽅法，分别在Bean的初始化⽅法前和初始化⽅法后执⾏，具体这个初始化⽅法指的是什么⽅法，类似我们在定义bean时，定义了init-method所指定的⽅法

定义⼀个类实现了BeanPostProcessor，默认是会对整个Spring容器中所有的bean进⾏处理。如果要对具体的某个bean处理，可以通过⽅法参数判断，两个类型参数分别为Object和String，第⼀个参数是每个bean的实例，第⼆个参数是每个bean的name或者id属性的值。所以我们可以通过第⼆个参数，来判断我们将要处理的具体的bean。

> 注意：处理是发⽣在Spring容器的实例化和依赖注⼊之后

> Tips: 与afterPropertiesSet(实现InitializingBean接口)，init-Method相比，先执行的是postProcessBeforeInitialization，然后是afterPropertiesSet，然后是init-method，然后是postProcessAfterInitialization












































