Java-Spring入门指南(五)Spring自动装配
前言
在上一篇博客中,我们掌握了Spring依赖注入的两种核心手动方式——构造器注入与setter注入,但也留下了一个明显的“痛点”:当Bean的依赖关系复杂(比如一个Service依赖多个Dao)时,我们需要在XML中反复编写<property ref="..."/>
,配置繁琐且容易出错。
有没有办法让容器“主动找”依赖,而不是我们“手动指”依赖?
- 答案就是这篇要讲的Spring自动装配(Autowire)。它是Spring在手动注入基础上的优化,核心是“容器根据预设规则自动匹配并注入依赖”,能大幅减少XML配置冗余。
本文将从“自动装配的本质”切入,手把手实战两种核心自动装配方式(byName
、byType
),再详解alias
(Bean别名)和import
(XML整合)这两个辅助配置。
我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的Java-Spring入门指南知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_13040333.html?spm=1001.2014.3001.5482
一、什么是Spring自动装配?
在讲实战前,我们先搞懂“自动装配”到底解决了什么问题——它不是替代手动注入,而是对setter注入的“简化”。
1.1 手动注入的痛点
回顾上一篇的User
依赖Address
,我们需要在XML中手动配置<property ref="address"/>
:
<!-- 手动注入:必须明确写ref指向依赖的Bean -->
<bean id="user" class="org.example.pojo.User">
<property name="address" ref="address"></property> <!-- 手动关联 -->
</bean>
<bean id="address" class="org.example.pojo.Address"></bean>
如果一个OrderService
依赖OrderDao
、UserDao
、LogDao
三个Bean,就需要写3个<property>
标签——依赖越多,配置越繁琐。
1.2 自动装配的定义
自动装配(Autowire)的核心是:容器根据预设的“匹配规则”,自动在IoC容器中查找当前Bean的依赖(如Person依赖的Dog、Cat),并完成注入,无需手动编写<property ref="..."/>
。
1.3 自动装配的本质
- 容器先通过无参构造器创建当前Bean(如Person);
- 按规则(byName/byType)在容器中找依赖的Bean;
- 找到后,自动调用当前Bean的setter方法完成注入。
关键前提:自动装配仅支持setter注入,不支持构造器注入!所以Bean必须有依赖属性的setter方法。
二、自动装配的核心方式
Spring提供多种自动装配方式,最常用、最核心的是byName(按名称匹配) 和byType(按类型匹配)
2.1 准备工作
首先编写我们的核心Bean类:
// Dog类(被依赖方)
public class Dog {
public void eat() {
System.out.println("狗喜欢吃骨头");
}
}
// Cat类(被依赖方)
public class Cat {
public void eat() {
System.out.println("猫喜欢吃小鱼干");
}
}
// Person类(依赖方:依赖Dog和Cat)
public class Person {
private Dog dog;
private Cat cat;
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
}
2.2 方式一:byName
2.2.1 核心规则
容器会根据当前Bean的“属性名”,去匹配IoC容器中其他Bean的“id
属性”——属性名与Bean的id完全一致时,自动注入。
用我们的Person
举例:
// Person类(依赖方:依赖Dog和Cat)
public class Person {
private Dog dog;
private Cat cat;
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
}
- Person有两个属性:
dog
(属性名)、cat
(属性名); - 容器中需有
id="dog"
的Dog类Bean、id="cat"
的Cat类Bean; - 配置
autowire="byName"
后,容器自动匹配并注入。
2.2.2 配置实战
<?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">
<!-- 1. 配置被依赖方Bean:id必须与Person的属性名一致 -->
<bean id="dog" class="org.example.pojo.autowire.Dog"></bean> <!-- id=dog 匹配 Person的dog属性 -->
<bean id="cat" class="org.example.pojo.autowire.Cat"></bean> <!-- id=cat 匹配 Person的cat属性 -->
<!-- 2. 配置依赖方Bean:开启byName自动装配 -->
<bean id="person" class="org.example.pojo.autowire.Person" autowire="byName">
<!-- 无需写<property ref="dog"/>和<property ref="cat"/>,容器自动匹配 -->
</bean>
</beans>
2.2.3 测试代码与结果
编写测试类,验证自动装配是否成功:
@Test
public void test2() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("autowire.xml");
Person person = applicationContext.getBean("person", Person.class);
person.getCat().eat();
person.getDog().eat();
}
预期结果:
2.2.4 关键注意点
- 属性名与Bean的id必须完全一致:若Person的
dog
属性改名为doggy
,而Bean的id还是dog
,容器找不到匹配的Bean,会注入null
; - 允许同类型多Bean:如你代码中注释的
cat1
,即使开启,只要Person没有cat1
属性,就不影响cat
属性的匹配(byName只看名称,不看类型); - 必须有setter方法:容器通过setter方法注入,若删除
setDog()
,会注入null
。
2.3 方式二:byType
2.3.1 核心规则
容器会根据当前Bean的“属性类型”,去匹配IoC容器中其他Bean的“class
类型”——属性类型与Bean的class完全一致,且容器中该类型Bean唯一时,自动注入。
。
2.3.2 配置实战
只需将person
Bean的autowire
改为byType
,其他不变:
<!-- 被依赖方Bean:id可任意(byType不依赖id) -->
<bean id="dog" class="org.example.pojo.autowire.Dog"></bean> <!-- 类型是Dog -->
<bean id="cat" class="org.example.pojo.autowire.Cat"></bean> <!-- 类型是Cat -->
<!-- 依赖方Bean:开启byType自动装配 -->
<bean id="person" class="org.example.pojo.autowire.Person" autowire="byType">
<!-- 同样无需手动配置property -->
</bean>
2.3.3 测试代码与结果
复用上面的test
方法(只需保证XML配置是byType
),预期结果与byName完全一致:
- 关键区别:此时即使把
dog
Bean的id改为myDog
,cat
Bean的id改为myCat
,依然能注入成功(byType不看id)。
2.3.4 关键注意点
容器中该类型Bean必须唯一:这是byType的核心限制!若此时容器有两个
Cat
类型Bean,启动容器会直接报错:
NoUniqueBeanDefinitionException: No qualifying bean of type 'org.example.pojo.autowire.Cat' available: expected single matching bean but found 2: cat,cat1
不依赖Bean的id:即使Bean的id与属性名完全不同(如id=myDog,属性名=dog),只要类型匹配且唯一,就能注入;
支持父子类/接口:若有
Dog
的子类BigDog
,Person的dog
属性是Dog
类型,容器中只有BigDog
类型Bean,也能匹配(多态支持)。
2.4 byName vs byType 对比
两种自动装配方式各有适用场景,用表格对比更清晰:
对比维度 | byName(按名称) | byType(按类型) |
---|---|---|
匹配规则 | 属性名 ≡ Bean的id | 属性类型 ≡ Bean的class(且唯一) |
依赖条件 | 必须保证属性名与Bean的id一致 | 必须保证容器中该类型Bean唯一 |
灵活性 | 依赖命名规范,改id需同步改属性名 | 不依赖id,改id不影响注入 |
容错性 | 同类型多Bean不报错(只看名称) | 同类型多Bean直接报错 |
适用场景 | 命名规范明确(如属性名=Bean id) | 类型唯一且稳定(如工具类Bean) |
三、alias与import
3.1 alias:给Bean起别名
3.1.1 核心作用
给IoC容器中的Bean分配多个“别名”,后续通过“原id”或“任意别名”都能获取该Bean,解耦Bean的引用与id的绑定。
比如:
<alias name="person" alias="person_name_1"/>
- 原id:
person
; - 别名:
person_name_1
; - 获取Bean时,
ac.getBean("person")
和ac.getBean("person_name_1")
得到的是同一个对象。
3.1.2 适用场景
- 多模块引用同一Bean:A模块习惯用
personA
称呼,B模块习惯用personB
,给Bean起两个别名,无需修改原id; - 避免id冲突:若导入的第三方XML中已有
user
Bean,你本地的user
Bean可起别名myUser
,避免冲突。
3.2 import:拆分与整合XML配置
3.2.1 核心作用
当项目中Bean数量庞大时,将所有Bean写在一个XML中会非常臃肿。import
可以将多个分散的XML配置文件导入到一个“主配置文件”,只需加载主配置,就能加载所有XML中的Bean。
比如:
<import resource="beans.xml"/>
- 主配置文件:
autowire.xml
(当前配置文件); - 导入的子配置:
beans.xml
(可存放其他Bean,如上一篇的Student、User); - 加载主配置时,
autowire.xml
和beans.xml
中的Bean都会被加载到容器。
3.2.2 实战场景
假设项目分3个模块,每个模块的Bean存放在独立XML中:
dao.xml
:存放Dao层Bean(如UserDao、OrderDao);service.xml
:存放Service层Bean(如UserService、OrderService);autowire.xml
:存放Person、Dog、Cat等Bean(主配置);
通过import
整合主配置:
<!-- 主配置文件:autowire.xml -->
<import resource="dao.xml"/>
<import resource="service.xml"/>
<!-- 本地Bean配置 -->
<bean id="dog" class="org.example.pojo.autowire.Dog"></bean>
<bean id="cat" class="org.example.pojo.autowire.Cat"></bean>
<bean id="person" class="org.example.pojo.autowire.Person" autowire="byName"></bean>
- 加载时只需加载
autowire.xml
,就能获取所有模块的Bean,大幅提升可维护性。
3.2.3 注意点
- 路径问题:
resource
属性写XML的“相对路径”,若子XML在config
目录下,需写resource="config/dao.xml"
; - 顺序无关:
import
的顺序不影响Bean的加载(Spring会处理依赖关系); - 重复导入不报错:同一XML被多次导入,Spring只会加载一次。
四、自动装配的优缺点与使用建议
4.1 优点
- 简化配置:无需手动写大量
<property ref="..."/>
,减少XML冗余; - 降低维护成本:依赖关系变更时,只需修改Bean的id或类型,无需修改注入配置;
- 支持模块化:结合
import
和alias
,适合大型项目拆分配置。
4.2 缺点
- 可读性下降:无法直观看到Bean的依赖来源(手动注入能明确看到ref指向);
- 排错难度高:注入
null
时,需排查命名、类型、Bean唯一性等多个维度; - 功能局限:仅支持setter注入,不支持构造器注入;不支持复杂依赖(如集合类型)。
4.3 推荐使用原则
- 简单场景用自动装配:如工具类、单一依赖的Bean(如Person依赖Dog/Cat),优先用byName(命名规范明确时)或byType(类型唯一时);
- 复杂场景用手动注入:如多依赖、构造器注入、集合类型注入,手动配置更明确,排错更简单;
- 大型项目结合注解:XML自动装配是基础,后续我们会讲注解驱动的自动装配(
@Autowired
、@Qualifier
),比XML更灵活、更简洁。
我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的Java-Spring入门指南知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_13040333.html?spm=1001.2014.3001.5482
非常感谢您的阅读,喜欢的话记得三连哦 |