Spring 依赖注入

64 阅读26分钟

依赖注入的模式

在 Spring 中,依赖注入的模式分为手动模式和自动模式是两种不同的方式来管理对象之间的依赖关系。

一、配置方式

  1. 手动模式(配置或者编程的方式,提前安排注入规则):

    • 需要开发者明确地进行配置,无论是通过 XML 资源配置元信息、Java 注解配置元信息还是 API 配置元信息,都需要开发者主动指定依赖关系和 bean 的定义。

    • 例如,使用 XML 配置时,要逐个定义 bean 并通过<property>等标签明确指定依赖关系;使用 Java 注解时,也需要在代码中添加相应的注解来指示依赖关系。

      • XML 资源配置元信息
      • Java 注解配置元信息
      • API 配置元信息
  2. 自动模式(以 Autowiring 为例,实现方提供依赖自动关联的方式,按照內建的注入规则)

    • 实现方(通常是框架)提供依赖自动关联的方式,按照内置的注入规则进行依赖注入。开发者不需要像手动模式那样详细地指定每个依赖关系,框架会根据一定的规则自动查找和注入依赖。

    • Autowiring(自动绑定)

二、灵活性与可维护性

  1. 手动模式:

    • 灵活性较高,可以根据具体需求进行精细的配置。例如,在 XML 配置中可以使用各种属性和标签来调整 bean 的生命周期、作用域等。
    • 但随着项目规模的增大,手动配置可能会变得复杂和难以维护,尤其是当依赖关系发生变化时,需要逐个修改配置文件或代码中的注解。
  2. 自动模式:

    • 通常较为简洁,减少了配置的工作量,提高了开发效率。特别是在项目中 bean 的数量较多且依赖关系较为复杂时,自动装配可以快速地建立依赖关系。

    • 然而,自动模式的灵活性可能相对较低。在某些特殊情况下,可能需要额外的配置来调整自动装配的行为,或者当自动装配出现问题时,排查和解决问题可能会比较困难。

三、对开发者的要求

  1. 手动模式:

    • 要求开发者对配置方式有一定的了解,熟悉 XML 配置语法、Java 注解的使用以及 API 配置的方法。
    • 需要开发者更加关注依赖关系的细节,确保配置的准确性。
  2. 自动模式:

    • 开发者相对来说不需要深入了解配置的细节,但需要理解自动装配的规则和可能出现的问题。

    • 当自动装配出现错误时,开发者需要能够分析问题并采取适当的措施进行解决,例如调整类的结构、使用更明确的注解等。

四、适用场景

  1. 手动模式:

    • 适用于复杂的项目架构,或者当需要对依赖关系进行精细控制时。例如,在企业级应用中,可能需要根据不同的环境进行不同的配置,手动模式可以更好地满足这种需求。
    • 对于一些传统项目或者开发者对特定配置方式有偏好的情况,手动模式也可能是更好的选择。
  2. 自动模式:

    • 适用于快速开发和小型项目,能够提高开发效率,减少配置的工作量。
    • 当项目的依赖关系相对简单且较为固定时,自动装配可以很好地发挥作用。

自动绑定(Autowiring)

在 Spring 中,自动装配(autowiring)是一种方便的机制,它可以自动将一个 bean 注入到另一个 bean 中,减少了手动配置的工作量。

官方说明

The Spring container can autowire relationships between collaborating beans. You can let Spring resolve collaborators (other beans) automatically for your bean by inspecting the contents of the ApplicationContext.

Spring 容器可以自动绑定那些需要合作的bean的关系(其实就是spring容器可以帮我们管理依赖的bean和被依赖的bean),通过检查ApplicationContext,你可以让Spring去处理这些合作者

优点

  • Autowiring can significantly reduce the need to specify properties or constructor arguments.(自动装配可以减少指定属性或构造函数参数)
  • Autowiring can update a configuration as your objects evolve.(自动装配可以随着对象的发展而更新配置)
Autowiring modes

spring官方提供了4中自动注入得模式:

模式说明
no默认值,未激活 Autowiring,需要手动指定依赖注入对象
byName根据被注入属性的名称作为 Bean 名称进行依赖查找,并将对象设置到 该属性
byType根据被注入属性的类型作为依赖类型进行查找,并将对象设置到该属性
constructor特殊 byType 类型,用于构造器参数
自动绑定(Autowiring)限制和不足

• 官方说明
Limitations and Disadvantages of Autowiring 小节

Limitations and Disadvantages of Autowiring

Autowiring works best when it is used consistently across a project. If autowiring is not used in general, it might be confusing to developers to use it to wire only one or two bean definitions.

Consider the limitations and disadvantages of autowiring:

  • Explicit dependencies in property and constructor-arg settings always override autowiring. You cannot autowire simple properties such as primitives, Strings, and Classes (and arrays of such simple properties). This limitation is by-design.
  • Autowiring is less exact than explicit wiring. Although, as noted in the earlier table, Spring is careful to avoid guessing in case of ambiguity that might have unexpected results. The relationships between your Spring-managed objects are no longer documented explicitly.
  • Wiring information may not be available to tools that may generate documentation from a Spring container.
  • Multiple bean definitions within the container may match the type specified by the setter method or constructor argument to be autowired. For arrays, collections, or Map instances, this is not necessarily a problem. However, for dependencies that expect a single value, this ambiguity is not arbitrarily resolved. If no unique bean definition is available, an exception is thrown.

In the latter scenario, you have several options:

  • Abandon autowiring in favor of explicit wiring.
  • Avoid autowiring for a bean definition by setting its autowire-candidate attributes to false, as described in the next section.
  • Designate a single bean definition as the primary candidate by setting the primary attribute of its <bean/> element to true.
  • Implement the more fine-grained control available with annotation-based configuration, as described in Annotation-based Container Configuration.

链接:docs.spring.io/spring/docs… reference/core.html#beans-autowired-exceptions

基于XML的Demo

装配方式

方式一:默认

Spring 将不会自动根据类型或名称等规则来为 bean 注入依赖。每个 bean 的依赖都需要通过显式的方式进行配置,比如使用<property>元素来设置 bean 的属性或者在构造函数中传递依赖对象。需要通过set方法注入

package com.evan.autowiring.no;

public class Role {

    private String id;
    private String name;

