依赖注入艺术:探索Spring如何实现松耦合的神奇

简介: 依赖注入艺术:探索Spring如何实现松耦合的神奇

什么是注入

Spring 的注入是指通过 Spring 框架提供的机制,将对象的依赖关系交由 Spring 容器来管理和维护。通常情况下,我们在编写 Java 应用程序时,需要手动创建和管理对象之间的依赖关系,这会导致代码耦合度高、可测试性差等问题。而使用 Spring 的注入功能,可以将对象的创建和依赖关系的维护交给 Spring 容器自动完成,大大简化了开发过程。

Spring 的依赖注入(Dependency Injection,简称 DI)是指通过框架自动将对象的依赖关系注入到对象中的过程。在传统的开发模式中,对象之间的依赖关系需要手动创建和维护,而使用依赖注入,可以由框架来管理对象之间的依赖关系,提高代码的可维护性和可测试性。

注入的方式

Spring 的注入主要有以下几种方式:

  1. 构造器注入(Constructor Injection):通过构造器来注入依赖对象,可以通过构造器参数的方式表达对象之间的依赖关系。
  2. Setter 方法注入(Setter Injection):通过 Setter 方法来注入依赖对象,可以为对象的属性提供设置方法,并由 Spring 容器在创建对象后调用 Setter 方法完成依赖注入。
  3. 接口注入(Interface Injection):通过接口的方式来注入依赖对象,一般使用 Java 提供的接口来定义注入方法,由 Spring 容器在创建对象后调用接口方法完成依赖注入。
  4. 注解注入(Annotation Injection):通过注解来标记需要注入的依赖对象,Spring 通过扫描注解并自动完成依赖注入。常用的注解包括 @Autowired@Resource@Inject 等。

无论使用何种方式,依赖注入的核心思想是将对象的创建和依赖关系交给外部容器来管理,使得代码更加灵活、可维护和可测试。

构造注入

创建有参构造方法

package world.xuewei;
/**
 * 用户实体
 *
 * @author 薛伟
 * @since 2023/9/14 17:17
 */
