装配Bean是Spring的IOC中必不可少的一步,本文介绍通过XML方式和通过注解的方式进行Bean装配的过程。
通过XML方式配置
使用之前果汁的例子,xml的装配过程如下:
<bean name="juice" class="pojo.Juice">
<property name="type" value="草莓"/>
<property name="suger" value="5分糖"/>
<property name="size" value="中杯"/>
</bean>
<bean name="order" class="pojo.Order">
<property name="juice" ref="juice"/>
</bean>
其中name属性定义bean元素的名称,能以逗号或者空格隔开起多个别名,并且可以使用很多的特殊字符。还可以使用id,id属性是Spring属性能找到当前Bean的一个依赖的编号。id和name都可以用来定义bean的名称。
如果id和name都没有声明的话,Spring会采用“全限定名#{number}”的方式生成编号,如上面juice没有定义的话编号就是“pojo.Juice#1”
class属性是一个类的全限定名,通俗讲就是类的位置
property定义类的属性,name是属性的名称,value是值,ref属性引用对应的bean名称
集合的装配过程如下:
首先我们写一个类包含各种集合:
public class ComplexAssmble {
private Long id;
private List<Juice> list;
private Map<String,String> map;
private Properties properties;
private Set<String> set;
private String[] array;
//Getter and Setter
}
xml文件配置如下
<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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean name="juice" class="pojo.Juice">
<property name="type" value="草莓"/>
<property name="suger" value="5分糖"/>
<property name="size" value="中杯"/>
</bean>
<bean id="complexAssembly" class="pojo.ComplexAssmble">
<property name="id" value="1"/>
<property name="list">
<list>
<ref bean="juice"/>
</list>
</property>
<property name="map">
<map>
<entry key="key1" value="value-key-1"/>
<entry key="key2" value="value-key-2"/>
<entry key="key3" value="value-key-3"/>
</map>
</property>
<property name="properties">
<props>
<prop key="prop1">value-prop-1</prop>
<prop key="prop2">value-prop-2</prop>
<prop key="prop3">value-prop-3</prop>
</props>
</property>
<property name="set">
<set>
<value>value-set-1</value>
<value>value-set-2</value>
<value>value-set-3</value>
</set>
</property>
<property name="array">
<array>
<value>value-array-1</value>
<value>value-array-2</value>
<value>value-array-3</value>
</array>
</property>
</bean>
</beans>
- List 属性为对应的 <list> 元素进行装配,然后通过多个 <value> 元素设值,当为集合注入时,使用多个 <ref> 元素的 Bean 属性去引用之前定义好的 Bean
- Map 属性为对应的 <map> 元素进行装配,然后通过多个 <entry> 元素设值,只是 entry 包含一个键值对(key-value)的设置,当为集合注入时,使用多个 <entry> 元素的 key-ref 属性去引用之前定义好的 Bean 作为键,而用 value-ref 属性引用之前定义好的 Bean 作为值
- Properties 属性为对应的 <properties> 元素进行装配,通过多个 <property> 元素设值,只是 properties 元素有一个必填属性 key ,然后可以设置值
- Set 属性为对应的 <set> 元素进行装配,然后通过多个 <value> 元素设值,当为集合注入时,使用多个 <ref> 元素的 bean 去引用之前定义好的 Bean
- 对于数组而言,可以使用 <array> 设置值,然后通过多个 <value> 元素设值。
说明:
property:通过setter对应的方法注入。
constructor-arg:通过构造函数注入。
下面介绍用constructor-arg注入:
首先新建一个带构造函数的Teacher类:
public class Teacher {
private int age;
private String name;
private List<String> justtest;
public Teacher(){}
public Teacher(int age, String name, List<String> justtest) {
this.age = age;
this.name = name;
this.justtest = justtest;
}
//Getter and Setter
}
XML文件中通过构造函数注入bean:
<bean id="teacher" class="pojo.Teacher">
<constructor-arg name="age" value="45"/>
<constructor-arg name="name" value="Bob"/>
<constructor-arg name="justtest">
<list>
<value>test1</value>
<value>test2</value>
<value>test3</value>
</list>
</constructor-arg>
</bean>
如果每条语句都要写constructor-arg或者property 会显得很麻烦,因此Spring引入了命名空间:
1.c命名空间
c-命名空间是在 Spring 3.0 中引入的,它是在 XML 中更为简洁地描述构造器参数的方式
首先在xml的顶部声明:
xmlns:p="http://www.springframework.org/schema/p"
<!--普通的xml方式装配-->
<bean name="juice" class="pojo.Juice">
<property name="type" value="草莓"/>
<property name="suger" value="5分糖"/>
<property name="size" value="中杯"/>
</bean>
<!--p命名空间装配-->
<bean name="juice2" class="pojo.Juice" p:type="草莓" p:suger="5分糖" p:size="中杯"/>
注意:c命名空间装配要以c:开头,如果装配的是对象,则需要加上 -ref ,idea上有很友好的提示功能
2.p命名空间
p命名空间是用setter的方式装配bean,首先也需要在xml的顶部声明:
xmlns:p="http://www.springframework.org/schema/p"
<!--普通的xml方式装配-->
<bean name="juice" class="pojo.Juice">
<property name="type" value="草莓"/>
<property name="suger" value="5分糖"/>
<property name="size" value="中杯"/>
</bean>
<!--p命名空间装配-->
<bean name="juice2" class="pojo.Juice" p:type="草莓" p:suger="5分糖" p:size="中杯"/>
同样的如果装配的是对象,则加入 ref。
3.util命名空间
util命名空间可以简化集合类的装配,同样需要现在xml的顶部声明,注意要用util工具时还需要在xsi:schemaLocation中加入以下两项地址
xmlns:util="http://www.springframework.org/schema/util"
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.0.xsd
<!--普通xml方式装配集合-->
<property name="map">
<map>
<entry key="key1" value="value-key-1"/>
<entry key="key2" value="value-key-2"/>
<entry key="key3" value="value-key-3"/>
</map>
</property>
<!--util方式装配集合-->
<util:map id="map2">
<entry key="key1" value="value-key-1"/>
<entry key="key2" value="value-key-2"/>
<entry key="key3" value="value-key-3"/>
</util:map>
其他集合方式方法相同,idea会有友好的提示
通过注解方式配置
学习完xml方式装配之后,我们会发现如果需要装配的内容很多时xml文件会很臃肿,因此我们会更多的使用注解的方式进行装配:
在 Spring 中,它提供了两种方式来让 Spring IoC 容器发现 bean:
- 组件扫描:通过定义资源的方式,让 Spring IoC 容器扫描对应的包,从而把 bean 装配进来。
- 自动装配:通过注解定义,使得一些依赖关系可以通过注解完成。
1.使用Component注解
@Component("teacher1")
public class Teacher {
@Value("45")
private int age;
@Value("李华")
private String name;
在之前的teacher类中进行修改,增加注解,上面的这段代码相当于之前使用构造器注入或者setter注入:
<bean id="teacher" class="pojo.Teacher">
<property name="age" value="45"/>
<property name="name" value="Bob"/>
</bean>
@Component注解:
表示 Spring IoC 会把这个类扫描成一个 bean 实例,而其中的value属性代表这个类在 Spring 中的id,如果Component不写参数,Spring IoC 容器就默认以类名首字母小写来命名作为 id,配置到容器中。
@Value注解:
表示值的注入,跟在 XML 中写 value 属性是一样的。
然后新建一个类去扫描这些注解,注意新建的类需要和Teacher放在同一个目录之下,用ComponentScan进行扫描。ComponentScan可以选择basePackages和basePackageClasses两个配置项,使用方法如下:
- basePackages:它是由 base 和 package 两个单词组成的,而 package 还是用了复数,意味着它可以配置一个 Java 包的数组,Spring 会根据它的配置扫描对应的包和子包,将配置好的 Bean 装配进来
- basePackageClasses:它由 base、package 和 class 三个单词组成,采用复数,意味着它可以配置多个类, Spring 会根据配置的类所在的包,为包和子包进行扫描装配对应配置的 Bean
//默认扫描所在包的java类
@ComponentScan
public class TeacherScan {
}
//第二种方法
@ComponentScan(basePackages = "pojo")
public class TeacherScan{
}
//第三种方法
@ComponentScan(basePackageClasses = pojo.Teacher.class)
public class TeacherScan{
}
接下来就可以测试了,注意这里不能再使用ClassPathXmlApplicationContext
@Test
public void test4(){
ApplicationContext context=new AnnotationConfigApplicationContext(TeacherScan.class);
Teacher teacher= (Teacher) context.getBean("teacher1");
System.out.println("age is "+teacher.getAge()+" name is " +teacher.getName());
}
测试结果如下:
达到了注入的效果,但是用@Value无法注入对象等,具有一定的局限性。
2.自动装配 @Autowired
自动装配是指由Spring自己发现对应的bean,并且完成装配的过程。
在目录下新建一个service包,首先建一个接口TeacherService,包含一个输出的方法:
package service;
public interface TeacherService {
public void teacherinfo();
}
新建TeacherServiceImpl实现类,使用Autowired注释自动装配:
@Component("teacherserver")
public class TeacherServiceImpl implements TeacherService{
@Autowired
private Teacher teacher=null;
@Override
public void teacherinfo() {
System.out.println("age is "+teacher.getAge()+" name is " +teacher.getName());
}
}
在TeacherScan类的注解中加上对service包的扫描:
@ComponentScan(basePackages = {"pojo","service"})
public class TeacherScan{
}
测试结果发现已经自动装配成功:
@Test
public void test5(){
ApplicationContext context=new AnnotationConfigApplicationContext(TeacherScan.class);
TeacherService service= (TeacherService) context.getBean("teacherserver");
service.teacherinfo();
}
包扫描还可以定义在xml文件中,如下所示:
<context:component-scan base-package="pojo"/>
<context:component-scan base-package="service"/>
测试代码需要相应进行修改:
@Test
public void test6(){
ApplicationContext context=new ClassPathXmlApplicationContext("mytestxml.xml");
TeacherService service= (TeacherService) context.getBean("teacherserver");
service.teacherinfo();
}
运行结果
@Autowired注解在 Spring IoC 定位所有的 Bean 后,再根据类型寻找资源,然后将其注入。
运行过程:定义 Bean —> 初始化 Bean(扫描) —>根据属性需要从 Spring IoC 容器中搜寻满足要求的 Bean —> 满足要求则注入
自动注入时可能会遇到的问题:
上面的Autowired会自动注入类型为Teacher的资源,但是当有两个Teacher资源时,Autowired就不知道选哪一个,新建一个xml文件用来测试同时有两个Teacher资源时Autowired如何选择:
<bean id="teacher1" class="pojo.Teacher">
<constructor-arg name="age" value="45"/>
<constructor-arg name="name" value="Bob"/>
<constructor-arg name="justtest">
<list>
<value>test1</value>
</list>
</constructor-arg>
</bean>
<bean id="teacher2" class="pojo.Teacher">
<constructor-arg name="age" value="35"/>
<constructor-arg name="name" value="Amy"/>
<constructor-arg name="justtest">
<list>
<value>test1</value>
</list>
</constructor-arg>
</bean>
此时测试时发现Autowired装配的一直是teacher1,如何装配teacher2呢?Spring 提供了两个注解:
@Primary 注解:
代表首要的,当 Spring IoC 检测到有多个相同类型的 Bean 资源的时候,会优先注入使用该注解的类。
@Qualifier 注解:
上面所谈及的歧义性,一个重要的原因是 Spring 在寻找依赖注入的时候是按照类型注入引起的。除了按类型查找 Bean
在上述的service实现类中加上这个注解:
@Autowired @Qualifier("teacher2") private Teacher teacher=null;
最后成功装配teacher2资源。
以上都是使用Component注解来装配bean,但是Component只能用在类上,当需要注解到方法之上时,就需要用到@Bean,测试一下@Bean
package pojo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//configuration相当于 XML 文件的根元素,必须有
@Configuration
public class BeanTest {
@Bean(name = "beantest")
public String printsomething(){
return "hello Spring";
}
}
@Test
public void test8(){
ApplicationContext context=new AnnotationConfigApplicationContext("pojo");
String str= (String) context.getBean("beantest");
System.out.println(str);
}
Bean的配置项中包含 4 个配置项:
name: 是一个字符串数组,允许配置多个 BeanName
autowire: 标志是否是一个引用的 Bean 对象,默认值是 Autowire.NO
initMethod: 自定义初始化方法
destroyMethod: 自定义销毁方法
Bean注解的好处就是能够动态获取一个 Bean 对象,能够根据环境不同得到不同的 Bean 对象。