而在 Spring 中提供了 3 种方法进行配置:
- 在 XML 文件中显式配置
- 在 Java 的接口和类中实现配置
- 隐式 Bean 的发现机制和自动装配原则
通过 XML 配置装配 Bean
使用 XML 装配 Bean 需要定义对应的 XML,这里需要引入对应的 XML 模式(XSD)文件,这些文件会定义配置 Spring Bean 的一些元素,当我们在 IDEA 中创建 XML 文件时,会有友好的提示:
一个简单的 XML 配置文件如下:
这就只是一个格式文件,引入了一个 beans 的定义,引入了 xsd 文件,它是一个根元素,这样它所定义的元素将可以定义对应的 Spring Bean
装配简易值
先来一个最简单的装配:
简单解释一下:
id
属性是 Spring 能找到当前 Bean 的一个依赖的编号,遵守 XML 语法的 ID 唯一性约束。必须以字母开头,可以使用字母、数字、连字符、下划线、句号、冒号,不能以/
开头。
不过id
属性不是一个必需的属性,name
属性也可以定义 bean 元素的名称,能以逗号或空格隔开起多个别名,并且可以使用很多的特殊字符,比如在 Spring 和 Spring MVC 的整合中,就得使用name
属性来定义 bean 的名称,并且使用/
开头。
注意: 从 Spring 3.1 开始,id
属性也可以是 String 类型了,也就是说 id
属性也可以使用 /
开头,而 bean 元素的 id 的唯一性由容器负责检查。
如果 id
和 name
属性都没有声明的话,那么 Spring 将会采用 “全限定名#{number}” 的格式生成编号。 例如这里,如果没有声明 “id="c"
” 的话,那么 Spring 为其生成的编号就是 “pojo.Category#0
”,当它第二次声明没有 id
属性的 Bean 时,编号就是 “pojo.Category#1
”,以此类推。
class
属性显然就是一个类的全限定名property
元素是定义类的属性,其中的name
属性定义的是属性的名称,而value
是它的值。
这样的定义很简单,但是有时候需要注入一些自定义的类,比如之前饮品店的例子,JuickMaker 需要用户提供原料信息才能完成 juice 的制作:
这里先定义了一个 name
为 source 的 Bean,然后再制造器中通过 ref
属性去引用对应的 Bean,而 source 正是之前定义的 Bean 的 name
,这样就可以相互引用了。
- 注入对象:使用
ref
属性
装配集合
有些时候我们需要装配一些复杂的东西,比如 Set、Map、List、Array 和 Properties 等,为此我们在 Packge【pojo】下新建一个 ComplexAssembly 类:
开始装配复杂的类型
-
装配复杂数据类型的总结:
- List 属性为对应的
<list>
元素进行装配,然后通过多个<value>
元素设值 - Map 属性为对应的
<map>
元素进行装配,然后通过多个<entry>
元素设值,只是entry
包含一个键值对(key-value)的设置 - Properties 属性为对应的
<properties>
元素进行装配,通过多个<property>
元素设值,只是properties
元素有一个必填属性key
,然后可以设置值 - Set 属性为对应的
<set>
元素进行装配,然后通过多个<value>
元素设值 - 对于数组而言,可以使用
<array>
设置值,然后通过多个<value>
元素设值。
上面看到了对简单 String 类型的各个集合的装载,但是有些时候可能需要更为复杂的装载,比如一个 List 可以是一个系列类的对象,为此需要定义注入的相关信息,其实跟上面的配置没什么两样,只不过加入了 ref
这一个属性而已:
集合注入总结:
List 属性使用 <list>
元素定义注入,使用多个 <ref>
元素的 Bean 属性去引用之前定义好的 Bean
Map 属性使用 <map>
元素定义注入,使用多个 <entry>
元素的 key-ref
属性去引用之前定义好的 Bean 作为键,而用 value-ref
属性引用之前定义好的 Bean 作为值
Set 属性使用 <set>
元素定义注入,使用多个 <ref>
元素的 bean
去引用之前定义好的 Bean
通过注解装配 Bean
上面,我们已经了解了如何使用 XML 的方式去装配 Bean,但是更多的时候已经不再推荐使用 XML 的方式去装配 Bean,更多的时候会考虑使用注解(annotation) 的方式去装配 Bean。
- 优势:
1.可以减少 XML 的配置,当配置项多的时候,臃肿难以维护
2.功能更加强大,既能实现 XML 的功能,也提供了自动装配的功能,采用了自动装配后,程序猿所需要做的决断就少了,更加有利于对程序的开发,这就是“约定由于配置”的开发原则
在 Spring 中,它提供了两种方式来让 Spring IoC 容器发现 bean:
- 组件扫描:通过定义资源的方式,让 Spring IoC 容器扫描对应的包,从而把 bean 装配进来。
- 自动装配:通过注解定义,使得一些依赖关系可以通过注解完成。
使用@Compoent 装配 Bean
我们把之前创建的 Student 类改一下:
解释一下:
- @Component注解:
表示 Spring IoC 会把这个类扫描成一个 bean 实例,而其中的value
属性代表这个类在 Spring 中的id
,这就相当于在 XML 中定义的 Bean 的 id:<bean id="student1" class="pojo.Student" />
,也可以简写成@Component("student1")
,甚至直接写成@Component
,对于不写的,Spring IoC 容器就默认以类名来命名作为id
,只不过首字母小写,配置到容器中。 - @Value注解:
表示值的注入,跟在 XML 中写value
属性是一样的。
这样我们就声明好了我们要创建的一个 Bean,就像在 XML 中写下了这样一句话:
但是现在我们声明了这个类,并不能进行任何的测试,因为 Spring IoC 并不知道这个 Bean 的存在,这个时候我们可以使用一个 StudentConfig 类去告诉 Spring IoC :
这个类十分简单,没有任何逻辑,但是需要说明两点:
- 该类和 Student 类位于同一包名下
- @ComponentScan注解:
代表进行扫描,默认是扫描当前包的路径,扫描所有带有@Component
注解的 POJO。
这样一来,我们就可以通过 Spring 定义好的 Spring IoC 容器的实现类——AnnotationConfigApplicationContext 去生成 IoC 容器了:
这里可以看到使用了 AnnotationConfigApplicationContext 类去初始化 Spring IoC 容器,它的配置项是 StudentConfig 类,这样 Spring IoC 就会根据注解的配置去解析对应的资源,来生成 IoC 容器了。
- 明显的弊端:
- 对于
@ComponentScan
注解,它只是扫描所在包的 Java 类,但是更多的时候我们希望的是可以扫描我们指定的类 - 上面的例子只是注入了一些简单的值,测试发现,通过
@Value
注解并不能注入对象
自动装配——@Autowired
上面提到的两个弊端之一就是没有办法注入对象,通过自动装配我们将解决这个问题。
所谓自动装配技术是一种由 Spring 自己发现对应的 Bean,自动完成装配工作的方式,它会应用到一个十分常用的注解 @Autowired
上,这个时候 Spring 会根据类型去寻找定义的 Bean 然后将其注入,听起来很神奇,让我们实际来看一看:
1.先在 Package【service】下创建一个 StudentService 接口:
2.为上面的接口创建一个 StudentServiceImp 实现类:
该实现类实现了接口的 printStudentInfo() 方法,打印出成员对象 student 的相关信息,这里的 @Autowired
注解,表示在 Spring IoC 定位所有的 Bean 后,这个字段需要按类型注入,这样 IoC 容器就会寻找资源,然后将其注入。
- 再次理解:
@Autowired
注解表示在 Spring IoC 定位所有的 Bean 后,再根据类型寻找资源,然后将其注入。 - 过程: 定义 Bean ——》 初始化 Bean(扫描) ——》 根据属性需要从 Spring IoC 容器中搜寻满足要求的 Bean ——》 满足要求则注入
- 问题: IoC 容器可能会寻找失败,此时会抛出异常(默认情况下,Spring IoC 容器会认为一定要找到对应的 Bean 来注入到这个字段,但有些时候并不是一定需要,比如日志)
- 解决: 通过配置项
required
来改变,比如@Autowired(required = false)
@Autowired
注解不仅仅能配置在属性之上,还允许方法配置,常见的 Bean 的 setter 方法也可以使用它来完成注入,总之一切需要 Spring IoC 去寻找 Bean 资源的地方都可以用到,例如:
自动装配的歧义性(@Primary和@Qualifier)
在上面的例子中我们使用 @Autowired
注解来自动注入一个 Source 类型的 Bean 资源,但如果我们现在有两个 Srouce 类型的资源,Spring IoC 就会不知所措,不知道究竟该引入哪一个 Bean:
我们可以会想到 Spring IoC 最底层的容器接口——BeanFactory 的定义,它存在一个按照类型获取 Bean 的方法,显然通过 Source.class 作为参数无法判断使用哪个类实例进行返回,这就是自动装配的歧义性。
为了消除歧义性,Spring 提供了两个注解:
- @Primary 注解:
代表首要的,当 Spring IoC 检测到有多个相同类型的 Bean 资源的时候,会优先注入使用该注解的类。 - 问题:该注解只是解决了首要的问题,但是并没有选择性的问题
- @Qualifier 注解:
上面所谈及的歧义性,一个重要的原因是 Spring 在寻找依赖注入的时候是按照类型注入引起的。除了按类型查找 Bean,Spring IoC 容器最底层的接口 BeanFactory 还提供了按名字查找的方法,如果按照名字来查找和注入不就能消除歧义性了吗? - 使用方法: 指定注入名称为 “source1” 的 Bean 资源
使用@Bean 装配 Bean
- 问题: 以上都是通过
@Component
注解来装配 Bean ,并且只能注解在类上,当你需要引用第三方包的(jar 文件),而且往往并没有这些包的源码,这时候将无法为这些包的类加入@Component
注解,让它们变成开发环境中的 Bean 资源。 - 解决方案:
1.自己创建一个新的类来扩展包里的类,然后再新类上使用 @Component
注解,但这样很 low
2.使用 @Bean
注解,注解到方法之上,使其成为 Spring 中返回对象为 Spring 的 Bean 资源。
我们在 Package【pojo】 下新建一个用来测试 @Bean
注解的类:
- 注意:
@Configuration
注解相当于 XML 文件的根元素,必须要,有了才能解析其中的@Bean
注解
然后我们在测试类中编写代码,从 Spring IoC 容器中获取到这个 Bean :
@Bean
的配置项中包含 4 个配置项:
- name: 是一个字符串数组,允许配置多个 BeanName
- autowire: 标志是否是一个引用的 Bean 对象,默认值是 Autowire.NO
- initMethod: 自定义初始化方法
- destroyMethod: 自定义销毁方法
使用 @Bean
注解的好处就是能够动态获取一个 Bean 对象,能够根据环境不同得到不同的 Bean 对象。或者说将 Spring 和其他组件分离(其他组件不依赖 Spring,但是又想 Spring 管理生成的 Bean)
Bean 的作用域
在默认的情况下,Spring IoC 容器只会对一个 Bean 创建一个实例,但有时候,我们希望能够通过 Spring IoC 容器获取多个实例,我们可以通过 @Scope
注解或者 <bean>
元素中的 scope
属性来设置,例如:
扩展阅读:@Profile 注解 、 条件化装配 Bean
Spring 表达式语言简要说明
Spring 还提供了更灵活的注入方式,那就是 Spring 表达式,实际上 Spring EL 远比以上注入方式都要强大,它拥有很多功能:
- 使用 Bean 的 id 来引用 Bean
- 调用指定对象的方法和访问对象的属性
- 进行运算
- 提供正则表达式进行匹配
- 集合配置
我们来看一个简单的使用 Spring 表达式的例子:
与属性文件中读取使用的 “$
” 不同,在 Spring EL 中则使用 “#
”
扩展阅读: Spring 表达式语言
来源于:---->