8.2.6 自动装配和精确装配1 @Autowired注解

8.2.6 自动装配和精确装配

@Autowired注解

Spring提供了@Autowired注解来指定自动装配,@Autowired可以修饰setter方法、普通方法实例变量构造器等。

@Autowired修饰setter时

当使用@Autowired标注setter方法时,默认采用by Type自动装配策略
例如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
@Component
public class Chinese implements Person
{
...
@Autowired
public void setAxe(Axe axe)
{
this.axe = axe;
}
...
}

上面的代码中使用了@Autowired指定对setAxe()方法进行自动装配, Spring将会自动搜索容器中类型为AxeBean实例,并将该Bean实例作为setAxe()方法的参数传入。

  • 如果正好在容器中找到一个类型为AxeBean, Spring就会以该Bean为参数来执行setAxe()方法;
  • 如果在容器中找到多个类型为AxeBean, Spring会引发异常;
  • 如果在容器中没有找到多个类型为AxeBean, Spring什么都不执行,也不会引发异常

@Autowired修饰普通方法时

Spring还允许使用@Autowired来标注多个参数的普通方法,如下面的代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class Chinese implements Person
{
...
@Autowired
public void prepare(Axe axe, Dog dog)
{
this.axe=axe;
this.dog=dog;
}
...
}

当使用@Autowired修饰带多个参数的普通方法时, Spring会自动到容器中寻找类型匹配的Bean,如果恰好为每个参数都找到一个类型匹配的Bean, 则Spring会自动以这些Bean作为参数来调用该方法。
以上面的prepare(Axe axe, Dog dog)方法为例, Spring会自动寻找容器中类型为AxeBean以及类型为DogBean,如果在容器中恰好找到一个类型为Axe和一个类型为DogBean, Spring就会以这两个Bean作为参数来调用prepare方法。

@Autowired也可用于修饰构造器和实例变量,如下面的代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class Chinese implements Person
{
...
@Autowired
private Axe axe;
@Autowired
public Chinese(Axe axe, Dog dog)
{
this.axe=axe;
this.dog=dog;
}
...
}

@Autowired修饰实例变量时

当使用@Autowired修饰一个实例变量时, Spring将会把容器中与该实例变量类型匹配的Bean设置为该实例变量的值。
例如,程序中使用@Autowired标注了private Axe axe这个实例变量,则Spring会自动搜索容器中类型为AxeBean

  • 如果恰好找到一个该类型的Bean, 那么Spring就会将该Bean设置成axe实例变量的值;
  • 如果容器中包含多于一个的Axe实例,则Spring容器会抛出BeanCreateException异常。

@Autowired修饰构造时

@Autowired修饰构造时的注入规则与修饰普通方法的规则相同。

@Autowired修饰数组类型的成员变量时

@Autowired甚至可以用于修饰数组类型的成员变量,如下面的代码所示:

1
2
3
4
5
6
7
@Component
public class Chinese implements Person
{
@Autowired
private Axe[] axes;
...
}

正如在上面的程序中看到的,被@Autowired修饰的axes实例变量的类型是Axe数组,在这种情况下, Spring会自动搜索容器中的所有Axe实例,并以这些Axe实例作为数组元素来创建数组,最后将该数组赋给上面Chinese实例的axes实例变量。

@Autowired修饰集合类型的实例变量时

与此类似的是,@Autowired也可标注集合类型的实例变量,或标注形参类型是集合的方法, Spring对这种集合属性集合形参的处理与前面对数组类型的处理是完全相同的。例如如下代码:

1
2
3
4
5
6
7
8
9
10
11
@Component
public class Chinese implements Person
{
private Set<Axe> axes;
@Autowired
public void setAxes(Set<Axe> axes)
{
this.axes=axes;
}
...
}

@Autowired修饰集合类型的实例变量时 必须使用泛型

对于这种集合类型的参数而言,程序代码中必须使用泛型,正如上面程序中的代码所示,程序指定了该方法参数是Set<Axe>类型,这表明Spring会自动搜索容器中的所有Axe实例,并将这些实例注入到axes实例变量中。
如果程序没有使用泛型来指明集合元素的类型,则Spring不知道搜索那些类型的实例来注入集合。

