Spring自动装配@Autowired(@Qualifier, @Primary), @Resource超详细用法分析

前提

本人是正在学习Spring的大三学生,但我在网课和有些博客上看到对IOC自动装配注解方面有些表意不当或者不详细之处,所以这篇文章我想仔细分享我所学的Spring IOC容器自动装配方面的学习收获。建议读这篇文章之前先了解下Spring IOC容器的装配基础知识(组件扫描等等)和Web MVC架构模式。如果有大神发现我的文章哪里有问题的话,请指出来,不胜感激!!


一、IOC基础理解

@Autowired是我们很多人用Spring的IOC都会使用的注解,可以理解成给一个对象注入IOC匹配的对象。

public class BookService
{
	@Autowired
	private BookDao bookDao;
}

比如在MVC代码中,Service层中就要有对应Dao的对象。以往用原生Servlet开发时,可以在构造方法中创建BookDao对象,然后在其他方法中使用。

public class BookService
{
	@Autowired
	private BookDao bookDao;

	public void BookService()
	{
		this.bookDao = new BookDao();
	}
}

但使用Spring框架控制反转后,这些对象的注入由IOC容器给你注入。

这个就好像车间的零件老师傅,每天早上工厂开工时,这个老师傅大喊一声:“谁需要我帮忙托管你们的工具?”。然后业务人员Service说"帮我托管吧,每次处理业务都要找Dao这个臭老头借工具",Controller等等几位工友也同意把自己的工具给这位零件老师傅托管。这样,零件老师傅把同意了托管的工友的工具都收集起来。然后谁需要用什么工具,直接喊工具的名字或类型,这位老师傅就从自己收集的工具中找到匹配的,马上送过去。这样各个员工就不用自己主动去找别人借工具了。“自己new对象”变为“框架反射出对象交给你”。

到这里,大家应该对控制反转有了基础的了解了,不是由你自己new对象,而是让框架帮你注入。


二、@Autowired匹配规则

@Autowired默认是按找类型来匹配的(byType),所以当在IOC容器中只匹配到一个同类bean的话,这个就没什么说的了,直接注入即可。

public class BookService
{
	@Autowired
	private BookDao bookDao; // 这里的就是默认按BookDao这个类型来匹配的
}

现在就是要来讨论按类型没匹配到或匹配到多个的情况下,Spring是怎么处理的。

1.没有匹配到对应bean

这种情况,Spring会自动按属性名来进行byName匹配。

public class BookService
{
	@Autowired
	private BookDao bookDao11;
}

如上面的代码片段,Spring会byName匹配为bookDao11的bean,匹配到了直接注入,否则抛出异常。

2.匹配到多个同类bean

我们先看一个例子。
首先,编写BookDao对象,在BookDao类中加一个属性flag以便标识对象。

public class BookDao
{
	public int flag= 0;

	public BookDao(int flag) {
		this.flag= flag;
	}

	public BookDao() {}
	
	@Override
	public String toString() {
		return "BookDao [flag=" + flag+ "]";
	}
}

完善BookService对象,添加print方法,打印bookDao的标识值。

@Service
public class BookService
{
	 @Autowired
	private BookDao bookDao;

	public BookService() {}

	public BookService(BookDao bookDao)
	{
		this.bookDao = bookDao;
	}

	public void print()
	{
		System.out.println(bookDao); // bookDao已经重写了toString方法
	}
}

我们先创建配置类(这里用配置类代替xml配置文件了)。

@Configuration // 配置类标识
@ComponentScan(value = {"cn.service", "cn.dao"}) // 包扫描
public class MyConfig
{
	@Bean("bookDao1") // 注册一个bean,id为bookDao1,类型为BookDao
	public BookDao bookDao1()
	{
		return new BookDao(1);
	}
	
	@Bean("bookDao2") // 注册一个bean,id为bookDao2,类型为BookDao
	public BookDao bookDao2()
	{
		return new BookDao(2);
	}
}

这样,就手动注册了2个bean了,类型都是BookDao,id分别是bookDao1,bookDao2。

接下来是测试方法的编写:

@Test
public void Test()
{
	// 读取配置类信息,创建applicationContext对象
	AnnotationConfigApplicationContext application = new AnnotationConfigApplicationContext(MyConfig.class);
	// 获取BookService对象
	BookService bookService = application.getBean(BookService.class);
	// 打印BookService对象中的BookDao
	bookService.print();
}

