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注解说明一下:

  1. @Autowired注解默认是优先按照类型去容器中找对应的组件,相当于是调用了如下这个方法:

    applicationContext.getBean(类名.class);
    

    若找到则就赋值。

  2. 如果找到多个相同类型的组件,那么是将属性名称作为组件的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注解指定初始化和销毁的方法?看这一篇就够了!!

[刘阳Java]_Spring常用注解介绍_第6讲

Spring 注解驱动开发

Spring注解驱动开发在@Import注解中使用ImportBeanDefinitionRegistrar向容器中注册bean

Spring 注解驱动WEB 注解开发