public class Student {
    /**
     * 用户名
     */
    private String userName;
    /**
     * 密码
     */
    private String password;
    public Student() {
    }
    public Student(String userName, String password) {
        this.userName = userName;
        this.password = password;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    @Override
    public String toString() {
        return "User{" +
                "userName='" + userName + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

编写配置文件

<bean id="student" class="world.xuewei.Student">
    <constructor-arg>
        <value>XW</value>
    </constructor-arg>
    <constructor-arg>
        <value>123456</value>
    </constructor-arg>
</bean>

constructor-arg 标签的个数和顺序需要和对应的构造方法保持一致。注意,标签内不是只能使用 value 标签,实际上取决于对象属性中的类型/泛型。

构造方法重载

参数个数不同时

可以通过配置 constructor-arg 标签的个数进行控制。

个数相同但类型不同

可以通过在 constructor-arg 使用 type 属性进行控制。

<bean id="student" class="world.xuewei.Student">
    <constructor-arg type="java.lang.String">
        <value>XW</value>
    </constructor-arg>
    <constructor-arg type="java.lang.String">
        <value>123456</value>
    </constructor-arg>
</bean>

Setter 方法注入

简单的注入使用

用户实体 User
package world.xuewei;
/**
 * 用户实体
 *
 * @author 薛伟
 * @since 2023/9/14 17:17
 */
public class User {
    /**
     * 用户名
     */
    private String userName;
    /**
     * 密码
     */
    private String password;
    public User() {
    }
    public User(String userName, String password) {
        this.userName = userName;
        this.password = password;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        System.out.println("User userName Setting...");
        this.userName = userName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        System.out.println("User password Setting...");
        this.password = password;
    }
    @Override
    public String toString() {
        return "User{" +
                "userName='" + userName + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
Spring 配置文件
<?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">
    <!-- id:创建 Bean 的名字,要求唯一 -->
    <!-- name:创建 Bean 别名 -->
    <!-- class:创建 Bean 的全限定名 -->
    <bean id="user" name="USER,u1,u2" class="world.xuewei.User">
        <property name="userName">
            <value>张三</value>
        </property>
        <property name="password">
            <value>123456</value>
        </property>
    </bean>
</beans>
测试程序 InjectTest
package world.xuewei;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * @author 薛伟
 * @since 2023/9/14 20:51
 */
public class InjectTest {
    private ApplicationContext context;
    @Before
    public void before() {
        // 指定配置文件,创建 Spring 工厂
        context = new ClassPathXmlApplicationContext("/applicationContext.xml");
    }
    /**
     * 通过 Spring 的工厂获得对象
     */
    @Test
    public void testGetBean() {
        // 通过 Bean 的 id/name 获取对象,同时指定 Bean 的类型,省略强转的步骤
        User user = context.getBean("user", User.class);
        System.out.println(user);
    }
}
运行结果

通过运行结果可以得知,Spring 通过底层调用对象的 set 方法完成属性的赋值,这种方式我们也称之为:Setter 注入。

注入 JDK 内置类型

String + 八种基本类型

在 properties 里面使用 value 标签。

private String userName;
<bean id="user" name="USER" class="world.xuewei.User">
    <property name="userName">
        <value>张三</value>
    </property>
</bean>
数组

在 properties 里面使用 list 标签,list 标签里面再写 value 标签。注意,标签内不是只能使用 value 标签,实际上取决于对象属性中的类型/泛型。

private String[] emails;
<bean id="user" name="USER" class="world.xuewei.User">
    <property name="emails">
        <list>
          <value>373675032@qq.com</value>
            <value>isxuewei@qq.com</value>
        </list>
    </property>
</bean>
Set 集合

在 properties 里面使用 set 标签,set 标签里面再写 value 标签,无序且不可重复。注意,标签内不是只能使用 value 标签,实际上取决于对象属性中的类型/泛型。

private Set<String> phones;
• 1
<bean id="user" name="USER" class="world.xuewei.User">
    <property name="phones">
        <set>
          <value>17888889999</value>
            <value>17888889998</value>
            <value>17888889998</value>
        </set>
    </property>
</bean>
List 集合

在 properties 里面使用 list 标签,list 标签里面再写 value 标签。注意,标签内不是只能使用 value 标签,实际上取决于对象属性中的类型/泛型。

private List<String> addresses;
<bean id="user" name="USER" class="world.xuewei.User">
    <property name="addresses">
        <list>
          <value>河北省-唐山市-迁安市</value>
            <value>四川省-成都市-简阳市</value>
        </list>
    </property>
</bean>
Map 集合

在 properties 里面使用 map 标签,list 标签里面写 entry 标签,entry 标签中再去定义 key、value。注意,标签内不是只能使用 value 标签,实际上取决于对象属性中的类型/泛型。

注意:键 是要有一个 key 标签包裹,而值则不需要。

private Map<String, Object> contact;
<property name="contact">
    <map>
        <entry>
            <key>
                <value>QQ</value>
            </key>
            <value>
                373675032
            </value>
        </entry>
        <entry>
            <key>
                <value>WeChat</value>
            </key>
            <value>
                XUEW2333333
            </value>
        </entry>
    </map>
</property>
Properties 集合

Properties 类型的注入要稍微特别一点,由于 Properties 类型的键值对都只能是 String 类型,所以不再需要使用 value 标签。

private Properties properties;
<property name="properties">
    <props>
        <prop key="birthplace">中国</prop>
        <prop key="info">我是一个乐观的人。</prop>
    </props>
</property>
其他复杂类型

其他复杂的 JDK 类型的对象,我们可以通过自定义类型转换器完成注入。

注入自定义类型

package world.xuewei;
/**
 * 用户数据库访问层
 *
 * @author 薛伟
 * @since 2023/10/16 13:27
 */
public class UserDao {
}
package world.xuewei;
/**
 * 用户业务层
 *
 * @author 薛伟
 * @since 2023/10/16 13:27
 */
public class UserService {
    private UserDao userDao;
    public UserDao getUserDao() {
        return userDao;
    }
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    @Override
    public String toString() {
        return "UserService{" +
                "userDao=" + userDao +
                '}';
    }
}
第一种方式
<bean id="userService" class="world.xuewei.UserService">
    <property name="userDao">
        <bean class="world.xuewei.UserDao"/>
    </property>
</bean>

上面这种方式存在:配置⽂件代码冗余、被注⼊的对象(UserDao),多次创建,浪费内存资源的问题。

第二种方式
<bean id="userDao" class="world.xuewei.UserDao"/>
<bean id="userService" class="world.xuewei.UserService">
    <property name="userDao">
        <ref bean="userDao"/>
    </property>
</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="user" name="USER,u1,u2" class="world.xuewei.User">
        <property name="userName" value="张三"/>
        <property name="password" value="123456"/>
        <property name="emails">
            <list>
                <value>373675032@qq.com</value>
                <value>isxuewei@qq.com</value>
            </list>
        </property>
        <property name="phones">
            <set>
                <value>17888889999</value>
                <value>17888889998</value>
                <value>17888889998</value>
            </set>
        </property>
        <property name="contact">
            <map>
                <entry key="QQ" value="373675032"/>
                <entry key="WeChat" value="XUEW2333333"/>
                <entry key="userDao" value-ref="userDao"/>
            </map>
        </property>
        <property name="properties">
            <props>
                <prop key="birthplace">中国</prop>
                <prop key="info">我是一个乐观的人。</prop>
            </props>
        </property>
    </bean>
    <bean id="userDao" class="world.xuewei.UserDao"/>
    <bean id="userService" class="world.xuewei.UserService">
        <property name="userDao" ref="userDao"/>
    </bean>
</beans>
使用命名空间 p 简化
<?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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user" class="world.xuewei.User" p:userName="张三" p:password="123456">
    </bean>
    <bean id="userDao" class="world.xuewei.UserDao"/>
    
    <bean id="userService" class="world.xuewei.UserService" p:userDao-ref="userDao">
    </bean>
</beans>

反转控制

Spring 的反转控制(Inversion of Control,简称 IoC)是指通过框架将对象的创建和依赖关系的管理转移到外部容器中,由容器负责创建对象并将依赖注入到对象中。传统的开发方式中,对象的创建和依赖关系的维护由开发者手动进行,而 IoC 则将这一过程反转,由框架来控制对象的创建和依赖关系的处理。

在 Spring 中,IoC 的核心是 Spring 容器。Spring 容器负责创建和管理应用程序中的对象,并在需要时将依赖注入到对象中。通过 IoC,我们不再需要直接依赖其他对象的具体实现类,而是通过接口或抽象类来定义依赖关系。这样可以降低代码的耦合性,提高可维护性和可测试性。

Spring 的 IoC 实现主要基于以下两个关键概念:

  1. Bean 定义(Bean Definition):Bean 定义是描述对象创建和依赖关系的元数据。它定义了对象的类型、属性、依赖关系等信息。可以通过 XML 配置文件、注解或 Java 代码等方式来定义 Bean。
  2. 容器(Container):容器是负责创建和管理 Bean 的运行环境。Spring 框架提供了多种容器实现,如 ApplicationContext 和 BeanFactory。容器会根据 Bean 定义创建对象,并根据依赖关系将所需的依赖注入到对象中。

通过 IoC,我们只需要关注对象的定义和依赖关系的描述,具体的对象创建和依赖注入由 Spring 容器负责处理。这样可以有效降低代码的耦合度,提高代码的可扩展性、可维护性和可测试性。

总结:Spring 底层使用工厂设计模式,把对于成员变量赋值的控制权,从代码中反转、转移到 Spring 工厂和配置文件中完成,大大降低代码的耦合性。



相关文章
|
4月前
|
XML Java 测试技术
Spring Boot中的依赖注入和控制反转
Spring Boot中的依赖注入和控制反转
|
6月前
|
XML Java 程序员
Spring6框架中依赖注入的多种方式(推荐构造器注入)
依赖注入(DI)是一种过程,对象通过构造函数参数、工厂方法的参数或在对象实例构建后设置的属性来定义它们的依赖关系(即与其一起工作的其他对象)。
90 3
|
6月前
|
Java 测试技术 开发者
Spring IoC容器通过依赖注入机制实现控制反转
【4月更文挑战第30天】Spring IoC容器通过依赖注入机制实现控制反转
61 0
|
3月前
|
Java Spring 容器
彻底改变你的编程人生!揭秘 Spring 框架依赖注入的神奇魔力,让你的代码瞬间焕然一新!
【8月更文挑战第31天】本文介绍 Spring 框架中的依赖注入(DI),一种降低代码耦合度的设计模式。通过 Spring 的 DI 容器,开发者可专注业务逻辑而非依赖管理。文中详细解释了 DI 的基本概念及其实现方式,如构造器注入、字段注入与 setter 方法注入,并提供示例说明如何在实际项目中应用这些技术。通过 Spring 的 @Configuration 和 @Bean 注解,可轻松定义与管理应用中的组件及其依赖关系,实现更简洁、易维护的代码结构。
51 0
|
3月前
|
设计模式 自然语言处理 Java
简单了解下Spring中的各种Aware接口实现依赖注入
在Spring框架中,Aware接口是一组用于提供特定资源或环境信息的回调接口。这些接口被设计用来允许Bean获取对Spring容器或其他相关资源的引用,并在需要时进行适当的处理。
36 2
|
3月前
|
自然语言处理 Java 开发者
简单了解下Spring中的各种Aware接口实现依赖注入
【8月更文挑战第21天】在Spring框架中,Aware接口系列是一种特殊的机制,它允许Bean在初始化过程中获取到Spring容器或容器中的特定资源,从而实现了更加灵活和强大的依赖注入方式。本文将围绕Spring中的各种Aware接口,详细探讨它们如何帮助开发者在工作和学习中更好地实现依赖注入。
107 0
|
4月前
|
缓存 Java Spring
Spring循环依赖问题之Spring不支持构造器内的强依赖注入如何解决
Spring循环依赖问题之Spring不支持构造器内的强依赖注入如何解决
|
5月前
|
设计模式 Java 测试技术
Spring Boot中的依赖注入详解
Spring Boot中的依赖注入详解
|
5月前
|
缓存 Java 测试技术
Spring 框架,不只是依赖注入和面向切面那么简单!
【6月更文挑战第25天】Spring框架超越DI和AOP,涵盖事务管理、数据访问抽象如`JdbcTemplate`、消息驱动支持如`@JmsListener`、缓存管理和测试工具。示例代码展示了其简化复杂性的能力,如自动事务处理、数据库操作及消息队列监听。Spring是构建高效企业级应用的全面解决方案。
40 4
|
5月前
|
Java Spring 容器
spring如何进行依赖注入,通过set方法把Dao注入到serves
spring如何进行依赖注入,通过set方法把Dao注入到serves