目前,容器中类型为BookDao的bean有三个,一个是包扫描出来的,默认id为类首字母小写,即为bookDao; 第二个和三个是配置类里面bookDao1,bookDao2。这三个对象的flag分别为0, 1, 2。

我们给BookService对象中的BookDao是使用@Autowired自动装配的,所以默认按类型查找,这样一来就匹配到了三个bean了。再该怎么办呢?Spring这个时候就会变为byName匹配了,而这个name,就是变量名。

@Autowired
private BookDao bookDao; // 按bookDao(变量名)进行byName匹配

正好,IOC中的3个bean刚好有一个的id是bookDao,就是扫描出的那一个bean。所以就把扫描出的那个bean装配到BookService中了,由于flag默认值是0,所以print出的结果就是:

BookDao [flag=0]

如果把BookService中的BookDao的变量名改一改,变成:

@Autowired
private BookDao bookDao2; // 按bookDao2进行byName匹配

就会匹配按bookDao2来进行byName匹配了,这样就匹配到flag=2的bean了。
当然,如果用id也无法匹配到,说明IOC容器中压根没有跟这个bean沾边的,就会抛异常了(除非有required=false属性)。

但如果我们找到多个同类bean时,不想按默认的处理方式(用变量名进行id匹配),我想自己指定id,按我指定的id来匹配bean,这时,@Qualifier就登场了。

3.@Qualifier注解使用

这个注解就是来协助@Autowired来处理同时匹配到多个同类bean的情况。既然是协助,这个Qualifier就不能单独出现。

public class BookService
{
	 @Autowired
	 @Qualifier("bookDao3")
	private BookDao bookDao;
}

这时匹配到多个类型为BookDao的bean时,就按bookDao3来进行byName匹配,匹配到就马上注入。

如果按bookDao3这个id没有匹配到bean时,会抛出异常。所以别想着Qualifier先乱写,然后按Qualifier的属性(bookDao3)没匹配到时,还会继续按上文说的默认方法按变量名(bookDao)进行匹配。

还有要注意的一点:如果Autowired按类型只匹配到一个bean时,那就没Qualifier什么事了(Qualifier里面乱写也不会抛异常),Qualifier只是在autowired匹配到多个同类bean时才起作用

@Qualifier优先级相当高,先记住这句话。我们接下来讨论下@Primary这个注解。

4.@Primary注解

这个注解也是解决byType匹配到多个同类bean时,该选择注入哪一个bean的问题。
Primary,翻译成中文差不多就能猜出意思了"主,优先的"。
即给一个注册的bean上面加上@Primary注解后,当匹配到同类的多个bean时,会首先选中这个primary-bean。

@Configuration // 配置类标识
@ComponentScan(value = {"cn.service", "cn.dao"}) // 包扫描
public class MyConfig
{
	@Bean("bookDao1") // 注册一个bean,id为bookDao1,类型为BookDao
	@Primary		// 优先匹配
	public BookDao bookDao1()
	{
		return new BookDao(1);
	}
	
	@Bean("bookDao2") // 注册一个bean,id为bookDao2,类型为BookDao
	public BookDao bookDao2()
	{
		return new BookDao(2);
	}
}

所以运行test后,找到了3个同类bean,匹配的就是bookDao1这个。使用起来很简单,
这时,我们的好兄弟@Autowired和@Qualifier又来了…

5.再谈 Qualifier

看完Primary的用处之后,有没有小伙伴想到,我把这几个一起用,会出现什么?

@Configuration // 配置类标识
@ComponentScan(value = {"cn.service", "cn.dao"}) // 包扫描
public class MyConfig
{
	@Bean("bookDao1") // 注册一个bean,id为bookDao1,类型为BookDao
	@Primary		// 优先匹配bookDao1
	public BookDao bookDao1()
	{
		return new BookDao(1);
	}
	
	@Bean("bookDao2") // 注册一个bean,id为bookDao2,类型为BookDao
	public BookDao bookDao2()
	{
		return new BookDao(2);
	}
}

public class BookService
{
	 @Autowired
	 @Qualifier("bookDao2") // 匹配bookDao2
	private BookDao bookDao;