    public void setId(String id) {
        this.id = id;
    }

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

    @Override
    public String toString() {
        return "Role{" +
                "id='" + id + ''' +
                ", name='" + name + ''' +
                '}';
    }
}
package com.evan.autowiring.no;

public class User {

    private Role myRole;

    public void setMyRole(Role myRole) {
        this.myRole = myRole;
    }


    @Override
    public String toString() {
        return "User{" +
                "myRole=" + myRole.toString() +
                '}';
    }
}
<?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">

    <!-- 对象中的属性需要提供set方法 -->
    <bean id="user" class="com.evan.autowiring.no.User">
        <!-- 这里不会自动注入依赖,需要手动配置 -->
        <property name="myRole" ref="role"/>

    </bean>

    <bean id="role" class="com.evan.autowiring.no.Role">
        <property name="id" value="123"/>
        <property name="name" value="eva"/>
    </bean>

</beans>
package com.evan.autowiring;

import com.evan.autowiring.constructor.UserBYConstructor;
import com.evan.autowiring.no.User;
import org.junit.Test;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;

public class Autowiring_Test {

    @Test
    public void test_auto_wiring_no(){
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        String xmlResourcePath = "dependency-autowiring-no.xml";
        beanDefinitionReader.loadBeanDefinitions(xmlResourcePath);
        User userHolder = beanFactory.getBean(User.class);
        System.out.println(userHolder);
    }
}
方式二:byName

根据属性名称自动装配。会查找Bean容器内部所有初始化的与属性名成相同的Bean,自动装配。(需要通过set方法注入,注入Bean的id名称需要和实体类的属性名称一致)

<?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">

    <!-- 对象中的属性需要提供set方法 -->
    <bean id="user" class="com.evan.autowiring.no.User" autowire="byName">
    </bean>


    <!-- Role类型的Bean name 必须为myRole,要不然会报错 -->
    <bean id="myRole" class="com.evan.autowiring.no.Role">
        <property name="id" value="123"/>
        <property name="name" value="eva"/>
    </bean>

</beans>
    @Test
    public void test_auto_wiring_byName(){
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        String xmlResourcePath = "dependency-autowiring-byname.xml";
        beanDefinitionReader.loadBeanDefinitions(xmlResourcePath);
        User userHolder = beanFactory.getBean(User.class);
        System.out.println(userHolder);
    }
方式三:byType

根据类型自动装配。如果容器中存在一个与所需Bean类型相同的Bean,则自动装配。如果存在多个相同类型的Bean,报错。找不到相匹配的Bean,什么都不发生。(需要通过set方法注入!!!)

<?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">

    <!-- 对象中的属性需要提供set方法 -->
    <bean id="user" class="com.evan.autowiring.no.User" autowire="byType">
    </bean>


    <bean id="role" class="com.evan.autowiring.no.Role">
        <property name="id" value="123"/>
        <property name="name" value="eva"/>
    </bean>

</beans>
    @Test
    public void test_auto_wiring_byType(){
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        String xmlResourcePath = "dependency-autowiring-bytype.xml";
        beanDefinitionReader.loadBeanDefinitions(xmlResourcePath);
        User userHolder = beanFactory.getBean(User.class);
        System.out.println(userHolder);
    }
方式四:Constructor

同byType相似。如果找不到匹配的Bean,报错。(需要通过构造方法注入,注入的Bean的id名称需要和实体类的属性名称一致!!!)

package com.evan.autowiring.constructor;

import com.evan.autowiring.no.Role;

public class UserBYConstructor {

    private Role myRole;

    public UserBYConstructor(Role myRole) {
        this.myRole = myRole;
    }


    @Override
    public String toString() {
        return "User{" +
                "myRole=" + myRole.toString() +
                '}';
    }
}
<?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="user" class="com.evan.autowiring.constructor.UserBYConstructor" autowire="constructor">
    </bean>

    <bean id="myRole" class="com.evan.autowiring.no.Role">
        <property name="id" value="123"/>
        <property name="name" value="eva"/>
    </bean>
</beans>
    @Test
    public void test_auto_wiring_constructor(){
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        String xmlResourcePath = "dependency-autowiring-constructor.xml";
        beanDefinitionReader.loadBeanDefinitions(xmlResourcePath);
        UserBYConstructor userHolder = beanFactory.getBean(UserBYConstructor.class);
        System.out.println(userHolder);
    }

依赖注入类型

依赖注入类型配置元数据举例
Setter 方法
构造器
字段@Autowired User user;
方法@Autowired public void user(User user) { ... }
接口回调class MyBean implements BeanFactoryAware { ... }

Setter 方法注入

手动模式
  • XML 资源配置元信息
  • Java 注解配置元信息
  • API 配置元信息
自动模式
  • byName
  • byType
  • default

DEMO

xml方式
package com.evan.injection.setter;

import java.util.List;

public class User {


    private String id;
    private String name;
    private String city;
    private List<String> workCities;
    private List<String> lifeCities;


    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public List<String> getWorkCities() {
        return workCities;
    }

    public void setWorkCities(List<String> workCities) {
        this.workCities = workCities;
    }

    public List<String> getLifeCities() {
        return lifeCities;
    }

    public void setLifeCities(List<String> lifeCities) {
        this.lifeCities = lifeCities;
    }

    @Override
    public String toString() {
        return "User{" +
                "id='" + id + ''' +
                ", name='" + name + ''' +
                ", city='" + city + ''' +
                ", workCities=" + workCities +
                ", lifeCities=" + lifeCities +
                '}';
    }
}
package com.evan.injection.setter;

public class SuperUser extends User {
    private String address;

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "SuperUser{" +
                " superClassInfo=" + super.toString() +
                ", address='" + address + ''' +
                '}';
    }
}
package com.evan.injection.setter;


public class UserHolder {

    private User user;

    public UserHolder() {
    }

    public UserHolder(User user) {
        this.user = user;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    @Override
    public String toString() {
        return "UserHolder{" +
                "user=" + user +
                '}';
    }
}
<?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">


