常用的注入方式有四种:
1. 属性注入
2. 构造方法注入
3. 工厂方法注入
4. 注解注入
下面先定义我们后面用到的POJO类:
package test;
public class User {
private String name;
private String gender;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "User [name=" + name + ", gender=" + gender + "]";
}
}
属性注入
属性注入(set方法注入)对Bean有两点要求:
1. 提供一个默认的构造函数
2. 为需要注入的属性提供set方法
配置示例如下:
xml文件配置:
<bean id="user" class="test.User">
<property name="name" value="zeng"></property>
<property name="gender" value="male"></property>
</bean>
测试函数:
public static void main(String args[]){
ApplicationContext atc = new ClassPathXmlApplicationContext("spring.xml");
User user = (User) atc.getBean("user");
System.out.println(user);//print User [name=zeng, gender=male]
}
而如果我们把POJO中的getName方法去掉,结果不变,但setName方法删掉,我们再运行程序,会看到如下报错信息:
Exception in thread “main” org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘user’ defined in class path resource [spring.xml]: Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property ‘name’ of bean class [test.User]: Bean property ‘name’ is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
显然,我们的属性注入,set方法是必不可少的。
2. 构造方法注入
1. 按类型匹配入参
先在User POJO中新增带参构造方法和age属性:
private Integer age;
get and set ...
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
//这里需特别注意的是,我们定义了带参的构造函数,JVM就不会因为没有定义构造函数为我们创建默认的构造函数了,因此这里我们需要自己重新定义
在xml文件定义Bean:
<bean id="user1" class="test.User">
<constructor-arg type="java.lang.String" value="zeng1" ></constructor-arg>
<constructor-arg type="java.lang.Integer" value="20"></constructor-arg>
</bean>
调用测试函数:
public static void main(String args[]){
ApplicationContext atc = new ClassPathXmlApplicationContext("spring.xml");
User user = (User) atc.getBean("user1");
System.out.println(user.getName() + "——" + user.getAge());
//print zeng1——20
}
这时候,我们再稍微修改下xml文件:
<bean id="user1" class="test.User">
<constructor-arg type="java.lang.String" value="male1"></constructor-arg>
<constructor-arg type="java.lang.Integer" value="12" ></constructor-arg>
<constructor-arg type="java.lang.String" value="zeng"></constructor-arg>
</bean>
同时对应User POJO类新定义构造方法:
public User(String name, String gender, Integer age) {
super();
this.name = name;
this.gender = gender;
this.age = age;
}
最后修改测试函数并运行:
public static void main(String args[]){
ApplicationContext atc = new ClassPathXmlApplicationContext("spring.xml");
User user = (User) atc.getBean("user1");
System.out.println(user.getName() + "——" + user.getAge() +"——" +user.getGender());
//print male1——12——zeng
}
通过这个有趣的试验,我们发现,当出现重复的类型时,构造函数会根据的定义顺序和构造函数的参数顺序对应入参。即使中间插入了其他类型参数,容器也只会对相同类型的进行顺序注入,下面可辅助验证这个特点:
假如我们去掉中间<constructor-arg type="java.lang.Integer" value="12" ></constructor-arg>
,就会打印male1——null——zeng。
2. 按索引匹配入参:
在前面第5点建立了三参数构造函数的基础上,我们在xml文件上编写:
<bean id="user2" class="test.User">
<constructor-arg index="0" value="male2"></constructor-arg>
<constructor-arg index="2" value="12" ></constructor-arg>
<constructor-arg index="1" value="zeng2"></constructor-arg>
</bean>
运行测试函数:
public static void main(String args[]){
ApplicationContext atc = new ClassPathXmlApplicationContext("spring.xml");
User user = (User) atc.getBean("user2");
System.out.println(user.getName() + "——" + user.getAge() +"——" +user.getGender());
//print male2——12——zeng2
}
注意索引为3的value必须为Integer(与构造函数对应,否则会报错:
Could not resolve matching constructor (hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)
)
注意到我这里是name=“male2”和gender=”zeng2”,即参数值注入是严格根据index来的,且index从0开始算起
关于构造函数注入的优点:
1. 保证一些主要的属性在Bean实例化时就配置好
2. 不需要为每个方法提供Setter方法,减少了类的方法个数,同时还能更好的封装变量,避免外界的错误调用Setter方法
关于构造函数注入的缺点:
1. 如果一个类的属性众多,且我们需要针对不同的属性堆构造特定的构造方法,那么构造函数的数量就会很多(否则需制定特点属性为null,这样可读性也差),而且构造函数本身会变得很庞大臃肿。同时我们还要考虑到配置文件的构造函数参数匹配的歧义问题
2. 构造函数不利于类的继承和扩展,因为子类需要引用到父类复杂的构造函数
3. 联合使用类型和参数索引
此方法能更有效地避免歧义产生
3. 工厂方法注入
1. 非静态工厂方法
因为工厂方法非静态,要调用改方法必须先初始化对象
先定义工厂类:
package test;
public class UserFactory {
public User createUser(){
User user = new User("zeng3","male3",20);
return user;
}
}
进行Bean配置:
<bean id="userFactory" class="test.UserFactory" />
<bean id="user3" factory-bean="userFactory" factory-method="createUser"></bean>
调用测试函数:
public static void main(String args[]){
ApplicationContext atc = new ClassPathXmlApplicationContext("spring.xml");
User user = (User) atc.getBean("user3");
System.out.println(user.getName() +" ——" +user.getGender()+ "——" + user.getAge() );
//print zeng3——male3——20
}
2. 静态工厂方法
无须另外创建对象,先将createUser方法给为静态,然后将bean配置为: <bean id="user3" class="test.UserFactory" factory-method="createUser"></bean>
调用测试函数即可得到与上次相同的结果。
这里需要注意的是,静态方法先定义工厂Bean,再配置在id为user3的bean中,而id=user3的bean没有定义class属性。因为它的类的归属在工厂类里判决,而静态工厂方法中,没有了userFactory bean的定义(即不用初始化),但在user3中定义了一个class,这个class指向了工厂类
4. 注解注入
1. 对类成员变量注解
使用@Autowire实现自动注入,格式如:
@Component
public class IdCard{
.....
}
public class User{
//Autowired默认按类型注入,@required 表明如果找不到对应的bean则为null,但如果设定为true(也是默认值),则要求一定要找到匹配的bean,否则会抛出异常。
//Qualifier常用于容器有一个以上相同类型的Bean,通过指定名字来指定唯一的Bean
@Autowired(required = false )
@Qualifier("idCard")
private IdCard idCard;//也可以将IdCard配置在xml文件中注入
.....
}
2. 对类成员方法进行注解
@Autowired
public void init(@Qualifier(“usar1")User user1,@Qualifier("user2")User user2){
this.user1 = user1;
this.user2 = user2;
}