Loading

Spring IoC实战

1 Spring IoC使用流程

IoC(或DI)是Spring框架的核心功能之一,是Spring生态系统的基础。

Spring IoC的主要功能是将项目中的各种POJO虚拟成一个个Bean,管理这些Bean的生命周期以及Bean之间的依赖关系。开发人员在需要使用某个POJO时,只需要通过Spring容器/工厂获取即可,不必关心该对象的创建过程和内部复杂的依赖关系,因此大大简化了Java开发。

我们可以将Spring的IoC功能理解成容器或者工厂。对于某些singleton作用域的Bean而言,Spring IoC需要对其进行缓存,以便每次获取都保证是同一个对象,因此充当了容器的功能。实际上Spring内部使用了Map的数据结构保存这些单例Bean。对于某些prototype作用域的Bean而言,每次获取都需要创建新的对象返回,此时Spring IoC充当了工厂的功能。

SpringIoC使用逻辑

上图对日常使用Spring IoC的流程进行了抽象。BeanFactory接口是Spring IoC对其容器/工厂功能的最顶层抽象,它有许多实现类可用于不同的应用场景。POJO类表示日常开发中的项目逻辑。配置文件中定义了Bean之间的依赖关系,表示将项目中各种POJO抽象成Bean,并交给Spring IoC管理。其中@Annotion配置包括Java配置类和POJO中的注解声明两种方式。

因此,日常开发流程如下:

  1. 根据项目需求开发各种POJO类;
  2. 通过配置文件(XML文件、Java配置类等)定义需要被管理的Bean以及它们之间的依赖关系;
  3. 根据不同应用场景,使用BeanFactory接口的具体容器/工厂实现类加载配置文件,它内部会对相关Bean进行管理;
  4. 在需要使用POJO时,调用上述容器/工厂实现类的getBean()方法获取Bean并使用。

接下来,我们使用实际代码来简单过一下这个流程。

1.1 开发POJO类——项目逻辑

这里我们简单创建出AB两个类,代表我们日常开发中的各种具体业务逻辑。在这里它们没有什么复杂的业务逻辑,仅仅是用于举例。

A

package com.xianhuii.spring.bean;

public class A {
    private String fieldA;
    private B b;

    public String getFieldA() {
        return fieldA;
    }

    public void setFieldA(String fieldA) {
        this.fieldA = fieldA;
    }

    public B getB() {
        return b;
    }

    public void setB(B b) {
        this.b = b;
    }
}

B

package com.xianhuii.spring.bean;

public class B {
    private String fieldB;

    public String getFieldB() {
        return fieldB;
    }

    public void setFieldB(String fieldB) {
        this.fieldB = fieldB;
    }
}

1.2 Spring IoC配置及使用

当我们完成了项目相关Java类的开发后,需要通过配置文件将项目中各种类声明为Bean,并配置好它们之间的依赖关系。随后Spring IoC容器/工厂加载这些配置文件进行Bean的管理。

根据配置文件的不同,后续的加载和使用流程都有些区别。这里我们分别以XML文件和Java配置文件为例,介绍它们的使用流程。

1.2.1 XML配置

1、创建XML配置文件

我们创建一个SpringApplication.xml文件,用于声明项目中类A和类B为Bean以及它们之间的依赖关系。

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="beanA" class="com.xianhuii.spring.bean.A">
        <property name="fieldA" value="A的属性"></property>
        <property name="b" ref="beanB"></property>
    </bean>

    <bean id="beanB" class="com.xianhuii.spring.bean.B">
        <property name="fieldB" value="B的属性"></property>
    </bean>
</beans>

2、加载及使用

对于XML配置文件,我们需要使用ClassPathXmlApplicationContext容器/工厂实现类对配置文件进行加载,然后从该容器中获取Bean并使用。

package com.xianhuii.spring;

import com.xianhuii.spring.bean.A;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        // 加载XML配置文件
        ApplicationContext beanFactory = new ClassPathXmlApplicationContext("SpringApplication.xml");
        
        // 获取并使用Bean
        A beanA = (A)beanFactory.getBean("beanA");
        System.out.println(beanA.getFieldA());
        System.out.println(beanA.getB().getFieldB());
    }
}