    <!-- Root BeanDefinition 不需要合并,不存在 parent -->
    <!-- 普通 beanDefinition GenericBeanDefinition -->
    <!-- 经过合并后 GenericBeanDefinition 变成 RootBeanDefinition -->
    <bean id="user" class="com.evan.injection.setter.User" primary="true">
        <property name="id" value="1"/>
        <property name="name" value="evan"/>
        <property name="city" value="SHANGHAI"/>
        <property name="workCities" value="BEIJING,SHANGHAI"/>
        <property name="lifeCities">
            <list>
                <value>BEIJING</value>
                <value>SHANGHAI</value>
            </list>
        </property>
    </bean>

    <!-- 普通 beanDefinition GenericBeanDefinition -->
    <!-- 合并后 GenericBeanDefinition 变成 RootBeanDefinition,并且覆盖 parent 相关配置-->
    <!-- primary = true , 增加了一个 address 属性 -->
    <bean id="superUser" class="com.evan.injection.setter.SuperUser" parent="user">
        <property name="address" value="SHANGHAI"/>
    </bean>

    <bean id="objectFactory" class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
        <property name="targetBeanName" value="user"/>
    </bean>

</beans>
<?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">

    <import resource="classpath:/dependency-lookup-context.xml"/>

    <!-- autowire 不设置或设置成byName\byType都可以实现-->
    <bean class="com.evan.injection.setter.UserHolder"
          autowire="byType">
    </bean>
</beans>
package com.evan.injection.setter;

import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;

/**
 * 基于 XML 资源的依赖 Setter 方法注入示例
 */
public class XmlDependencySetterInjectionDemo {
    public static void main(String[] args) {

        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        String xmlResourcePath = "injection/dependency-setter-injection.xml";
        // 加载 XML 资源,解析并且生成 BeanDefinition
        beanDefinitionReader.loadBeanDefinitions(xmlResourcePath);
        // 依赖查找并且创建 Bean
        UserHolder userHolder = beanFactory.getBean(UserHolder.class);
        System.out.println(userHolder);

    }
}
Java 注解方式
package com.evan.injection.setter;

import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;

/**
 * 基于 Java注解方式 实现依赖 Setter 方法注入示例
 */
public class AnnotationDependencySetterInjectionDemo {

    public static void main(String[] args) {

        // 创建 BeanFactory 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 注册 Configuration Class(配置类)
        applicationContext.register(AnnotationDependencySetterInjectionDemo.class);

        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(applicationContext);

        String xmlResourcePath = "dependency-lookup-context.xml";
        // 加载 XML 资源,解析并且生成 BeanDefinition
        beanDefinitionReader.loadBeanDefinitions(xmlResourcePath);

        // 启动 Spring 应用上下文
        applicationContext.refresh();

        // 依赖查找并且创建 Bean
        UserHolder userHolder = applicationContext.getBean(UserHolder.class);
        System.out.println(userHolder);

        // 显示地关闭 Spring 应用上下文
        applicationContext.close();
    }

    @Bean
    public UserHolder userHolder(User user) {
        UserHolder userHolder = new UserHolder();
        userHolder.setUser(user);
        return userHolder;
    }
}
API 配置元信息
package com.evan.injection.setter;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * 基于 API 实现依赖 Setter 方法注入示例
 */
public class ApiDependencySetterInjectionDemo {

    public static void main(String[] args) {

        // 创建 BeanFactory 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();

        // 生成 UserHolder 的 BeanDefinition
        BeanDefinition userHolderBeanDefinition = createUserHolderBeanDefinition();
        // 注册 UserHolder 的 BeanDefinition
        applicationContext.registerBeanDefinition("userHolder", userHolderBeanDefinition);

        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(applicationContext);

        String xmlResourcePath = "classpath:/dependency-lookup-context.xml";
        // 加载 XML 资源,解析并且生成 BeanDefinition
        beanDefinitionReader.loadBeanDefinitions(xmlResourcePath);

        // 启动 Spring 应用上下文
        applicationContext.refresh();

        // 依赖查找并且创建 Bean
        UserHolder userHolder = applicationContext.getBean(UserHolder.class);
        System.out.println(userHolder);

        // 显示地关闭 Spring 应用上下文
        applicationContext.close();
    }

    /**
     * 为 {@link UserHolder} 生成 {@link BeanDefinition}
     *
     * @return
     */
    private static BeanDefinition createUserHolderBeanDefinition() {
        BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(UserHolder.class);
        definitionBuilder.addPropertyReference("user", "superUser");
        return definitionBuilder.getBeanDefinition();
    }

}

构造器注入

手动模式
  • XML 资源配置元信息
  • Java 注解配置元信息
  • API 配置元信息
自动模式
  • constructor

DEMO

package com.evan.injection.contructor;

import java.util.List;

public class User {

    private String id;
    private String name;
    private String city;
    private List<String> workCities;
    private List<String> lifeCities;

    public User(String id, String name, String city, List<String> workCities, List<String> lifeCities) {
        this.id = id;
        this.name = name;
        this.city = city;
        this.workCities = workCities;
        this.lifeCities = lifeCities;
    }

    @Override
    public String toString() {
        return "User{" +
                "id='" + id + ''' +
                ", name='" + name + ''' +
                ", city='" + city + ''' +
                ", workCities=" + workCities +
                ", lifeCities=" + lifeCities +
                '}';
    }
}
package com.evan.injection.contructor;

import java.util.List;

public class SuperUser extends User {
    private String address;

    public SuperUser(String id, String name, String city, List<String> workCities, List<String> lifeCities, String address) {
        super(id, name, city, workCities, lifeCities);
        this.address = address;
    }

    @Override
    public String toString() {
        return "SuperUser{" +
                " superClassInfo=" + super.toString() +
                ", address='" + address + ''' +
                '}';
    }
}
package com.evan.injection.contructor;


public class UserHolder {

    private User user;

    public UserHolder(User user) {
        this.user = user;
    }

    @Override
    public String toString() {
        return "UserHolder{" +
                "user=" + user +
                '}';
    }
}
<?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">


    <!-- Root BeanDefinition 不需要合并,不存在 parent -->
    <!-- 普通 beanDefinition GenericBeanDefinition -->
    <!-- 经过合并后 GenericBeanDefinition 变成 RootBeanDefinition -->
    <bean id="user" class="com.evan.injection.contructor.User" primary="true">
        <constructor-arg name="id" value="1"/>
        <constructor-arg name="name" value="evan"/>
        <constructor-arg name="city" value="SHANGHAI"/>
        <constructor-arg name="workCities" value="BEIJING,SHANGHAI"/>
        <constructor-arg name="lifeCities">
            <list>
                <value>BEIJING</value>
                <value>SHANGHAI</value>
            </list>
        </constructor-arg>
    </bean>

