Spring注解驱动开发第20讲——使用@Autowired@Qualifier@Primary这三大注解自动装配组件,你会了吗?
Posted 李阿昀
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring注解驱动开发第20讲——使用@Autowired@Qualifier@Primary这三大注解自动装配组件,你会了吗?相关的知识,希望对你有一定的参考价值。
你知道@Autowired、@Qualifier、@Primary这些注解吗?
@Autowired注解
@Autowired注解可以对类成员变量、方法和构造函数进行标注,完成自动装配的工作。@Autowired注解可以放在类、接口以及方法上。
在使用@Autowired注解之前,我们对一个bean配置属性时,是用如下XML配置文件的形式进行配置的。
<property name="属性名" value=" 属性值"/>
下面我们来看一下@Autowired注解的源码,如下所示。
这儿对@Autowired注解说明一下:
-
@Autowired注解默认是优先按照类型去容器中找对应的组件,相当于是调用了如下这个方法:
applicationContext.getBean(类名.class);
若找到则就赋值。
-
如果找到多个相同类型的组件,那么是将属性名称作为组件的id,到IOC容器中进行查找,这时就相当于是调用了如下这个方法:
applicationContext.getBean("组件的id");
@Qualifier注解
@Autowired是根据类型进行自动装配的,如果需要按名称进行装配,那么就需要配合@Qualifier注解来使用了。
下面我们来看一下@Qualifier注解的源码,如下所示。
@Primary注解
在Spring中使用注解时,常常会使用到@Autowired这个注解,它默认是根据类型Type来自动注入的。但有些特殊情况,对同一个接口而言,可能会有几种不同的实现类,而在默认只会采取其中一种实现的情况下,就可以使用@Primary注解来标注优先使用哪一个实现类。
下面我们来看一下@Primary注解的源码,如下所示。
自动装配
在进行项目实战之前,我们先来说说什么是Spring组件的自动装配。Spring组件的自动装配就是Spring利用依赖注入,也就是我们通常所说的DI,完成对IOC容器中各个组件的依赖关系赋值。
项目实战
测试@Autowired注解
这里,我们以之前项目中创建的BookDao、BookService和BookController为例进行说明。BookDao、BookService和BookController的初始代码分别如下所示。
-
BookDao
package com.meimeixia.dao; import org.springframework.stereotype.Repository; // 名字默认是类名首字母小写 @Repository public class BookDao
-
BookService
package com.meimeixia.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.meimeixia.dao.BookDao; @Service public class BookService @Autowired private BookDao bookDao; public void print() System.out.println(bookDao);
-
BookController
package com.meimeixia.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import com.meimeixia.service.BookService; @Controller public class BookController @Autowired private BookService bookService;
可以看到,我们在BookService中使用@Autowired注解注入了BookDao,在BookController中使用@Autowired注解注入了BookService。为了方便测试,我们可以在BookService类中生成一个toString()方法,如下所示。
package com.meimeixia.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.meimeixia.dao.BookDao;
@Service
public class BookService
@Autowired
private BookDao bookDao;
public void print()
System.out.println(bookDao);
@Override
public String toString()
return "BookService [bookDao=" + bookDao + "]";
为了更好的看到演示效果,我们在项目的com.meimeixia.config包下创建一个配置类,例如MainConfigOfAutowired,如下所示。
package com.meimeixia.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
*
* @author liayun
*
*/
@Configuration
@ComponentScan("com.meimeixia.service", "com.meimeixia.dao", "com.meimeixia.controller")
public class MainConfigOfAutowired
接下来,我们便来测试一下上面的程序。在项目的src/test/java目录下的com.meimeixia.test包中创建一个单元测试类,例如IOCTest_Autowired,如下所示。
package com.meimeixia.test;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.meimeixia.config.MainConfigOfAutowired;
import com.meimeixia.service.BookService;
public class IOCTest_Autowired
@Test
public void test01()
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAutowired.class);
BookService bookService = applicationContext.getBean(BookService.class);
System.out.println(bookService);
applicationContext.close();
测试方法比较简单,这里我就不做过多说明了。然后,我们运行一下IOCTest_Autowired类中的test01()方法,得出的输出结果信息如下所示。
可以看到,输出了BookDao信息。
那么问题来了,我们在BookService类中使用@Autowired注解注入的BookDao(最后输出了该BookDao的信息),和我们直接在Spring IOC容器中获取的BookDao是不是同一个对象呢?
为了说明这一点,我们可以在IOCTest_Autowired类的test01()方法中添加获取BookDao对象的方法,并输出获取到的BookDao对象,如下所示。
package com.meimeixia.test;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.meimeixia.config.MainConfigOfAutowired;
import com.meimeixia.dao.BookDao;
import com.meimeixia.service.BookService;
public class IOCTest_Autowired
@Test
public void test01()
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAutowired.class);
BookService bookService = applicationContext.getBean(BookService.class);
System.out.println(bookService);
BookDao bookDao = applicationContext.getBean(BookDao.class);
System.out.println(bookDao);
applicationContext.close();
我们再次运行以上test01()方法,输出的结果信息如下所示。
可以看到,我们在BookService类中使用@Autowired注解注入的BookDao对象和直接从IOC容器中获取的BookDao对象是同一个对象。
你可能会问了,如果在Spring容器中存在对多个BookDao对象,那么这时又该如何处理呢?
首先,为了更加直观的看到我们使用@Autowired注解装配的是哪个BookDao对象,我们得对BookDao类进行改造,为其加上一个lable字段,并为其赋一个默认值,如下所示。
package com.meimeixia.dao;
import org.springframework.stereotype.Repository;
// 名字默认是类名首字母小写
@Repository
public class BookDao
private String lable = "1";
public String getLable()
return lable;
public void setLable(String lable)
this.lable = lable;
@Override
public String toString()
return "BookDao [lable=" + lable + "]";
然后,我们就在MainConfigOfAutowired配置类中注入一个BookDao对象,并且显示指定该对象在IOC容器中的bean的名称为bookDao2,并还为该对象的lable字段赋值为2,如下所示。
package com.meimeixia.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import com.meimeixia.dao.BookDao;
/**
*
* @author liayun
*
*/
@Configuration
@ComponentScan("com.meimeixia.service", "com.meimeixia.dao", "com.meimeixia.controller")
public class MainConfigOfAutowired
@Bean("bookDao2")
public BookDao bookDao()
BookDao bookDao = new BookDao();
bookDao.setLable("2");
return bookDao;
目前,在我们的IOC容器中就会注入两个BookDao对象。那此时,@Autowired注解到底装配的是哪个BookDao对象呢?
接着,我们来运行一下IOCTest_Autowired类中的test01()方法,发现输出的结果信息如下所示。
可以看到,结果信息输出了lable=1
,这说明,@Autowired注解默认是优先按照类型去容器中找对应的组件,找到就赋值;如果找到多个相同类型的组件,那么再将属性的名称作为组件的id,到IOC容器中进行查找。
那我们如何让@Autowired注解装配bookDao2呢? 这个问题问的好,其实很简单,我们只须将BookService类中的bookDao属性的名称全部修改为bookDao2即可,如下所示。
package com.meimeixia.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.meimeixia.dao.BookDao;
@Service
public class BookService
@Autowired
private BookDao bookDao2;
public void print()
System.out.println(bookDao2);
@Override
public String toString()
return "BookService [bookDao2=" + bookDao2 + "]";
此时,我们再运行IOCTest_Autowired类中的test01()方法,输出的结果信息如下所示。
可以看到,此时Eclipse控制台中输出了bookDao2的信息。
测试@Qualifier注解
从测试@Autowired注解的结果来看,@Autowired注解默认是优先按照类型去容器中找对应的组件,找到就赋值;如果找到多个相同类型的组件,那么再将属性的名称作为组件的id,到IOC容器中进行查找。
如果IOC容器中存在多个相同类型的组件时,那么我们可不可以显示指定@Autowired注解装配哪个组件呢?有些小伙伴肯定会说:废话!你都这么问了,那肯定可以啊!没错,确实是可以的!此时,@Qualifier注解就派上用场了!
在之前的测试案例中,Eclipse控制台中输出了BookDao [lable=2]
,这说明@Autowired注解装配了bookDao2,那我们如何显示的让@Autowired注解装配bookDao呢?
比较简单,我们只需要在BookService类里面的bookDao2字段上添加@Qualifier注解,显示指定@Autowired注解装配bookDao即可,如下所示。
package com.meimeixia.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import com.meimeixia.dao.BookDao;
@Service
public class BookService
@Qualifier("bookDao")
@Autowired
private BookDao bookDao2;
public void print()
System.out.println(bookDao2);
@Override
public String toString()
return "BookService [bookDao2=" + bookDao2 + "]";
此时,我们再次运行IOCTest_Autowired类中的test01()方法,输出的结果信息如下所示。
可以看到,此时尽管字段的名称为bookDao2,但是我们使用了@Qualifier注解显示指定了@Autowired注解装配bookDao对象,所以,最终的结果中输出了bookDao对象的信息。
测试容器中无组件的情况
如果IOC容器中无相应的组件,那么会发生什么情况呢?这时我们可以做这样一件事情,先注释掉BookDao类上的@Repository注解,
package com.meimeixia.dao;
import org.springframework.stereotype.Repository;
// 名字默认是类名首字母小写
//@Repository
public class BookDao
private String lable = "1";
public String getLable()
return lable;
public void setLable(String lable)
this.lable = lable;
@Override
public String toString()
return "BookDao [lable=" + lable + "]";
然后再注释掉MainConfigOfAutowired配置类中的bookDao()方法上的@Bean注解,如下所示。
package com.meimeixia.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import com.meimeixia.dao.BookDao;
/**
*
* @author liayun
*
*/
@Configuration
@ComponentScan("com.meimeixia.service", "com.meimeixia.dao", "com.meimeixia.controller")
public class MainConfigOfAutowired
// @Bean("bookDao2")
public BookDao bookDao()
BookDao bookDao = new BookDao();
bookDao.setLable("2");
return bookDao;
此时IOC容器中不再有任何BookDao对象了。
接着,我们再次运行IOCTest_Autowired类中的test01()方法,发现Eclipse控制台报了一个错误,截图如下。
详细的错误信息如下:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bookService': Unsatisfied dependency expressed through field 'bookDao2'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.meimeixia.dao.BookDao' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: @org.springframework.beans.factory.annotation.Qualifier(value=bookDao), @org.springframework.beans.factory.annotation.Autowired(required=true)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:84)
at com.meimeixia.test.IOCTest_Autowired.test01(IOCTest_Autowired.java:14)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipseSpring注解驱动第三讲
Spring注解驱动开发如何使用@Bean注解指定初始化和销毁的方法?看这一篇就够了!!
Spring注解驱动开发在@Import注解中使用ImportBeanDefinitionRegistrar向容器中注册bean