什么是注入
Spring 的注入是指通过 Spring 框架提供的机制,将对象的依赖关系交由 Spring 容器来管理和维护。通常情况下,我们在编写 Java 应用程序时,需要手动创建和管理对象之间的依赖关系,这会导致代码耦合度高、可测试性差等问题。而使用 Spring 的注入功能,可以将对象的创建和依赖关系的维护交给 Spring 容器自动完成,大大简化了开发过程。
Spring 的依赖注入(Dependency Injection,简称 DI)是指通过框架自动将对象的依赖关系注入到对象中的过程。在传统的开发模式中,对象之间的依赖关系需要手动创建和维护,而使用依赖注入,可以由框架来管理对象之间的依赖关系,提高代码的可维护性和可测试性。
注入的方式
Spring 的注入主要有以下几种方式:
- 构造器注入(Constructor Injection):通过构造器来注入依赖对象,可以通过构造器参数的方式表达对象之间的依赖关系。
- Setter 方法注入(Setter Injection):通过 Setter 方法来注入依赖对象,可以为对象的属性提供设置方法,并由 Spring 容器在创建对象后调用 Setter 方法完成依赖注入。
- 接口注入(Interface Injection):通过接口的方式来注入依赖对象,一般使用 Java 提供的接口来定义注入方法,由 Spring 容器在创建对象后调用接口方法完成依赖注入。
- 注解注入(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 实现主要基于以下两个关键概念:
- Bean 定义(Bean Definition):Bean 定义是描述对象创建和依赖关系的元数据。它定义了对象的类型、属性、依赖关系等信息。可以通过 XML 配置文件、注解或 Java 代码等方式来定义 Bean。
- 容器(Container):容器是负责创建和管理 Bean 的运行环境。Spring 框架提供了多种容器实现,如 ApplicationContext 和 BeanFactory。容器会根据 Bean 定义创建对象,并根据依赖关系将所需的依赖注入到对象中。
通过 IoC,我们只需要关注对象的定义和依赖关系的描述,具体的对象创建和依赖注入由 Spring 容器负责处理。这样可以有效降低代码的耦合度,提高代码的可扩展性、可维护性和可测试性。
总结:Spring 底层使用工厂设计模式,把对于成员变量赋值的控制权,从代码中反转、转移到 Spring 工厂和配置文件中完成,大大降低代码的耦合性。