    <!-- 普通 beanDefinition GenericBeanDefinition -->
    <!-- 合并后 GenericBeanDefinition 变成 RootBeanDefinition,并且覆盖 parent 相关配置-->
    <!-- primary = true , 增加了一个 address 属性 -->
    <bean id="superUser" class="com.evan.injection.contructor.SuperUser" parent="user">
        <constructor-arg name="address" value="SHANGHAI"/>
    </bean>

    <bean id="objectFactory" class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
        <property name="targetBeanName" value="user"/>
    </bean>

</beans>
<?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">

    <import resource="classpath:/dependency-lookup-context-1.xml"/>

    <bean class="com.evan.injection.contructor.UserHolder"
          autowire="constructor">
        <constructor-arg name="user" ref="superUser"/>
    </bean>
</beans>
xml方式
package com.evan.injection.contructor;

import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;

/**
 * 基于 XML 资源的依赖 构造 方法注入示例
 */
public class XmlDependencySetterInjectionDemo {
    public static void main(String[] args) {

        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        String xmlResourcePath = "injection/dependency-constructor-injection.xml";
        // 加载 XML 资源,解析并且生成 BeanDefinition
        beanDefinitionReader.loadBeanDefinitions(xmlResourcePath);
        // 依赖查找并且创建 Bean
        UserHolder userHolder = beanFactory.getBean(UserHolder.class);
        System.out.println(userHolder);

    }
}
注解方式
package com.evan.injection.contructor;

import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
/**
 * 基于注解 的依赖 构造 方法注入示例
 */
public class AnnotationDependencySetterInjectionDemo {

    public static void main(String[] args) {

        // 创建 BeanFactory 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 注册 Configuration Class(配置类)
        applicationContext.register(AnnotationDependencySetterInjectionDemo.class);

        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(applicationContext);

        String xmlResourcePath = "dependency-lookup-context-1.xml";
        // 加载 XML 资源,解析并且生成 BeanDefinition
        beanDefinitionReader.loadBeanDefinitions(xmlResourcePath);

        // 启动 Spring 应用上下文
        applicationContext.refresh();

        // 依赖查找并且创建 Bean
        UserHolder userHolder = applicationContext.getBean(UserHolder.class);
        System.out.println(userHolder);

        // 显示地关闭 Spring 应用上下文
        applicationContext.close();
    }

    @Bean
    public UserHolder userHolder(User user) {
        UserHolder userHolder = new UserHolder(user);
        return userHolder;
    }
}
API 配置元数据
package com.evan.injection.contructor;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * 基于 API 实现依赖 构造 方法注入示例
 */
public class ApiDependencySetterInjectionDemo {

    public static void main(String[] args) {

        // 创建 BeanFactory 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();

        // 生成 UserHolder 的 BeanDefinition
        BeanDefinition userHolderBeanDefinition = createUserHolderBeanDefinition();
        // 注册 UserHolder 的 BeanDefinition
        applicationContext.registerBeanDefinition("userHolder", userHolderBeanDefinition);

        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(applicationContext);

        String xmlResourcePath = "classpath:/dependency-lookup-context-1.xml";
        // 加载 XML 资源,解析并且生成 BeanDefinition
        beanDefinitionReader.loadBeanDefinitions(xmlResourcePath);

        // 启动 Spring 应用上下文
        applicationContext.refresh();

        // 依赖查找并且创建 Bean
        UserHolder userHolder = applicationContext.getBean(UserHolder.class);
        System.out.println(userHolder);

        // 显示地关闭 Spring 应用上下文
        applicationContext.close();
    }

    /**
     *  Bean property 'user' is not writable or has an invalid setter method.
     *  Does the parameter type of the setter match the return type of the getter?
     *
     * 为 {@link UserHolder} 生成 {@link BeanDefinition}
     *
     * @return
     */
    private static BeanDefinition createUserHolderBeanDefinition() {

        return BeanDefinitionBuilder.rootBeanDefinition(UserHolder.class)
                .addConstructorArgReference("superUser")
                .getBeanDefinition();
    }

}

字段注入

Java 注解配置元信息

  • @Autowired
  • @Resource
  • @Inject(可选)

Spring 的 @Autowired 注解不会处理静态字段(static 字段)。当你尝试将 @Autowired 应用到一个静态字段上时,Spring 容器将忽略这个注解,并且该字段不会被自动注入。这是因为 Spring 的依赖注入机制是基于实例的,而静态变量属于类级别,在类加载时就已经初始化了,不与任何特定的对象实例关联。

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

@Component
public class MyComponent {

    @Autowired  // 这个注解会被忽略
    public static AnotherComponent anotherComponent;  // 静态字段

    @Autowired
    private AnotherComponent nonStaticAnotherComponent;  // 非静态字段

    // ...
}

在这个例子中,nonStaticAnotherComponent 字段将会由 Spring 自动注入,但是 anotherComponent 不会。

如果你确实需要对静态变量进行设置,通常的做法是在非静态的setter方法或者构造函数中注入依赖,然后手动设置静态变量。例如:

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

@Component
public class MyComponent {

    public static AnotherComponent anotherComponent;  // 静态字段

    @Autowired
    public void setAnotherComponent(AnotherComponent anotherComponent) {
        MyComponent.anotherComponent = anotherComponent;  // 设置静态变量
    }

    @Autowired
    private AnotherComponent nonStaticAnotherComponent;  // 非静态字段

    // ...
}

这样,通过 setAnotherComponent 方法,你可以在运行时为静态变量 anotherComponent 赋值。

DEMO

package com.evan.injection.Field;

import com.evan.entity.User;
import com.evan.injection.UserHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;

import javax.annotation.Resource;

public class AnnotationDependencyFieldInjectionDemo {

    //@Autowired 会忽略掉静态字段
    @Autowired
    private UserHolder userHolder;

    @Resource
    private UserHolder userHolder2;

