Dependency Injection,依赖注入。
- 对象之间依赖关系的管理交给 Spring 维护;
- 是实现控制反转的方式之一;
- 可以降低程序之间的耦合(依赖关系)。
Spring 实现的 IoC 容器中,基本类型和对象都可以注入,按配置细节区分:
- Java 基本类型
- String
- bean
- Spring 自建 bean(未自己申明的 bean,可以通过 getBean 方法获取到)
- 非 bean(无法通过 getBean 方法获取到)
- 数组
- 集合(List、Set、Map)
pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ssm</artifactId>
<groupId>com.fengx</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<!--依赖注入-->
<artifactId>spring_02</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<spring-version>5.2.2.RELEASE</spring-version>
</properties>
<!--spring-context-->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-version}</version>
</dependency>
</dependencies>
<build>
</build>
</project>
使用 setter 方法演示以上数据类型的注入
bean 的类
package com.fengx.spring_02.datatype;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author: Fengx
* @date: 2021-10-19
* @description: bean 类
**/
public class User {
/**
* 基本数据类型
*/
private double id;
/**
* 字符串类
*/
private String name;
/**
* 实体类
*/
private User wife;
/**
* 实体数组
*/
private User[] friends;
/**
* List列表
*/
private List<User> schoolmates;
/**
* Set集合
*/
private Set<String> favorites;
/**
* Map集合
*/
private Map<String, String> emails;
public void setId(double id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setWife(User wife) {
this.wife = wife;
}
public void setFriends(User[] friends) {
this.friends = friends;
}
public void setSchoolmates(List<User> schoolmates) {
this.schoolmates = schoolmates;
}
public void setFavorites(Set<String> favorites) {
this.favorites = favorites;
}
public void setEmails(Map<String, String> emails) {
this.emails = emails;
}
/**
* 重写 Object 类 toString()
* @return
*/
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", wife=" + wife + ", friends=" + Arrays.toString(friends)
+ ", schoolmates=" + schoolmates + ", favorites=" + favorites + ", emails=" + emails + "]";
}
}
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">
<bean id="wife" class="com.fengx.spring_02.datatype.User">
<!--基本数据类型-->
<property name="id" value="2"/>
<!--字符串-->
<property name="name" value="wife"/>
</bean>
<bean id="fengx_1" class="com.fengx.spring_02.datatype.User">
<!--基本数据类型-->
<property name="id" value="1"/>
<!--字符串-->
<property name="name" value="Fengx"/>
<!--对象,ref id 引用 bean 类-->
<property name="wife" ref="wife"/>
<!--数组-->
<property name="friends">
<list>
<ref bean="wife"/>
</list>
</property>
<!--list,ref id 引用 bean 类-->
<property name="schoolmates">
<list>
<ref bean="wife"/>
</list>
</property>
<!--set-->
<property name="favorites">
<set>
<value>写代码</value>
<value>睡觉</value>
</set>
</property>
<!--map-->
<property name="emails">
<map>
<entry key="公司" value="123@123.com"/>
<entry key="个人" value="123@abc.com"/>
</map>
</property>
</bean>
<!-- <bean id="specialUser" class="com.fengx.spring_02.datatype.SpecialUser" autowire="byType">-->
<!-- </bean>-->
</beans>
测试代码
package com.fengx.spring_02.datatype;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.env.Environment;
/**
* @author: Fengx
* @date: 2021-10-19
* @description: 依赖注入,可注入的数据类型
* 基本类型 double、String、bean、数组、List、Set、Map 的注入
**/
public class Test {
@SuppressWarnings("resource")
public static void main(String[] args) {
BeanFactory factory = new ClassPathXmlApplicationContext("spring-config-01.xml");
User fengx = (User)factory.getBean("fengx_1");
System.out.println(fengx);
// SpecialUser specialUser = (SpecialUser)factory.getBean("specialUser");
// // Spring 自建 bean,并未在 spring-config-01.xml 配置文件中申明,却可以注入与依赖查找
// // 通过 autowire="byType" 可以注入成功
// System.out.println(specialUser.getEnvironment());
// System.out.println(factory.getBean(Environment.class));
//
// // 非 bean,可以注入,但无法通过 getBean 方法依赖查找
// // 通过 autowire="byType" 可以注入成功
// System.out.println(specialUser.getBeanFactory());
// // getBean 方法无法获取到 BeanFactory
// System.out.println(factory.getBean(BeanFactory.class));
}
}
结果打印
User [id=1.0, name=Fengx, wife=User [id=2.0, name=wife, wife=null, friends=null, schoolmates=null, favorites=null, emails=null], friends=[User [id=2.0, name=wife, wife=null, friends=null, schoolmates=null, favorites=null, emails=null]], schoolmates=[User [id=2.0, name=wife, wife=null, friends=null, schoolmates=null, favorites=null, emails=null]], favorites=[写代码, 睡觉], emails={公司=123@123.com, 个人=123@abc.com}]
上面这个例子演示了基本类型 double、String、bean、数组、List、Set、Map 的注入。
那什么是 Spring 自建的 bean 和 非 bean 呢?这两个概念,看下面这个例子。
新增类 SpecialUser,包含了 BeanFactory 和 Environment 两个类型的属性
package com.fengx.spring_02.datatype;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.env.Environment;
/**
* @author: Fengx
* @date: 2021-10-19
* @description: 包含了 BeanFactory 和 Environment 两个类型的属性
**/
public class SpecialUser {
private BeanFactory beanFactory;
private Environment environment;
public BeanFactory getBeanFactory() {
return beanFactory;
}
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public Environment getEnvironment() {
return environment;
}
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}
xml 新增一个 bean 配置
<bean id="specialUser" class="com.fengx.spring_02.datatype.SpecialUser" autowire="byType">
</bean>
测试类的 main 方法中新增测试代码
package com.fengx.spring_02.datatype;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.env.Environment;
/**
* @author: Fengx
* @date: 2021-10-19
* @description: 依赖注入,可注入的数据类型
* 基本类型 double、String、bean、数组、List、Set、Map 的注入
**/
public class Test {
@SuppressWarnings("resource")
public static void main(String[] args) {
BeanFactory factory = new ClassPathXmlApplicationContext("spring-config-01.xml");
// User fengx = (User)factory.getBean("fengx_1");
// System.out.println(fengx);
SpecialUser specialUser = (SpecialUser)factory.getBean("specialUser");
// Spring 自建 bean,并未在 spring-config-01.xml 配置文件中申明,却可以注入与依赖查找
// 通过 autowire="byType" 可以注入成功
System.out.println(specialUser.getEnvironment());
System.out.println(factory.getBean(Environment.class));
//
// // 非 bean,可以注入,但无法通过 getBean 方法依赖查找
// // 通过 autowire="byType" 可以注入成功
// System.out.println(specialUser.getBeanFactory());
// // getBean 方法无法获取到 BeanFactory
// System.out.println(factory.getBean(BeanFactory.class));
}
}
打印结果
StandardEnvironment {activeProfiles=[], defaultProfiles=[default], propertySources=[PropertiesPropertySource {name='systemProperties'}, SystemEnvironmentPropertySource {name='systemEnvironment'}]}
StandardEnvironment {activeProfiles=[], defaultProfiles=[default], propertySources=[PropertiesPropertySource {name='systemProperties'}, SystemEnvironmentPropertySource {name='systemEnvironment'}]}
非 bean
package com.fengx.spring_02.datatype;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.env.Environment;
/**
* @author: Fengx
* @date: 2021-10-19
* @description: 依赖注入,可注入的数据类型
* 基本类型 double、String、bean、数组、List、Set、Map 的注入
**/
public class Test {
@SuppressWarnings("resource")
public static void main(String[] args) {
BeanFactory factory = new ClassPathXmlApplicationContext("spring-config-01.xml");
// User fengx = (User)factory.getBean("fengx_1");
// System.out.println(fengx);
SpecialUser specialUser = (SpecialUser)factory.getBean("specialUser");
// Spring 自建 bean,并未在 spring-config-01.xml 配置文件中申明,却可以注入与依赖查找
// 通过 autowire="byType" 可以注入成功
// System.out.println(specialUser.getEnvironment());
// System.out.println(factory.getBean(Environment.class));
// 非 bean,可以注入,但无法通过 getBean 方法依赖查找
// 通过 autowire="byType" 可以注入成功x
System.out.println(specialUser.getBeanFactory());
// getBean 方法无法获取到 BeanFactory
System.out.println(factory.getBean(BeanFactory.class));
}
}
org.springframework.beans.factory.support.DefaultListableBeanFactory@15975490: defining beans [wife,fengx_1,specialUser]; root of factory hierarchy
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.beans.factory.BeanFactory' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1126)
at com.fengx.spring_02.datatype.Test.main(Test.java:31)
从打印结果可以看出
- Environment bean 并未在 xml 中配置,但却可以注入与通过 getBean 方法依赖查找,即上面提到的 Spring 自建的 bean
- BeanFactory 可以被注入,但通过 getBean 方法获取报找不到 bean 的错误,即上面提到的非 bean
依赖注入的实现方式
首先,bean 的配置文件可以通过 xml 和 properties 两种方式。其中 xml 是主流,properties 基本不用,具体实现方式:
- setter 方法
- 构造器
- 接口回调
- 注解
- API
xml 配置文件
<?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
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="fengx_2" class="com.fengx.spring_02.impltype.User">
<constructor-arg value="1"/>
<property name="name" value="Fengx"/>
</bean>
<bean id="favorites" class="java.lang.String">
<constructor-arg value="写代码、睡觉"/>
</bean>
<!-- 开启注解能力 -->
<context:component-scan base-package="com.fengx.spring_02"/>
</beans>
bean 的类代码
package com.fengx.spring_02.impltype;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* @author: Fengx
* @date: 2021-10-19
* @description: bean 类
**/
public class User implements ApplicationContextAware {
/**
* 构造方法注入
*/
private final Integer id;
public User(Integer id) {
this.id = id;
}
/**
* set 方法注入
*/
private String name;
public void setName(String name) {
this.name = name;
}
/**
* 实现接口 ApplicationContextAware 及其回调方法注入 applicationContextAware
*/
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 使用注解,xml需要开启注解能力
* 使用 @Autowired 给属性字段注入
*/
@Autowired
private String favorites;
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", applicationContext=" + applicationContext +
", friend=" + favorites +
'}';
}
}
测试代码,包含 bean 通过 api 注册与注入
package com.fengx.spring_02.impltype;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author: Fengx
* @date: 2021-10-19
* @description: 依赖注入的实现方式
**/
public class Test {
@SuppressWarnings("resource")
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config-02.xml");
User fengx = (User)context.getBean("fengx_2");
System.out.println(fengx);
// api 构造、注入、组装、注册 bean
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)context.getBeanFactory();
AbstractBeanDefinition userBeanDefinition = new RootBeanDefinition(User.class);
// 构造方法注入
beanFactory.registerBeanDefinition("ApiUser", userBeanDefinition);
ConstructorArgumentValues argValues = new ConstructorArgumentValues();
argValues.addIndexedArgumentValue(0, 1);
// set 方法注入
userBeanDefinition.setConstructorArgumentValues(argValues);
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.addPropertyValue(new PropertyValue("name", "ApiUser"));
userBeanDefinition.setPropertyValues(propertyValues);
System.out.println(beanFactory.getBean("ApiUser"));
}
}
结果打印
{id=1, name='Fengx', applicationContext=org.springframework.context.support.ClassPathXmlApplicationContext@68de145, started on Tue Oct 19 17:41:03 CST 2021, friend=写代码、睡觉}
User{id=1, name='ApiUser', applicationContext=org.springframework.context.support.ClassPathXmlApplicationContext@68de145, started on Tue Oct 19 17:41:03 CST 2021, friend=写代码、睡觉}
- 注解的使用需要在 xml 里添加 context 的命名空间,context:component-scan 开启注解的能力,设置 bean 扫描路径。
- setter 方法注入的缺点是可能放大了 bean 的修改权限、如果字段之间有依赖关系与校验逻辑 set 顺序不可控。
- 构造方法注入的缺点是属性较多时构造方法参数配置较多,顺序易出错、对 null 处理灵活性较差、不利于子类继承与扩展。
- 与注入的相关的注解除了示例中的 @Autowired,还包括 @Autowired + @Qualifier 指定 bean 名称注入,@Resource(name="") 指定 bean 名称注入,JSR250 规范;@Value 注入字符串、系统信息、配置文件信息、表达式等;@Inject JSR330规范。一般 @Autowired 使用最多,注解的使用都支持在属性字段上,有些也可以用在 setter 方法和构造方法上。
- 标签还可以指定 autowire,自动绑定(Autowiring)的模式,这个配置也会影响依赖注入的结果。no:默认该值,不进行自动注入,需要手动配置要注入的 bean;byName:根据 bean 名称注入;byType:根据类型注入;constructor:根据构造方法加其参数注入。
- 网上还有资料提到了静态工厂方法注入和实例工厂方法注入,个人觉得这两个只是 bean 的创建的方式,严格意义上还未到注入的范畴。