@Autowired主机默认使用byType策略来自动装配

由于@Autowired默认使用byType策略来完成自动装配,系统可能出现有多个匹配类型的候选组件,此时就会导致异常。

@Autowired根据泛型进行自动装配

Spring 4.0增强后的@Autowired注解还可以根据泛型进行自动装配。例如,项目中定义了如下Dao组件(后文会介绍,Dao组件是Java EE应用中最重要的一类组件,用于执行数据库访问),本示例的基础Dao组件代码如下。

1
2
3
4
5
6
7
8
9
10
11
package org.crazyit.app.dao.impl;

import org.crazyit.app.dao.*;

public class BaseDaoImpl<T> implements BaseDao<T>
{
public void save(T e)
{
System.out.println("程序保存对象:" + e);
}
}

BaseDaolmpl类中定义了所有Dao组件都应该实现的通用方法,而应用的其他Dao组件则只要继承BaseDaolmpl,并指定不同泛型参数即可。例如如下UserDaolmplItemDaoImpl

1
2
3
4
@Component("itemDao")
public class ItemDaoImpl extends BaseDaoImpl<Item> implements ItemDao
{
}
1
2
3
4
@Component("userDao")
public class UserDaoImpl extends BaseDaoImpl<User> implements UserDao
{
}

接下来程序希望定义两个Service组件: UserServicelmplItemServicelmp,而UserServicelmpl需要依赖于UserDaolmpl组件, ItemServicelmpl需要依赖于ItemDaolmpl组件,传统的做法可能需要为UserServicelmplItemServicelmpl分别定义成员变量,并配置依赖注入。
考虑到UserDaolmplItemServicelmpl依赖的都是BaseDaolmpl组件的子类,只是泛型参数不同而程序可以直接定义一个BaseServicelmpl,该组件依赖于BaseDaolmpl即可。例如如下代码:

1
2
3
4
5
6
7
8
9
public class BaseServiceImpl<T> implements BaseService<T>
{
@Autowired
private BaseDao<T> dao;
public void addEntity(T entity)
{
System.out.println("调用" + dao + "保存实体:" + entity);
}
}

上面程序中两行粗体字代码指定Spring应该寻找容器中类型为Basedao<T>Bean,并将该Bean设置为dao实例变量的值。注意到BaseDao<T>类型中的泛型参数T, Spring不仅会根据Basedao类型进行搜索,还会严格匹配泛型参数T
接下来程序只要定义如下UserServicelmpl即可

1
2
3
4
5
@Component("userService")
public class UserServiceImpl extends BaseServiceImpl<User>
implements UserService
{
}

UserServiceImpl继承了BaseServiceImpl<User>,这就相当于指定了上面BaseDao<T>类型中T的类型为User,因此Spring会在容器中寻找类型为BaseDao<User>Bean—此时会找到UserDaoImpl组件,从而实现将UserDaolmpl注入UserServiceImpl组件的功能。
ItemServiceImpl的处理方法也与此类似,这样就可以很方便地将ItemDaolmpl注入ItemServiceImpl组件——而程序只要在UserServicelmplItemServiceImpl的基类中定义成员变量,并配置依赖注入即可,这就是Spring从4.0开始增强的自动装配。
该示例的主程序如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BeanTest
{
public static void main(String[] args) throws Exception
{
// 创建Spring容器
@SuppressWarnings("resource")
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"beans.xml");
UserService us = ctx.getBean("userService", UserService.class);
us.addEntity(new User());
ItemService is = ctx.getBean("itemService", ItemService.class);
is.addEntity(new Item());
}
}

该主程序只是获取容器中的userServiceitemService两个Bean,并调用它们的方法。编译、运行该示例,可以看到如下输出:

1
2
调用org.crazyit.app.dao.impl.UserDaoImpl@133e16fd保存实体:org.crazyit.app.domain.User@51b279c9
调用org.crazyit.app.dao.impl.ItemDaoImpl@1ad282e0保存实体:org.crazyit.app.domain.Item@7f416310

从上面输出可以看出,@Autowired可以精确地利用泛型执行自动装配,这样即可实现将UserDaolmpl注入UserServiceImpl组件,将ItemDaolmpl注入Item Servicelmpl组件。