    public static void main(String[] args) {

        // 创建 BeanFactory 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 注册 Configuration Class(配置类) -> Spring Bean
        applicationContext.register(AnnotationDependencyFieldInjectionDemo.class);

        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(applicationContext);

        String xmlResourcePath = "classpath:/dependency-lookup-context.xml";
        // 加载 XML 资源,解析并且生成 BeanDefinition
        beanDefinitionReader.loadBeanDefinitions(xmlResourcePath);

        // 启动 Spring 应用上下文
        applicationContext.refresh();

        // 依赖查找 AnnotationDependencyFieldInjectionDemo Bean
        AnnotationDependencyFieldInjectionDemo demo = applicationContext.getBean(AnnotationDependencyFieldInjectionDemo.class);

        // @Autowired 字段关联
        UserHolder userHolder = demo.userHolder;
        System.out.println(userHolder);
        System.out.println(demo.userHolder2);

        System.out.println(userHolder == demo.userHolder2);


        // 显示地关闭 Spring 应用上下文
        applicationContext.close();
    }

    @Bean
    public UserHolder userHolder(User user) {
        return new UserHolder(user);
    }
}

方法注入

在字段上直接使用 @Autowired/@Resource/@Bean等注解,让 Spring 自动将依赖对象注入到该字段中。

Java 注解配置元信息

  • @Autowired
  • @Resource
  • @Inject:在 Spring 中,@Inject是 Java 依赖注入规范(JSR-330)中的注解,其功能与 Spring 自身的@Autowired注解类似,用于实现依赖注入。
  • @Bean

spring @Autowired@Resource@Inject注解的异同

在 Spring 中,@Autowired@Resource@Inject这三个注解都用于实现依赖注入,但它们之间存在一些异同点。

相同点
目的相同:

这三个注解的主要目的都是为了在 Spring 容器中自动注入依赖对象,减少手动配置的工作量,提高代码的可维护性和可测试性。

可用于字段、构造函数和 setter 方法:

它们都可以应用于类的字段、构造函数参数以及 setter 方法上,以实现依赖对象的自动注入。

不同点
来源不同:
  • @Autowired是 Spring 框架特有的注解。
  • @Resource是 Java EE 规范中的注解,在 Spring 中也得到了支持。
  • @Inject是 Java 依赖注入规范(JSR-330)中的注解,同样在 Spring 中可用。
默认注入方式不同:
  • @Autowired默认按类型进行自动装配。如果存在多个相同类型的 bean,则需要结合@Qualifier注解来明确指定要注入的 bean 的名称。
  • @Resource默认按名称进行自动装配。如果没有指定名称,则按字段名或 setter 方法名作为 bean 的名称进行查找。如果按名称无法找到匹配的 bean,则按类型进行查找。
  • @Inject默认也按类型进行自动装配。与@Autowired类似,如果存在多个相同类型的 bean,也需要额外的方式来明确指定要注入的 bean。
功能特性略有不同:
  • @Autowired支持一些高级特性,如可以在构造函数上使用required=false来表示该依赖不是必需的,如果找不到对应的 bean 也不会抛出异常。

  • @Resource可以通过name属性明确指定要注入的 bean 的名称,也可以通过type属性指定要注入的 bean 的类型。

  • @Inject相对简洁,没有像@Autowired那样的高级特性,但在跨框架的情况下可能更具通用性。

使用场景
@Autowired

在纯 Spring 项目中使用非常方便,尤其是当需要结合@Qualifier等 Spring 特定的注解来进行更精细的依赖注入控制时。

@Resource

如果项目中同时使用了 Spring 和 Java EE 技术,或者希望使用 Java EE 规范中的注解来保持一定的兼容性,可以选择@Resource

@Inject

当需要编写跨框架的代码,或者希望遵循 Java 依赖注入规范时,可以使用@Inject。但在实际应用中,由于@Autowired在 Spring 项目中的广泛使用和强大功能,@Inject的使用相对较少。

歧义性问题:

如果容器中有多个同类型的 bean,可能会出现歧义性问题。在这种情况下

  • 如果使用@Inject,可以使用@Named注解来指定要注入的 bean 的名称。
  • 如果使用@Autowired,可以使用@Qualifier注解来进行更精细的依赖注入控制
  • 如果使用@Resource,由于默认按名称进行自动装配。可以指定依赖bean的名称
public interface UserMapper{
    void demoTest();
}


@Component("UserMapperByMysql")
public class UserMapperByMysql implements UserMapper {

    @Override
    public void demoTest(){
    }
}


@Component("UserMapperByPg")
public class UserMapperByPg implements UserMapper {

    @Override
    public void demoTest(){
    }
}


public class UseService{
    
	@Autowired
    @Qualifier("UserMapperByPg")   //指定注入实现类 UserMapperByPg
    private UserMapper userMapper;
    
    public void doSomething(){
        userMapper.demoTest();    //在此处调用的是实现类UserMapperByPg的实现方法
    }
}

@Qualifier的意思是合格者,通过这个标示,表明了哪个实现类才是我们所需要的,添加@Qualifier注解,需要注意的是@Qualifier的参数名称为我们之前定义@Component注解的名称之一。

public class UseService {

    //@Resource注解默认按名称进行自动装配。如果希望按照特定的名称注入依赖对象,可以直接指定名称。
    @Resource(name = "UserMapperByPg")
    private UserMapper userMapper;

     public void doSomething(){
        userMapper.demoTest();    //在此处调用的是实现类UserMapperByPg的实现方法
    }  
}
public interface UserMapper{
    void demoTest();
}


@Named("UserMapperByMysql")
public class UserMapperByMysql implements UserMapper {

    @Override
    public void demoTest(){
    }
}


@Named("UserMapperByPg")
public class UserMapperByPg implements UserMapper {

    @Override
    public void demoTest(){
    }
}


public class UseService{
    
	@Inject
    @Named("UserMapperByPg")   //指定注入实现类 UserMapperByPg
    private UserMapper userMapper;
    
    public void doSomething(){
        userMapper.demoTest();    //在此处调用的是实现类UserMapperByPg的实现方法
    }
}

DEMO

方法注入Demo1
@Component
public class AutowiredClass {
    private DependenceClass dependedObj;
    
