7、bean 的自动装配
- 自动装配是使用 Spring 满足 bean 依赖的一种方式
- Spring 会在应用上下文中为某个 bean 寻找其依赖的 bean,即在上下文自动寻找并自动给 bean 装配属性
由于在手动配置 xml 过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。采用自动装配将避免这些错误,并且使配置简单化。
在 Spring 中有三种装配方式:
- 在 xml 中显式的配置
- 在 Java 中显式的配置
- 隐式的自动装配 bean【重要】
Spring 的自动装配需要从两个角度来实现,或者说是两个操作:
- 组件扫描(component scanning):Spring 会自动发现应用上下文中所创建的 bean;
- 自动装配(autowiring):Spring 自动满足 bean 之间的依赖,也就是我们说的 IoC/DI;
组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。
推荐不使用自动装配xml配置,而使用注解。
7.1 测试
环境搭建:一个人有两个宠物
public class Dog {
public void shout(){
System.out.println("汪~");
}
}
public class Cat {
public void shout(){
System.out.println("喵~");
}
}
public class People {
private Cat cat;
private Dog dog;
private String name;
set/get/toString...
}
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="cat" class="com.song.pojo.Cat"/>
<bean id="dog" class="com.song.pojo.Dog"/>
<bean id="people" class="com.song.pojo.People">
<property name="name" value="张三"/>
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
</bean>
</beans>
public class MyTest {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
People people = context.getBean("people", People.class);
people.getCat().shout(); // 喵~
people.getDog().shout(); // 汪~
}
}
结果正常输出
7.2 byName 自动装配
autowire byName (按名称自动装配)
修改bean配置,增加一个属性 autowire="byName",使用 ref 的 property 可以省略:
<bean id="cat" class="com.song.pojo.Cat"/>
<bean id="dog" class="com.song.pojo.Dog"/>
<!--
byName: 会自动在容器上下文中查找,和自己对象 set 方法后面的值对应的 beanid
-->
<bean id="people" class="com.song.pojo.People" autowire="byName">
<property name="name" value="张三"/>
<!--<property name="cat" ref="cat"/>-->
<!--<property name="dog" ref="dog"/>-->
</bean>
结论:
当一个bean节点带有 autowire byName 的属性时:
- 将查找其类中所有的 set 方法名,例如 setCat,获得将 set 去掉并且首字母小写的字符串,即 cat。
- 去 spring 容器中寻找是否有此字符串名称 id 的对象。
- 如果有,就取出注入;如果没有,就报空指针异常。
即,byName 必须保证 bean 的 id 唯一,并且这个 bean 要和自动注入的属性的 setXxx 方法的名字 xxx(将 set 去掉并且首字母小写) 一致。
7.3 byType 自动装配
autowire byType (按类型自动装配)
修改bean配置,增加一个属性 autowire="byType":
<bean id="cat" class="com.song.pojo.Cat"/>
<bean class="com.song.pojo.Dog"/>
<!--
byType:会自动在容器上下文中查找,和自己对象 属性类型相同的 bean
-->
<bean id="people" class="com.song.pojo.People" autowire="byType">
<property name="name" value="张三"/>
<!--<property name="cat" ref="cat"/>-->
<!--<property name="dog" ref="dog"/>-->
</bean>
结论:
- byType 必须保证所有 bean 的 class 唯一,并且这个 bean 需要和自动注入的属性的类型一致。
- byType 必须保证类型全局唯一。即:同一类型的对象,在spring容器中唯一,如果不唯一,会报不唯一的异常
NoUniqueBeanDefinitionException
。
注意:对于 byName 自动装配,自动注入的属性的 bean 的 id 是可以省略的。
7.4 使用注解实现自动装配
The introduction of annotation-based configuration raised the question of whether this approach is “better” than XML.
jdk1.5 开始支持注解,spring2.5 开始全面支持注解。
使用注解的准备工作:
- 导入约束:context 约束
- 配置注解的支持:
<context:annotation-config/>
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
@Autowried
直接在属性上使用即可,也可以在 set 方式上使用。
使用 Autowired 可以不用编写 set 方法,前提是这个自动装配的属性在 IOC(Spring)容器中存在,且符合类型 byType。
测试:
- 在属性上使用 @Autowired 注解:
public class People {
@Autowired
@Qualifier(value = "cat1")
private Cat cat;
@Autowired
private Dog dog;
private String name;
getter方法……
}
- 配置文件内容:
<!--开启注解支持-->
<context:annotation-config/>
<bean id="cat" class="com.song.pojo.Cat"/>
<bean id="dog" class="com.song.pojo.Dog"/>
<bean id="people" class="com.song.pojo.People"/>
- 测试结果:成功输出
总结:
@Autowired 首先按类型(byType)自动装配;如果同一类型有多个 bean 存在,再按照属性名(byName)进行装配,即 bean 的 id 与属性名一致;如果属性名装配不成功,可以使用 @Qualifier(value = “xxx”)
装配与 id 一致的属性名。
扩展:
Autowired 源码:
public @interface Autowired { boolean required() default true; }
关于对象为空的问题
- Autowired 的 required 属性
required 属性默认为 true,对象必须存对象,不能为 null;如果为 false,则这个对象可以为 null。
// 如果显式定义了 Autowired 的 required 属性为false,说明这个对象可以为 null,否则不允许为空
@Autowired(required = false)
- @Nullable
字段标记了这个注解,说明这个字段可以为 null
@Qualifier
如果 @Autowired 自动装配的环境比较复杂,自动装配无法通过一个注解【@Autowired】完成的时候,可以使用@Qualifier(value = "xxx")
去配置 @Autowired 的使用,指定一个唯一的 bean 对象注入(byName)。
注意:@Qualifier(value = "xxx")
不能单独使用
测试:
- 配置文件修改内容,保证类型存在,但名字不为类的默认名字!
<!--开启注解支持-->
<context:annotation-config/>
<bean id="cat1" class="com.song.pojo.Cat"/>
<bean id="cat111" class="com.song.pojo.Cat"/>
<bean id="dog2" class="com.song.pojo.Dog"/>
<bean id="dog222" class="com.song.pojo.Dog"/>
<bean id="people" class="com.song.pojo.People"/>
- 没有加 @Qualifier 测试,直接报错
- 在属性上添加 @Qualifier 注解
public class People {
@Autowired(required = false)
@Qualifier(value = "cat111")
private Cat cat;
@Autowired
@Qualifier(value = "dog222")
private Dog dog;
private String name;
}
- 测试结果:成功输出
@Resource
@Resource 如果有指定的 name 属性,先按该属性进行 byName 方式查找装配;其次再进行默认的 byName 方式进行装配;如果以上都不成功,则按 byType 的方式自动装配;都不成功,则报异常。
public class People {
@Resource(name = "cat2")
private Cat cat;
@Resource
private Dog dog;
private String name;
}
小结:
@Autowired 和 @Resource 异同:
- 作用相同都是用注解方式注入对象,都是用来自动装配 bean 的,都可以放在属性字段,或写在 setter 方法上。
- @Autowired (属于spring规范)默认通过 byType 的方式实现,而且必须要求这个对象存在(除非制定了 required 属性为 false),类型重复再通过 id 与属性名匹配(byName)找,都不合适则结合 @Qualifier 注解使用名字装配。
- @Resource(属于 J2EE 复返) 默认通过 byName 的方式实现,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在 setter 方法上默认取属性名进行装配。如果找不到与名称匹配的bean时,则通过 byType 实现自动装配。如果两个都找不到的情况下就报错。注意,如果 name 属性一旦指定,就只会按照名称进行装配。
- 执行顺序不同:@Autowired 先 byType 再 byName;@Resource 先 byName 再 byType。
8、使用注解开发
- 在 spring4 之后,想要使用注解形式,必须得要引入 aop 的包。
- 使用注解需要导入 context 约束,增加注解的支持。
- 必须指定要扫描的包,使这个包下的注解生效。扫描是为了类上的注解。
Spring 的配置文件 applicationContext.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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="com.song"/>
<context:annotation-config/>
</beans>
8.1 Bean 的实现(@Component)
在指定包下编写类,增加注解
// 等价于配置文件中 <bean id="user" class="com.song.pojo.User"/>
@Component // 组件
public class User {
public String name = "张三";
}
测试:
public void test(){
ApplicationContext Context =
new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) Context.getBean("user");
System.out.println(user.name); // 张三
}
注意:@Component("xxx") 可以指定 id
- 如果是 @Component,即没有指定 id,那么这个 bean 的 id 就是当前类的首字母小写的字符串
- 如果是 @Component("xxx"),即指定了 id,那么 getBean 时只能使用这个 id,即
Context.getBean("xxx")
8.2 属性注入(@Value)
可以在属性名上添加注解,也可以在setter方法上添加。
@Component
public class User {
// 相当于 <property name="name" value="张三"/>
//@Value("张三")
public String name;
@Value("张三")
public void setName(String name) {
this.name = name;
}
}
8.3 衍生的注解
@Component 的三个衍生注解,在 web 开发中,会按照 mvc 三层架构分层。
- dao 层【@Repository】
- service 层【@Service】
- web 层【@Controller】
这四个注解功能都是一样的,都是代表将某个类注册到 Spring 中,装配 bean。
8.4 自动装配
前面已经学过。
8.5 作用域(@Scope)
- singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
- prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
@Component
@Scope("prototype")
public class User {
//@Value("张三")
public String name;
@Value("张三")
public void setName(String name) {
this.name = name;
}
}
8.6 小结
xml 和注解比较:
- xml 更加万能,适用于任何场合,结构清晰,维护简单方便
- 注解,不是自己提供的类使用不了,维护相对复杂,开发简单方便
xml 与注解整合开发:
- xml 用来管理 bean
- 注解只负责完成属性的注入
- 使用时需要注意:必须让注解生效,需要开启注解的支持
<context:annotation-config/>
9、使用 java 的方式配置 Spring
完全不使用 Spring 的 xml 配置,全权交给 java来做。
javaConfig 是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 之后它成为了一个核心功能。这种纯 java 的配置方式,在 SpringBoot 中随处可见。
测试:
- 实体类
// 注解意思:这个类被 Spring 接管了,注册到了容器中
@Component // 其实不加这个注解也可以测试成功
public class User {
private String name;
@Value("zhangsan") //属性注入值
public void setName(String name) {
this.name = name;
}
getter/toString……
}
- 新建一个 config 配置包,编写一个 javaConfig 配置类
//这个也会被 Spring 容器托管,注册到容器中,因为它本来就是一个 @Component
// @Configuration 代表这是一个配置类,和之前的 beans.xml 一样
@Configuration
@ComponentScan("com.song.pojo")
@Import(MyConfig2.class) //导入合并其他配置类,类似于配置文件中的 import 标签
public class MyConfig {
// 通过方法注册一个 bean,相当于之前写的一个 bean 标签
// 方法的名字,相当于 bean 标签中的 id 属性
// 方法的返回值,相当于 bean 标签中的 class 属性
@Bean
public User getUser(){
return new User(); // 返回要注入 bean 的对象
}
}
@Configuration //代表这是一个配置类
public class MyConfig2 {
}
- 测试类
public class MyTest {
public static void main(String[] args) {
// 如果完全使用了配置类方式去做,就只能通过 AnnotationConfig 上下文来获取容器,通过配置类的class对象加载
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
User getUser = (User) context.getBean("getUser"); // 参数为方法名
System.out.println(getUser.getName()); // zhangsan
User user = (User) context.getBean("user"); // 参数为 类名的首字母小写
System.out.println(user.getName()); // zhangsan
System.out.println(getUser == user);//fasle
}
}
思考:
发现以下两种方式的注解操作都可以正确输出
- 只在 MyConfig 类中加 @Bean 注解,
context.getBean("getUser")
参数需要是 @Bean 注解时的方法名; - 不加 @Bean 注解,在 User 类中加 @Component 注解,在 MyConfig 类中加 @ComponentScan("xxx.xxx.xxx") 注解,
context.getBean("user")
参数需要是类名的首字母小写字符串。 - 如上面的测试案例,将 @Bean 和 @Component 注解都加上,通过 getBean 得到的对象是两个不同的对象,也就是说,在 Spring 容器中存在着该类的两个对象。