获取资源的传统方式:
在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。
反转控制方式获取资源:
反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主
动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源
的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。
DI:
DI:Dependency Injection,翻译过来是依赖注入。
DI 是 IOC 的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接
所以结论是:IOC 就是一种反转控制的思想, 而 DI 是对 IOC 的一种具体实现。
IOC容器在Spring中的实现
Spring 的 IOC 容器就是 IOC 思想的一个落地的产品实现。IOC 容器中管理的组件也叫做 bean。在创建bean 之前,首先需要创建 IOC 容器。Spring 提供了 IOC 容器的两种实现方式:
1.BeanFactory:
这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。
2.ApplicationContext
BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用 ApplicationContext而不是底层的 BeanFactory。
3.ApplicationContext的主要实现类
关于ClassPathXmlApplicationContext和FileSystemXmlApplicationContext,我们通常使用前者,因为后者是通过文件系统路径读取配置文件,而我们程序之后可能会在不同的电脑上运行,且这些电脑的文件路径不一定相同
基于XML管理bean
bean:组件,即我们交给IOC管理的类
入门案例
1.引入依赖
<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>
2.创建需要被管理的类 HelloWorld
3.在resources下创建Spring的配置文件
4.在Spring的配置文件中配置bean
<!--
配置HelloWorld所对应的bean,即将HelloWorld的对象交给Spring的IOC容器管理
通过bean标签配置IOC容器所管理的bean
属性:
id:设置bean的唯一标识
class:设置bean所对应类型的全类名
-->
<bean id="helloworld" class="com.atguigu.spring.bean.HelloWorld"></bean>
5.创建Application对象,并用该对象获取并使用Bean
public void testHelloWorld(){
//获取Application,需要入参Spring配置文件的路径
ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
//使用方法获取Bean
HelloWorld helloworld = (HelloWorld) ioc.getBean("helloworld");
//使用Bean
helloworld.sayHello();
}
大体思路:
注:Spring底层默认通过反射来调用组件类的无参构造器来创建组件对象,因此组件类需要有无参构造器
获取bean的三种方式
我们有多种方式来获取bean,通用的有以下三种
1.根据id获取
由于id属性指定了bean的唯一标识,因此我们可以直接根据bean标签的id属性获取组件对象
注:通过该方式获取的对象默认为Object属性
2.根据类型获取
我们可以通过组件类的class来获取组件对象
注:这种方式使用的前提是IOC容器中指定类型的bean有且只有一个
3.根据id和类型获取
注意事项
如果组件类实现了接口,我们可以根据接口类型获取bean,前提是该接口只有一个实现类配置了bean,且bean唯一
结论
根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 instanceof 指定的类型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到。
且我们最常用的方式为根据类型获取,原因:根据id获取时的返回类型是Object类型,需要手动转换。
我们可以配置多个类型相同的bean,但通常一个类型只会配置一个bean,且这个bean是单例的。
如果要实现多例,只需要在配置时更改属性scope即可
依赖注入
依赖注入,即DI,对IOC的一种实现
一个类中有多个属性,我们称这个类依赖于这些属性,依赖注入就是我们对这些属性赋值的过程
例:一个Student类,其内部有id,name,age,gender等属性,那么Student类就依赖于这些属性
注:属性是由getxxx(),setxxx()方法定义的,和成员变量无关,将方法中的get和set去掉,剩余部分首字母大写的结果就是属性
如我们有方法getId()和方法setId(),那么即使类中没有id这个成员变量,仍有一个id属性
我们可以在配置bean时为属性赋值
setter注入
bean标签中含有子标签property:通过成员变量的set方法进行赋值
property中有两个属性
name属性:指定属性名
value属性:指定属性值,除value属性外,还有一个value标签,有相同的效果
例:
<bean id="student" class="com.spring.bean.Student">
<property name="id" value="1001"></property>
<property name="name" value="张三"></property>
<property name="age" value="18"></property>
<property name="gender" value="男"></property>
</bean>
注:bean默认是单例对象
构造器注入
bean标签中含有子标签constructor-arg:通过构造器赋值
constructor-arg有多个属性
name:指定参数名(注:是参数名,不是成员变量)
value:指定值,除value属性外,还有一个value标签,有相同的效果
index:指定参数所在位置的索引
注:1.构造器注入会根据constructor-arg的个数去寻找需要同样数目参数的构造器
如我们配置了四个constructor-arg标签,那么如果此时没有需要四个参数的构造器就会报错,如果有对应的构造器,那么在设置constructor-arg时可以只设置value值,可以按照顺序自动匹配
例:
<bean id="studentTwo" class="com.spring.bean.Student">
<constructor-arg value="1002"></constructor-arg>
<constructor-arg value="李四"></constructor-arg>
<constructor-arg value="男"></constructor-arg>
<constructor-arg value="23"></constructor-arg>
</bean>
public Student(int id, String name, String gender ,int age) {
this.id = id;
this.name = name;
this.gender = gender;
this.age = age;
}
那么从上到下constructor-arg的value就会对应给id,name,gender,age赋值
2.如果我们四个constructor-arg标签,但是我们有多个四个参数的构造器,且按照顺序赋值时value值的类型均符合,那么默认会选择在下面的那个构造器,但我们一般会设置name属性来选择参数名(可以只设置某一个constructor-arg)
特殊值处理
1.字面量赋值
字面量就是看到的这个数据本身
如 int a = 10;
此时10是一个字面量。a不代表字母a,而是作为一个变量的名称。
当我们使用value属性给bean的属性赋值时,Spring会把value属性的值看做为字面量
2.null
我们如果要给一个property赋值为null时,需要使用一个子标签null,如下
<property name="gender">
<null></null>
</property>
注:不能直接在value属性中填写null,否则会被识别成字符串
3.特殊符号
当我们在xml中使用<,>等符号时,会与标签的<>冲突,导致识别错误
有以下两种方式解决
3.1xml实体
当我们用到特殊符号时,可以使用使用xml实体来代替,即html的转义字符
3.2CDATA节
我们可以使用CDATA标签,这是一个特殊标签,其内部的值会被当做纯文本数据,而不是XML标签或者属性,例:
<property name="gender">
<value><![CDATA[<男>]]></value>
</property>
CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据
注:
1.在idea中我们可以打出大写的CD,可以快速打出CDATA标签
2.CDATA是一个标签,不能在属性中使用
3.我们使用快捷键打出CDATA时会自动分成三行,但是换行也会被识别出来,因此需要手动删掉换行
为类类型属性赋值
一个类中除普通类型的属性外还可能有类类型的属性,而value只能为字面量赋值
对类类型属性赋值有以下三种方式
1.引用外部已声明的bean
property和constructor-arg中均含有属性ref,该属性可以填写一个bean的id来获取这个bean的对象并赋值给对应的property或constructor-arg
<bean id="clazz" class="com.atguigu.spring.bean.Clazz">
<property name="clazzId" value="1111"></property>
<property name="clazzName" value="财源滚滚班"></property>
</bean>
<bean id="studentFour" class="com.atguigu.spring.bean.Student">
<property name="id" value="1004"></property>
<property name="name" value="赵六"></property>
<property name="age" value="26"></property>
<property name="sex" value="女"></property>
<!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
<property name="clazz" ref="clazzOne"></property>
</bean>
2.内部bean
property和constructor-arg均含有子标签bean
我们可以在子标签中创建一个bean并为property或constructor-arg赋值,且这个内部bean不能从外界获取,因此内部bean可以省略id属性
<bean id="studentFour" class="com.atguigu.spring.bean.Student">
<property name="id" value="1004"></property>
<property name="name" value="赵六"></property>
<property name="age" value="26"></property>
<property name="sex" value="女"></property>
<property name="clazz">
<!-- 在一个bean中再声明一个bean就是内部bean -->
<!-- 内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,因此可以省略id属性 -->
<bean id="clazzInner" class="com.atguigu.spring.bean.Clazz">
<property name="clazzId" value="2222"></property>
<property name="clazzName" value="远大前程班"></property>
</bean>
</property>
</bean>
3.级联
使用 类属性.类属性的属性来赋值
但是使用该方式的前提是类属性已经有值才行,因此要么引用外部已声明的bean,要么在该类属性中new一个对象,那么此时级联的作用就是更新属性
<bean id="studentFour" class="com.atguigu.spring.bean.Student">
<property name="id" value="1004"></property>
<property name="name" value="赵六"></property>
<property name="age" value="26"></property>
<property name="sex" value="女"></property>
<!-- 一定先引用某个bean为属性赋值,才可以使用级联方式更新属性 -->
<property name="clazz" ref="clazzOne"></property>
<property name="clazz.clazzId" value="3333"></property>
<property name="clazz.clazzName" value="最强王者班"></property>
</bean>
注:级联的方式用的较少
为数组类型属性赋值
property和constructor-arg有子标签array,array下有子标签value和ref
若需要为字面量类型的数组赋值,则使用value标签
若需要为类类型的数组赋值,则使用ref标签的bean属性
<property name="hobbies">
<array>
<value>抽烟</value>
<value>喝酒</value>
<value>烫头</value>
<!-- <ref bean=""></ref> -->
</array>
</property>
为集合类型的属性赋值
1.为List集合类型属性赋值
property和constructor-arg有子标签list,list下有子标签value和ref(和数组类型类似)
<property name="students">
<list>
<ref bean="studentOne"></ref>
<ref bean="studentTwo"></ref>
<ref bean="studentThree"></ref>
</list>
</property>
若为Set集合类型属性赋值,只需要将其中的list标签改为set标签即可
2.为Map集合类型属性赋值
property和constructor-arg有子标签map,map下有子标签entry,代表一个键值对,entry下有属性key:设置字面量类型的键
key-ref:设置类类型的键
value:设置字面量类型的值
value-ref:设置类类型的值
<property name="teacherMap">
<map>
<entry key="10086" value-ref="teacherOne"></entry>
<entry key="10010" value-ref="teacherTwo"></entry>
</map>
</property>
entry下也有子标签key和value
key有子标签value和ref,ref有属性bean
当使用标签来为entry的值赋值时
若值的类型为字面量类型,则使用entry的子标签value
若值的类型为类类型,则直接使用ref标签的bean属性
<property name="teacherMap">
<map>
<entry>
<key>
<value>10010</value>
</key>
<ref bean="teacherOne"></ref>
</entry>
<entry>
<key>
<value>10086</value>
</key>
<ref bean="teacherTwo"></ref>
</entry>
</map>
</property>
注:entry为键值对赋值时,entry的属性和子标签可以混用
3.引用集合类型的bean
我们也可以配置一个集合类型的bean,然后使用ref来引用这个bean
需要注意的是,如果我们要配置集合类型的bean,需要先引入一个util的约束
<!-- 配置一个集合类型的bean,需要使用util的约束-->
<util:list id="studentList">
<ref bean="studentOne"></ref>
<ref bean="studentTwo"></ref>
</util:list>
<!--map集合类型的bean-->
<util:map id="teacherMap">
<entry>
<key>
<value>10010</value>
</key>
<ref bean="teacherOne"></ref>
</entry>
<entry>
<key>
<value>10086</value>
</key>
<ref bean="teacherTwo"></ref>
</entry>
</util:map>
p命名空间
我们可以引用p命名空间的约束,由此来使用它的方式为bean的各个属性赋值
在引入约束,并为bean标签指定class属性后,就可以使用该标签新增的属性
这些属性均以p:开头,后面是class类的每个属性以及该属性的ref形式,分别用于引用字面量类型和外部bean
<bean id="studentSix" class="com.atguigu.spring.bean.Student"
p:id="1006" p:name="小明" p:clazz-ref="clazzOne" p:teacherMap-ref="teacherMap">
</bean>
引入外部属性文件
由于之后Spring会和Mybatis整合,可以由Spring来提供对应的数据源而数据源的数据我们会在一个property中存储,因此我们需要在bean中访问外部的property文件
步骤:
1.创建外部属性文件
2.引入属性文件
需要使用到标签context:property-placeholder的属性location,且需要引入约束
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
注:在引入路径时,若为web工程,则需要加上classpath:来表明访问的是类路径下的配置文件
3.配置bean
在设置完上述标签后就可以使用${key}的形式来访问property中的值
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
bean的作用域
在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义如下
取值 | 含义 | 创建对象的时机 |
singleton(默认) | 在IOC容器中这个bean对象始终为单例 | IOC容器初始化时 |
prototype | 这个bean在IOC容器中有多个实例 | 获取bean时 |
如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用):
取值 | 含义 |
request | 在一个请求范围内有效 |
session | 在一个会话范围内有效 |
bean的生命周期
1.实例化(调用无参构造器创建bean对象时)
2.依赖注入(为bean对象设置属性)
3.bean对象初始化之前操作(由bean的后置处理器负责)
4.bean对象初始化
5.bean对象初始化之后操作(由bean的后置处理器负责)
6.bean对象销毁
注意事项:
1.初始化和销毁两个生命周期分别需要在配置bean时使用init-method和destroy-method属性指定对应的方法,且该方法应该在该bean对应的类中
2.IOC容器关闭时才会执行销毁过程,而关闭IOC的需要使用ApplicationContext的子接口ConfigurableApplicationContext
3.若bean的作用域为单例,那么由于单例的bean会在IOC容器初始化时创建,因此该bean的前5个生命周期会在IOC容器初始化时进行;若bean的作用域为多例,那么该bean的前5个生命周期会在获取bean对象时进行。
<!-- 使用init-method属性指定初始化方法 -->
<!-- 使用destroy-method属性指定销毁方法 -->
<bean class="com.atguigu.bean.User" scope="prototype" init-method="initMethod"
destroy-method="destroyMethod">
<property name="id" value="1001"></property>
<property name="username" value="admin"></property>
<property name="password" value="123456"></property>
<property name="age" value="23"></property>
</bean>
bean的后置处理器
bean的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口,且配置到IOC容器中
bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行
package com.atguigu.spring.process;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanProcessor implements BeanPostProcessor {
//在初始化前执行
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
//在初始化之后执行
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
}
<!-- bean的后置处理器要放入IOC容器才能生效 -->
<bean id="myBeanProcessor" class="com.atguigu.spring.process.MyBeanProcessor"/>
FactoryBean
FactoryBean是Spring提供的一种整合第三方框架的常用机制。
和普通的bean不同,配置一个 FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是 getObject()方法的返回值。
由此我们就能省略掉创建工厂的过程,直接获取对象
FactoryBean是一个接口,需要创建一个类实现该接口
其中有三个方法:
getObject():提供一个对象交给IOC容器管理
getObjectType():设置所提供对象的类型
isSingleton()所提供的对象是否单例,该方法不是抽象方法,有默认的方法体,返回的是true
当我们把FactoryBean的实现类配置为bean时,会将当前类中的getObject()所返回的对象交给IOC容器管理,即我们通过该bean获取的对象不是工厂对象,而是getObject()所返回的对象
public class UserFactoryBean implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
return new User();
}
@Override
public Class<?> getObjectType() {
return User.class;
}
}
<bean id="user" class="com.atguigu.bean.UserFactoryBean"></bean>
@Test
public void testUserFactoryBean(){
//获取IOC容器
ApplicationContext ac = new ClassPathXmlApplicationContext("springfactorybean.xml");
User user = ac.getBean(User.class);
System.out.println(user);
}
获取对象时可以直接填写getObjectType()所返回的类型
自动装配
自动装配:根据指定的策略,在IOC容器中匹配一个bean,自动为指定的bean中的所依赖的类类型或接口类型的属性赋值
可以通过bean标签中的autowire属性设置自动装配的策略
自动装配的策略:
1.no,default:表示不装配,即bean中的属性不会自动匹配某个bean为属性赋值,此时属性使用默认值
2.byType:根据要赋值的属性的类型,在IOC容器中匹配某个bean,为属性赋值
注:
- 若通过类型没有找到任何一个类型匹配的bean,此时不匹配,使用默认值
- 若通过类型找到了多个匹配的bean,会抛出异常NoUniqueBeanDefinitionException
- 总结:当使用byType实现自动装配时,IOC容器中应有且只有一个类型匹配的bean能为属性赋值
3.byName:将要赋值的属性的属性名与IOC容器中bean的id去匹配,为属性赋值
注:
- 若没有任何一个bean能匹配上,则不匹配,使用默认值
- 若有多个id相同的bean,则此时无法获取IOC容器
- 通过id匹配到的bean的类型必须和对应属性的类型相同
- 总结:当类型匹配的bean有多个时,可以使用byName实现自动装配
<bean id="userController"
class="com.atguigu.autowire.xml.controller.UserController" autowire="byType">
</bean>
<bean id="userService"
class="com.atguigu.autowire.xml.service.impl.UserServiceImpl" autowire="byType">
</bean>
<bean id="userDao" class="com.atguigu.autowire.xml.dao.impl.UserDaoImpl"></bean>
<!-- UserController类中有类型为UserServiceImpl的属性 -->
<!-- UserServiceImpl类中有类型为UserDaoImpl的属性 -->
<bean id="userController"
class="com.atguigu.autowire.xml.controller.UserController" autowire="byName">
</bean>
<bean id="userService"
class="com.atguigu.autowire.xml.service.impl.UserServiceImpl" autowire="byName">
</bean>
<bean id="userServiceImpl"
class="com.atguigu.autowire.xml.service.impl.UserServiceImpl" autowire="byName">
</bean>
<bean id="userDao" class="com.atguigu.autowire.xml.dao.impl.UserDaoImpl"></bean>
<bean id="userDaoImpl" class="com.atguigu.autowire.xml.dao.impl.UserDaoImpl">
</bean>
基于注解管理bean
注解
和XML配置文件一样,注解本身并不能执行,注解本身只是做一个标记,具体功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能执行具体操作
本质上:所有的操作都是Java代码来完成的,XML和注解只是告诉框架中的Java代码如何执行
标记
在使用注解标记时,我们要在 需要被IOC容器管理的类上 加上注解
常用注解:
@Component:将类标识为普通组件
@Controller:将类标识为控制层组件
@Service:将类标识为业务层组件
@Repository:将类标识为持久层组件
注:
1.这四个注解的作用是完全一样的,下面三个注解都是在第一个注解的基础上起了三个新的名字
但是为了代码的可读性,我们仍然要根据对应的关系来进行注解
2.这四个注解应该标记在要被管理的类上,不能被标记在接口上
扫描组件
在使用主键标记了需要被管理的类后,我们还需要扫描这些类,判断是否使用了注解
基础的扫描方式
在IOC容器中使用context:component-scan来设置我们需要扫描的包(需要context约束)
<context:component-scan base-package="com.controller"></context:component-scan>
若我们有多个要扫描的包,可以直接使用逗号分隔
<context:component-scan
base-package="com.controller,com.dao.impl,com.service">
</context:component-scan>
也可以直接扫描一个大包,将需要扫描的包都包括进去
<context:component-scan base-package="com"></context:component-scan>
注:我们选择的包的范围越大,扫描到没有注解的类或接口就越多,导致效率降低,因此包的范围越小,越精确,效率就越高
指定要排除的组件
我们在进行扫描时,可能会出现某些类不想要让这个IOC容器管理的情况,那么此时就需要排除这些类
context:component-scan有子标签
context:exclude-filter:指定排除规则,其有属性
type和expression
type="annotation",根据注解排除,expression中设置要排除的注解的全类名
type="assignable",根据类型排除,expression中设置要排除的类型的全类名
<context:component-scan base-package="com.atguigu">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
<!--<context:exclude-filter type="assignable"
expression="com.atguigu.controller.UserController"/>-->
</context:component-scan>
仅扫描指定组件
为实现进扫描指定组件的功能,需要进行两步操作:
1.设置context:component-scan的属性 use-default-filters="false"
2.使用context:component-scan的子标签 context:include-filter ,然后设定type和expression来指定要扫描那些类
use-default-filters="true"(默认),所设置的包下所有的类都需要扫描,此时可以使用排除扫描
use-default-filters="false",所设置的包下所有的类都不需要扫描,此时可以使用包含扫描
<context:component-scan base-package="com.atguigu" use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
<!--<context:include-filter type="assignable"
expression="com.atguigu.controller.UserController"/>-->
</context:component-scan>
仅扫描使用的较少
注:一个扫描中可以设置多个排除或多个包含,但不能同时使用排除和包含
组件所对应的bean的id
默认id
在我们使用XML方式管理bean时,每个bean都有一个唯一标识,便于在其他地方引用。现在使用注解后,每个组件仍然应该有一个唯一标识
默认情况下,类名首字母小写就是bean的id
例:UserController类对应的bean的id就时userController
自定义id
除默认id外,我们也可以通过表示组件的注解的value属性设置自定义id
如图,此时UserController对应的bean的id就是controller
自动装配
在使用XML管理bean时,如果bean中的某个属性是一个类类型的属性,我们可以自动装配为其赋值
在注解管理bean中,要实现自动装配功能,我们可以使用@Autowired注解,该注解的标记位置有三种
1.在成员变量上使用@Autowired注解,此时不需要提供set方法
如图,为UserController类的userService成员变量实现自动装配,只需要在成员变量上使用注解即可
2.在成员变量的set方法上使用@Autowired注解
3.在为当前成员变量赋值的有参构造上使用@Autowired注解
注:一般使用第二种
@Autowire注解的原理
默认通过byType的方式,在IOC容器中通过类型匹配某个bean为属性赋值
若有多个类型匹配的bean,此时会使用byName的方式实现自动装配的效果,即将要赋值的属性的属性名作为bean的id去匹配bean并为其赋值
若IOC容器中有多个类型匹配的bean,且这些bean的id和要赋值的属性的属性名都不同,则会抛异常,此时可以在要赋值的属性上添加一个@Qualifier注解,通过该注解的value属性值,指定某个bean的id,用这个bean为属性赋值
注:若IOC容器中没有任何一个类型匹配的bean,会抛出异常
在@Autowired注解中有个属性required,默认值为true,表示必须完成自动装配
可以将其设置为false,此时若无法装配则会使用默认值