常用的注入方式有四种:
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方法是必不可少的。
先在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。
在前面第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. 构造函数不利于类的继承和扩展,因为子类需要引用到父类复杂的构造函数
此方法能更有效地避免歧义产生
因为工厂方法非静态,要调用改方法必须先初始化对象
先定义工厂类:
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
}
无须另外创建对象,先将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指向了工厂类
使用@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文件中注入
.....
}
@Autowired
public void init(@Qualifier(“usar1")User user1,@Qualifier("user2")User user2){
this.user1 = user1;
this.user2 = user2;
}