运行该程序,最后的输出如下:

A的属性
B的属性

1.2.2 Java配置类

1、创建Java配置类

同样,我们可以使用Java配置类声明Bean以及它们之间的依赖关系。Java配置类本质上与XML配置文件没有任何区别。

package com.xianhuii.spring.config;

import com.xianhuii.spring.bean.A;
import com.xianhuii.spring.bean.B;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringApplicationConfig {
    @Bean("beanB")
    public B b() {
        B b = new B();
        b.setFieldB("B的属性");
        return b;
    }

    @Bean("beanA")
    public A a(){
        A a = new A();
        a.setFieldA("A的属性");
        a.setB(b());
        return a;
    }
}

2、加载及使用

对于Java配置类,我们需要使用AnnotationConfigApplicationContext容器/工厂实现类进行加载,后续也使用该类对象获取并使用Bean。

package com.xianhuii.spring;

import com.xianhuii.spring.bean.A;
import com.xianhuii.spring.config.SpringApplicationConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        // 加载Java配置类
        ApplicationContext beanFactory = new AnnotationConfigApplicationContext(SpringApplicationConfig.class);

        // 获取并使用Bean
        A beanA = (A)beanFactory.getBean("beanA");
        System.out.println(beanA.getFieldA());
        System.out.println(beanA.getB().getFieldB());
    }
}

最后得到的结果与之前一样:

A的属性
B的属性

1.2.3 注解配置

1、直接在POJO类中声明为Bean并指定依赖关系

上述两种方法将Bean的声明和依赖关系的配置集合到了特定的文件中。实际上我们可以在项目开发过程中,直接通过注解的方式指定依赖关系。因此,我们可以将类AB进行修改。

A

package com.xianhuii.spring.bean;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("beanA")
public class A {
    @Value("A的属性")
    private String fieldA;
    @Autowired
    private B b;

    public String getFieldA() {
        return fieldA;
    }

    public void setFieldA(String fieldA) {
        this.fieldA = fieldA;
    }

    public B getB() {
        return b;
    }

    public void setB(B b) {
        this.b = b;
    }
}

B

package com.xianhuii.spring.bean;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("beanB")
public class B {
    @Value("B的属性")
    private String fieldB;

    public String getFieldB() {
        return fieldB;
    }

    public void setFieldB(String fieldB) {
        this.fieldB = fieldB;
    }
}

2、加载及使用

因为我们使用注解进行配置,可以直接使用AnnotationConfigApplicationContext容器/工厂实现类对指定包进行扫描,它会将这个包里的所有Bean加载到容器中进行管理。后续我们也可以使用这个容器获取并使用Bean。

package com.xianhuii.spring;

import com.xianhuii.spring.bean.A;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        // 扫描指定包下的Bean
        ApplicationContext beanFactory = new AnnotationConfigApplicationContext("com.xianhuii.spring.bean");

        // 获取并使用Bean
        A beanA = (A)beanFactory.getBean("beanA");
        System.out.println(beanA.getFieldA());
        System.out.println(beanA.getB().getFieldB());
    }
}

更麻烦一点,我们也可以将ComponentScan属性配置到XML配置文件或Java配置类中,然后通过上述方法使用特定容器/工厂实现类对XML配置文件或Java配置类进行加载和使用。这种方法实际上是为多种场景提供了组合的可能。

XML配置文件中配置ComponentScan属性:

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.xianhuii.spring.bean"/>
</beans>

Java配置类中配置ComponentScan属性:

package com.xianhuii.spring.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.xianhuii.spring.bean")
public class SpringApplicationConfig {
}

1.3 总结

以上对Spring IoC的基本使用流程及各种配置方式进行了简要介绍。从中我们可以发现对于Sprig IoC开发而言,它包含了三个要素:POJO类、配置文件和对应的容器/工厂实现类。

POJO类代表了实际项目的业务逻辑,与Spring IoC容器的关系不大。

配置文件代表了我们需要将哪些POJO类交给Spring容器管理,并且定义了它们之间的依赖关系。配置文件可以是XML文件或Java配置类,也可以直接通过注解的方式在POJO类中进行配置。