	public BookService() {}

	public BookService(BookDao bookDao)
	{
		this.bookDao = bookDao;
	}

	public void print()
	{
		System.out.println(bookDao); // bookDao已经重写了toString方法
	}
}

仔细看上述代码,@Primary指定的是bookDao1这个bean,@Qualifier指定的是bookDao2这个bean。所以当Spring进行byType匹配时,是取哪个呢?当然是按Qualifier为主了,上文我就说过,@Qualifier优先级相当高,所以是匹配的bookDao2这个对象。

这里我突发奇想,如果这时的Qualifier中的值如果没找到,会不会轮到@Primary弟弟呢,你起码还给了bean一个优先选择功能呢,是吧弟弟?代码如下:

@Configuration // 配置类标识
@ComponentScan(value = {"cn.service", "cn.dao"}) // 包扫描
public class MyConfig
{
	@Bean("bookDao1") // 注册一个bean,id为bookDao1,类型为BookDao
	@Primary		// 优先匹配bookDao1
	public BookDao bookDao1()
	{
		return new BookDao(1);
	}
	
	@Bean("bookDao2") // 注册一个bean,id为bookDao2,类型为BookDao
	public BookDao bookDao2()
	{
		return new BookDao(2);
	}
}

public class BookService
{
	 @Autowired
	 @Qualifier("bookDao8") // 匹配bookDao8,很显然容器中没有这个
	private BookDao bookDao;

	public BookService() {}

	public BookService(BookDao bookDao)
	{
		this.bookDao = bookDao;
	}

	public void print()
	{
		System.out.println(bookDao); // bookDao已经重写了toString方法
	}
}

Qualifier中要匹配bookDao8,很显然容器中没有bookDao8吧,Qualifier会不会交给别人处理呢。比如匹配被@Primary标识的bookDao1?

想多了,Qualifier可是超残忍的,找不到直接给你抛异常,嘿嘿嘿。根本轮不到@Primary。


*提前总结下byType装配和byName装配,以下@Resource注解会更容易理解些*。

6.byType装配总结

默认根据被装配对象的类型来匹配,如private BookDao bookDao;
默认type就是BookDao.class。

  1. 若只匹配到一个对象的话,直接装配。
  2. 若一个对象都没匹配到,则交给byName装配器,或抛出异常。
  3. 若匹配到多个对象后,判断是否有@Qualifier注解。有的话,根据Qualifier的值匹配id,匹配到了直接装配,匹配不到抛异常。
  4. 没有@Qualifier注解的话,就扫描符合条件的Bean是否有@Primary方法,如果有,直接将这个bean装配。否则交给byName装配器处理,或抛出异常。

7.byName装配总结

默认根据被装配对象的变量名来匹配,如private BookDao bookDao;
默认name就是"bookDao"。

  1. 因为Spring容器中不会出现两个相同的bean(就算配置里面强行写一样的,Spring也是进行一个覆盖操作),所以要么匹配到一个,要么没匹配到。
  2. 如果匹配到相应id的bean,就直接装配。否则交给byType装配其或者抛异常。

三、@Resource注解

@Resource是一个JSR-250规范注解。@Resource和@Autowired都是负责bean自动装配的注解,它们主要区别是@Resource默认使用byName装配器,而@Autowired默认使用byType装配器。接下来详细介绍下Resource的匹配规则。


1.常用属性介绍

如下代码是Resource注解的属性源码,最主要的两个属性就是name和type了
name的值是byName匹配时的值,type是byType匹配时的值。

public @interface Resource {
    /**
     * The JNDI name of the resource.  For field annotations,
     * the default is the field name.  For method annotations,
     * the default is the JavaBeans property name corresponding
     * to the method.  For class annotations, there is no default
     * and this must be specified.
     */
    String name() default "";
    
    /**
     * The Java type of the resource.  For field annotations,
     * the default is the type of the field.  For method annotations,
     * the default is the type of the JavaBeans property.
     * For class annotations, there is no default and this must be
     * specified.
     */
    Class<?> type() default java.lang.Object.class;

    enum AuthenticationType {
		CONTAINER,
		APPLICATION
    }
    String lookup() default "";
    AuthenticationType authenticationType() default AuthenticationType.CONTAINER;
    boolean shareable() default true;
    String mappedName() default "";
    String description() default "";
}