    public void setDependedObj(@Autowired DependenceClass dependedObj) {
        this.dependedObj = dependedObj;
    }
}
方法注入Demo2
package com.evan.injection.method;


import com.evan.injection.contructor.User;
import com.evan.injection.contructor.UserHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;

import javax.annotation.Resource;

/**
 * 基于 Java 注解的依赖方法注入示例
 */
public class AnnotationDependencyMethodInjectionDemo {

    private UserHolder userHolder;

    private UserHolder userHolder2;

    @Autowired
    public void init1(UserHolder userHolder) {
        this.userHolder = userHolder;
    }

    @Resource
    public void init2(UserHolder userHolder2) {
        this.userHolder2 = userHolder2;
    }

    @Bean
    public UserHolder userHolder(User user) {
        return new UserHolder(user);
    }

    public static void main(String[] args) {

        // 创建 BeanFactory 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 注册 Configuration Class(配置类) -> Spring Bean
        applicationContext.register(AnnotationDependencyMethodInjectionDemo.class);

        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(applicationContext);

        String xmlResourcePath = "dependency-lookup-context-1.xml";
        // 加载 XML 资源,解析并且生成 BeanDefinition
        beanDefinitionReader.loadBeanDefinitions(xmlResourcePath);

        // 启动 Spring 应用上下文
        applicationContext.refresh();

        // 依赖查找 AnnotationDependencyFieldInjectionDemo Bean
        AnnotationDependencyMethodInjectionDemo demo = applicationContext.getBean(AnnotationDependencyMethodInjectionDemo.class);

        // @Autowired 字段关联
        UserHolder userHolder = demo.userHolder;
        System.out.println(userHolder);
        System.out.println(demo.userHolder2);

        System.out.println(userHolder == demo.userHolder2); //true


        // 显示地关闭 Spring 应用上下文
        applicationContext.close();
    }

}

字段注入和方法注入的异同点

字段注入(Field Injection)和方法注入(Method Injection)都是依赖注入(Dependency Injection, DI)的实现方式,但它们在使用场景、实现机制以及优缺点上有所不同。下面是两者之间的异同点:

相同点
  • 目的:两种方式都旨在将对象的依赖关系从代码内部转移到外部配置或容器中管理,从而提高代码的可测试性和灵活性。
  • 简化构造函数:两者都可以避免构造函数参数过多的问题,使得类的初始化更加简洁。
不同点
字段注入 (Field Injection)
  • 定义:通过在类成员变量(字段)上直接添加注解(如 @Autowired@Inject),由IoC容器自动注入相应的依赖。
  • 语法:非常简洁,不需要额外的方法签名或构造器参数。
  • 可见性:依赖直接暴露在类的字段级别,可能会影响封装性。
  • 单元测试:通常不利于单元测试,因为无法直接设置这些字段,除非使用反射或其他手段。
  • 性能:在某些情况下,由于需要进行反射操作来访问私有字段,可能会带来一定的性能开销。
  • 循环依赖:容易导致循环依赖问题,特别是当两个bean互相引用时。
  • 适用性:适用于快速原型开发或者小型项目,在大型企业级应用中不太推荐。
@Component
public class MyComponent {
    @Autowired
    private AnotherComponent anotherComponent;
}
方法注入 (Method Injection)
  • 定义:通过定义一个带有特定注解的方法(通常是setter方法),由IoC容器调用该方法来注入依赖。
  • 语法:需要显式地定义一个方法,这比字段注入稍微复杂一些。
  • 可见性:依赖是通过方法传递进来的,可以保持更好的封装性。
  • 单元测试:有利于单元测试,因为可以直接调用setter方法来设置模拟对象。
  • 灵活性:可以在运行时动态地改变依赖,甚至可以根据不同的条件注入不同的实现。
  • 性能:通常没有性能上的负面影响,因为它不涉及反射。
  • 适用性:适用于需要灵活控制依赖注入时机的情况,或是希望明确显示依赖关系的地方。
@Component
public class MyComponent {

    private AnotherComponent anotherComponent;

    @Autowired
    public void setAnotherComponent(AnotherComponent anotherComponent) {
        this.anotherComponent = anotherComponent;
    }
}
选择建议
  • 如果你的项目对测试友好度要求较高,并且你希望更好地控制依赖的生命周期,那么方法注入可能是更好的选择。
  • 如果你需要快速开发并且项目的规模较小,同时不介意使用反射带来的潜在性能影响,那么字段注入可能更合适。
  • 对于关键业务逻辑和服务层,推荐使用构造器注入,因为它提供了最清晰的不可变性和强制性的依赖检查。
  • 在实际开发中,可以根据具体情况混合使用不同的注入方式,以达到最佳的设计效果。

接口回调注入

Aware 系列接口回调

Aware 接口称为“某某的意识接口”,这些接口大部分以Aware结尾。这些接口的作用就是要注入的对象是Aware前面描述的东西,比如BeanFactoryAware,他是要注入一个BeanFactory,获取当前Bean容器。

自动模式

內建接口说明
BeanFactoryAware获取 IoC 容器 - BeanFactory
ApplicationContextAware获取 Spring 应用上下文 - ApplicationContext 对象
EnvironmentAware获取 Environment 对象
ResourceLoaderAware获取资源加载器 对象 - ResourceLoader
BeanClassLoaderAware获取加载当前 Bean Class 的 ClassLoader
BeanNameAware获取当前 Bean 的名称

自动模式

內建接口说明
MessageSourceAware获取 MessageSource 对象,用于 Spring 国际化
ApplicationEventPublisherAware获取 ApplicationEventPublishAware 对象,用于 Spring 事件
EmbeddedValueResolverAware获取 StringValueResolver 对象,用于占位符处理

DEMO


package org.geekbang.thinking.in.spring.ioc.dependency.injection;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * 基于 {@link Aware} 接口回调的依赖注入示例
 */
public class AwareInterfaceDependencyInjectionDemo implements BeanFactoryAware, ApplicationContextAware {

    private static BeanFactory beanFactory;

    private static ApplicationContext applicationContext;