容器/工厂实现类负责处理特定的配置方式:

  • 对于XML配置文件,使用ClassPathXmlApplicationContext
  • 对于Java配置类或注解方式,使用AnnotationConfigApplicationContext

本质上Java配置类和注解方式没有任何区别,因为Java配置类也需要声明@Configuration注解,因此它们使用的容器/工厂实现类是一样的。

2 Spring IoC配置

通过上述的介绍我们知道,对Bean的配置是Spring IoC开发过程中的主要工作,它将POJO与Spring容器关联起来,是Spring容器获取Bean信息的入口。

对Bean的配置可以分成以下方式:

  • XML配置文件
  • @Annotation方式:
    • Java配置类
    • POJO注解声明

我们首先创建出两个POJO类,作为后续介绍的工具。

A

package com.xianhuii.spring.bean;

import java.util.List;
import java.util.Map;
import java.util.Set;

public class A {
    private String fieldA;
    private B b;
    private List<String> list;
    private Set<String> set;
    private Map<String, String> map;

    public String getFieldA() {
        return fieldA;
    }

    public void setFieldA(String fieldA) {
        this.fieldA = fieldA;
    }

    public B getB() {
        return b;
    }

    public void setB(B b) {
        this.b = b;
    }

    public List<String> getList() {
        return list;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public Set<String> getSet() {
        return set;
    }

    public void setSet(Set<String> set) {
        this.set = set;
    }

    public Map<String, String> getMap() {
        return map;
    }

    public void setMap(Map<String, String> map) {
        this.map = map;
    }
}

B

package com.xianhuii.spring.bean;

public class B {

    private String fieldB;

    public B() {
    }

    public B(String fieldB) {
        this.fieldB = fieldB;
    }

    public String getFieldB() {
        return fieldB;
    }

