Spring 通过 DI(依赖注入)实现 IOC(控制反转),常用的注入方式主要有三种:构造方法注入,setter 注入,基于注解的注入
构造方法注入
在 spring 的配置文件中注册 UserService,将 UserDao 通过 constructor-arg 标签注入到 UserService 的与之匹配(参数类型和参数个数匹配)的构造方法中
<!-- 注册 userService -->
<bean id="userService" class="com.spring.service.impl.UserService">
<constructor-arg ref="userDao"></constructor-arg>
</bean>
<!-- 注册 dao -->
<bean id="userDao" class="com.spring.dao.impl.UserDao"></bean>
public class UserService implements IUserService {
private IUserDao userDao;
public UserService(IUserDao userDao) {
this.userDao = userDao;
}
public void loginUser() {
userDao.loginUser();
}
}
如果想向有多个参数的构造方法中注入值,就在配置文件中通过 name 属性指定要注入的值,与构造方法参数列表参数的顺序无关
如果有多个构造方法,每个构造方法只有参数的顺序不同,这种情况下就与构造方法顺序有关
setter 注入
<!-- 注册userService -->
<bean id="userService" class="com.lyu.spring.service.impl.UserService">
<!-- 写法一 -->
<!-- <property name="UserDao" ref="userDaoMyBatis"></property> -->
<!-- 写法二 -->
<property name="userDao" ref="userDaoMyBatis"></property>
</bean>
<!-- 注册mybatis实现的dao -->
<bean id="userDaoMyBatis" class="com.lyu.spring.dao.impl.UserDaoMyBatis"></bean>
注:上面这两种写法都可以,spring 会将 name 值的每个单词首字母转换成大写,然后再在前面拼接上 "set" 构成一个方法名,然后去对应的类中查找该方法,通过反射调用,实现注入
切记:name 属性值与类中的成员变量名以及 set 方法的参数名都无关,只与对应的 set 方法名有关,下面的这种写法是可以运行成功的
public class UserService implements IUserService {
private IUserDao userDao1;
public void setUserDao(IUserDao userDao1) {
this.userDao1 = userDao1;
}
public void loginUser() {
userDao1.loginUser();
}
}
还有一点需要注意:如果通过 set 方法注入属性,那么 spring 会通过默认的空参构造方法来实例化对象,所以如果在类中写了一个带有参数的构造方法,一定要把空参数的构造方法写上,否则 spring 没有办法实例化对象,导致报错
基于注解的注入
在介绍注解注入的方式前,先简单了解 bean 的一个属性 autowire,autowire 主要有三个属性值:constructor,byName,byType
constructor:通过构造方法进行自动注入,spring 会匹配与构造方法参数类型一致的 bean 进行注入,如果有一个多参数的构造方法,一个只有一个参数的构造方法,在容器中查找到多个匹配多参数构造方法的 bean,那么 spring 会优先将 bean 注入到多参数的构造方法中
byName:被注入 bean 的 id 名必须与 set 方法后半截匹配,并且 id 名称的第一个单词首字母必须小写,这一点与手动 set 注入有点不同
byType:查找所有的 set 方法,将符合参数类型的 bean 注入
下面进入正题:注解方式注册 bean,注入依赖
主要有四种注解可以注册 bean,每种注解可以任意使用,只是语义上有所差异:
- @Component:用于注册所有 bean
- @Repository:用于注册 dao 层的 bean
- @Controller:用于注册控制层的 bean
- @Service:用于注册服务层的 bean
描述依赖关系主要有两种:
@Resource:java 的注解,默认以 byName 的方式去匹配与属性名相同的 bean 的 id,如果没有找到就会以 byType 的方式查找,如果 byType 查找到多个的话,使用 @Qualifier 注解指定某个具体名称的 bean
@Resource
@Qualifier("userDaoMyBatis")
private IUserDao userDao;
public UserService(){
}
@Autowired:spring 注解,默认是以 byType 的方式去匹配类型相同的 bean,如果只匹配到一个,那么就直接注入该 bean,无论要注入的 bean 的 name 是什么;如果匹配到多个,就会调用 DefaultListableBeanFactory 的 determineAutowireCandidate 方法来决定具体注入哪个 bean。determineAutowireCandidate 方法的内容如下:
// candidateBeans 为上一步通过类型匹配到的多个bean,该 Map 中至少有两个元素。
protected String determineAutowireCandidate(Map<String, Object> candidateBeans, DependencyDescriptor descriptor) {
// requiredType 为匹配到的接口的类型
Class<?> requiredType = descriptor.getDependencyType();
// 1. 先找 Bean 上有@Primary 注解的,有则直接返回
String primaryCandidate = this.determinePrimaryCandidate(candidateBeans, requiredType);
if (primaryCandidate != null) {
return primaryCandidate;
} else {
// 2.再找 Bean 上有 @Order,@PriorityOrder 注解的,有则返回
String priorityCandidate = this.determineHighestPriorityCandidate(candidateBeans, requiredType);
if (priorityCandidate != null) {
return priorityCandidate;
} else {
Iterator var6 = candidateBeans.entrySet().iterator();
String candidateBeanName;
Object beanInstance;
do {
if (!var6.hasNext()) {
return null;
}
// 3. 再找 bean 的名称匹配的
Entry<String, Object> entry = (Entry)var6.next();
candidateBeanName = (String)entry.getKey();
beanInstance = entry.getValue();
} while(!this.resolvableDependencies.values().contains(beanInstance) && !this.matchesBeanName(candidateBeanName, descriptor.getDependencyName()));
return candidateBeanName;
}
}
}
determineAutowireCandidate 方法的逻辑是:
- 先找 Bean 上有@Primary 注解的,有则直接返回 bean 的 name
- 再找 Bean 上有 @Order,@PriorityOrder 注解的,有则返回 bean 的 name
- 最后再以名称匹配(ByName)的方式去查找相匹配的 bean
可以简单的理解为先以 ByType 的方式去匹配,如果匹配到了多个再以 ByName 的方式去匹配,找到了对应的 bean 就去注入,没找到就抛出异常
还有一点要注意:如果使用了 @Qualifier 注解,那么当自动装配匹配到多个 bean 的时候就不会进入 determineAutowireCandidate 方法,而是直接查找与 @Qualifer 指定的 bean name 相同的 bean 去注入,找到了就直接注入,没有找到则抛出异常
ByName 的注入方式和 @Qualifier 有点类似,都是在自动装配匹配到多个 bean 的时候,指定一个具体的 bean,ByName 的方式需要遍历,@Qualifier 直接一次定位。在匹配到多个 bean 的情况下,使用 @Qualifier 来指明具体装配的 bean 效率会更高