本人是正在学习Spring的大三学生,但我在网课和有些博客上看到对IOC自动装配注解方面有些表意不当或者不详细之处,所以这篇文章我想仔细分享我所学的Spring IOC容器自动装配方面的学习收获。建议读这篇文章之前先了解下Spring IOC容器的装配基础知识(组件扫描等等)和Web MVC架构模式。如果有大神发现我的文章哪里有问题的话,请指出来,不胜感激!!
@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默认是按找类型来匹配的(byType),所以当在IOC容器中只匹配到一个同类bean的话,这个就没什么说的了,直接注入即可。
public class BookService
{
@Autowired
private BookDao bookDao; // 这里的就是默认按BookDao这个类型来匹配的
}
现在就是要来讨论按类型没匹配到或匹配到多个的情况下,Spring是怎么处理的。
这种情况,Spring会自动按属性名来进行byName匹配。
public class BookService
{
@Autowired
private BookDao bookDao11;
}
如上面的代码片段,Spring会byName匹配为bookDao11的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就登场了。
这个注解就是来协助@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这个注解。
这个注解也是解决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又来了…
看完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。
默认根据被装配对象的类型来匹配,如private BookDao bookDao;
默认type就是BookDao.class。
默认根据被装配对象的变量名来匹配,如private BookDao bookDao;
默认name就是"bookDao"。
@Resource是一个JSR-250规范注解。@Resource和@Autowired都是负责bean自动装配的注解,它们主要区别是@Resource默认使用byName装配器,而@Autowired默认使用byType装配器。接下来详细介绍下Resource的匹配规则。
如下代码是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中情况可以分析了。
public class BookService
{
@Resource(name="bookDao", type=BookDao.class)
private BookDao bookDao;
}
这个没得说,精确匹配,匹配不到抛异常。
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就不同了。
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并注入。
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里面属性少,就一个required。势单力薄,默认byType匹配,想要指定byName的属性得加@Qualifier。Autowired性情温顺,佛系匹配,byType匹配不到马上交给byName。
@Resource就是属于装配精良,name和type都能干,默认byName匹配。只要指定了name,性情就及其恶劣,byName找不到直接抛异常,根本不给byType装配器机会。但没指定name的情况下,就会给byType装配器操作的余地,就是默认byName没配到的话会交给byType来匹配。