    public static void main(String[] args) {

        // 创建 BeanFactory 容器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 注册 Configuration Class(配置类) -> Spring Bean
        context.register(AwareInterfaceDependencyInjectionDemo.class);

        // 启动 Spring 应用上下文
        context.refresh();

        System.out.println(beanFactory == context.getBeanFactory());  //true
        System.out.println(applicationContext == context); //true

        // 显示地关闭 Spring 应用上下文
        context.close();
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        AwareInterfaceDependencyInjectionDemo.beanFactory = beanFactory;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        AwareInterfaceDependencyInjectionDemo.applicationContext = applicationContext;
    }
}

依赖注入类型选择

依赖注入(Dependency Injection, DI)是实现控制反转(Inversion of Control, IoC)的一种设计模式,它可以帮助我们更好地管理对象之间的依赖关系。在选择依赖注入方式时,确实需要考虑不同场景的需求。不同的注入方式各有特点,适用于不同的情况:

  1. 构造器注入 (Constructor Injection)

    • 适用场景:当类只有少量依赖或者这些依赖都是必需的,并且在整个对象生命周期中不会改变时。
    • 优点:
      • 确保了不可变性,一旦创建后依赖不能被更改。
      • 使得对象更容易进行单元测试,因为可以在测试时直接通过构造函数传入模拟对象。
      • 强制执行了依赖的存在性,避免了空指针异常。
    • 缺点:对于有大量依赖的情况,构造函数会变得非常复杂。
  2. Setter 方法注入 (Setter Injection)

    • 适用场景:当一个类有许多可选的依赖项或依赖可能在运行时变化时。

    • 优点:

      • 提供了灵活性,允许在任何时候设置或更新依赖。
      • 对于已经存在的代码,添加新的依赖更为容易。
    • 缺点:可能导致部分依赖未初始化的状态,需要额外的逻辑来确保所有依赖都已正确设置。

  3. 字段注入 (Field Injection)

    • 适用场景:当希望保持简洁的代码并且不需要特别强调依赖的可见性时。
    • 优点:
      • 语法简单,减少了样板代码。
      • 易于使用,特别是在快速原型开发中。
    • 缺点:
      • 不利于单元测试,因为它隐藏了依赖关系。
      • 可能导致循环依赖问题。
      • 在某些情况下,可能会导致性能开销,尤其是在大型应用中。
  4. 方法注入 (Method Injection)

    • 适用场景:当需要在方法调用过程中动态地提供依赖时。
    • 优点:
      • 允许更细粒度的控制依赖的提供时机。
      • 支持更加复杂的注入逻辑。
    • 缺点:增加了理解和维护的复杂性。

总结

  • 必要依赖:倾向于使用构造器注入,以保证对象的完整性和不变性。
  • 可选依赖:可以使用Setter方法注入,以便灵活地配置和修改依赖。
  • 便利性:如果追求简洁快速的编码体验,可以考虑字段注入,但要注意其潜在的问题。
  • 特定需求:对于需要在方法级别处理依赖的情况,可以使用方法注入。(声明类:方法注入)

选择哪种注入方式没有绝对的好坏之分,关键在于理解每种方式的特点并根据实际项目的需求做出合理的选择。通常建议优先使用构造器注入,除非有明确的理由选择其他方式。

基础类型注入

原生类型(Primitive):boolean、byte、char、short、int、float、long、double

原生类型指的是Java的基本数据类型,如 booleanbytecharshortintfloatlongdouble。当我们在Spring配置文件或使用注解时,可以直接指定这些类型的值。

<bean id="exampleBean" class="com.example.ExampleBean">
    <property name="someInt" value="42"/>
</bean>

或者使用@Value注解:

public class ExampleBean {
    @Value("42")
    private int someInt;
}
标量类型(Scalar):Number、Character、Boolean、Enum、Locale、Charset、Currency、 Properties、UUID

标量类型包括了像 NumberCharacterBooleanEnumLocaleCharsetCurrencyPropertiesUUID 这样的类型。它们不是基本类型,但通常表示单一数值或概念。

public class ExampleBean {
    @Value("42")
    private Integer anInteger;

    @Value("3.14")
    private Double aDouble;
    
    @Value("true")
    private Boolean aBoolean;

    @Value("#{'key1=value1\nkey2=value2'}")
    private Properties properties;
}
常规类型(General):Object、String、TimeZone、Calendar、Optional 等
public class StringExample {
    @Value("${app.name:MyApp}")
    private String appName;
}
Spring 类型:Resource、InputSource、Formatter 等\
public class ResourceExample {
    @Autowired
    @Qualifier("classpath:/META-INF/myfile.txt")
    private Resource resourceFile;
}

集合类型注入

数组类型(Array):原生类型、标量类型、常规类型、Spring 类型

数组类型的注入可以通过XML配置或者使用@Value注解来完成。数组中的元素可以是原生类型、标量类型、常规类型或Spring特定类型。

XML配置
<bean id="exampleBean" class="com.example.ExampleBean">
    <property name="intArray">
        <array>
            <value>1</value>
            <value>2</value>
            <value>3</value>
        </array>
    </property>
    <property name="stringArray">
        <list>
            <value>one</value>
            <value>two</value>
            <value>three</value>
        </list>
    </property>
</bean>
使用@Value注解
public class ExampleBean {
    @Value("${my.ints:1,2,3}")
    private int[] intArray;

    @Value("#{'${my.strings:one,two,three}'.split(',')}")
    private String[] stringArray;
}

在上述例子中,如果属性文件中没有定义my.intsmy.strings,那么将使用默认值。

集合类型(Collection)
  • Collection:List、Set(SortedSet、NavigableSet、EnumSet)
  • Map:Properties
List
XML配置
<bean id="exampleBean" class="com.example.ExampleBean">
    <property name="items">
        <list>
            <value>Item 1</value>
            <value>Item 2</value>
            <value>Item 3</value>
        </list>
    </property>
</bean>
使用@Value注解
public class ExampleBean {
    @Value("${my.items:Item 1,Item 2,Item 3}")
    private List<String> items;
}
Set
XML配置
<bean id="exampleBean" class="com.example.ExampleBean">
    <property name="uniqueItems">
        <set>
            <value>Unique Item 1</value>
            <value>Unique Item 2</value>
        </set>
    </property>
</bean>
使用@Value注解
public class ExampleBean {
    @Value("#{'${my.unique.items:Unique Item 1,Unique Item 2}'.split(',')}")
    private Set<String> uniqueItems;
}
Map
XML配置
<bean id="exampleBean" class="com.example.ExampleBean">
  <property name="config">
    <map>
      <entry key="key1" value="value1"/>
      <entry key="key2" value="value2"/>
    </map>
  </property>
</bean>
使用@Value注解
public class ExampleBean {
    @Value("#{ ${'key1=value1\nkey2=value2'} }")
    private Map<String, String> config;
}
DEMO
    <bean id="user" class="org.geekbang.thinking.in.spring.ioc.overview.domain.User">
        <property name="id" value="1"/>
        <property name="name" value="小马哥"/>
        <property name="city" value="HANGZHOU"/>
        <property name="workCities" value="BEIJING,HANGZHOU"/>
        <property name="lifeCities">
            <list>
                <value>BEIJING</value>
                <value>SHANGHAI</value>
            </list>
        </property>
        <property name="configFileLocation" value="classpath:/META-INF/user-config.properties"/>
    </bean>

限定注入

使用注解 @Qualifier 限定