所以@Resource注解既可支持name,又支持type了,不像@Autowired一样,想自己配个id还得要@Qualifier来帮助。既然同时有name和type属性,那么就有4中情况可以分析了。


1.提供name和type

public class BookService
{
	@Resource(name="bookDao", type=BookDao.class)
	private BookDao bookDao;
}

这个没得说,精确匹配,匹配不到抛异常。

2.只提供name

public class BookService
{
	@Resource(name="bookDao")
	private BookDao bookDao;
}

这里我们只需要考虑,当byName匹配不到时,会不会交给byType装配器?
测试可知

public class BookService
{
	@Resource(name="bookDao5")
	private BookDao bookDao;
}

// 配置类
public class MyConfig
{
	@Bean("bookDao1")
	public BookDao bookDao()
	{
		return new BookDao(1);
	}

	@Primary
	@Bean("bookDao3")
	public BookDao bookDao3()
	{
		return new BookDao(3);
	}
}

IOC容器中只有默认扫描bookDao,和配置类中的bookDao1,bookDao3一共三个bean。
装配规则是name=“bookDao5”,byName肯定是匹配不到的。所以预想如果能交给byType装配器,就会配到带有Primary的bookDao3。

运行结果是:

Error creating bean with name 'bookService': 
Injection of resource dependencies failed; nested exception is 
org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No bean named 'bookDao5' available

抛出了个异常,说明根本没交给byType装配器,自己找不到就直接抛了…

做个小总结,以上两种情况,Resource的态度都是比较坚决的,byName失败后,直接抛异常,也不交给byType处理。以上两种情况有一个共同点,都提供了name属性。但后面的情况,Resource就不同了。


3.只提供type

public class BookService
{
	@Resource(type=BookDao.class)
	private BookDao bookDao1; // 这里是bookDao1 !!!
}

// 配置类
public class MyConfig
{
	@Bean("bookDao1")
	public BookDao bookDao()
	{
		return new BookDao(1);
	}

	@Primary
	@Bean("bookDao3")
	public BookDao bookDao3()
	{
		return new BookDao(3);
	}
}

当用户只指定type时,还是先得byName匹配,这个只要是Resource就得先进行byName匹配,不管你设不设置name属性。默认还是按变量名来匹配,所以以上的代码就是用bookDao1进行byName匹配,正好,匹配上了bookDao1这个对象,直接装配。

但如果byName没有匹配到怎么办,如下:

public class BookService
{
	@Resource(type=BookDao.class)
	private BookDao bookDao5; // 注意!!这里是bookDao5
}

// 配置类
public class MyConfig
{
	@Bean("bookDao1")
	public BookDao bookDao()
	{
		return new BookDao(1);
	}

	@Primary
	@Bean("bookDao3")
	public BookDao bookDao3()
	{
		return new BookDao(3);
	}
}

会不会和上面的一样,直接抛异常?答案是不会。运行结果如下:

BookDao [id=3]

此时的Resource没之前那么绝情了,byName没找到就交给byType装配器,最后byType装配器找到了@Primary修饰的bean并注入。


4.既没提供name,也没提供type

public class BookService
{
	@Resource
	private BookDao bookDao5; // 这里是bookDao5 !!!
}

// 配置类
public class MyConfig
{
	@Bean("bookDao1")
	public BookDao bookDao()
	{
		return new BookDao(1);
	}

	@Primary
	@Bean("bookDao3")
	public BookDao bookDao3()
	{
		return new BookDao(3);
	}
}

匹配规则是先byName, 再byType,具体的细节看上面的byName/byType总结。

所以上述代码的运行结果是:

BookDao [id=3]

四、@Autowired和@Resource区别

@Autowired里面属性少,就一个required。势单力薄,默认byType匹配,想要指定byName的属性得加@Qualifier。Autowired性情温顺,佛系匹配,byType匹配不到马上交给byName。

@Resource就是属于装配精良,name和type都能干,默认byName匹配。只要指定了name,性情就及其恶劣,byName找不到直接抛异常,根本不给byType装配器机会。但没指定name的情况下,就会给byType装配器操作的余地,就是默认byName没配到的话会交给byType来匹配。

你可能感兴趣的:(Spring)