    public void setFieldB(String fieldB) {
        this.fieldB = fieldB;
    }
}

2.1 XML配置文件

XML配置文件的形式相对来说比较古老,如今在日常开发中一般不会使用,我们可以简单了解一下其使用方法。

我们首先在项目中创建一个XML文件,文件名可以自己指定。然后我们就可以在该文件的<beans></beans>标签内使用各种标签声明Bean,并指它们之间的定依赖关系。

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    
</beans>

后续我们可以使用ClassPathXmlApplicationContext容器加载该配置文件,从而实现对Bean的管理和使用。

2.1.1 声明Bean

我们使用<bean>标签将POJO声明为Bean。例如我们要将类B声明为一个Bean,可以在XML文件中配置如下:

<bean id="beanB" class="com.xianhuii.spring.bean.B"></bean>

此时表示Spring容器将会管理类B,当你需要获取类B的实例时,Spring容器会使用无参构造器生成一个单例对象。如果类B中没有无参构造器,那么就会报错。

上述步骤相当于执行了以下代码:

B beanB = new B();

2.1.2 指定依赖关系

当我们要在配置文件中指定POJO之间相互依赖时,可以使用构造器或者setXxx()方法注入属性。

1、构造函数指定——<constructor-arg>

默认情况下,Spring容器会通过无参构造器进行实例化。我们也可以手动指定某个构造函数进行实例化,并且在实例化过程中完成对依赖关系的注入。

例如,我们通过<constructor-arg>标签明确指定使用类BB(String fieldB)构造函数注入属性fieldB

<bean id="beanB" class="com.xianhuii.spring.bean.B">
    <constructor-arg name="fieldB" value="B的属性"/>
</bean>

其中name属性用于指定构造函数的形参,value用于指定注入的值。

上述步骤相当于执行了以下代码:

B beanB = new B("B的属性");

2、setXxx()函数指定——<property>

我们还可以使用不同属性的setXxx()函数进行注入,表明在实例化完成后,继续调用setXxx()方法进行设值。

例如,我们通过<property>标签明确指定使用类BsetFieldB(String fieldB)方法注入属性fieldB

<bean id="beanB" class="com.xianhuii.spring.bean.B">
    <property name="fieldB" value="B的属性"/>
</bean>

其中name属性用于指定构造函数的形参,value用于指定注入的值。

上述步骤相当于执行了如下代码:

B beanB = new B();
beanB.setFieldB("B的属性");

让我们开一下脑洞,如果我们对同一个属性同时使用了构造函数注入和setXxx()方法注入,那么会怎么样呢?我们配置如下:

<bean id="beanB" class="com.xianhuii.spring.bean.B">
    <constructor-arg name="fieldB" value="constructor"/>
    <property name="fieldB" value="setter"/>
</bean>

通过ClassPathXmlApplicationContext容器实现类加载,我们可以发现beanBfieldB属性最后变成了:setter

这是因为上述配置相当于执行了如下代码:

B beanB = new B("constructor");
beanB.setFieldB("setter");

因此,我们可将上述两种灵活地组合起来,实现我们实际的业务需求。

2.1.3 各种类型属性的注入

上面对属性的构造函数和setXxx()方法两种注入方式进行了介绍,代表了Spring容器会调用POJO中的不同方法对属性进行注入。但是针对具体不同类型的属性,它们的注入形式又有点区别。这种区别与注入方式无关,而与属性的类型有关。

1、注入字面量

这里的字面量包括Java中的各种基本类型和String等常用类。我们使用<constructor-arg><property>标签的value属性进行注入。

例如,我们对类BfieldB属性注入字符串:

<bean id="beanB" class="com.xianhuii.spring.bean.B">
    <property name="fieldB" value="B的属性"/>
</bean>

2、注入Bean

在配置文件中的Bean表示POJO对象,在日常项目中难免会有POJO之间的相互引用,这时候我们就需要使用<constructor-arg><property>标签的ref属性进行注入。

例如,我们对类A注入类B的Bean引用:

<bean id="beanB" class="com.xianhuii.spring.bean.B"></bean>

<bean id="beanA" class="com.xianhuii.spring.bean.A">
    <property name="b" ref="beanB"/>
</bean>

上述配置相当于如下代码:

B beanB = new B();
A beanA = new A();
beanA.setB(beanB);

3、注入List

Java中List对其中的对象进行了包装。同样,在XML配置中使用<list>标签,其内部可以进一步指定<value><ref>标签具体内容。

例如,我们对类Alist属性注入列表:

<bean id="beanA" class="com.xianhuii.spring.bean.A">
    <property name="list">
        <list>
            <value>list1</value>
            <value>list2</value>
            <value>list3</value>
        </list>
    </property>
</bean>

上述配置相当于执行了以下代码:

A beanA = new A();
List<String> list = new ArrayList();
list.add("list1");
list.add("list2");
list.add("list3");
a.setList(list);

4、注入Set

注入Set的方式与上述注入List的方式极其类似。只不过是将<list>标签换成了<set>标签。

例如,我们注入类Aset属性:

<bean id="beanA" class="com.xianhuii.spring.bean.A">
    <property name="set">
        <set>
            <value>set1</value>
            <value>set2</value>
            <value>set3</value>
        </set>
    </property>
</bean>

上述配置相当于执行了如下代码:

A beanA = new A();
Set<String> set = new LinkedHashSet();
set.add("set1");
set.add("set2");
set.add("set3");
beanA.setSet(set);

5、注入Map

Map是键-值对集合,配置文件中使用<map>标签表示一个Map,并在其中使用<entry>标签表示某一个键-值对,<entry>keyvalue属性分别表示键-值对的键和值。

例如,我们注入类Amap属性:

<bean id="beanA" class="com.xianhuii.spring.bean.A">
    <property name="map">
        <map>
            <entry key="key1" value="val1"/>
            <entry key="key2" value="val2"/>
        </map>
    </property>
</bean>

上述配置相当于执行了如下代码:

A beanA = new A();
Map<String,String> map = new LinkedHashMap();
map.put("key1", "val1");
map.put("key2", "val2");
beanA.setMap(map);

2.1.4 其他配置

1、Bean的生命周期

通过为Bean指定生命周期方法,可以在使用Bean前预先执行载入资源,在销毁Bean之前释放资源等操作。在XML中使用<bean>标签的init-methoddestroy-method属性分别为该Bean执行初始化方法和销毁方法。

2、组件扫描

通过配置<context:component-scan base-package=""/>,Spring容器会扫描指定包下的Java类。如果该类声明了相关注解,那么会自动将其加入到Spring容器中进行管理。这对注解配置方式至关重要。

3、导入配置文件

通过配置<import resource=""/>,可以将其他XML配置文件导入到本配置文件中。而通过将Java配置类声明为Bean<bean id="config" class="config.ApplicationConfig.java"/>则可以导入Java配置类。

在日常开发中,我们可以声明一个根XML配置文件,然后通过以上方法将其他配置文件导入。最后Spring容器实现类只需要加载根XML配置文件,即可读取到所有配置信息。这种方式有利于模块化开发。

4、作用域

通过<bean scope="prototype"/>中的scope属性可以指定该Bean的类型:singletonprototype等。

其中singleton表明该Bean是单例的,Spring容器会保存该实例,从而保证每次获取都是同一个对象。而prototype表明Spring容器不会保存该实例,每次获取时都会重新创建一个新的对象。

一般来说我们日常开发中遇到的ControllerService等组件都是单例的,因为它们不保存状态信息,而是作为工具使用,每次使用时都是一样的。

2.2 Java配置类

Java配置类与XML配置文件的功能类似,都是通过文件的形式项目中的Bean集中起来声明和管理。日常开发中我们在配置某些框架的核心类时,一般来说都会采用此方法。

首先,我们需要在项目中创建一个类,并添加@Configuration注解,表示其为配置类。配置类实际上是一个特殊的Bean,我们也可以从Spring容器中获取其引用,进而获取其属性或调用其方法。

package com.xianhuii.spring.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringApplicationConfig {

}

后续我们可以使用各种方法加载此配置类:

