1、IOC容器
1.1、普通容器
① 生活中的普通容器
普通容器只能用来存储,没有更多功能。
② 程序中的普通容器
- 数组
- 集合:List
- 集合:Set
1.2、复杂容器
① 生活中的复杂容器
政府管理我们的一生,生老病死都和政府有关。
② 程序中的复杂容器
Servlet 容器能够管理 Servlet、Filter、Listener 这样的组件的一生,所以它是一个复杂容器。我们即将要学习的 IOC 容器也是一个复杂容器。它们不仅要负责创建组件的对象、存储组件的对象,还要负责调用组件的方法让它们工作,最终在特定情况下销毁组件。
[1]Servlet生命周期
名称 | 时机 | 次数 |
---|---|---|
创建对象 | 默认情况:接收到第一次请求 修改启动顺序后:Web应用启动过程中 | 一次 |
初始化操作 | 创建对象之后 | 一次 |
处理请求 | 接收到请求 | 多次 |
销毁操作 | Web应用卸载之前 | 一次 |
[2] Filter生命周期
生命周期阶段 | 执行时机 | 执行次数 |
---|---|---|
创建对象 | Web应用启动时 | 一次 |
初始化 | 创建对象后 | 一次 |
拦截请求 | 接收到匹配的请求 | 多次 |
销毁 | Web应用卸载前 | 一次 |
2、IOC思想
IOC:Inversion of Control,翻译过来是反转控制。
① 获取资源的传统方式
自己做饭:买菜、洗菜、择菜、改刀、炒菜,全过程参与,费时费力,必须清楚了解资源创建整个过程中的全部细节且熟练掌握。
在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。
② 反转控制方式获取资源
点外卖:下单、等、吃,省时省力,不必关心资源创建过程的所有细节。
反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。
③ DI
DI:Dependency Injection,翻译过来是依赖注入。
DI 是 IOC 的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接。
所以结论是:IOC 就是一种反转控制的思想, 而 DI 是对 IOC 的一种具体实现。
3、IOC容器在Spring中的实现
Spring 的 IOC 容器就是 IOC 思想的一个落地的产品实现。IOC 容器中管理的组件也叫做 bean。在创建 bean 之前,首先需要创建 IOC 容器。Spring 提供了 IOC 容器的两种实现方式:
① BeanFactory
这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。
② ApplicationContext
BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory。
以后在 Spring 环境下看到一个类或接口的名称中包含 ApplicationContext,那基本就可以断定,这个类或接口与 IOC 容器有关。
③ ApplicationContext的主要实现类
类型名 | 简介 |
---|---|
ClassPathXmlApplicationContext | 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象 |
FileSystemXmlApplicationContext | 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象 |
ConfigurableApplicationContext | ApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close() ,让 ApplicationContext 具有启动、关闭和刷新上下文的能力。 |
WebApplicationContext | 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。 |
4、基于XML管理bean
4.1、实验一:入门案例 -- 创建bean
① 实验目标和思路
1)目标
由 Spring 的 IOC 容器创建类的对象。
2)思路
② 创建Maven Module
略
③ 引入依赖
<dependencies>
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
④ 创建组件类
public class HelloWorld {
public void sayHello() {
System.out.println("hello world");
}
}
⑤ 创建Spring的配置文件
⑥ 配置组件 -- 在Spring的配置文件中配置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:配置一个bean对象,将对象交给IOC容器管理
id:bean的唯一表示,不能重复
class:设置bean对象所对应的类型
-->
<bean id="hello" class="com.chenyixin.ssm.HelloWorld"/>
</beans>
- bean标签:通过配置bean标签告诉IOC容器需要创建对象的组件是什么
- id属性:bean的唯一标识
- class属性:组件类的全类名(注意:不能写接口)
⑦ 创建测试类
public class HelloWorldTest {
@Test
public void sayHello() {
// 从配置文件中获取 IOC 容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从 IOC 容器对象中获取bean,也就是组件对象
HelloWorld hello = (HelloWorld) context.getBean("hello");
hello.sayHello();
// hello world
}
}
⑧ 注意
Spring 底层默认通过反射技术调用组件类的无参构造器来创建组件对象,这一点需要注意。如果在需要无参构造器时,没有无参构造器,则会抛出下面的异常:
所以对一个JavaBean来说,无参构造器和属性的getXxx()、setXxx()方法是必须存在的,特别是在框架中。
⑨ 用IOC容器创建对象和自己建区别
在Spring环境下能够享受到的所有福利,都必须通过 IOC 容器附加到组件类上,所以随着我们在 Spring 中学习的功能越来越多,IOC 容器创建的组件类的对象就会比自己 new 的对象强大的越来越多。
4.2、实验二:获取bean
① 创建组件类
public class Student {
private Integer sid;
private String sname;
private Integer age;
private String gender;
public Student() {
}
public Student(Integer sid, String sname, Integer age, String gender) {
this.sid = sid;
this.sname = sname;
this.age = age;
this.gender = gender;
}
public Integer getSid() {
return sid;
}
public void setSid(Integer sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Student{" +
"sid=" + sid +
", sname='" + sname + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
'}';
}
}
② 创建Spring的配置文件
<bean id="studentOne" class="com.chenyixin.ssm.pojo.Student"/>
③ 根据id获取 bean
由于 id 属性指定了 bean 的唯一标识,所以根据 bean 标签的 id 属性可以精确获取到一个组件对象。上个实验中我们使用的就是这种方式。
// 获取 ioc 容器
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
// 获取 bean 对象
// 1、根据id获取bean
Student studentOne1 = (Student) ioc.getBean("studentOne");
System.out.println(studentOne1);
// Student{sid=null, sname='null', age=null, gender='null'}
④ 根据类型获取 bean (最常用)
// 2、根据类型获取 bean
Student studentOne2 = ioc.getBean(Student.class);
System.out.println(studentOne2);
// Student{sid=null, sname='null', age=null, gender='null'}
⑤ 注意
<bean id="studentOne" class="com.chenyixin.ssm.pojo.Student"/>
<bean id="studentTwo" class="com.chenyixin.ssm.pojo.Student"/>
当IOC容器中一个都没有配置
根据类型获取时会抛出异常:
⑥ 思考
如果一个接口有多个实现类,这些实现类都配置了 bean,根据接口类型可以获取 bean 吗?
不行,因为bean不唯一
如果组件类实现了接口,根据接口类型可以获取 bean 吗?
可以,前提是bean唯一
代码示例:
前提步骤:创建接口 Person ,并使Student 实现接口
Person person1 = ioc.getBean(Person.class); System.out.println(person1); // Student{sid=null, sname='null', age=null, gender='null'} Person person2 = ioc.getBean(Student.class); System.out.println(person2); // Student{sid=null, sname='null', age=null, gender='null'}
结论
根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 instanceof 指定的类型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到。即通过bean的类型、bean所继承的类的类型、bean所实现的接口的类型都可以获取bean
⑦ 根据 id 和类型获取 bean
// 3、根据 id 和类型获取 bean
Student studentOne3 = ioc.getBean("studentOne", Student.class);
System.out.println(studentOne3);
// Student{sid=null, sname='null', age=null, gender='null'}
4.3、实验三:依赖注入之setter注入
① 配置bean时为属性赋值
通过property标签配置的属性值会通过setXxx()方法注入
<bean id="studentOne" class="com.chenyixin.ssm.pojo.Student"/>
<bean id="studentTwo" class="com.chenyixin.ssm.pojo.Student">
<!--
property标签:通过成员变量的set方法进行赋值
name属性:设置需要赋值的属性名(和set方法有关,和成员变量无关)
value属性:指定属性的值
-->
<property name="sid" value="1001"/>
<property name="sname" value="张三"/>
<property name="age" value="23"/>
<property name="gender" value="女"/>
</bean>
② 测试:
@Test
public void testDI() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
Student studentTwo = ioc.getBean("studentTwo", Student.class);
System.out.println(studentTwo);
// Student{sid=1001, sname='张三', age=23, gender='女'}
}
4.4、实验四:依赖注入之构造器注入
① 配置bean时为属性赋值
<bean id="studentThree" class="com.chenyixin.ssm.pojo.Student">
<constructor-arg value="1002"/>
<constructor-arg value="李四"/>
<constructor-arg value="22"/>
<constructor-arg value="男"/>
</bean>
constructor-arg标签还有两个属性可以进一步描述构造器参数:
- index属性:指定参数所在位置的索引(从0开始)
- name属性:指定参数名
② 测试:
@Test
public void testDI2() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
Student studentThree = ioc.getBean("studentThree", Student.class);
System.out.println(studentThree);
// Student{sid=1002, sname='李四', age=22, gender='男'}
}
4.5、实验五:特殊值处理
① 字面量赋值
什么是字面量?int a = 10;声明一个变量a,初始化为10,此时a就不代表字母a了,而是作为一个变量的名字。当我们引用a的时候,我们实际上拿到的值是10。而如果a是带引号的:'a',那么它现在不是一个变量,它就是代表a这个字母本身,这就是字面量。所以字面量没有引申含义,就是我们看到的这个数据本身。
<!-- 使用value属性给bean的属性赋值时,Spring会把value属性的值看做字面量 -->
<property name="sid" value="1001"/>
② null值
<bean id="studentFour" class="com.chenyixin.ssm.pojo.Student">
<!-- 使用value属性给bean的属性赋值时,Spring会把value属性的值看做字面量 -->
<property name="sid" value="1001"/>
<property name="sname" value="张三"/>
<property name="age">
<!-- null标签:将一个属性值明确设置为null -->
<null/>
</property>
<property name="gender" value="女"/>
</bean>
注意:<property name="name" value="null"></property>
以上写法,为name所赋的值是字符串null
③ XML实体
<!-- XML实体 -->
<!-- 小于号在XML文档中用来定义标签的开始,不能随便使用 -->
<!-- 解决方案一:使用XML实体来代替 -->
<property name="sname" value="<张三>"/>
④ CDATA节
<!-- 解决方案二:使用CDATA节 -->
<!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 -->
<!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 -->
<!-- 所以CDATA节中写什么符号都随意 -->
<property name="gender">
<value><![CDATA[<<女>>]]></value>
</property>
<: <;
>:>
CDATA节:<![CDATA[...]]>
CDATA节的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据
XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析
所以CDATA节中写什么符号都随意
注意:CDATA 节是 xml 中一个特殊的标签,因此不能写在一个属性中
⑤ 测试:
spring-ioc.xml配置文件:
<bean id="studentFour" class="com.chenyixin.ssm.pojo.Student">
<!-- 使用value属性给bean的属性赋值时,Spring会把value属性的值看做字面量 -->
<property name="sid" value="1001"/>
<!-- XML实体 -->
<!-- 小于号在XML文档中用来定义标签的开始,不能随便使用 -->
<!-- 解决方案一:使用XML实体来代替 -->
<property name="sname" value="<张三>"/>
<property name="age">
<null/>
</property>
<!-- 解决方案二:使用CDATA节 -->
<!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 -->
<!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 -->
<!-- 所以CDATA节中写什么符号都随意 -->
<property name="gender">
<value><![CDATA[<<女>>]]></value>
</property>
</bean>
测试方法:
@Test
public void testDI3() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
Student studentFour = ioc.getBean("studentFour", Student.class);
System.out.println(studentFour);
// Student{sid=1001, sname='<张三>', age=null, gender='<<女>>'}
}
4.6 实验六:为类类型属性赋值
① 创建班级类Clazz
public class Clazz {
private Integer cid;
private String cname;
public Clazz() {
}
public Clazz(Integer cid, String cname) {
this.cid = cid;
this.cname = cname;
}
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
@Override
public String toString() {
return "Clazz{" +
"cid=" + cid +
", cname='" + cname + '\'' +
'}';
}
}
② 修改Student类
private Clazz clazz;
public Clazz getClazz() {
return clazz;
}
public void setClazz(Clazz clazz) {
this.clazz = clazz;
}
@Override
public String toString() {
return "Student{" +
"sid=" + sid +
", sname='" + sname + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", clazz=" + clazz +
'}';
}
③方式一:引用外部已声明的bean
<bean id="clazzOne" class="com.chenyixin.ssm.pojo.Clazz">
<property name="cid" value="1111"/>
<property name="cname" value="最强王者班"/>
</bean>
<bean id="studentFive" class="com.chenyixin.ssm.pojo.Student">
<property name="sid" value="1003"/>
<property name="sname" value="王五"/>
<property name="age" value="25"/>
<property name="gender" value="男"/>
<!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
<property name="clazzOne" ref="clazz"/>
</bean>
测试:
@Test
public void testDI4() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
Student studentFive = ioc.getBean("studentFive", Student.class);
System.out.println(studentFive);
// Student{sid=1003, sname='王五', age=25, gender='男', clazz=Clazz{cid=1111, cname='最强王者班'}}
}
<bean id="studentFive" class="com.chenyixin.ssm.pojo.Student">
<property name="sid" value="1003"/>
<property name="sname" value="王五"/>
<property name="age" value="25"/>
<property name="gender" value="男"/>
<!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
<property name="clazz" value="clazzOne"/>
如果错把 ref 属性写成了 value 属性,会 抛出异常 :意思是不能把String类型转换成我们要的Clazz类型,说明我们使用value属性时,Spring只把这个
属性看做一个普通的字符串,不会认为这是一个bean的id,更不会根据它去找到bean来赋值
④ 方式二:内部bean
<bean id="studentSix" class="com.chenyixin.ssm.pojo.Student">
<property name="sid" value="1004"/>
<property name="sname" value="赵六"/>
<property name="age" value="21"/>
<property name="gender" value="男"/>
<property name="clazz">
<!-- 在一个bean中再声明一个bean就是内部bean -->
<!-- 内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,因此可以省略id属性 -->
<bean id="clazzTwo" class="com.chenyixin.ssm.pojo.Clazz">
<property name="cid" value="2222"/>
<property name="cname" value="远大前程班"/>
</bean>
</property>
</bean>
测试:
@Test
public void testDI5() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
Student studentSix = ioc.getBean("studentSix", Student.class);
System.out.println(studentSix);
// Student{sid=1004, sname='赵六', age=21, gender='男', clazz=Clazz{cid=2222, cname='远大前程班'}}
}
错误演示:
@Test
public void testClazzTwo() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
Clazz clazzTwo = ioc.getBean("clazzTwo", Clazz.class);
System.out.println(clazzTwo);
}
内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,否者抛出异常
⑤ 方式三:级联属性赋值
<bean id="studentSeven" class="com.chenyixin.ssm.pojo.Student">
<property name="sid" value="1003"/>
<property name="sname" value="王五"/>
<property name="age" value="25"/>
<property name="gender" value="男"/>
<!--级联方式: 要保证提前 clazz 属性赋值或者实例化-->
<property name="clazz" ref="clazzOne"/>
<property name="clazz.cid" value="3333"/>
<property name="clazz.cname" value="荣耀王者班"/>
</bean>
测试:
@Test
public void testDI6() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
Student studentSeven = ioc.getBean("studentSeven", Student.class);
System.out.println(studentSeven);
// Student{sid=1003, sname='王五', age=25, gender='男', clazz=Clazz{cid=3333, cname='荣耀王者班'}}
}
错误演示:
<bean id="studentSeven" class="com.chenyixin.ssm.pojo.Student">
<property name="sid" value="1003"/>
<property name="sname" value="王五"/>
<property name="age" value="25"/>
<property name="gender" value="男"/>
<property name="clazz.cid" value="3333"/>
<property name="clazz.cname" value="荣耀王者班"/>
</bean>
若没有提前给 clazz 属性赋值或者实例化 ,会抛出异常:
4.7、实验七:为数组类型属性赋值
① 修改Student类
private String[] hobbies;
public String[] getHobbies() {
return hobbies;
}
public void setHobbies(String[] hobbies) {
this.hobbies = hobbies;
}
@Override
public String toString() {
return "Student{" +
"sid=" + sid +
", sname='" + sname + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", hobbies=" + Arrays.toString(hobbies) +
", clazz=" + clazz +
'}';
}
② 配置bean
<bean id="studentEight" class="com.chenyixin.ssm.pojo.Student">
<property name="sid" value="1003"/>
<property name="sname" value="王五"/>
<property name="age" value="25"/>
<property name="gender" value="男"/>
<property name="hobbies" >
<array>
<!-- 这里也可以使用 ref 标签 注入类类型的属性 -->
<!--<ref bean="要注入bean对象的id值"/>-->
<value>抽烟</value>
<value>喝酒</value>
<value>烫头</value>
</array>
</property>
<property name="clazz" ref="clazzOne"/>
</bean>
③ 测试
@Test
public void testDI7() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
Student studentEight = ioc.getBean("studentEight", Student.class);
System.out.println(studentEight);
// Student{sid=1003, sname='王五', age=25, gender='男',
// hobbies=[抽烟, 喝酒, 烫头], clazz=Clazz{cid=3333, cname='荣耀王者班'}}
}
4.8、实验八:为集合类型属性赋值
① 为List集合类型属性赋值(内部bean)
在Clazz类中 添加或修改 以下代码:
private List<Student> students;
public List<Student> getStudents() {
return students;
}
public void setStudents(List<Student> students) {
this.students = students;
}
@Override
public String toString() {
return "Clazz{" +
"cid=" + cid +
", cname='" + cname + '\'' +
", students=" + students +
'}';
}
配置bean:
<bean id="clazzThree" class="com.chenyixin.ssm.pojo.Clazz">
<property name="cid" value="4444"/>
<property name="cname" value="非同一班"/>
<property name="students">
<list>
<ref bean="studentTwo"/>
<ref bean="studentThree"/>
<ref bean="studentFive"/>
</list>
</property>
</bean>
测试:
@Test
public void testDI8() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
Clazz clazzThree = ioc.getBean("clazzThree", Clazz.class);
System.out.println(clazzThree);
}
结果:
若为Set集合类型属性赋值,只需要将其中的list标签改为set标签即可
② 引用 list 集合类型的bean (外部bean)
配置bean:
<bean id="clazzFour" class="com.chenyixin.ssm.pojo.Clazz">
<property name="cid" value="5555"/>
<property name="cname" value="非同二班"/>
<property name="students" ref="studentList"/>
</bean>
<!--配置一个集合类型的bean,需要使用 util 的约束(输入 util:list 即可自动引用)-->
<util:list id="studentList">
<ref bean="studentTwo"/>
<ref bean="studentThree"/>
</util:list>
测试:
@Test
public void testDI9() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
Clazz clazzFour = ioc.getBean("clazzFour", Clazz.class);
System.out.println(clazzFour);
// Clazz{cid=5555, cname='非同二班', students=
// [Student{sid=1001, sname='张三', age=23, gender='女', hobbies=null, clazz=null},
// Student{sid=1002, sname='李四', age=22, gender='男', hobbies=null, clazz=null}]}
}
③ 为Map集合类型属性赋值(内部bean)
public class Teacher {
private Integer tid;
private String tname;
public Teacher() {
}
public Teacher(Integer tid, String tname) {
this.tid = tid;
this.tname = tname;
}
public Integer getTid() {
return tid;
}
public void setTid(Integer tid) {
this.tid = tid;
}
public String getTname() {
return tname;
}
public void setTname(String tname) {
this.tname = tname;
}
@Override
public String toString() {
return "Teacher{" +
"tid=" + tid +
", tname='" + tname + '\'' +
'}';
}
}
private Map<String, Teacher> teacherMap;
public Map<String, Teacher> getTeacherMap() {
return teacherMap;
}
public void setTeacherMap(Map<String, Teacher> teacherMap) {
this.teacherMap = teacherMap;
}
@Override
public String toString() {
return "Student{" +
"sid=" + sid +
", sname='" + sname + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", hobbies=" + Arrays.toString(hobbies) +
", teacherMap=" + teacherMap +
", clazz=" + clazz +
'}';
}
<bean id="teacherOne" class="com.chenyixin.ssm.pojo.Teacher">
<property name="tid" value="101"/>
<property name="tname" value="王老"/>
</bean>
<bean id="teacherTwo" class="com.chenyixin.ssm.pojo.Teacher">
<property name="tid" value="102"/>
<property name="tname" value="刘老"/>
</bean>
<bean id="studentNine" class="com.chenyixin.ssm.pojo.Student">
<property name="sid" value="1003"/>
<property name="sname" value="王五"/>
<property name="age" value="25"/>
<property name="gender" value="男"/>
<property name="hobbies" >
<array>
<value>篮球</value>
<value>足球</value>
<value>乒乓球</value>
</array>
</property>
<property name="teacherMap">
<map>
<entry key="101" value-ref="teacherOne"/>
<entry key="102" value-ref="teacherTwo"/>
</map>
</property>
<property name="clazz" ref="clazzOne"/>
</bean>
测试:
@Test
public void testDI10() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
Student studentNine = ioc.getBean("studentNine", Student.class);
System.out.println(studentNine);
// Student{sid=1003, sname='王五', age=25, gender='男',
// hobbies=[篮球, 足球, 乒乓球],
// teacherMap={101=Teacher{tid=101, tname='王老'}, 102=Teacher{tid=102, tname='刘老'}},
// clazz=Clazz{cid=3333, cname='荣耀王者班', students=null}}
}
④ 引用map集合类型的bean(外部bean)
配置bean:
<bean id="studentTen" class="com.chenyixin.ssm.pojo.Student">
<property name="sid" value="1005"/>
<property name="sname" value="田七"/>
<property name="age" value="35"/>
<property name="gender" value="男"/>
<property name="hobbies" >
<array>
<value>篮球</value>
<value>足球</value>
<value>乒乓球</value>
</array>
</property>
<property name="teacherMap" ref="studentMap"/>
<property name="clazz" ref="clazzOne"/>
</bean>
<util:map id="studentMap">
<entry key="101" value-ref="teacherOne"/>
<entry key="102" value-ref="teacherTwo"/>
</util:map>
测试:
@Test
public void testDI11() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
Student studentTen = ioc.getBean("studentTen", Student.class);
System.out.println(studentTen);
// Student{sid=1005, sname='田七', age=35, gender='男', hobbies=[篮球, 足球, 乒乓球],
// teacherMap={101=Teacher{tid=101, tname='王老'}, 102=Teacher{tid=102, tname='刘老'}},
// clazz=Clazz{cid=3333, cname='荣耀王者班', students=null}}
}
4.9、实验九:p命名空间
引入p命名空间:在标签中写入p: + Alt + Enter ,IDEA会提示所需导入名称空间 + Enter
<bean id="studentEleven" class="com.chenyixin.ssm.pojo.Student"
p:sid="1006" p:sname="小明" p:age="22" p:gender="未知" p:teacherMap-ref="studentMap"/>
测试:
@Test
public void testDI12() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
Student studentEleven = ioc.getBean("studentEleven", Student.class);
System.out.println(studentEleven);
// Student{sid=1006, sname='小明', age=22, gender='未知', hobbies=null,
// teacherMap={101=Teacher{tid=101, tname='王老'}, 102=Teacher{tid=102, tname='刘老'}},
// clazz=null}
}
4.10、实验十:管理数据源
① 加入依赖
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!-- 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
② 配置bean
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
③ 测试:
@Test
public void test() throws SQLException {
ApplicationContext ioc = new ClassPathXmlApplicationContext("dataSource.xml");
DruidDataSource dataSource = ioc.getBean(DruidDataSource.class);
System.out.println(dataSource.getConnection());
// com.mysql.cj.jdbc.ConnectionImpl@1f1c7bf6
}
4.11 实验十一:引入外部属性
① 创建外部属性文件
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
jdbc.username=root
jdbc.password=root
② 引入属性文件
<!-- 引入外部属性文件 -->
<context:property-placeholder location="jdbc.properties"/>
③ 配置bean
<!-- 引入外部属性文件 -->
<context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
④ 测试
@Test
public void test() throws SQLException {
ApplicationContext ioc = new ClassPathXmlApplicationContext("dataSource.xml");
DruidDataSource dataSource = ioc.getBean(DruidDataSource.class);
System.out.println(dataSource.getConnection());
// com.mysql.cj.jdbc.ConnectionImpl@1f1c7bf6
}
4.12 实验十二:bean的作用域
① 概念
在 Spring 中可以通过配置 bean 标签的 scope 属性来指定 bean 的作用域范围,各取值含义参加下表:
取值 | 含义 | 创建对象的时机 | 默认值 |
---|---|---|---|
singleton | 在 IOC 容器中,这个 bean 的对象始终为单实例(默认) | IOC 容器初始化时 | 是 |
prototype | 这个 bean 在 IOC 容器中有多个实例 | 获取 bean 时 | 否 |
如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用):
取值 | 含义 |
---|---|
request | 在一个请求范围内有效 |
session | 在一个会话范围内有效 |
② 创建类User
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
public User(Integer id, String username, String password, Integer age) {
this.id = id;
this.username = username;
this.password = password;
this.age = age;
}
public User() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
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;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
'}';
}
}
③ 配置bean
<!-- scope属性:取值singleton(默认值),bean在IOC容器中只有一个实例,IOC容器初始化时创建 对象 -->
<!-- scope属性:取值prototype,bean在IOC容器中可以有多个实例,getBean()时创建对象 -->
<!--
scope: 设置bean的作用域
scope="singleton(默认) | prototype"
singleton(单例) :表示获取该bean所对应的对象都是同一个
prototype(多例):表示获取该bean所对应的对象不是同一个
-->
<bean id="user1" class="com.chenyixin.ssm.pojo.User">
<property name="id" value="1"/>
<property name="username" value="张三"/>
<property name="password" value="zhangsan"/>
<property name="age" value="19"/>
</bean>
<bean id="user2" class="com.chenyixin.ssm.pojo.User" scope="prototype">
<property name="id" value="2"/>
<property name="username" value="李四"/>
<property name="password" value="lisi"/>
<property name="age" value="21"/>
</bean>
④ 测试
@Test
public void test() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("User.xml");
User user11 = ioc.getBean("user1", User.class);
User user12 = ioc.getBean("user1", User.class);
System.out.println(user11 == user12); // true
User user21 = ioc.getBean("user2", User.class);
User user22 = ioc.getBean("user2", User.class);
System.out.println(user21 == user22); //false
}
4.13 实验十三:bean的生命周期
① 生命周期介绍
1、实例化 (无参数构造)2、依赖注入 (调用 set 方法)
3、初始化,需要通过 bean 的init-method 属性指定初始化的方法
4、调用 bean 对象
5、IOC容器关闭时销毁,需要通过 bean 的 deestroy-method 属性指定销毁的方法
② 修改 User 类型
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
public User(Integer id, String username, String password, Integer age) {
this.id = id;
this.username = username;
this.password = password;
this.age = age;
}
public User() {
System.out.println("生命周期1 实例化");
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
System.out.println("生命周期2 依赖注入");
this.id = id;
}
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;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
'}';
}
public void initMethod() {
System.out.println("生命周期3 初始化");
}
public void destroyMethod() {
System.out.println("生命周期5 销毁");
}
}
③ 配置 xml:
<!--
通过 bean 的init-method 属性指定初始化的方法
通过 bean 的 deestroy-method 属性指定销毁的方法
-->
<bean id="user" class="com.chenyixin.ssm.pojo.User"
init-method="initMethod" destroy-method="destroyMethod">
<property name="id" value="3"/>
<property name="username" value="admin"/>
<property name="password" value="123456"/>
<property name="age" value="23"/>
</bean>
④ 测试
public class LiftTest {
@Test
public void testLift() {
// CofigurableApplicationContext 是 ApplicationContext 的子接口,其中扩展了刷新和关闭容器的方法
ConfigurableApplicationContext ioc =
new ClassPathXmlApplicationContext("spring-lift.xml");
User user = ioc.getBean(User.class);
System.out.println("生命周期4:" + user.toString());
ioc.close();
}
}
结果:
⑤ 注意
① 若bean 的作用域 为单例是,生命周期的前三个步骤会在获取IOC容器时执行(饿汉式)
② 若 bean 的作用域为多例时,生命周期的前三个步骤会在获取 bean 时执行(懒汉式),并IOC容器的关闭不由 bean 对象操作
⑦ 添加 bean 的后置处理器,bean 生命周期+2步
1、实例化 (无参数构造)2、依赖注入 (调用 set 方法)
3、bean对象初始化之前操作(由bean的后置处理器负责)
4、初始化,需要通过 bean 的init-method 属性指定初始化的方法
5、bean对象初始化之后操作(由bean的后置处理器负责)
6、调用 bean 对象
7、IOC容器关闭时销毁,需要通过 bean 的 deestroy-method 属性指定销毁的方法
⑧ bean 的后置处理器
⑨ 如何添加bean 的后置处理器
1. 创建类,实现接口 BeanPostProcessor,创建后置处理器
2. 实现 BeanPostProcessor 中的 postProcessBeforeInitialization 方法 与 postProcessAfterInitialization 方法
3. 在配置文件中 创建该类的 bean标签,因为该类实现接口 BeanPostProcessor,使该类成为了后置处理器,该类的 bean标签会对其所在的xml配置文件中的所有bean标签的类添加后置处理器
⑩ 创建 bean 的后置处理器
1. 创建 后置处理器 类
public class MyBeanProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("生命周期: MyBeanProcessor 中的 postProcessBeforeInitialization");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("生命周期: MyBeanProcessor 中的 postProcessAfterInitialization方法");
return bean;
}
}
2. 在xml配置文件中添加后置处理器
<!--
通过 bean 的init-method 属性指定初始化的方法
通过 bean 的 deestroy-method 属性指定销毁的方法
-->
<bean id="user" class="com.chenyixin.ssm.pojo.User"
init-method="initMethod" destroy-method="destroyMethod">
<property name="id" value="3"/>
<property name="username" value="admin"/>
<property name="password" value="123456"/>
<property name="age" value="23"/>
</bean>
<!--bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行-->
<bean id="myBeanProcessor" class="com.chenyixin.ssm.MyBeanProcessor"/>
3、测试
public void testLift() {
// CofigurableApplicationContext 是 ApplicationContext 的子接口,其中扩展了刷新和关闭容器的方法
ConfigurableApplicationContext ioc =
new ClassPathXmlApplicationContext("spring-lift.xml");
User user = ioc.getBean(User.class);
System.out.println("生命周期4:" + user.toString());
ioc.close();
}
4、结果
4.14 实验十四:FactoryBean
① 简介
FactoryBean是Spring 提供的一种整合第三方框架的常用机制。和普通的 bean 不同,配置一个 FactoryBean类型的 bean ,在获取 bean 的时候得到的并不是 class 属性中配置的这个类的对象,而是 getObject()方法的返回值。通过这种机制, Spring 可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。
FactoryBean 是一个接口,需要创建一个类实现接口
其中有三个方法:
getObject():通过一个对象交给IOC容器
getOBjectType():设置所提供对象的类型
isSingleton():所提供的对象是否为单例
当 FactoryBean 的实现类配置为bean时,会将当前类中getObject()所返回的对象交给IOC容器管理
② 创建类UserFactoryBean 实现FactoryBean接口
public class UserFactoryBean implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
return new User(4,"王五","wangwu",20);
}
@Override
public Class<?> getObjectType() {
return User.class;
}
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
}
③ 配置bean
<bean id="userFactoryBean" class="com.chenyixin.ssm.factory.UserFactoryBean"/>
④ 测试
public class UserFactoryBeanTest {
@Test
public void test() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("FactoryBean.xml");
User user = ioc.getBean(User.class);
System.out.println(user);
// User{id=4, username='王五', password='wangwu', age=20}
}
}
4.15 实验十五:自动装配
① 场景模拟
public class UserController {
private UserService userService;
public UserService getUserService() {
return userService;
}
public void setUserService(UserService userService) {
this.userService = userService;
}
public void saveUser() {
userService.saveUser();
}
}
创建接口UserService
public interface UserService {
void saveUser();
}
创建类UserServiceImpl实现接口UserService
public class UserServiceImpl implements UserService {
private UserDao userDao;
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void saveUser() {
userDao.saveUser();
}
}
创建接口UserDao
public interface UserDao {
void saveUser();
}
创建类UserDaoImpl实现接口UserDao
public class UserDaoImpl implements UserDao {
@Override
public void saveUser() {
System.out.println("保存成功");
}
}
② 手动配置bean
<bean id="controller" class="com.chenyixin.ssm.controller.UserController">
<property name="userService" ref="service"/>
</bean>
<bean id="service" class="com.chenyixin.ssm.service.impl.UserServiceImpl">
<property name="userDao" ref="dao"/>
</bean>
<bean id="dao" class="com.chenyixin.ssm.dao.impl.UserDaoImpl"/>
③ 自动装配
自动装配:
根据指定的策略,在IOC容器中匹配某个bean,自动为bean中的类类型的属性或接口类型的属性赋值
可以通过bean 标签中的autowire属性设置自动装配的策略
1、no,default:表示不装配,即bean中的属性不会自动匹配某个bean为其属性赋值,此时属性使用默认值
2、byType:根据要赋值的属性的类型,在IOC容器中匹配某个bean,为属性赋值
注意:
a>若通过类型没有找到任何一个类型匹配的bean,此时不装配,属性使用默认值
b>若通过类型找到了多个类型匹配的bean,此时会抛出异常:NoUnqueBeanDefinitionException
总结:当使用 byType 实现自动装配时,IOC容器有且只有一个类型匹配的bean能够为属性赋值。
3、byName:将要赋值的属性的属性名作为bean 的 id 在IOC容器中匹配某个bean,为属性赋值
总结:当类类型的bean有点多个时,此时可以使用byName实现自动装配
<bean id="userController" class="com.chenyixin.ssm.controller.UserController" autowire="byType"/>
<bean id="userService" class="com.chenyixin.ssm.service.impl.UserServiceImpl" autowire="byName"/>
<bean id="userDao" class="com.chenyixin.ssm.dao.impl.UserDaoImpl"/>
④ 测试
@Test
public void test() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-autowire.xml");
UserController userController = ioc.getBean(UserController.class);
userController.saveUser(); //保存成功
}
5、基于 注解 管理 Bean
5.1、标记与扫描
① 注解的作用
和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。
本质上:所有一切的操作都是 Java 代码来完成的,XML 和注解只是告诉框架中的 Java 代码如何执行。
举例:元旦联欢会要布置教室,蓝色的地方贴上元旦快乐四个字,红色的地方贴上拉花,黄色的地方贴上气球。 班长做了所有标记,同学们来完成具体工作。墙上的标记相当于我们在代码中使用的注解,后面同学们做的工作,相当于框架的具体操作。
② 扫描
Spring 为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注解进行后续操作。
③ 新建Maven Module 并引入依赖
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
④ 标识组件的常用注解
@Component:将类标识为普通组件@Controller:将类标识为控制层组件@Service:将类标识为业务层组件@Repository:将类标识为持久层组件
四个典型注解没有本质区别
通过查看源码我们得知,@Controller、@Service、@Repository这三个注解只是在@Component注解的基础上起了三个新的名字。
对于Spring使用IOC容器管理这些组件来说没有区别,也就是语法层面没有区别。所以@Controller、@Service、@Repository这三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。
注意:虽然它们本质上一样,但是为了代码的可读性,为了程序结构严谨我们肯定不能随便胡乱标记。
⑤ 创建组件 并 添加注解
@Controller
public class UserController {
}
public interface UserSerice {
}
创建业务层组件UserServiceImpl
@Service
public class UserServiceImpl implements UserSerice {
}
public interface UserDao {
}
@Repository
public class UserDaoImpl implements UserDao {
}
⑥ 创建Spring配置文件
⑦ 扫描组件
<context:component-scan base-package="com.chenyixin.ssm"/>
<context:component-scan base-package="com.chenyixin.ssm">
<!-- context:exclude-filter标签:指定排除规则 -->
<!--
type:设置排除或包含的依据
type="annotation",根据注解排除,expression中设置要排除的注解的全类名
type="assignable",根据类型排除,expression中设置要排除的类型的全类名
-->
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
<!--<context:exclude-filter type="assignable"
expression="com.chenyixin.ssm.controller.UserController"/>-->
</context:component-scan>
<context:component-scan base-package="com.chenyixin.ssm" use-default-filters="false">
<!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
<!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
<!-- 此时必须设置use-default-filters="false",因为默认规则即扫描指定包下所有类 -->
<!--
type:设置排除或包含的依据
type="annotation",根据注解排除,expression中设置要排除的注解的全类名
type="assignable",根据类型排除,expression中设置要排除的类型的全类名
-->
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
<!--<context:include-filter type="assignable"
expression="com.chenyixin.ssm.controller.UserController"/>-->
</context:component-scan>
总结:
扫描组件
1、context:exclude-filter:排除扫描
type=" annotation | assignable "
annotation :根据注解的类型进行排除,expression需要设置排除的注解的全类名
assignable :根据类的类型进行排除,expression需要设置排除的类的全类名
2、context:include-filter:包含扫描
注意:需要在context: component-scan标签中设置use-default-filters 属性
use-default-filters="true”(默认),所设置的包下所有的类都需要扫描,此时可以使用排除扫描
use-default-filters="false",所设置的包下所有的类都不需要扫描,此时可以使用包含扫描
⑧ 测试
public class IOCAnnotationTest {
@Test
public void test() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-annotation.xml");
UserController userController = ioc.getBean(UserController.class);
System.out.println(userController);
UserSerice userSerice = ioc.getBean(UserSerice.class);
System.out.println(userSerice);
UserDao userDao = ioc.getBean(UserDao.class);
System.out.println(userDao);
}
}
结果:
⑨ 组件所对应的bean的id
默认情况 : 类名首字母小写就是bean的id。例如:UserController类对应的bean的id就是userController。自定义bean的id可通过标识组件的注解的value属性设置自定义的bean的id例如:@Service(value="userService")//默认为userServiceImpl (value= 可以省略)public class UserServiceImpl implements UserService {}
5.2 实验二:基于注解的自动装配
① 场景模拟
参考基于xml的自动装配在UserController中声明UserService对象在UserServiceImpl中声明UserDao对象
② @Autowired注解
@Autowired:实现自动装配功能的注解
1、@Autowired注解能够标识的位置
a>标识在成员变量上,此时不需要设置成员变量的set方法
b>标识在set方法上
c>标识在为当前成员变量赋值的有参构造上
2、@Autowired注解的原理
a> 默认通过byType的方式,在IOC容器中通过类型匹配某个bean为属性赋值
b> 若有多个类型匹配的bean,此时会自动转换为byName的方式实现自动装配的效果即将要赋值的属性的属性名作为bean 的id匹配某个bean为属性赋值
c> 若byType和byName的方式都无妨实现自动装配,即Ioc容器中有多个类型匹配的bean
且这些bean的id和要赋值的属性的属性名都不一致,此时抛异常:NouniqueBeanDefinitionException
d> 此时可以在要赋值的属性上,添加一个注解@Qualifier 通过该注解的value属性值,指定某个bean的id,将这个bean为属性赋值
注意:荐Toc容器中没有任何一个类型匹配的bean,此时抛出异常:NoSuchBeanDefinitionException
在@Autowired注解中有个属性required,默认值为true,要求必须完成自动装配
可以将required设置为false,此时能装配则装配,无法装配则使用属性的默认值
③ 常用代码示例
@Controller
public class UserController {
@Autowired
private UserService userService;
// @Autowired
// public UserController(UserService userService) {
// this.userService = userService;
// }
// @Autowired
// public void setUserService(UserService userService) {
// this.userService = userService;
// }
public void saveUser() {
userService.saveUser();
}
}
public interface UserService {
void saveUser();
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public void saveUser() {
userDao.saveUser();
}
}
public interface UserDao {
void saveUser();
}
@Repository
public class UserDaoImpl implements UserDao {
@Override
public void saveUser() {
System.out.println("保存成功");
}
}
④ 测试
@Test
public void test2() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-annotation.xml");
UserController userController = ioc.getBean(UserController.class);
userController.saveUser(); //保存成功
}
⑤ @Autowired 工作流程
- 首先根据所需要的组件类型到 IOC 容器中查找
- 能够找到唯一的 bean:直接执行装配
- 如果完全找不到匹配这个类型的 bean:装配失败
- 和所需类型匹配的 bean 不止一个
- 没有 @Qualifier 注解:根据 @Autowired 标记位置成员变量的变量名作为 bean 的 id 进行匹配
- 能够找到:执行装配
- 找不到:装配失败
- 使用 @Qualifier 注解:根据 @Qualifier 注解中指定的名称作为 bean 的id进行匹配
- 能够找到:执行装配
- 找不到:装配失败
- 没有 @Qualifier 注解:根据 @Autowired 标记位置成员变量的变量名作为 bean 的 id 进行匹配
@Autowired
@Qualifier(value="userServiceImpl")
private UserService userService;
⑥ 佛系装配
给 @Autowired 注解设置 required = false 属性表示:能装就装,装不上就不装。但是实际开发时,基本上所有需要装配组件的地方都是必须装配的,用不上这个属性。
@Autowired(required = false)
private UserDao userDao;
WARNING
如果类中同时存在装配属性的 setXxx() 方法会使 required = false 设定失效。
5.3 实验三:完全注解开发
① 创建配置类,完全替代XML配置文件
⑴为该类添加注解@Configuration,以示是注解配置类
⑵添加注解@ComponentScan,设置注解扫描路径
② 代码示例
@Configuration // 声明该类为 配置类
@ComponentScan(basePackages = "com.chenyixin.ssm") // 注解扫描
public class SpringConfig {
}
③ 编写测试方法获取IOC容器的语句
@Test
public void test3() {
// ClassPathXmlApplicationContext 根据 XM L配置文件创建 IOC 容器对象
// AnnotationConfigApplicationContext 根据配置类创建 IOC 容器对象
ApplicationContext ioc = new AnnotationConfigApplicationContext(SpringConfig.class);
UserController userController = ioc.getBean(UserController.class);
userController.saveUser(); //保存成功
}
5.4 实验四:整合junit4
① 整合的好处
- 好处1:不需要自己创建IOC容器对象了
- 好处2:任何需要的bean都可以在测试类中直接享受自动装配
② 引入依赖
<!-- Spring的测试包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.1</version>
</dependency>
③ 创建测试类
// junit的@RunWith注解:指定Spring为Junit提供的运行器
@RunWith(SpringJUnit4ClassRunner.class)
// Spring的@ContextConfiguration的value指定Spring配置文件的位置,需要以classpath:+文件路径
// 如:value = {"classpath:spring-annotation.xml"}
// Spring的@ContextConfiguration的classes指定Spring配置类的位置,如:classes = SpringConfig.class
// @ContextConfiguration(value = {"classpath:spring-annotation.xml"})
@ContextConfiguration(classes = SpringConfig.class)
public class JunitIntegrationSpring {
@Autowired
private UserController userController;
@Test
public void testIntegration() {
userController.saveUser();
}
}