  • 通过 Bean 名称限定
  • 通过分组限定

在Spring框架中,@Qualifier 注解用于解决当存在多个相同类型的bean时的歧义问题。它允许你更具体地指定哪一个bean应该被注入。@Qualifier 可以与 @Autowired 或者 @Inject 一起使用来限定具体的依赖。

通过 Bean 名称限定

当你有多个相同类型的bean,并且你想明确指出哪个bean应该被注入时,可以使用 @Qualifier 指定bean的名字。

假设你有两个实现同一接口的不同类:

@Component
public class PrimaryService implements MyService {
    // 实现...
}

@Component
public class SecondaryService implements MyService {
    // 实现...
}

在需要注入其中一个服务的地方,你可以这样使用 @Qualifier 来指定具体的bean:

@Autowired
@Qualifier("primaryService")
private MyService myService;

这里 "primaryService"PrimaryService 类的默认bean名称(因为没有显式指定,所以默认使用类名首字母小写作为bean名称)。

通过分组限定

有时候,除了根据bean的名字来限定之外,我们还可以通过自定义注解来进一步区分不同类型的bean。这种方式称为“分组限定”。

自定义注解示例

首先,定义一个自定义的 @Qualifier 注解:

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Primary1 {
}

然后,在你的bean上使用这个自定义注解:

@Component
@Primary1
public class PrimaryService implements MyService {
    // 实现...
}

@Component
public class SecondaryService implements MyService {
    // 实现...
}

最后,在需要注入的地方使用自定义的 @Qualifier 注解:

@Autowired
@Primary1
private MyService primaryService;

在这个例子中,@Primary 就是一个自定义的 @Qualifier,它帮助我们区分了两个实现 MyService 接口的bean。

总结

  • 通过 Bean 名称限定:直接使用 @Qualifier 并提供bean的名字来指定要注入的具体bean。
  • 通过分组限定:创建自定义的 @Qualifier 注解,然后在相应的bean和注入点上使用这些注解来进行更细粒度的控制。

这两种方式都有效地解决了多bean同类型的情况下的注入问题。选择哪种方式取决于你的具体需求以及项目结构。如果你只需要简单地区分几个特定的bean,使用bean名字可能就足够了;如果需要对一组相关的bean进行更复杂的管理,那么自定义 @Qualifier 注解会更加合适。

延迟依赖注入

使用 API ObjectFactory 延迟注入
  • 单一类型
  • 集合类型
使用 API ObjectProvider 延迟注入(推荐)
  • 单一类型
  • 集合类型
package com.evan.injection;

import com.evan.injection.setter.User;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;

import java.util.Set;

@Configuration
public class LazyAnnotationDependencyInjectionDemo {

    @Autowired
    @Qualifier("user")
    private User user; // 实时注入

    @Autowired
    private ObjectProvider<User> userObjectProvider; // 延迟注入

    @Autowired
    private ObjectFactory<Set<User>> usersObjectFactory;

    public static void main(String[] args) {

        // 创建 BeanFactory 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 注册 Configuration Class(配置类) -> Spring Bean
        applicationContext.register(LazyAnnotationDependencyInjectionDemo.class);

        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(applicationContext);

        String xmlResourcePath = "dependency-lookup-context.xml";
        // 加载 XML 资源,解析并且生成 BeanDefinition
        beanDefinitionReader.loadBeanDefinitions(xmlResourcePath);

        // 启动 Spring 应用上下文
        applicationContext.refresh();

        // 依赖查找 QualifierAnnotationDependencyInjectionDemo Bean
        LazyAnnotationDependencyInjectionDemo demo = applicationContext.getBean(LazyAnnotationDependencyInjectionDemo.class);

        // 期待输出 user Bean
        System.out.println("demo.user = " + demo.user);
        // 期待输出 user Bean
        System.out.println("demo.userObjectProvider = " + demo.userObjectProvider.getObject()); // 继承 ObjectFactory
        // 期待输出 superUser user Beans
        System.out.println("demo.usersObjectFactory = " + demo.usersObjectFactory.getObject());

        demo.userObjectProvider.forEach(System.out::println);


        // 显示地关闭 Spring 应用上下文
        applicationContext.close();
    }

}
demo.user = User{id='1', name='evan', city='SHANGHAI', workCities=[BEIJING,SHANGHAI], lifeCities=[BEIJING, SHANGHAI]}

demo.userObjectProvider = User{id='1', name='evan', city='SHANGHAI', workCities=[BEIJING,SHANGHAI], lifeCities=[BEIJING, SHANGHAI]}

demo.usersObjectFactory = [User{id='1', name='evan', city='SHANGHAI', workCities=[BEIJING,SHANGHAI], lifeCities=[BEIJING, SHANGHAI]}, SuperUser{ superClassInfo=User{id='1', name='evan', city='SHANGHAI', workCities=[BEIJING,SHANGHAI], lifeCities=[BEIJING, SHANGHAI]}, address='SHANGHAI'}]
User{id='1', name='evan', city='SHANGHAI', workCities=[BEIJING,SHANGHAI], lifeCities=[BEIJING, SHANGHAI]}
SuperUser{ superClassInfo=User{id='1', name='evan', city='SHANGHAI', workCities=[BEIJING,SHANGHAI], lifeCities=[BEIJING, SHANGHAI]}, address='SHANGHAI'}