作者简介:☕️大家好,我是Aomsir,一个爱折腾的开发者!
个人主页:Aomsir_Spring5应用专栏,Netty应用专栏,RPC应用专栏-CSDN博客
当前专栏:Spring5应用专栏_Aomsir的博客-CSDN博客
文章目录
参考文献
引言
之前我们已经初步了解了Spring框架的一些基本概念,今天我们将深入学习依赖注入(Dependency Injection,DI)
这一重要概念,它是实现控制反转(Inversion of Control,IoC)
的关键手段和思想。在Spring中,我们可以使用多种注入方式来实现依赖注入,其中包括以下几种常见的方式:Set注入、构造注入、反射注入以及基于字段的注入等等
概念详解
什么是依赖注入?
依赖注入是通过Spring容器以及配置文件,为所创建的对象(Bean)的成员变量赋值的过程
为什么需要依赖注入?
在以下示例中,我们有一个Person类,其中包含id和name两个属性。在测试代码中,我们从工厂获取了Person对象,并手动为其属性赋值。然而,这种方式引入了紧耦合,如果将来需要更改id和name的属性值,就必须修改代码、重新编译、打包和部署。这种做法并没有充分体现依赖注入的优势。依赖注入的核心概念在于使用Spring容器和配置文件来实现对象间的解耦,从而提高灵活性和可维护性
public class TestSpring {
@Test
public void test4() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = ctx.getBean("person", Person.class);
// 通过编码为属性赋值存在耦合
person.setId(1);
person.setName("Aomsir");
System.out.println("person = " + person);
}
}
开发步骤
- 为类成员变量提供Set/Get方法
- 编写Spring的配置文件
- 编写测试代码
<bean id="person" class="com.aomsir.basic.entity.Person">
<property name="id" >
<value>10</value>
</property>
<property name="name">
<value>Aomsir</value>
</property>
</bean>
简易实现原理
Spring通过底层调用对象属性对应的set方法,从而完成成员变量的赋值。这种方式也被称为set注入。通过这种机制,我们可以在配置文件中指定依赖对象,然后让Spring容器自动完成对象的创建和属性赋值,从而实现解耦和依赖注入的目标
Set注入详解
在Spring容器中,一个Bean的成员变量类型可能涵盖多种种类,例如Integer、String、List、HashMap,以及我们开发者自定义的类型等。这些成员变量的类型主要可以分为两种:第一种是由JDK提供的内置类型,第二种是我们开发者自己定义的类型。如下图所示:
JDK类型
简单的JDK类型
以下示例涵盖了对所有常见的简单JDK数据类型的测试,展示了如何在Spring中进行Set注入
@Data
public class Customer implements Serializable {
private String name;
private int age;
private String[] emails;
private Set<String> tels;
private List<String> address;
private Map<String, String> qqs;
private Properties p;
}
<bean class="com.aomsir.basic.entity.Customer" id="customer">
<property name="name">
<value>Aomsir</value>
</property>
<property name="age">
<value>21</value>
</property>
<property name="emails">
<list>
<value>aomsir1@apache.org</value>
<value>aomsir2@apache.org</value>
<value>aomsir3@apache.org</value>
</list>
</property>
<property name="tels">
<set>
<value>010-1</value>
<value>010-2</value>
<value>010-3</value>
</set>
</property>
<property name="address">
<list>
<value>北京市</value>
<value>武汉市</value>
</list>
</property>
<property name="qqs">
<map>
<entry>
<key><value>name</value></key>
<value>Aomsir</value>
</entry>
</map>
</property>
<property name="p">
<props>
<prop key="name">Aomsir</prop>
</props>
</property>
</bean>
复杂的JDK类型
更为复杂的JDK类型,比如Date等,用得相对较少。等知识储备足够,单独开设一个章节来深入讲解这些复杂类型的处理方式
自定义类型
下面这个示例,UserService依赖于UserDAO。UserDAO是我们自定义的类型,将会通过Set注入的方式来实现
第一种方式
在Bean内部进行嵌套,以创建另一个Bean并进行依赖注入的操作
<bean id="userService" class="com.aomsir.basic.service.impl.UserServiceImpl">
<property name="userDAO">
<bean class="com.aomsir.basic.dao.impl.UserDAOImpl"/>
</property>
</bean>
第二种方式
上述第一种方式中存在一些问题。首先,会产生代码冗余的情况。其次,被注入的对象(UserDAO)可能会被多次创建,从而浪费了JVM内存资源。因此,我们可以采用以下引用的方式来进行依赖注入
<bean id="userDAO" class="com.aomsir.basic.dao.impl.UserDAOImpl"/>
<bean id="userService" class="com.aomsir.basic.service.impl.UserServiceImpl">
<property name="userDAO">
<ref bean="userDAO" />
</property>
</bean>
简化写法
属性简化
上面的注入写法还是太臃肿了,可以对其简化,将之前使用的value标签替换为bean标签中的value属性,同时将之前的ref标签替换为bean标签中的ref属性。需要注意的是:value属性仅适用于8种基本类型以及String类型
<!--JDK类型 value属性-->
<bean id="person" class="com.aomsir.basic.entity.Person">
<property name="id" value="10" />
<property name="name" value="Aomsir" />
</bean>
<!--自定义类型 ref属性-->
<bean id="userService" class="com.aomsir.basic.service.impl.UserServiceImpl">
<property name="userDAO" ref="userDAO" />
</bean>
p标签简化
另外,基于p标签,我们可以进一步简化上述的value属性设置。要使用这种方式,需要引入p命名空间标签。具体使用方式如下所示。这种方式的注意点和之前的方式相同
<bean id="person" class="com.aomsir.basic.entity.Person" p:id="1" p:name="Aomsir"/>
<bean id="userService" class="com.aomsir.basic.service.impl.UserServiceImpl" p:userDAO-ref="userDAO" />
构造注入详解
在上述的Set注入中,Spring会调用类的Set方法,通过配置文件为成员变量赋值。而构造注入则是Spring会调用类的构造方法,在配置文件中指定参数值,从而为成员变量赋值
开发步骤
- 类提供有参构造方法
- 使用
constructor-arg标签
替换propertity标签
public class Employer {
private Integer id;
private String name;
public Employer(Integer id, String name) {
this.id = id;
this.name = name;
}
}
<bean id="employer" class="com.aomsir.basic.entity.Employer">
<constructor-arg name="id" value="1" />
<constructor-arg name="name" value="Aomsir" />
</bean>
重载构造
在实际开发中,我们可能会为一个类提供多个重载构造函数。如果这些构造函数参数个数不同,我们可以通过constructor-arg标签的数量来区分。而如果参数个数相同,但类型不同,我们则可以在constructor-arg标签
中使用type属性来进行区分
控制反转&依赖注入
- 控制反转(IoC):全称为Inversion of Control,即控制权的反转。它涉及到成员变量的赋值控制。这种机制的关键在于,将控制权从代码中转移到Spring容器和配置文件中。这种反转带来的最大好处之一是解耦合,底层实现基于之前章节中提到的工厂设计模式
- 依赖注入(DI):全称为Dependency Injection,是一种思想。通过Spring的工厂和配置文件,为对象的成员变量赋值。当一个类需要另一个类时,就存在依赖关系。为了解决这种依赖,我们将另一个类作为本类的成员变量,最终通过Spring配置文件完成注入。这种机制能够更好地管理对象之间的关系,提高了灵活性和可维护性
总结
在未来的实际应用中,Set注入可能会更加常见。这主要是因为构造注入在处理重载构造函数时可能较为繁琐,容易出现参数数量和顺序的问题。另外,虽然Spring官方并未直接明确推荐使用Set注入,但实际上,其底层源码中却广泛地应用了Set注入的方式。这说明了Spring团队自己也意识到了Set注入的一些优势,并在内部使用这种方式来处理依赖关系