  1. 通过AnnotationConfigApplicationContext容器实现类指定加载此配置类。
  2. 通过AnnotationConfigApplicationContext容器实现类扫描此配置类所在包。
  3. 通过XML配置文件声明此配置类为Bean,然后通过ClassPathXmlApplicationContext进行加载。

2.2.1 声明Bean

通过@Bean对某个方法进行注解可以声明Bean:

@Bean
public B b() {
    return new B();
}

如果没有显示为Bean指定id,那么默认为方法名,在这里返回的Bean的id就为b

除了指定id,在@Bean注解中还可指定初始化方法initMethod、销毁方法destroyMethod和是否可以被自动注入autowireCandidate

2.2.2 依赖注入

通过Java配置类进行依赖注入的方式显而易见,就是直接使用Java的构造函数或setXxx()方法进行赋值,较为灵活多变。

对于Java内置的基本数据类型、String和集合等的注入,直接使用构造函数或setXxx()方法赋值即可。而对于Bean的注入,则可能会感到有些奇怪。

第一种方法:

@Bean("beanB")
public B b() {
    return new B();
}

@Bean("beanA")
public A a() {
    A beanA = new A();
    beanA.setB(b());
    return beanA;
}

第一种方法中,我们先通过b()方法声明了beanB。但是在a()方法中我们直接调用b(),在这种情况下,由于b()方法添加了@Bean注解,该注解会拦截对b()的直接调用,转而在Spring容器中寻找beanB并赋值。因此可以保证beanB是的作用域是正确的。但是这种方法要求beanBbeanA的声明在同一个Java配置类中。

我们假设将上述beanB的声明移动到另外一个配置文件中,此时为了获取beanB的引用,我们可以使用第二种方法:

@Bean("beanA")
public A a(B b) {
    A beanA = new A();
    beanA.setB(b);
    return beanA;
}

此时,由于a()方法添加了@Bean注解,当需要获取beanA时,首先会按照形参类型在Spring容器中找到beanB,然后在a()方法中进行注入。而如果beanB没有被声明,那么就会报错。

2.2.3 其他配置

1、Bean的生命周期

如前所述,Bean的生命周期在其声明过程中通过@Bean注解的initMethoddestroyMethod属性指定。

2、Bean的作用域

Bean的作用域通过@Scope注解声明:

@Scope("prototype")
@Bean("beanB")
public B b() {
    return new B("BBB");
}

3、组件扫描

通过在Java配置类上添加@ConponentScan注解,Spring容器会扫描指定包下的Java类(默认为当前包)。如果该类声明了相关注解,那么会自动将其加入到Spring容器中进行管理。这对注解配置方式至关重要。

package com.xianhuii.spring.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@ComponentScan("com.xianhuii.spring.bean")
@Configuration
public class SpringApplicationConfig {
}

4、导入配置文件

通过在Java配置类上添加@Import注解可以导入额外Java配置类,添加@ImportResource注解则可以导入额外XML配置文件。

package com.xianhuii.spring.config;

import org.springframework.context.annotation.*;

@Import(Config2.class)
@ImportResource("SpringApplication.xml")
@Configuration
public class SpringApplicationConfig {
}

2.3 POJO注解声明

POJO注解声明方式指的是我们在开发POJO类的过程中,直接在类中通过各种注解的方式配置依赖关系。这是日常开发中使用最多的方式。

在这个过程中,由于项目中的Bean配置分散到各个POJO类中,因此组件扫描功能至关重要。此时,Spring容器实现类有以下方式读取相关配置进行加载:

  1. 通过XML配置文件中设置<context:component-scan base-package=""/>标签,随后通过ClassPathXmlApplicationContext容器实现类加载该XML配置文件。
  2. 通过Java配置类中设置@ComponentScan注解,随后通过AnnotationConfigApplicationContext容器实现类加载此Java配置类。
  3. 直接使用AnnotationConfigApplicationContext容器实现类扫描相关包。

2.3.1 声明Bean

在POJO对象上添加@Component注解,即可将该类声明为一个Bean。在Spring MVC开发中,根据不同层次的语义,可以进一步使用@Controller@Service@Repository@Mapper等注解。之前介绍的@Configuration注解也可以将该类声明为一个Bean,只不过该Bean比较特殊,专门用来处理配置关系。

package com.xianhuii.spring.bean;

import org.springframework.stereotype.Component;

@Component
public class B {
}

此时,如果不在@Component中指定id,那么默认使用类名首字母小写作为id,此时为b

2.3.2 依赖注入

1、注入字面量

使用@Value注解可以为Bean注入字面量,包括各种基本数据类型和String

我们还可以在不同位置上使用该注解进行注入:属性或setXxx()方法。

在属性上注入:

@Value("B的属性")
private String fieldB;

setXxx()方法上注入:

@Value("B的属性")
public void setFieldB(String fieldB) {
    this.fieldB = fieldB;
}

2、注入Bean

通过在属性、构造函数、setXxx()方法或配置方法上添加@Autowired注解,会在Spring容器中找到符合条件的Bean进行注入。

通过属性注入:

@Autowired
private B b;

通过构造函数注入:

@Autowired
public A (B b) {
    this.b = b;
}

通过setXxx()方法注入:

@Autowired
public void setB(B b) {
    this.b = b;
}

无论是通过构造函数或setXxx()方法进行注入,它本质上都会根据形参的类型在Spring容器中查找相关Bean,如果找到唯一一个满足条件则顺利注入,否则报错。

如果需要特别指定注入哪个Bean,还可以同时使用@Qualifier注解进行声明。

@Autowired
@Qualifier("beanB")
private B b;

另外,@Autowired还可以对ListSetMap进行注入,它会在Spring容器中查找所有该实体类的Bean,并将它们添加到相应的集合中。

2.3.3 其他配置

1、Bean的作用域

通过在POJO类上添加@Scope注解以声明其作用域:

package com.xianhuii.spring.bean;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Scope("prototype")
@Component
public class B {
}

2.4 总结

我们对Spring IoC的各种配置方式进行了详细介绍。总的来说,配置方式可以分成三种:

  1. XML配置文件。
  2. Java配置类。
  3. POJO注解声明。

其中,XML配置文件是很古老的方式,现在日常开发中一般很少使用。

而Java配置类和POJO注解声明方式本质上没有区别,都是通过相关注解指定Bean之间的关系。只不过Java配置类显式将所有Bean集中到类中进行配置,而POJO注解声明方式则分散到各个POJO中。

日常开发中我们通常将Java配置类和POJO注解声明这两种方式结合使用。对于代表普通项目逻辑的POJO使用注解声明,而对于某些框架中的核心类使用Java配置类进行显式配置。因为Java配置类在Spring容器中也是Bean,我们可以使用AnnotationConfigApplicationContext容器实现类直接扫描相关的包,就可以把所有Bean管理起来。

3 常用Spring IoC容器

Spring IoC的配置方式根本上可以分成两种:XML配置文件和注解配置。

针对于这两种不同的配置方式,Spring IoC为我们提供了不同的容器实现类。接下来我们对其中最常见的两种进行介绍:

  • ClassPathXmlApplicationContext
  • AnnotationConfigApplicationContext

3.1 ClassPathXmlApplicationContext

顾名思义,ClassPathXmlApplicationContext是针对类路径下的XML配置文件的容器实现类。它会自动将类路径补齐,从而能准确定位文件位置。这种方式主要用于测试或者读取Jar包中的配置文件。

如果加载了多个XML配置文件,那么对于同一个Bean,后面添加的XML配置文件会对之前的进行覆盖。

ClassPathXmlApplicationContext是一站式的容器实现类,它将XML配置文件的定位、读取和加载过程封装了起来,方便我们使用,但是也缺失了灵活性。

我们可以结合使用更加底层的GenericApplicationContext 容器实现类和XmlBeanDefinitionReader配置文件读取类进行灵活设置。

回到ClassPathXmlApplicationContext,其使用方法十分简单,它提供了各种重载的构造方法,用于不同情况下的加载。其中,最基础的构造函数如下:

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
    super(parent);
	setConfigLocations(configLocations);
	if (refresh) {
		refresh();
	}
}

它的作用是根据给定的父容器,创建一个新的ClassPathXmlApplication容器,并加载指定类路径下的XML配置文件。此时加载XML配置文件实际上只是将文件路径赋给了属性Resource[]

我们一般可以使用形参只有XML文件路径的构造函数,分别可以加载一个或多个XML配置文件:

public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
	this(new String[] {configLocation}, true, null);
}

public ClassPathXmlApplicationContext(String... configLocations)throws BeansException{
	this(configLocations, true, null);
}

3.2 AnnotationConfigApplicationContext

顾名思义,AnnotationConfigApplicationContext是针对诸如@Configuration@Component@Inject等注解的类进行加载的容器实现类。

同样,如果加载了多个@Configuration,那么后加载的@Bean会对之前加载的进行覆盖。

AnnotationConfigApplicationContext也提供了很多重载的构造方法,但其核心是额外提供的两个方法:

@Override
public void register(Class<?>... componentClasses) {
	Assert.notEmpty(componentClasses, "At least one component class must be specified");
	this.reader.register(componentClasses);
}

@Override
public void scan(String... basePackages) {
	Assert.notEmpty(basePackages, "At least one base package must be specified");
	this.scanner.scan(basePackages);
}

这两个方法的功能分别是加载Java注解类、扫描指定包下的所有注解类。

而我们常用的构造函数,内部都会调用上述两个方法:

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
	this();
	register(componentClasses);
	refresh();
}

public AnnotationConfigApplicationContext(String... basePackages) {
	this();
	scan(basePackages);
	refresh();
}

因此,我们也可以发现AnnotationConfigApplicationContext既可以指定加载的注解类,也可以扫描某包下的所有注解类。

4 总结

本文对Spring IoC的使用进行了详细介绍,日常开发流程中大体上可以分成三个部分:

  1. POJO:日常项目中的各种Java类。
  2. 配置文件:POJO的依赖关系。
  3. 容器/工厂类:管理Bean。

其中POJO是日常项目逻辑涉及到的Java类,而容器/工厂类由Spring Ioc提供,因此我们的主要工作内容就是配置POJO之间的依赖关系。

传统使用XML配置文件进行配置,而目前流行的是使用注解声明的方式配置。我们只要熟悉掌握配置Bean依赖关系的各种注解,就能够在日常开发中灵活使用Spring。

最后我们来总结一下Spring IoC的功能。我们在日常开发中会创建出各种POJO,而Spring IoC会通过配置文件将这些POJO联系起来。当我们需要某个Bean时,只需要指定获取该Bean即可。Bean之间的依赖关系已经通过Spring IoC帮我们构建好了,我们不必关心其细节。

SpringIoC的功能

posted @ 2021-10-25 19:26  Xianuii  阅读(289)  评论(0编辑  收藏  举报