文章目录
1、装配Bean概述
在平时的大部分开发场景中,我们都会使用org.springFramework.context.ApplicationContext的具体实现类,因为对应的Spring IoC容器功能相对强大。而在Spring中提供了3种方法进行配置:
- 在XML中显式配置
- 在Java的接口和类中实现配置
- 隐式Bean的发现机制和自动装配原则
在显示的开发中,这3中方式都会被用到,并且在学习和工作中常常混合使用,因此需要我们明白这三种方式的优先级,即我们应该如何选择使用哪种方式去把Bean发布到Spring IoC容器中。
第一,基于“约定优于配置”的原则,最优先的应该是通过隐式Bean的发现机制和自动装配的原则。这样的好处是减少程序开发者的决定权,简单又不失灵活。
第二,在没有办法使用自动装配原则的情况下应该优先考虑Java接口和类中实现配置,这样的好处是避免XML配置的泛滥,也更为容易。这种场景典型的例子是一个父类有多个子类,比如学生类有两个子类:男生类和女生类,通过IoC容器初始化一个学生类,容器将无法知道使用哪个子类去初始化,这个时候可以使用Java的注解配置去指定。
第三,在上述方法都无法使用的情况下,那么只能选择XML去配置Spring IoC容器。由于现实开发中常常用到第三方类库,有些类并不是我们开发的,我们无法修改里面的代码,这个时候就通过XML的方式配置使用了。
通俗来讲,当配置的类时你自身正在开发的工程,那么应该考虑Java配置为主,而Java配置又分为自动装配和Bean名称配置。在没有歧义的基础上,优先使用自动装配,这样就可以减少大量的XML配置。如果所需配置的类并不是你的工程开发的,那么建议使用XML的方式。
2、演示项目环境搭建
该演示项目开发环境为IDEA,Project名为“Spring Demo”。
首先,创建名为“BeanAssembling”的Spring Module,具体过程如下:
通过该种方式即可在IDEA中快速构建Spring开发环境。然后在lib文件夹中补充导入测试及日志jar包如下:
注意,这里采用的Junit的版本与上一篇博客中的版本不一致,该篇博客中使用的是Junit-4.12,之所以没有选择之前的版本,是因为由于之前的版本过低可能会导致在使用Spring上下文测试的时候出现异常,详细信息参考如下博客:
- spring+junit4时用Junit单元测试遇到的bug:java.lang.ExceptionInInitializerError.
- 【Junit】JUnit-4.12使用报java.lang.NoClassDefFoundError: org/hamcrest/SelfDescribing错误.
3、通过XML配置装配Bean
使用XML装配Bean需要定义对应的XML,这里需要引入对应的XML模式(XSD)文件,这些文件会定义配置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">
<!--Spring Bean配置代码-->
</beans>
在上述配置中引入了一个beans的定义,它是一个根元素,而XSD文件也被引入了,这样它所定义的元素将可以定义对应的Spring Bean。
3.1 装配简易值
首先,在com.ccff.spring.assembling.pojo包下创建一个Student类,具体代码如下:
package com.ccff.spring.assembling.pojo;
public class Student {
private String name;
private String sex;
private String studentNo;
private Integer age;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", studentNo='" + studentNo + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getStudentNo() {
return studentNo;
}
public void setStudentNo(String studentNo) {
this.studentNo = studentNo;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
然后在spring-config配置文件中对Student类进行装配,具体配置如下:
<?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">
<!--Spring Bean配置代码-->
<!--通过XML配置装配Bean:装配简易值-->
<bean id="student" class="com.ccff.spring.assembling.pojo.Student">
<property name="name" value="学生一" />
<property name="studentNo" value="S001" />
<property name="age" value="24" />
<property name="sex" value="男" />
</bean>
</beans>
其中,id属性时Spring找到的这个Bean的编号,不过id不是一个必需的属性,如果没有声明它,那么Spring将采用“全限定名#{number}”的格式生成编号。如果只声明一个这样的类,而没有声明id=“student”,那么Spring为其生成的编号就是“com.ccff.spring.assembling.pojo.Student#0”。当它第二次声明没有id属性的Bean时,编号就是“com.ccff.spring.assembling.pojo.Student#1”,但是一般我们都会选择自己定义id,因为自动生成的id会比较麻烦。
class属性显然是一个类全限定名。
property元素时定义类的属性,其中name属性定义的是属性名称,而value是其值。
在com.ccff.spring.assembling.test包下创建名为TestStudent的测试类,并在测试类中添加名为“TestEasyAssembingByXML”的测试方法,具体代码如下:
package com.ccff.spring.assembling.test;
import com.ccff.spring.assembling.pojo.Student;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestStudent {
private ApplicationContext context;
@Before
public void getContainer(){
context = new ClassPathXmlApplicationContext("spring-config.xml");
}
@Test
public void TestEasyAssembingByXML(){
Student student1 = (Student) context.getBean("student");
System.out.println(student1);
}
}
运行该测试方法后,查看输出到控制台的日志信息如下,则说明Student类的bean已经被创建。
这样的定义很简单,但是有时候需要注入一些自定义的类。例如每个学生有属于某一个学校,而学校可以单独抽象出一个类。在com.ccff.spring.assembling.pojo包下创建School类,具体代码如下所示:
package com.ccff.spring.assembling.pojo;
public class School {
private String school_name;
private String school_no;
private String school_location;
public String getSchool_name() {
return school_name;
}
public void setSchool_name(String school_name) {
this.school_name = school_name;
}
public String getSchool_no() {
return school_no;
}
public void setSchool_no(String school_no) {
this.school_no = school_no;
}
public String getSchool_location() {
return school_location;
}
public void setSchool_location(String school_location) {
this.school_location = school_location;
}
@Override
public String toString() {
return "School{" +
"school_name='" + school_name + '\'' +
", school_no='" + school_no + '\'' +
", school_location='" + school_location + '\'' +
'}';
}
}
然后,修改Student类,为其指定所属的学校,具体代码如下:
package com.ccff.spring.assembling.pojo;
public class Student {
private String name;
private String sex;
private String studentNo;
private Integer age;
private School school;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getStudentNo() {
return studentNo;
}
public void setStudentNo(String studentNo) {
this.studentNo = studentNo;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", studentNo='" + studentNo + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
由于增加了School类,因此需要在spring-config中先配置School类Bean。然后在配置的school bean中增加School类型的属性的配置,具体配置如下:
<?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">
<!--Spring Bean配置代码-->
<!--通过XML配置装配Bean:装配简易值-->
<bean id="school" class="com.ccff.spring.assembling.pojo.School">
<property name="school_name" value="中国科学院大学" />
<property name="school_no" value="Sch001" />
<property name="school_location" value="北京市怀柔区怀北镇怀北庄中国科学院大学雁西湖校区" />
</bean>
<bean id="student" class="com.ccff.spring.assembling.pojo.Student">
<property name="name" value="学生一" />
<property name="studentNo" value="S001" />
<property name="age" value="24" />
<property name="sex" value="男" />
<property name="school" ref="school" />
</bean>
</beans>
修改测试方法TestEasyAssembingByXML如下:
package com.ccff.spring.assembling.test;
import com.ccff.spring.assembling.pojo.School;
import com.ccff.spring.assembling.pojo.Student;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestStudent {
private ApplicationContext context;
@Before
public void getContainer(){
context = new ClassPathXmlApplicationContext("spring-config.xml");
}
@Test
public void TestEasyAssembingByXML(){
School school = (School) context.getBean("school");
System.out.println(school);
Student student1 = (Student) context.getBean("student");
System.out.println(student1);
}
}
这里先定义了一个id为school的Bean,然后在id为student的Bean中使用ref属性引用对应的Bean,而school正是之前创建的school的id,这样就可以相互引用了。
运行该测试方法后,查看输出到控制台的日志信息如下,则说明Student类的bean已经被创建。
3.2 装配集合
有些时候需要做一些复杂的装配工作,比如Set、Map、List、Array和Properties等。为了演示这些复杂的装配工作,首先在com.ccff.spring.assembling.pojo包中定义名为“ComplexAssembly”的类,具体代码如下:
package com.ccff.spring.assembling.pojo;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class ComplexAssembly {
private Long id;
private List<String> list;
private Map<String,String> map;
private Properties properties;
private Set<String> set;
private String[] array;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public Properties getProperties() {
return properties;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
public Set<String> getSet() {
return set;
}
public void setSet(Set<String> set) {
this.set = set;
}
public String[] getArray() {
return array;
}
public void setArray(String[] array) {
this.array = array;
}
}
接下来在spring-config中完成这些复杂的装配工作,具体代码如下所示:
<?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">
<!--Spring Bean配置代码-->
<!--通过XML配置装配Bean:装配简易值-->
<bean id="school" class="com.ccff.spring.assembling.pojo.School">
<property name="school_name" value="中国科学院大学" />
<property name="school_no" value="Sch001" />
<property name="school_location" value="北京市怀柔区怀北镇怀北庄中国科学院大学雁西湖校区" />
</bean>
<bean id="student" class="com.ccff.spring.assembling.pojo.Student">
<property name="name" value="学生一" />
<property name="studentNo" value="S001" />
<property name="age" value="24" />
<property name="sex" value="男" />
<property name="school" ref="school" />
</bean>
<!--通过XML配置装配Bean:装配集合-->
<bean id="complexAssembly" class="com.ccff.spring.assembling.pojo.ComplexAssembly">
<property name="id" value="1" />
<property name="list">
<list>
<value>value-list-1</value>
<value>value-list-2</value>
<value>value-list-3</value>
</list>
</property>
<property name="map">
<map>
<entry key="key1" value="value-map-1" />
<entry key="key2" value="value-map-2" />
<entry key="key3" value="value-map-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元素设值。
Map属性为对应的map元素进行装配,然后通过多个entry元素设值,只是entry包含一个键(key)和一个值(value)的设置。
Properties属性为对应的props元素进行装配,然后通过多个prop元素设置,只是prop元素有一个必填的key,然后可以设置值。
Set属性为对应的set元素进行装配,然后通过多个value元素设值。
对于数组而言,可以使用array设置值,然后通过多个value元素设值。
在com.ccff.spring.assembling.test包下创建名为“TestComplexAssembly”的测试类,并添加名为“TestComplexAssembingByXML”的测试方法,具体代码如下:
package com.ccff.spring.assembling.test;
import com.ccff.spring.assembling.pojo.ComplexAssembly;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class TestComplexAssembly {
private ApplicationContext context;
@Before
public void getContainer(){
context = new ClassPathXmlApplicationContext("spring-config.xml");
}
@Test
public void TestComplexAssembingByXML(){
ComplexAssembly complexAssembly = (ComplexAssembly) context.getBean("complexAssembly");
List list = complexAssembly.getList();
Map map = complexAssembly.getMap();
Properties properties = complexAssembly.getProperties();
Set set = complexAssembly.getSet();
String[] array = complexAssembly.getArray();
System.out.println("List集合为:");
System.out.println(list);
System.out.println();
System.out.println("Map集合为:");
System.out.println(map);
System.out.println();
System.out.println("Properties为:");
System.out.println(properties);
System.out.println();
System.out.println("Set为:");
System.out.println(set);
System.out.println();
System.out.println("Array为:");
System.out.println(array);
System.out.println();
}
}
运行该测试方法后,查看输出到控制台的日志信息如下,则说明bean已经被创建。
从上面可以看到对字符串的各个集合的装载,但是有些时候可能需要更为复杂的装载,比如一个List可以是一个系列类的对象,又如一个Map集合类,键可以是一个类对象,而值也要是一个类对象,这些也是Java中常常可以看到的。因此,接下来继续演示更为复杂的集合的装配。
首先,在com.ccff.spring.assembling.pojo包下创建名为“Role”和“User”的两个POJO类,具体代码如下:
package com.ccff.spring.assembling.pojo;
public class Role {
private Long roleId;
private String roleName;
private String roleNote;
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public String getRoleNote() {
return roleNote;
}
public void setRoleNote(String roleNote) {
this.roleNote = roleNote;
}
@Override
public String toString() {
return "Role{" +
"roleId=" + roleId +
", roleName='" + roleName + '\'' +
", roleNote='" + roleNote + '\'' +
'}';
}
}
package com.ccff.spring.assembling.pojo;
public class User {
private Long userId;
private String userName;
private String userNote;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserNote() {
return userNote;
}
public void setUserNote(String userNote) {
this.userNote = userNote;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", userNote='" + userNote + '\'' +
'}';
}
}
然后,为测试上面的用户和角色,在com.ccff.spring.assembling.pojo包下创建名为“UserRoleAssembly”的POJO类,具体代码如下:
package com.ccff.spring.assembling.pojo;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class UserRoleAssembly {
private Long id;
private List<Role> list;
private Map<Role,User> map;
private Set<Role> set;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<Role> getList() {
return list;
}
public void setList(List<Role> list) {
this.list = list;
}
public Map<Role, User> getMap() {
return map;
}
public void setMap(Map<Role, User> map) {
this.map = map;
}
public Set<Role> getSet() {
return set;
}
public void setSet(Set<Role> set) {
this.set = set;
}
}
接下来在spring-config中对这些类进行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">
<!--Spring Bean配置代码-->
<!--通过XML配置装配Bean:装配简易值-->
<bean id="school" class="com.ccff.spring.assembling.pojo.School">
<property name="school_name" value="中国科学院大学" />
<property name="school_no" value="Sch001" />
<property name="school_location" value="北京市怀柔区怀北镇怀北庄中国科学院大学雁西湖校区" />
</bean>
<bean id="student" class="com.ccff.spring.assembling.pojo.Student">
<property name="name" value="学生一" />
<property name="studentNo" value="S001" />
<property name="age" value="24" />
<property name="sex" value="男" />
<property name="school" ref="school" />
</bean>
<!--通过XML配置装配Bean:装配字符串集合-->
<bean id="complexAssembly" class="com.ccff.spring.assembling.pojo.ComplexAssembly">
<property name="id" value="1" />
<property name="list">
<list>
<value>value-list-1</value>
<value>value-list-2</value>
<value>value-list-3</value>
</list>
</property>
<property name="map">
<map>
<entry key="key1" value="value-map-1" />
<entry key="key2" value="value-map-2" />
<entry key="key3" value="value-map-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>
<!--通过XML配置装配Bean:装配复杂集合-->
<bean id="role1" class="com.ccff.spring.assembling.pojo.Role">
<property name="roleId" value="1" />
<property name="roleName" value="role-name-1" />
<property name="roleNote" value="role-note-1" />
</bean>
<bean id="role2" class="com.ccff.spring.assembling.pojo.Role">
<property name="roleId" value="2" />
<property name="roleName" value="role-name-2" />
<property name="roleNote" value="role-note-2" />
</bean>
<bean id="user1" class="com.ccff.spring.assembling.pojo.User">
<property name="userId" value="1" />
<property name="userName" value="user-name-1" />
<property name="userNote" value="user-note-1" />
</bean>
<bean id="user2" class="com.ccff.spring.assembling.pojo.User">
<property name="userId" value="2" />
<property name="userName" value="user-name-2" />
<property name="userNote" value="user-note-2" />
</bean>
<bean id="userRoleAssembly" class="com.ccff.spring.assembling.pojo.UserRoleAssembly">
<property name="id" value="1" />
<property name="list">
<list>
<ref bean="role1" />
<ref bean="role2" />
</list>
</property>
<property name="map">
<map>
<entry key-ref="role1" value-ref="user1" />
<entry key-ref="role2" value-ref="user2" />
</map>
</property>
<property name="set">
<set>
<ref bean="role1" />
<ref bean="role2" />
</set>
</property>
</bean>
</beans>
这里先定义了两个角色Bean(role1和role2)和两个用户Bean(user1和user2),它们和之前的定义并没有什么不同,只是后面的定义略微不一样而已。其中:
List属性使用list元素定义注入,使用多个ref元素的Bean属性去引用之前定义好的Bean。
Map属性使用map元素定义注入,使用多个entry元素的key-ref属性去引用之前定义好的Bean作为键,而用value-ref属性去引用之前定义好的Bean作为值。
Set属性使用set元素定义注入,使用多个ref元素的Bean去引用之前定义好的Bean。
在TestComplexAssembly测试类中添加名为“TestUserRoleAssembly”的测试方法,具体代码如下所示:
package com.ccff.spring.assembling.test;
import com.ccff.spring.assembling.pojo.ComplexAssembly;
import com.ccff.spring.assembling.pojo.UserRoleAssembly;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class TestComplexAssembly {
private ApplicationContext context;
@Before
public void getContainer(){
context = new ClassPathXmlApplicationContext("spring-config.xml");
}
@Test
public void TestComplexAssembingByXML(){
ComplexAssembly complexAssembly = (ComplexAssembly) context.getBean("complexAssembly");
List list = complexAssembly.getList();
Map map = complexAssembly.getMap();
Properties properties = complexAssembly.getProperties();
Set set = complexAssembly.getSet();
String[] array = complexAssembly.getArray();
System.out.println("List集合为:");
System.out.println(list);
System.out.println();
System.out.println("Map集合为:");
System.out.println(map);
System.out.println();
System.out.println("Properties为:");
System.out.println(properties);
System.out.println();
System.out.println("Set为:");
System.out.println(set);
System.out.println();
System.out.println("Array为:");
System.out.println(array);
System.out.println();
}
@Test
public void TestUserRoleAssembly(){
UserRoleAssembly userRoleAssembly = (UserRoleAssembly) context.getBean("userRoleAssembly");
List list = userRoleAssembly.getList();
Map map = userRoleAssembly.getMap();
Set set = userRoleAssembly.getSet();
System.out.println("UserRoleAssembly的id为:"+userRoleAssembly.getId());
System.out.println("UserRoleAssembly的list为:");
System.out.println(list);
System.out.println("UserRoleAssembly的Map为:");
System.out.println(map);
System.out.println("UserRoleAssembly的Set为:");
System.out.println(set);
}
}
运行该测试方法后,查看输出到控制台的日志信息如下,则说明bean已经被创建。
3.3 命名空间装配
Spring还提供了对应的命名空间的定义,只是在使用命名空间的时候要先引入对应的命名空间和XML模式(XSD)文件。配置样例如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
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">
<!--Spring Bean配置代码-->
</bean>
与之前的spring-config的约束文件相比,增加了如下两个约束:
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
其中c前缀使用构造方法的依赖注入方式实现Bean的装配,而p前缀代表引用属性。由于之前的Role类和User类不具有构造方法,因此对之前的Role类和User类进行修改,显式地添加有参与无参构造方法,具体代码如下:
package com.ccff.spring.assembling.pojo;
public class Role {
private Long roleId;
private String roleName;
private String roleNote;
public Role() {
}
public Role(Long roleId, String roleName, String roleNote) {
this.roleId = roleId;
this.roleName = roleName;
this.roleNote = roleNote;
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public String getRoleNote() {
return roleNote;
}
public void setRoleNote(String roleNote) {
this.roleNote = roleNote;
}
@Override
public String toString() {
return "Role{" +
"roleId=" + roleId +
", roleName='" + roleName + '\'' +
", roleNote='" + roleNote + '\'' +
'}';
}
}
package com.ccff.spring.assembling.pojo;
public class User {
private Long userId;
private String userName;
private String userNote;
public User() {
}
public User(Long userId, String userName, String userNote) {
this.userId = userId;
this.userName = userName;
this.userNote = userNote;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserNote() {
return userNote;
}
public void setUserNote(String userNote) {
this.userNote = userNote;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", userNote='" + userNote + '\'' +
'}';
}
}
在spring-config中添加了约束后,添加如下配置,实现通过c和p前缀装配Bean,具体配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
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">
<!--Spring Bean配置代码-->
<!--通过命名空间装配Bean:简单的设值-->
<bean id="role3" class="com.ccff.spring.assembling.pojo.Role" c:_0="3" c:_1="role-name-3" c:_2="role-note-3" />
<bean id="role4" class="com.ccff.spring.assembling.pojo.Role" p:roleId="4" p:roleName="role-name-4" p:roleNote="role-note-4" />
</beans>
id为role3的角色定义,c:_0代表构造方法的第一个参数,c:_1代表的是第二个参数,c:_2代表的是第三个参数,以此类推。
id为role4的角色定义,p代表引用属性,其中p:id=“2”以2为值,使用setId方法设置。roleName、roleNote属性也是一样的道理。
在com.ccff.spring.assembling.test包下创建名为“TestNamespace”的测试类,并添加名为“TestAssembingBeanByCAndP”的测试方法,具体代码如下:
package com.ccff.spring.assembling.test;
import com.ccff.spring.assembling.pojo.Role;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestNamespace {
private ApplicationContext context;
@Before
public void getContainer(){
context = new ClassPathXmlApplicationContext("spring-config.xml");
}
@Test
public void TestAssembingBeanByCAndP(){
Role role3 = (Role) context.getBean("role3");
Role role4 = (Role) context.getBean("role4");
System.out.println(role3);
System.out.println(role4);
}
}
运行该测试方法后,查看输出到控制台的日志信息如下,则说明bean已经被创建。
上面介绍了通过c和p标签进行简单设置的方式,而在实际开发中也可能需要为属性设值,例如之前的UserRoleAssembly类。此时可以借助引入XML文档(XSD)文件的方法,把UserRoleAssembly类实例注册给Spring IoC容器。此时应首先添加约束,具体配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
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
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<!--Spring Bean配置代码-->
<!--通过命名空间装配Bean:简单的设值-->
<bean id="role3" class="com.ccff.spring.assembling.pojo.Role" c:_0="3" c:_1="role-name-3" c:_2="role-note-3" />
<bean id="role4" class="com.ccff.spring.assembling.pojo.Role" p:roleId="4" p:roleName="role-name-4" p:roleNote="role-note-4" />
<!--通过命名空间装配Bean:为bean的属性设值-->
<bean id="role5" class="com.ccff.spring.assembling.pojo.Role" c:_0="5" c:_1="role-name-5" c:_2="role-note-5" />
<bean id="role6" class="com.ccff.spring.assembling.pojo.Role" p:roleId="6" p:roleName="role-name-6" p:roleNote="role-note-6" />
<bean id="user3" class="com.ccff.spring.assembling.pojo.User" c:_0="3" c:_1="user-name-3" c:_2="user-note-3" />
<bean id="user4" class="com.ccff.spring.assembling.pojo.User" p:userId="4" p:userName="user-name-4" p:userNote="user-note-4" />
<util:list id="list">
<ref bean="role5" />
<ref bean="role6" />
</util:list>
<util:map id="map">
<entry key-ref="role5" value-ref="user3" />
<entry key-ref="role6" value-ref="user4" />
</util:map>
<util:set id="set">
<ref bean="role5" />
<ref bean="role6" />
</util:set>
<bean id="userRoleAssembly2" class="com.ccff.spring.assembling.pojo.UserRoleAssembly" p:id="2" p:list-ref="list" p:map-ref="map" p:set-ref="set" />
</beans>
在上面的配置中首先定义了两个角色类对象(role5和role6)和两个用户类对象(user3和user4)。通过命名空间util去定义List、Map和Set对象,这些在装配集合的时候也已经讨论过。最后就是定义id为“userRoleAssembly2”的Bean,这里的list-ref代表采用List属性,但是其值引用上下文定义好的Bean,这里显然就是util命名空间定义的List。Map和Set的配置同理。
修改TestNamespace测试类,添加名为“TestAssembingBeanByXMLUtils”的测试方法,具体代码如下:
package com.ccff.spring.assembling.test;
import com.ccff.spring.assembling.pojo.Role;
import com.ccff.spring.assembling.pojo.UserRoleAssembly;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class TestNamespace {
private ApplicationContext context;
@Before
public void getContainer(){
context = new ClassPathXmlApplicationContext("spring-config.xml");
}
@Test
public void TestAssembingBeanByCAndP(){
Role role3 = (Role) context.getBean("role3");
Role role4 = (Role) context.getBean("role4");
System.out.println(role3);
System.out.println(role4);
}
@Test
public void TestAssembingBeanByXMLUtils(){
UserRoleAssembly userRoleAssembly = (UserRoleAssembly) context.getBean("userRoleAssembly2");
List list = userRoleAssembly.getList();
Map map = userRoleAssembly.getMap();
Set set = userRoleAssembly.getSet();
System.out.println("List集合为:");
System.out.println(list);
System.out.println();
System.out.println("Map集合为:");
System.out.println(map);
System.out.println();
System.out.println("Set为:");
System.out.println(set);
}
}
运行该测试方法后,查看输出到控制台的日志信息如下,则说明bean已经被创建。
从上面可知,在使用XML定义的时候,无论使用原始的配置,还是使用命名空间定义都是允许的。
4、通过注解装配Bean
前面已经介绍了如何使用XML的方式去装配Bean,但是更多的时候已经不再推荐使用XML的方式去装配Bean,更多的时候会考虑使用注解(annotation)的方式去装配Bean。使用注解的方式可以减少XML的配置,注解功能更为强大,它既能实现XML功能,也提供了自动装配的功能。采用了自动装配后,程序员所需要做的决断就少了,更加有利于程序的开发,这就是“约定优于配置”的开发原则。
在Spring中,它提供了两种方式来让Spring IoC容器发现Bean。第一种是组件扫描: 通过定义资源的方式,让Spring IoC容器扫描对应的包,从而把Bean装配进来。第二种是自动装配: 通过注解定义,使得一些依赖关系可以通过注解完成。
通过扫描和自动装配,大部分的工程都可以用Java配置完成,而不是XML,这样可以有效减少配置和引入大量XML,它解决了在Spring3之前的版本需要大量的XML配置的问题。目前注解已经成为Spring开发的主体,但不使用XML也存在着一定的弊端,比如系统存在多个公共的配置文件(比如多个properties和XML文件),如果写在注解李,那么那些公共资源的配置就会比较分散了,这样不利于统一的管理,又或者一些类来自于第三方,而不是我们系统开发的配置文件,这是利用XML的方式来完成会更加明确一些,因此目前企业所流行的开发方式为:以注解为主,以XML为辅。
4.1 使用@Component装配Bean
在com.ccff.spring.assembling.pojo包下创建名为“Animal”的POJO类,具体代码如下所示:
package com.ccff.spring.assembling.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component(value = "animal")
public class Animal {
@Value("animal_name_1")
private String animal_name;
@Value("animal_type_1")
private String animal_type;
@Value("1000000")
private Long animal_number;
public String getAnimal_name() {
return animal_name;
}
public void setAnimal_name(String animal_name) {
this.animal_name = animal_name;
}
public String getAnimal_type() {
return animal_type;
}
public void setAnimal_type(String animal_type) {
this.animal_type = animal_type;
}
public Long getAnimal_number() {
return animal_number;
}
public void setAnimal_number(Long animal_number) {
this.animal_number = animal_number;
}
@Override
public String toString() {
return "Animal{" +
"animal_name='" + animal_name + '\'' +
", animal_type='" + animal_type + '\'' +
", animal_number=" + animal_number +
'}';
}
}
上面在POJO类中通过注解的方式已经完成了对装配Bean的基本配置,其中:
注解@Component 代表Spring IoC会把这个类扫描生成Bean实例,而其中的value属性代表这个类在Spring中的id,这就相当于XML方式定义的Bean的id,由于此时注解@Component中只有一个属性,因此也可以简写成@Component(“animal”)。甚至可以直接写成@Component,对于不写value值的,Spring IoC容器就默认类名,但是以首字母小写的形式作为id,为其生成对象,配置到容器中。
注解@Value 代表的是值的注入,这里只是简单注入一些值,其中animal_number是一个Long型,注入的时候Spring会为其转化类型。
现在有了这个类,但是还不能进行测试,因为Spring IoC并不知道需要去哪里扫描对象,这个时候可以使用一个Java Config来告诉它。在com.ccff.spring.assembling.pojo包下创建名为“PojoConfig”的Java Config类,具体代码如下所示:
package com.ccff.spring.assembling.pojo;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class PojoConfig {
}
这个类十分简单,几乎没有逻辑,但是要注意两点:
第一,该Java Config配置类一定要和Animal类处于同一包下,即均处于com.ccff.spring.assembling.pojo包下。
第二,@ComponentScan注解代表进行扫描,默认扫描当前包的路径,POJO的包名和它保持一致才能扫描,否则是没有的。
接下来就可以通过Spring定义好的Spring IoC容器的实现类——AnnotationConfigApplicationContext去生成IoC容器了。在com.ccff.spring.assembling.test包下创建名为“TestAnnotationAssembly”的测试类,并创建名为“TestComponent”的测试方法,具体代码如下所示:
package com.ccff.spring.assembling.test;
import com.ccff.spring.assembling.pojo.Animal;
import com.ccff.spring.assembling.pojo.PojoConfig;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestAnnotationAssembly {
private ApplicationContext context;
@Before
public void getContainer(){
context = new AnnotationConfigApplicationContext(PojoConfig.class);
}
@Test
public void TestComponent(){
Animal animal = context.getBean(Animal.class);
System.out.println(animal);
}
}
这里使用了AnnotationConfigApplicationContext类去初始化Spring IoC容器,它的配置项是PojoConfig类。这样Spring IoC就会根据注解的配置去解析对应的资源,来生成IoC容器了。
执行上面的测试方法后,查看输出到控制台的日志信息如下,则说明Bean装配完成。
通过上面的配置能够正确的装配Animal的Bean,但是仍然存在着两个弊端:其一,对于@ComponentScan注解,它目前只能扫描其所在包的Java类,但是更多的时候真正需要的是可以扫描所指定的类;其二,上面只注入了一些简单的值(基本数据类型),而没有注入对象,同样在现实开发中可以注入对象时十分重要的,也是常见的场景。接下来该系列博客会陆续完善并改正这两个弊端。
4.2 使用@ComponentScan指定扫描路径
@ComponentScan存在着两个配置项:第一个是basePackages,它是由base和package两个单词组成的,而package还使用了复数,意味着它可以配置一个Java包的数组,Spring会根据它的配置扫描对应的包和子包,将配置好的Bean装配进来;第二个是basePackageClasses,它是由base、package和class三个单词组成的,采用复数,意味着它可以配置多个类,Spring会根据配置的类所在的包,为包和子包进行扫描装配对应配置的Bean。
为了更好地验证@ComponentScan的两个配置项,首先在com.ccff.spring.assembling.service包下定义名为“AnimalService”的接口,具体代码如下:
package com.ccff.spring.assembling.service;
import com.ccff.spring.assembling.pojo.Animal;
public interface AnimalService {
void animalIdentification(Animal animal);
}
使用接口来编写一些操作类是Spring所推荐的,它可以将定义和实现相分离,这样就更为灵活了。对于一个接口而言,这里开发了一个实现类,在com.ccff.spring.assembling.service包下创建名为“AnimalServiceImpl”的实现类,具体代码如下:
package com.ccff.spring.assembling.service;
import com.ccff.spring.assembling.pojo.Animal;
import org.springframework.stereotype.Component;
@Component
public class AnimalServiceImpl implements AnimalService {
@Override
public void animalIdentification(Animal animal) {
System.out.println("动物身份是:");
System.out.println(animal);
}
}
这里的@Component表明它是一个Spring所需要的Bean,而且也实现了对应的AnimalService接口所定义的animalIdentification方法。为了装配AnimalServiceImpl和Animal这两个Bean,需要给@ComponentScan注解加上对应的配置。在com.ccff.spring.assembling.config包下创建名为“AnimalConfig”的Java Config类,具体代码如下:
package com.ccff.spring.assembling.config;
import com.ccff.spring.assembling.pojo.Animal;
import com.ccff.spring.assembling.service.AnimalServiceImpl;
import org.springframework.context.annotation.ComponentScan;
//@ComponentScan(basePackageClasses = {Animal.class,AnimalServiceImpl.class})
//@ComponentScan(basePackages = {"com.ccff.spring.assembling.pojo","com.ccff.spring.assembling.service"})
@ComponentScan(basePackages = {"com.ccff.spring.assembling.pojo","com.ccff.spring.assembling.service"},basePackageClasses = {Animal.class,AnimalServiceImpl.class})
public class AnimalConfig {
}
这里对扫描包的定义,可以采用任意一个@ComponentScan去定义。如果采用多个@ComponentScan去定义对应的包,但是每定义一个@ComponentScan,Spring就会为所定义的类生成一个新的对象,也就是所配置的Bean将会生成多个实例,这往往不是我们需要的。
对于已经定义了basePackages和basePackageClasses的@ComponentScan,Spring会进行专门的区分,也就是说在同一个@ComponentScan中即使重复定义相同的包或者存在其子包定义,也不会造成因同一个Bean的多次扫描,而导致一次配置生成多个对象。
基于上述几点,建议不要采用多个@ComponentScan注解进行配置,因为一旦有重复的包和子包就会产生重复的对象,这往往不是真是的需求。对于basePackages和basePackageClasses的选择问题,basePackages的可读性会更好一些,因此在项目中会优先选择使用它,但是在需要大量重构的工程中,尽量不要使用basePackages定义,因为很多时候重构修改包名需要反复地配置,而IDE不会给你任何的提示。而采用basePackageClasses,当你对包移动的时候,IDE会报错提示,并且可以轻松处理这些错误。
修改TestAnnotationAssembly测试类中的getContainer方法,修改获取Java Config文件为AnimalConfig.class。同时添加名为“TestComponentScan”的测试方法,具体修改的代码如下:
package com.ccff.spring.assembling.test;
import com.ccff.spring.assembling.config.AnimalConfig;
import com.ccff.spring.assembling.pojo.Animal;
import com.ccff.spring.assembling.service.AnimalService;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestAnnotationAssembly {
private ApplicationContext context;
@Before
public void getContainer(){
context = new AnnotationConfigApplicationContext(AnimalConfig.class);
}
@Test
public void TestComponent(){
Animal animal = context.getBean(Animal.class);
System.out.println(animal);
}
@Test
public void TestComponentScan(){
Animal animal = context.getBean(Animal.class);
AnimalService animalService = context.getBean(AnimalService.class);
animalService.animalIdentification(animal);
}
}
执行上面的测试方法后,查看输出到控制台的日志信息如下,则说明Bean装配完成。
4.3 使用@Autowired实现自动装配
到目前为止,我们都在讨论使用注解的方式实现简单类型的注入和装配,关于通过使用注解注入对象的问题,在注解中略微有点复杂,在大部分的情况下建议使用自动装配,因为这样可以减少配置的复杂度。
通过学习Spring IoC容器,我们知道Spring是先完成Bean的定义和生成,然后寻找需要注入的资源。也就是说当Spring生成所有的Bean后,如果发现这个注解,它就会在Bean中查找,然后找到对应的类型,将其注入进来,这样就完成依赖注入了。所谓自动装配技术是一种由Spring自己发现对应的Bean,自动完成装配工作的方式,它会应用到一个十分常用的注解@Autowired上,这个时候Spring会根据类型去寻找定义的Bean然后将其注入,这里需要留意按类型的方式。
为了演示自动装配实例,在com.ccff.spring.assembling.service下创建名为“AnimalService2”的接口,具体代码如下所示:
package com.ccff.spring.assembling.service;
public interface AnimalService2 {
void animalIdentification();
}
在com.ccff.spring.assembling.service下创建名为“AnimalServiceImpl2”的类,并实现AnimalService2接口,具体代码如下所示:
package com.ccff.spring.assembling.service;
import com.ccff.spring.assembling.pojo.Animal;
import org.springframework.beans.factory.annotation.Autowired;
public class AnimalServiceImpl2 implements AnimalService2 {
@Autowired
private Animal animal;
@Override
public void animalIdentification() {
System.out.println("动物身份是:");
System.out.println(animal);
}
}
这里的@Autowired注解,表示在Spring IoC定位所有Bean后,这个字段需要按类型注入,这样IoC容器就会寻找资源,然后将其注入。比如定义的Animal和AnimalServiceImpl2两个Bean,假设将其定义,那么Spring IoC容器会为他们先生成对应的实例,然后根据@Autowired注解,按照类型找到定义的实例,将其注入。
IoC容器有时候会寻找失败,在默认的情况下寻找失败它会抛出异常,也就是说默认情况下,Spring IoC容器会认为一定要找到对应的Bean来注入这个字段,有些时候这并不是一个真实的需求,比如日志,有时候我们会觉得这是一个可有可无的,这个时候通过@Autowired的配置项required来改变它,比如@Autowired(required = false)。
正如之前所谈到的在默认情况下是必须注入成功的,所以这里的required的默认值为true。当把配置修改为false时,就会告诉Spring IoC容器,假如在已经定义好的Bean中找不到对应的类型,允许不注入,这样也就没有了异常抛出,只是这样这个字段可能为空,需要开发者自行校验,以避免发生空指针异常。在大部分情况下,都不需要这样的修改。
@Autowired除可以配置在属性之外,还允许方法配置,常见的Bean的setter方法也可以使用它来完成注入。例如我们可以将AnimalServiceImpl2修改为如下所示:
package com.ccff.spring.assembling.service;
import com.ccff.spring.assembling.pojo.Animal;
import org.springframework.beans.factory.annotation.Autowired;
@Component("AnimalServiceImpl2")
public class AnimalServiceImpl2 implements AnimalService2 {
private Animal animal;
@Override
public void animalIdentification() {
System.out.println("动物身份是:");
System.out.println(animal);
}
@Autowired
public void setAnimal(Animal animal) {
this.animal = animal;
}
}
为区别于之前的Animal对象,因此修改Animal类中的属性值如下:
package com.ccff.spring.assembling.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component(value = "animal")
public class Animal {
@Value("animal_name_2")
private String animal_name;
@Value("animal_type_2")
private String animal_type;
@Value("1000000")
private Long animal_number;
public String getAnimal_name() {
return animal_name;
}
public void setAnimal_name(String animal_name) {
this.animal_name = animal_name;
}
public String getAnimal_type() {
return animal_type;
}
public void setAnimal_type(String animal_type) {
this.animal_type = animal_type;
}
public Long getAnimal_number() {
return animal_number;
}
public void setAnimal_number(Long animal_number) {
this.animal_number = animal_number;
}
@Override
public String toString() {
return "Animal{" +
"animal_name='" + animal_name + '\'' +
", animal_type='" + animal_type + '\'' +
", animal_number=" + animal_number +
'}';
}
}
修改TestAnnotationAssembly测试类,在该测试类中添加名为“TestAutoWried”的测试方法,具体代码如下:
package com.ccff.spring.assembling.test;
import com.ccff.spring.assembling.config.AnimalConfig;
import com.ccff.spring.assembling.pojo.Animal;
import com.ccff.spring.assembling.service.AnimalService;
import com.ccff.spring.assembling.service.AnimalService2;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestAnnotationAssembly {
private ApplicationContext context;
@Before
public void getContainer(){
context = new AnnotationConfigApplicationContext(AnimalConfig.class);
}
@Test
public void TestComponent(){
Animal animal = context.getBean(Animal.class);
System.out.println(animal);
}
@Test
public void TestComponentScan(){
Animal animal = context.getBean(Animal.class);
AnimalService animalService = context.getBean(AnimalService.class);
animalService.animalIdentification(animal);
}
@Test
public void TestAutoWried(){
AnimalService2 animalService2 = context.getBean(AnimalService2.class);
animalService2.animalIdentification();
}
}
执行上面的测试方法后,查看输出到控制台的日志信息如下,则说明Bean装配完成。
在大部分的配置中推荐使用@Autowired注解,这是Spring IoC自动装配完成的,使得配置大幅度减少,满足约定优于配置的原则,增强程序的健壮性。但是在有些时候是不能进行自动装配的。
4.4 自动装配的歧义性(@Primary和@Qualifier)
上面提到的@Autowired注解,它可以完成一些自动装配的功能,并且使用方式十分简单,但是有时候这样的方式并不能使用。这一切的根源来自于按类型的方式,按照Spring的建议,在大部分情况下会使用接口编程,但是定义一个接口,并不一定只有与之对应的一个实现类。换句话说,一个接口可以有多个实现类。
在com.ccff.spring.assembling.service包下创建一个名为“AnimalServiceImpl3”的类,并实现AnimalService,具体代码如下所示:
package com.ccff.spring.assembling.service;
import com.ccff.spring.assembling.pojo.Animal;
import org.springframework.stereotype.Component;
@Component("animalServiceImpl3")
public class AnimalServiceImpl3 implements AnimalService {
@Override
public void animalIdentification(Animal animal) {
System.out.println("动物身份是:");
System.out.println(animal);
}
}
在com.ccff.spring.assembling.controller包下创建名为“AnimalController”的类,具体代码如下所示:
package com.ccff.spring.assembling.controller;
import com.ccff.spring.assembling.pojo.Animal;
import com.ccff.spring.assembling.service.AnimalService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("animalController")
public class AnimalController {
@Autowired
private AnimalService animalService = null;
public void printAnimal(Animal animal){
animalService.animalIdentification(animal);
}
}
上面代码中的字段animalService是一个AnimalService接口类型。AnimalService有两个实现类(AnimalServiceImpl和AnimalServiceImpl3),此时Spring IoC容器就会犯糊涂了,它无法判断把哪个对象注入进来,于是就会抛出异常,这样@Autowired注入就失败了。
产生上述状况是因为它采用的是按类型来注入对象,而在Java中接口可以有多个实现类,同样的抽象类也可以有多个实例化的类,这样就会造成通过类型获取Bean的不唯一,从而导致Spring IoC类似于按类型的方法无法获得唯一的实例化类。
为消除这种歧义性,Spring提供了两个注解@Primary和@Qualifier,这是两个不同的注解,其消除歧义性的理念不太一样。
4.4.1 注解@Primary
注解@Primary代表首要的,当Spring IoC通过一个接口或者抽象类注入对象的时候,由于存在多个实现类或者具体类,就会犯糊涂,不知道采用哪个类注入为好。注解@Primary则是告诉Spring IoC容器,请优先使用该类注入。
修改AnimalService接口的实现类AnimalServiceImpl3,为其添加注解@Primary,具体代码如下所示:
package com.ccff.spring.assembling.service;
import com.ccff.spring.assembling.pojo.Animal;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
@Component("animalServiceImpl3")
@Primary
public class AnimalServiceImpl3 implements AnimalService {
@Override
public void animalIdentification(Animal animal) {
System.out.println("动物身份是:");
System.out.println(animal);
}
}
这里的@Primary注解告诉Spring IoC容器,如果存在多个AnimalService类型,无法判断注入哪个的时候,优先将AnimalServiceImpl3的实例注入,这样就可以消除歧义性。
同样的,或许你可以想到将@Primary注解也加入到AnimalServiceImpl中,这样就存在两个首选的AnimalService接口的实例了,但是在Spring IoC容器中这样定义是允许的,只是在注入的时候将抛出异常。但是无论如何@Primary注解只能解决首要性问题,而不能解决选择性的问题,简而言之,它不能选择使用接口具体的实现类去注入。
修改AnimalConfig类,将com.ccff.spring.assembling.config包和AnimalConfig类加入到Spring Bean的扫描路径中,具体代码如下所示:
package com.ccff.spring.assembling.config;
import com.ccff.spring.assembling.controller.AnimalController;
import com.ccff.spring.assembling.pojo.Animal;
import com.ccff.spring.assembling.service.AnimalServiceImpl;
import org.springframework.context.annotation.ComponentScan;
//@ComponentScan(basePackageClasses = {Animal.class,AnimalServiceImpl.class})
//@ComponentScan(basePackages = {"com.ccff.spring.assembling.pojo","com.ccff.spring.assembling.service"})
@ComponentScan(basePackages = {"com.ccff.spring.assembling.pojo","com.ccff.spring.assembling.service","com.ccff.spring.assembling.config"},basePackageClasses = {Animal.class,AnimalServiceImpl.class,AnimalController.class})
public class AnimalConfig {
}
修改TestAnnotationAssembly测试类,在该类中添加测试方法TestAnimalController,具体代码如下所示:
package com.ccff.spring.assembling.test;
import com.ccff.spring.assembling.config.AnimalConfig;
import com.ccff.spring.assembling.controller.AnimalController;
import com.ccff.spring.assembling.pojo.Animal;
import com.ccff.spring.assembling.service.AnimalService;
import com.ccff.spring.assembling.service.AnimalService2;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestAnnotationAssembly {
private ApplicationContext context;
@Before
public void getContainer(){
context = new AnnotationConfigApplicationContext(AnimalConfig.class);
}
@Test
public void TestComponent(){
Animal animal = context.getBean(Animal.class);
System.out.println(animal);
}
@Test
public void TestComponentScan(){
Animal animal = context.getBean(Animal.class);
AnimalService animalService = context.getBean(AnimalService.class);
animalService.animalIdentification(animal);
}
@Test
public void TestAutoWried(){
AnimalService2 animalService2 = context.getBean(AnimalService2.class);
animalService2.animalIdentification();
}
@Test
public void TestAnimalController(){
Animal animal = context.getBean(Animal.class);
AnimalController animalController = context.getBean(AnimalController.class);
animalController.printAnimal(animal);
}
}
执行上面的测试方法后,查看输出到控制台的日志信息如下,则说明Bean装配完成。
4.4.2 注解@Qualifier
之前讨论的出现歧义性的一个重要原因是Spring在寻找依赖注入的时候采用按类型注入引起的。除了按类型查找Bean,Spring IoC容器最底层的接口BeanFactory,也定义了按名称查找的方法,如果采用名称查找的方法,而不是采用按类型查找的方法,那么就可以解决歧义性的问题。注解@Qualifier就是基于这样的一个注解。
首先,修改AnimalService接口的实现类AnimalServiceImpl3,去掉注解@Primary,具体代码如下所示:
package com.ccff.spring.assembling.service;
import com.ccff.spring.assembling.pojo.Animal;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
@Component("animalServiceImpl3")
//@Primary
public class AnimalServiceImpl3 implements AnimalService {
@Override
public void animalIdentification(Animal animal) {
System.out.println("动物身份是:");
System.out.println(animal);
}
}
然后,为AnimalController类添加注解@Qualifier,具体代码如下所示:
package com.ccff.spring.assembling.controller;
import com.ccff.spring.assembling.pojo.Animal;
import com.ccff.spring.assembling.service.AnimalService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component("animalController")
public class AnimalController {
@Autowired
@Qualifier("animalServiceImpl3")
private AnimalService animalService = null;
public void printAnimal(Animal animal){
animalService.animalIdentification(animal);
}
}
通过上面的代码我们可以发现,在注解@Qualifier中,我们配置了AnimalServiceImpl3类的Bean的别名。这个时候Spring IoC容器就不会再按照类型的方式注入,而是按照名称的方式注入,这样既能注入成功,也不存在歧义性。
最后,再次执行TestAnimalController测试方法后,查看输出到控制台的日志信息如下,则说明Bean装配完成。
使用注解@Qualifier后就可以使用这个方法通过名称从Spring IoC容器中获取对象进行注入。
4.5 装载带有参数的构造方法类
我们之前讨论的都是类都是隐式的无参构造方法的,而事实上在某些时候构造方法是带参数的,对于一些带有参数的构造方法,也允许我们通过注解进行注入。
在com.ccff.spring.assembling.controller包下创建名为“AnimalController2”的类,具体代码如下所示:
package com.ccff.spring.assembling.controller;
import com.ccff.spring.assembling.pojo.Animal;
import com.ccff.spring.assembling.service.AnimalService;
import com.ccff.spring.assembling.service.AnimalService2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component("animalController2")
public class AnimalController2 {
private AnimalService animalService = null;
public AnimalController2(@Autowired @Qualifier(value="animalServiceImpl3")AnimalService animalService) {
this.animalService = animalService;
}
public void printAnimal2(Animal animal){
animalService.animalIdentification(animal);
}
}
通过上面的代码可以看出,我们仍然可以使用@Autowired和@Qualifier进行注入,换句话说,这两个注解还能支持到参数。
修改TestAnnotationAssembly测试类中的TestAnimalController测试方法,具体代码如下所示:
package com.ccff.spring.assembling.test;
import com.ccff.spring.assembling.config.AnimalConfig;
import com.ccff.spring.assembling.controller.AnimalController;
import com.ccff.spring.assembling.controller.AnimalController2;
import com.ccff.spring.assembling.pojo.Animal;
import com.ccff.spring.assembling.service.AnimalService;
import com.ccff.spring.assembling.service.AnimalService2;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestAnnotationAssembly {
private ApplicationContext context;
@Before
public void getContainer(){
context = new AnnotationConfigApplicationContext(AnimalConfig.class);
}
@Test
public void TestComponent(){
Animal animal = context.getBean(Animal.class);
System.out.println(animal);
}
@Test
public void TestComponentScan(){
Animal animal = context.getBean(Animal.class);
AnimalService animalService = context.getBean(AnimalService.class);
animalService.animalIdentification(animal);
}
@Test
public void TestAutoWried(){
AnimalService2 animalService2 = context.getBean(AnimalService2.class);
animalService2.animalIdentification();
}
@Test
public void TestAnimalController(){
Animal animal = context.getBean(Animal.class);
AnimalController animalController = context.getBean(AnimalController.class);
animalController.printAnimal(animal);
AnimalController2 animalController2 = context.getBean(AnimalController2.class);
animalController2.printAnimal2(animal);
}
}
执行上面的测试方法后,查看输出到控制台的日志信息如下,则说明Bean装配完成。
这里需要注意:@Autowired注解不仅能够在构造方法上,还能用在属性的setter方法上。实际上,setter方法并没有什么特殊之处,@Autowired注解可以用在类的任何方法上。不管构造方法、setter方法还是其他的方法,Spring都会尝试满足方法参数上所声明的依赖。
4.6 使用@Bean装配Bean
上面的讨论中均通过@Component装配Bean,但是@Component只能注解在类上,不能注解到方法上。对于Java而言,大部分的开发都需要引入第三方的包(jar文件),而且往往并没有这些包的源码,这时候将无法为这些包的类加入@Component注解,让他们并未开发环境的Bean。此时可以使用新类扩展(extends)其包内的类,然后在新类上使用@Component注解,但是这样就会显得不伦不类。
这个时候Spring给予一个注解@Bean,它可以注解到方法上,并且将方法返回的对象作为Spring的Bean,存放在IoC容器中。@Bean注解被用在通过Java代码进行显示配置的场景。在进行显示配置时,JavaConfig是更好的方案,因为它更为强大,类型安全并且对重构友好。
第一步,在com.ccff.spring.assembling.service包下创建名为“CompactDisc”的CD光盘接口,并在该接口中定义一个名为“play”的播放音乐方法,具体代码如下所示:
package com.ccff.spring.assembling.service;
public interface CompactDisc {
void play();
}
第二步,在在com.ccff.spring.assembling.service包下创建CompactDisc接口的一个实现类CompactDiscImpl,具体代码如下所示:
package com.ccff.spring.assembling.service;
public class CompactDiscImpl implements CompactDisc {
private String title = "依然范特西";
private String artist = "周杰伦";
@Override
public void play() {
System.out.println("当前正在播放"+artist+"的专辑《"+title+"》");
}
}
第三步,在com.ccff.spring.assembling.pojo包下创建名为“CDPlayer”的CD播放器类,具体代码如下所示:
package com.ccff.spring.assembling.pojo;
import com.ccff.spring.assembling.service.CompactDisc;
public class CDPlayer {
private CompactDisc compactDisc;
public CDPlayer(CompactDisc compactDisc) {
this.compactDisc = compactDisc;
}
public CompactDisc getCompactDisc() {
return compactDisc;
}
public void setCompactDisc(CompactDisc compactDisc) {
this.compactDisc = compactDisc;
}
public void playMusic(){
compactDisc.play();
}
}
第四步,通过JavaConfig的显示配置方式对Bean进行装配,并实现Bean之间的依赖注入。在com.ccff.spring.assembling.config包下创建名为“CDPlayerConfig”的JavaConfig配置类,具体代码如下所示:
package com.ccff.spring.assembling.config;
import com.ccff.spring.assembling.pojo.CDPlayer;
import com.ccff.spring.assembling.service.CompactDisc;
import com.ccff.spring.assembling.service.CompactDiscImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CDPlayerConfig {
@Bean
public CompactDisc compactDiscImpl(){
return new CompactDiscImpl();
}
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(compactDisc);
}
}
在上面的Java Config配置类中需要注意的是:创建JavaConfig类的关键在于为其添加注解@Configuration,该注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建Bean的细节;注解@Bean会告诉Spring这个方法将返回一个对象,该对象要注册为Spring应用上下文中的Bean,方法体中包含了最终产生Bean的实例逻辑。在默认情况下,Bean的id与带有注解@Bean的方法名是一样的,如果想要为其设置一个不同的名字的话,那么可以重命名该方法,也可以通过注解@Bean中的name属性指定一个不同的名字。
第五步,在com.ccff.spring.assembling.test包下创建名为“TestCD”的测试类,并在该测试类中创建名为“TestBean”的测试方法,具体代码如下所示:
package com.ccff.spring.assembling.test;
import com.ccff.spring.assembling.config.CDPlayerConfig;
import com.ccff.spring.assembling.pojo.CDPlayer;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestCD {
private ApplicationContext context;
@Before
public void getContext(){
context = new AnnotationConfigApplicationContext(CDPlayerConfig.class);
}
@Test
public void TestBean(){
CDPlayer cdPlayer = (CDPlayer) context.getBean("cdPlayer");
cdPlayer.playMusic();
}
}
执行上面的测试方法后,查看输出到控制台的日志信息如下,则说明Bean装配完成。
4.7 注解自定义Bean的初始化和销毁方法
通过对Spring Bean的生命周期的学习可知,对于Bean的初始化可以通过实现Spring所定义的一些关于生命周期的接口来实现,这样BeanFactory或者其他高级容器ApplicationContext就可以调用这些接口所定义的方法了,这点和使用XML是一样的。但是我们还没有讨论如何在注解中实现自定义的初始化方法和销毁方法。
这其实很简单,主要是运用注解@Bean的配置项,注解@Bean不能使用在类的标注上,它主要使用在方法上,@Bean的配置项中包含4个配置项:
第一,name:是一个字符串数组,允许配置多个BeanName。
第二,autowire:标志是否是一个引用的Bean对象,默认值是Autowire. NO。
第三,initMethod:自定义初始化方法。
第四,destroyMethod:自定义销毁方法。
基于上述介绍,自定义的初始化方法是配置initMethod,而销毁方法则是destroyMethod。