Spring2️⃣浅聊 IoC、Bean管理

相关概念

  • 依赖:一个类中使用到另一个类。
  • 依赖倒置原则:尽量依赖抽象类,而不依赖具体类。
  • 工厂模式:处理创建对象的细节,实现创建者和调用者的分离。
  • 反射:动态创建对象和编译,体现出很大的灵活性。

1、控制反转(IoC)

控制反转(Inversion of Control)

OO 设计思想,目的是降低耦合度。

  1. 含义:将对象的创建和对象之间的依赖关系,交给 Spring 管理。
  2. 实现方式
    • 依赖注入
    • 依赖查找
  3. 底层原理XML 解析 + 工厂 + 反射
    1. 使用 XML 解析技术,获取配置文件中的配置信息。
    2. 反射 + 工厂(BeanFactory),创建对象实例。
    3. 需要使用对象时,从工厂获取对象实例。

1.1、Spring Bean

Spring Bean

Spring 框架的基本构建块。

简单来说,Bean 就是 Spring 容器管理的 Java 对象。

  1. 类型
    • Bean:一般的 Java 对象。
    • FactoryBean:用于生产对象的工厂类,(👉 工厂模式)。
  2. 管理内容:可以基于 XML 配置、注解实现。
    • 创建对象、依赖注入(IoC
    • 拦截方法调用、提供额外功能(AOP
    • 销毁

1.2、静态工厂 - BeanFactory

BeanFactory 接口:Spring 内部创建对象的工厂。

IoC 容器的基本实现,通常由 Spring 内部使用

image-20220308150156675

常用接口ApplicationContext

  • FileSystemXmlApplicationContext:使用 XML 配置,参数为系统路径。

  • ClassPathXmlApplicationContext:使用 XML 配置,参数为类路径(推荐)

  • AnnotationConfigApplicationContext:使用 JavaConfig 配置,参数为配置类的 Class(基于注解开发使用)

BeanFactory 和 FactoryBean 是两个不同的概念,注意区分。

2、XML 开发

2.1、创建对象

创建方式

  1. 无参构造 + setter
  2. 有参构造
  3. BeanFactory

示例:Person

public class Person {
    private String name;
    private int age;
}

无参构造

注意:Person 类必须提供 setter。

<bean id="person" class="indi.jaywee.Person">
    <property name="name" value="Jaywee"/>
    <property name="age" value="17"/>
</bean>

有参构造 (❗)

根据参数列表,自动选择构造方法。

三种创建方式

  1. 按下标:指定构造方法的参数顺序,从 0 开始。

    <bean id="person1" class="indi.jaywee.Person">
        <constructor-arg index="0" value="Jaywee"/>
        <constructor-arg index="1" value="17"/>
    </bean>
    
  2. 按名称:指定方法参数名(推荐使用)

    <bean id="person2" class="indi.jaywee.Person">
        <constructor-arg name="name" value="Jaywee"/>
        <constructor-arg name="age" value="17"/>
    </bean>
    
  3. 按类型:指定方法参数类型(不建议使用)

    • 基本类型写简单类名,引用类型写全限类名(或别名)。

    • 若有多个参数类型相同,无法使用。

      <bean id="person3" class="indi.jaywee.Person">
          <constructor-arg type="java.lang.String" value="Jaywee"/>
          <constructor-arg type="int" value="17"/>
      </bean>
      

FactoryBean

实现 FactoryBean 接口,泛型指定生产的对象类型。

  1. 创建 Person 工厂

    public class PersonFactory implements FactoryBean<Person> {
        @Override
        public Person getObject() throws Exception {
            return new Person("default", 0);
        }
    
        @Override
        public Class<?> getObjectType() {
            return Person.class;
        }
        
        @Override
        public boolean isSingleton() {
            return true;
        }
    }
    
  2. 配置元数据:配置的 Bean 类型是 PersonFactory

    <bean id="personFactory" class="indi.jaywee.PersonFactory">
    </bean>
    

2.2、依赖注入

注入方式

  1. 有参构造方法(即通过有参构造方法创建对象)
  2. setter
  3. 命名空间
    • p 命名空间:本质是 setter 注入
    • c 命名空间:本质是有参构造注入

2.2.1、setter 注入(❗)

不同数据类型的注入基本相同,

注意区分标签和属性名。

简单类型

使用 property 标签

  1. 简单类型(基本类型 + 包装类 + String):value 属性。

  2. 引用类型:ref 属性。

    <property name="参数名" value="参数值"/>
    <property name="参数名" ref="引用其他Bean"/>
    

聚合类型

使用 property 标签,

内部使用子标签来区分具体类型。

  1. 单值类型:数组、List、Set

    1. 数据类型:array/list/set 标签。

    2. 元素:value 标签。

      <property name="参数名">
          <类型>
              <value>元素1</value>
              <value>元素2</value>
          </类型>
      </property>
      
  2. 键值对类型:Map

    1. 数据类型:map 标签。

    2. 元素:entry 标签,key/value 属性。

      <property name="参数名">
          <map>
              <entry key="key1" value="value1"/>
              <entry key="key2" value="value2"/>
      					...
          </map>
      </property>
      
  3. 配置文件

    1. 数据类型:props 标签

    2. 元素:prop 标签,key 属性 + value 内容。

      <property name="参数名">
          <props>
              <prop key="prop1">value1</prop>
              <prop key="prop2">value2</prop>
                  		...
          </props>
      </property>
      
  4. null:自闭和标签。

    <property name="参数名">
        <null/>
    </property>
    

2.2.2、property 配置

在 property 标签中,可以直接填写 key 和 value 的内容。

  1. 缺点:硬编码,不利于统一维护。
  2. 对策:使用 context 标签引入配置文件,${} 引用配置。

示例:配置数据库连接池

<!--引入外部配置文件-->
<context:property-placeholder location="classpath:jdbc.properties"/> 

<!--配置连接池--> 
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${prop.driverClass}"></property>
    <property name="url" value="${prop.url}"></property> 
    <property name="username" value="${prop.userName}"></property>
    <property name="password" value="${prop.password}"></property> 
</bean>

2.3、自动装配(❗)

指定装配规则,Spring 根据规则进行匹配属性值,完成依赖注入。

  • 实现方式:基于 XML、基于注解实现。
  • 好处:自动注入和更新属性值

2.4.1、装配规则

使用 Bean 标签的 autowire 属性,

有以下取值。

装配规则 匹配规则 说明
no 不自动装配 - 默认
byName 匹配属性名 对象属性名 == 待注入 Bean 的 ID 值 如果匹配到多个相同类型的对象,会报错
byType 匹配属性类型 对象属性类型 == 待注入 Bean 的 class 类型 -
constructor 匹配构造方法 按顺序匹配构造方法的参数列表 前提是只存在一个匹配的构造方法

2.4.2、示例

环境搭建

Java 类

public class Department {
    private String name;
    // setter、toString()
}
public class Employee {
    private Department dept;
    // setter、toString()、打印dept
}

先注册几个 Department

<bean id="dept" class="indi.jaywee.auto.Department">
    <property name="name" value="HR"/>
</bean>
<bean id="dept1" class="indi.jaywee.auto.Department">
    <property name="name" value="Dev"/>
</bean>
<bean id="dept2" class="indi.jaywee.auto.Department">
    <property name="name" value="Operation"/>
</bean>

演示 byName

<bean id="employee" class="indi.jaywee.auto.Employee" autowire="byName">
</bean>

对象中的属性名为 dept,匹配到 ID 值为 dept 的 bean。

image-20220308015705843

演示 byType

<bean id="employee" class="indi.jaywee.auto.Employee" autowire="byType">
</bean>
  • 容器中注册了多个 Department 类型的 bean,报错。

  • 假如只保留 dept1:匹配到 class 为 Department 的 bean,将其注入。

    image-20220308020935095

3、再谈 Spring Bean

3.1、装配时机

装配:对象创建 + 依赖注入的完整过程

  1. 对象创建:实例化容器时,Spring 读取元数据配置并创建所有对象。
  2. 依赖注入:不同方式有所区别。
    • 有参构造实例化容器时完成。
    • 无参构造:从容器中获取 Bean 实例时才完成。

测试思路

  1. 在对象的有参、无参构造方法中调用 toString()。
  2. 分别使用有参、无参构造方式,配置元数据。
  3. 实例化容器:
    • 若有输出语句,说明对象已创建。
    • 若有 toString() 打印出了属性的赋值,说明已注入。
  4. 获取对象,调用 toString()。

3.2、生命周期 (🔥)

Bean 从创建到销毁的 5 个过程

  1. 创建对象:创建 Bean 实例(无参构造)
  2. 依赖注入:设置属性值(setter)
  3. 初始化:装配 bean 时调用(initMethod)
  4. 获取并使用对象
  5. 销毁:容器关闭时调用(destroy-method)

演示

定义一个类,演示 bean 的生命周期

public class LifeCycle {
    private String name;
    private Person person;

    public LifeCycle() {
        System.out.println("创建对象");
    }

    public void setName(String name) {
        this.name = name;
        System.out.println("依赖注入:name");
    }

    public void setPerson(Person person) {
        this.person = person;
        System.out.println("依赖注入:person");
    }

    public void initBean() {
        System.out.println("初始化");
    }

    public void destroyBean() {
        System.out.println("销毁");
    }
}

配置元数据

  • 初始化、销毁方法,需要使用相应的属性绑定。
  • 不同属性依赖的注入顺序,由配置文件中的顺序决定。
<bean id="person" class="indi.jaywee.Person">
</bean>

<bean id="lifeCycle" class="indi.jaywee.LifeCycle"
      init-method="initBean" destroy-method="destroyBean">
    <property name="name" value="abc"/>
    <property name="person" ref="person"/>
</bean>

3.3、后置处理器

后置处理器(Post Processor)

  1. 作用:处理 Bean 的生命周期。
  2. 类型
    1. Bean 后置处理器
    2. BeanFactory 后置处理器

TODO...

Spring 根据 XML 配置,进行以下操作:

  1. 扫描 .class 文件,将包含类级别注解的 Bean 注册到 BeanFactory 中。
  2. 注册并实例化一个后置处理器,用于处理类内部的注解
  3. 将后置处理器放到 BeanFactory 的 beanPostProcessors 列表中。
  4. 属性注入或者初始化 Bean 时,调用相应的处理器进行处理。

4、注解开发

注解

  1. 简化 XML 配置,方便开发。
  2. 若同时对一个 bean 进行配置,XML 会覆盖注解的配置。

4.1、Spring 注解 (❗)

4.1.1、原始注解

创建对象

约定大于配置

@Component:Java Bean 元注解

  • @Repository:只能用于 DAO 层,否则报错。
  • @Controller
  • @Service

依赖注入

注入

  • @Value:简单类型。
  • @Autowired:引用类型

微调

  • @Qualifier:在待注入的属性上使用,指定要注入的 Bean 名称。
  • @Primary:在对象上使用,指定当前 Bean 优先注入。

其它

  1. 允许空值:@Nullable
  2. 作用域:@Scope
  3. 生命周期:@PostConstruct、@PreDestory

4.1.2、新注解

用于完全注解开发,摆脱 XML 配置文件

  • @Configuration:注册配置类(相当于 XML 配置文件)
  • @ComponentScan:组件扫描
  • @Bean:将方法返回值注册到容器中(相当于工厂 Bean)
  • @PropertySource:加载 properties 文件的配置
  • @Import:导入其它配置类的配置

4.1.3、相关配置

若要基于注解开发,需要导入以下依赖和配置。

  1. 依赖:Spring 4 + 注解需要 AOP 支持。

    image-20210801005134502
  2. XML 配置

    • 引入 context 命名空间

    • 开启组件扫描:多个包之间用逗号隔开

      image-20220308113230405
  3. 扫描范围:指定包内的注解生效,默认是所有注解生效。

    1. include-filter:仅指定的注解生效。

    2. exclude-filter:排除指定的注解。

      <!-- 只扫描@Component -->
      <context:component-scan base-package="indi.jaywee.anno">
          <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
      </context:component-scan>
      
      <!-- 排除@Controller -->
      <context:component-scan base-package="indi.jaywee.anno">
          <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
      </context:component-scan>
      

4.2、创建对象

示例@Component

相当于 XML 中的 <bean> 标签。

bean 名称

  1. 默认简单类名的首字母小写形式。

  2. 显式设置:使用 value 属性,覆盖默认 Bean 名。

    // 默认:person
    @Component
    public class Person {
        ...
    }
    
    // 显式设置
    @Component(value = "p")
    public class Person {
        ...
    }
    @Component("p")
    public class Person {
        ...
    }
    

假如注解的属性只有一个注解,并且是 value

此时可以省略 value =,直接填写字面量

4.3、依赖注入

简单类型

@Value

// 基本类型
@Value(value = "18")
private int age;

// 包装类型
@Value("12345")
private Integer id;

// String
@Value("Jaywee")
private String name;

引用类型(❗)

@Autowired 是 Spring 注解,与 Spring 框架强耦合。

建议使用 J2EE 规范的 @Resource 注解。

@Autowired @Resource
提供者 Spring J2EE(JSR-250)
默认装配模式 先 byType,再 byName 先 byName,再 byType
搭配 @Qualifier@Primary 注解 name、type 属性

@Autowired

工作机制先 byType,再 byName

步骤:先扫描容器中是否存在类型匹配的 bean。

  1. 没有:抛异常。

  2. 有且唯一:则直接注入。

  3. 有多个

    1. 扫描 @Qualifier 指定的 Bean 是否存在。

    2. 扫描是否有 @Primary 的对象。

    3. 匹配名称。有则注入,没有则抛异常。

      image-20220327001226040

示例:假设容器中有 UserServiceImplA 和 userServiceImplB。

  1. @Qualifier:按指定 "UserServiceImplA" 查找注入

    class Client {
        @Autowired
        @Qualifier("UserServiceImplA")
        private User user;
    }
    
  2. @Primary:优先注入 UserServiceImplB

    class UserServiceImplA implements UserService{} 
    @Primary
    class UserServiceImplB implements UserService{} 
    

@Resource

注解的属性决定工作机制

  1. 不指定:默认 先 byName ,再 byType
  2. 同时指定:按 name 和 type 唯一匹配。
  3. 只指定 name:有则注入,没有则抛异常。
  4. 只指定 type:有且其唯一则注入,否则抛异常。

5、完全注解开发

步骤

  1. 使用 @Configuration 注解(取代 XML 配置文件)。
  2. 使用 AnnotationConfigApplicationContext 容器。

示例

  1. 配置类

    @Configuration
    @ComponentScan(basePackages = "indi.jaywee.anno")
    public class MyConfig {
        @Bean
        public Employee getEmployee() {
            return new Employee();
        }
    }
    
  2. 初始化容器

    ApplicationContext context = 
        new AnnotationConfigApplicationContext(MyConfig.class);
    

6、注解 > XML ?

注解一定比配置文件好吗?

image-20210801200408031
  1. XML:配置繁琐,但易于维护。
  2. 注解:使用简单,但不易于维护、需要修改类代码。

在实际开发中,仍推荐使用注解开发。

posted @ 2021-07-30 21:53  Jaywee  阅读(128)  评论(0编辑  收藏  举报

👇