[TOC]
# 装配Bean
## Spring 配置的可选方案
Spring 提供了三种主要的装配机制
- 在XML中进行显示配置
- 在java中进行显示配置
- 隐式的bean发现机制和自动装配
**配置的选择**
Spring配置可以进行搭配使用,但是我们建议尽可能的使用自动配置的机制,显示配置越少越好,当你必须要显示配置bean的时候,我们推荐使用类型安全并且比XML更加强大的JavaConfig. 最后当你想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML.
## 自动化装配Bean
Spring 从两个角度来实现自动化装配:
- 组件扫描:Spring会自动发现应用上下文中所创建的bean.
- 自动装配:Spring自动满足bean之间的依赖。
组件扫描和自动装配组合在一起就能发挥出强大的威力,他们能够将你的显示配置降低到最少。
### 创建可被发现的bean
使用注解标明`@Component`组件类。这个注解会告知Spring要为这个类创建bean.没有必要显示配置bean,因为这个类使用了`@Component`注解
使用注解`@ComponentScan`启用组件扫描
如果没有其他配置的话、`@ComponentScan`默认扫描与配置类相同的包。
### 为组件扫描的bean命名
Spring 应用上下文中所有的bean都给定一个ID.如果我们没有明确的为bean设置ID,那么Spring会根据类名为其指定一个ID.具体来讲就是将类名第一个字母变为小写。
- 如何为bean设置不同的ID
```java
@Component("myTest")
public class Test implements TestI{
....
}
```
- 如何设置组件扫描不同的包
- 通过value属性指明包的名称
```java
@ComponentScan("com.test")
public class TestConfig {}
```
- 通过basePackages 属性进行配置
```java
/**
第一种设置方法
*/
@ComponentScan(basePackages="com.test")
public class TestConfig {}
/**
第二种设置方法,设置扫描多个包
*/
@ComponentScan(basePackages={"com.test","com.test2"})
public class TestConfig {}
```
上面的列子中,所设置的基础包都是以String类型表示的,这种方法的缺点就是类型不安全,如果重构代码的话,那么所指定的基础包就可能会出错误。
```java
/**
第三种设置方法,将其指定为包中所包含的类或接口
*/
@ComponentScan(basePackageClasses={Test1.class,Test2.class})
public class TestConfig {}
```
为basePackageClasses属性所设置的数组包含了类,这些类所在的包将会作为组件扫描的基础包。
### 通过为bean添加注解实现自动装配
简单来说,自动装配就是让Spring自动满足bean依赖的一种方法。在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean.为了声明要进行自动装配,我们可以借助Spring的`@Autowired`注解
不管是构造器,Setter方法还是其他的方法,Spring 都会尝试满足方法参数所声明的依赖。假如有且只有一个bean匹配,那么这个bean会被装配进来。
如果没有匹配的bean,那么应用上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现。我们可以把`@Audowired`的required属性设置为false:
```java
@Audowired(required=false)
public Test(Tests tests){
this.tests=tests;
}
```
required属性设置为false时,Spring会尝试执行自动装配,但是如果没有匹配的bean的话,Spring将会让这个bean处于未装配的状态。需要注意,如果代码没有null检查的话,这个处于未装配状态的属性有可能出现空指针异常。
## 通过Java配置bean
Spring自动化配置是最为推荐的方式,但是有时候自动化配置的方案行不通,因此需要明确配置Spring.
在这种情况下,必须采用显示的装配方式 XML和Java配置。接下来我们先介绍下Java配置
**注意JavaConfig与其他Java代码的区别**
JavaConfig是配置代码,这意味着它不应该包含任何业务逻辑,JavaConfig 也不应该侵入到业务逻辑代码之中。
***尽管不是必须,通常会将JavaConfig放在单独的包中,使它和其他应用程序逻辑分离开来***
### 创建配置类
创建JavaConfig类的关键在于为其添加`@Configuration`注解,`@Configuration`注解表明这个类是一个配置类,该应该包含Spring应用上下文中如何创建bean的细节。
```java
@Configuration
public class TestConfig {}
```
- 如何声明一个简单bean
```java
@Configuration
public class TestConfig {
@Bean
public Test test(){
return new Test1();
}
}
```
**注意**
带有`@Bean`注解的的方法可以采用任何必要的java功能产生bean实例
## 通过XML装配bean
XML配置是spring刚刚出现时的解决方案,现在它已经不是我们唯一的可选方案,而且Spring现在有强大的自动化配置和基于Java的配置,XML不应该再是我们的第一选择了,但是鉴于存在很多基于XML的Spring配置,理解如何在Spring中使用XML还是很重要的。理解XML配置用来帮助我们理解维护已有的XML配置,在完成新的Spring工作时,希望你能够使用自动化配置和JavaConfig.
> 如何创建XML配置规范-如下
```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:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
```
- 声明一个简单bean
```XML
<bean class="com.yc.Test" />
```
这里声明了一个简单的bean,创建这个bean的类通过class属性来指定,并且要使用全限定的类名。
**如果没有明确给的ID**,这个bean将会根据全限定类名来进行命名。本例中bean的id将会是com.yc.Test#0,
'#0'是一个计数的形式,用来区分相同类型的其他bean,如果声明了一个Test,并且没有明确的进行标识。那么它自动得到的ID是com.yc.Test#1。
> 减少繁琐
- 为了减少XML中繁琐的配置,只对那些需要按名字引用的bean通过id属性进行明确的命名
> XML的bean声明与JavaConfi对比
- 显然XML配置并没有JavaConfig那样强,主要表现如下
1. 在JavaConfig配置中,你可以通过任何可以想象到的方法创建bean实例
2. Spring的XML配置并不能从编译期的类型检查中受益,即便它所引用的是实际的类型。(如果重命名了类,会发生什么)
### 如何借助构造器注入初始化bean
在SpringXml配置中,声明DI时,会有多种可选配置方案和风格,具体到构造器注入有两种基本的配置方案:
1. `<constructor-arg>`元素
2. 使用Spring3.0所引入的c-命名空间
以上两种的区别很大程度是是否冗长繁琐。c-命名空间为了解决`<constructor-arg>`元素冗长难懂而设置,但是有些事情`<constructor-arg>`元素可以做到,c-命名空间却无法实现。
> 如何为构造器注入bean引用?
- xml使用方法
```java
public class Test{
private CompatDisc compatDisc;
public Test(CompatDisc compatDisc){
this.compatDisc=compatDisc;
}
}
```
```XML
<bean id="test" class="com.yc.test">
<constructor-arg ref="compactDisc">
</bean>
```
- c-命名空间使用方法
```xml
<bean id="test" class="com.yc.test" c:compatDisc-ref="compatDisc">
```
**c-命名空间使用说明**
在这里,我们使用c-命名空间声明构造器参数,它作为<bean> 元素的一个属性,不过属性的名字有点诡异。图 c-命名空间描述了属性名如何组合而成。
![c-命名空间属性名如何组合而成](image/spring实战/c-命名空间.png)
- c-命名空间 诡异的用法
```xml
<bean id="test" class="com.yc.test" c:_0-ref="compatDisc">
```
这个c-命名空间属性看起来有点怪异,我将参数名替换成了"0",也就是参数的索引。因为在XML中不允许作为属性的第一个字符,因此必须要添加一个下划线作为前缀。
- 使用参数索引的优点
使用索引来识别构造器参数比使用名字更好一些:即使我们移除掉了调试标志,参数却会依然保持相同的顺序,如果有多个构造器参数的话,这当然是很有用处的。
只有一个构造器参数时,我们还有另外一个方案------根本不用去标示参数
```xml
<bean id="test" class="com.yc.test" c:_-ref="compatDisc">
```
> 如何将字面量注入到构造器?
```JAVA
public class TestTwo {
private String title;
private String name;
public TestTwo(String title,String name){
this.title=title;
this.name=name;
}
}
```
1. `<constructor-arg>`元素 进行参数注入
```xml
<bean id="testTwo" class="com.yc.test.TestTwo">
<constructor-arg value="Title 标题"/>
<constructor-arg value="name 姓名"/>
</bean>
```
2. 使用c-命名空间
```xml
<bean id="testTwo" class="com.yc.test.TestTwo"
c:_tilte="Title 标题"
c:_name="name 姓名"
/>
```
或者
```xml
<bean id="testTwo" class="com.yc.test.TestTwo"
c:_0="Title 标题"
c:_1="name 姓名"
/>
```
> 如何装配集合 ?
如下数据:
```JAVA
public class TestTwo {
private String title;
private String name;
private List<String> lists;
public TestTwo(String title,String name,List<String> lists){
this.title=title;
this.name=name;
this.lists=lists;
}
public void dlay(){
System.out.println(lists.toString);
}
}
```
如上我们更新了我们TestTwo,变更后我们必须要提供一个集合列表,最简单的方法是为设置集合列表为null。因为他是构造器参数,所以必须要声明它。不过我们可以用如下方式传递null給它。
```XML
<bean id="testTwo" class="com.yc.test.TestTwo">
<constructor-arg value="Title 标题"/>
<constructor-arg value="name 姓名"/>
<constructor-arg><null/><constructor-arg/>
</bean>
```
<null/> 元素所做的事情与我们期望是一样的:将null传递给构造器。但这并不是解决问题的好办法。虽然注入期也能正常执行。但是调用dlay()方法会遇到,空指针异常。更好的解决方法是提供一个集合列表,要达到这一点,我们有多个可选方案。
1. 首先我们可以使用<list>元素来声明一个列表
```XML
<bean id="testTwo" class="com.yc.test.TestTwo">
<constructor-arg value="Title 标题"/>
<constructor-arg value="name 姓名"/>
<constructor-arg>
<list>
<value>Test1<value/>
<value>Test2<value/>
<value>Test3<value/>
<list/>
<constructor-arg/>
</bean>
```
> 如果集合泛型为自定义对象怎么办?
```JAVA
public TestTwo(String title,String name,List<Test1> lists){...}
```
```XML
<bean id="testTwo" class="com.yc.test.TestTwo">
<constructor-arg value="Title 标题"/>
<constructor-arg value="name 姓名"/>
<constructor-arg>
<list>
<ref bean="test1"/>
<ref bean="test2"/>
<ref bean="test3"/>
<list/>
</constructor-arg>
</bean>
```
> 当构造参数时List时,使用<list>是合理的,尽管如此,我们可以按照同样的方法使用<set>元素
```XML
<bean id="testTwo" class="com.yc.test.TestTwo">
<constructor-arg value="Title 标题"/>
<constructor-arg value="name 姓名"/>
<constructor-arg>
<set>
<ref bean="test1"/>
<ref bean="test2"/>
<ref bean="test3"/>
<set/>
</constructor-arg>
</bean>
```
**总结**
在装配结合方面,<constructor-arg> 比c-命名空间更有优势。目前c-命名空间无法实现装配集合的功能
### 如何设置属性
到目前为止,我们的类完成是通过构造器注入,没有使用属性的setter方法,接下来我们看下SpringXml是如何实现属性注入的。
```JAVA
public class TestThree {
private TestTwo testTwo;
public void setTestTwo(TestTwo testTwo){
this.testTwo=testTwo;
}
}
```
现在TestThree 没有任何构造器(除了隐含的),他没有强依赖,因此我们可以采用如下方式声明Spring bean
```XML
<bean id="testThree" class="com.yc.test.TestThree">
<property name="testTwo" ref="testTwo"/>
</bean>
```
<property>元素为属性的Setter方法所提供的功能与<constructor-arg>元素为构造器所提供的功能是一样的。在本列中,它引用ID为testTwo的bean(通过ref属性),并将其注入到属性testTwo中(通过setTestTwo()方法)
- p-命名空间
```xml
<bean id="testThree" class="com.yc.test.TestThree"
p:testTwo-ref="testTwo"
/>
```
p-命名空间中属性遵循的命名与c-命名空间中属性类似。图p-命名空间属性名组成 阐述了它
![p-命名空间属性名如何组合而成](image/spring实战/p-命名空间.png)
> 如何将字面量注入到属性中
属性也可以注入字面量,这和构造器类似,下面请看例子
```java
public class TestThree {
private TestTwo testTwo;
private String title;
private String name;
private List<String> lists;
public void setTestTwo(TestTwo testTwo){
this.testTwo=testTwo;
}
public void setTitle(String title){
this.title=title;
}
public void setName(String name){
this.name=name;
}
public void setLists(String lists){
this.lists=lists;
}
}
```
```xml
<bean id="testThree" class="com.yc.test.TestThree">
<property name="testTwo" ref="testTwo"></property>
<property name="title" value="Title 标题"></property>
<property name="name" value="Name 姓名"></property>
<property name="lists">
<list>
<value>测试</value>
<value>测试</value>
<value>测试</value>
</list>
</property>
</bean>
```
### util-命名空间及其模式
util-命名空间所提供的功能之一就是<util:list>元素,它会创建一个列表的bean,借助<util:list>,我们可以将集合列表转移到testThree bean之外,如下所示
```xml
<util:list id="lists">
<value>测试</value>
<value>测试</value>
<value>测试</value>
</util:list>
```
现在我们能够像使用其他bean那样,将集合列表bean注入到testThree bean的lists属性中:
```xml
<bean class="soundsystem.TestTwo"
p:title="Title 标题"
p:lists-ref="lists"
p:testTwo-ref="testTwo"
p:name="Nmae 姓名"
/>
```
- Spring util 命名空间中的元素
![Spring util 命名空间中的元素](image/spring实战/spring-util.png)
## 导入和混合配置
在spring应用中,我们可能同时使用自动化和显示配置,即便我们更喜欢通过JavaConfig实现显示配置,但有时候XML却是最佳方案。
> 如何在JavaConfig中引用XML配置
答案是`@ImportResoure`注解
```java
@Configuration
@Import(CDPlayerConfig.class)
@ImportResource("classpath:cd-config.xml")
public class SystemConfig{
}
```
> 如何在XML配置中引用JavaConfig
在JavaConfig 配置中,我们已经展现了如何使用`@Import`和`@ImportResource`来拆分JavaConfig类。
在XML中,我们可以使用<import>元素来拆分XML配置。对于JavaConfig类导入XML配置,我们只需要把JavaConfig配置声明bean就行了。