分类
spring

Spring @Autowired 注解

Autowired并不是一个英文词汇,而是auto + wired两个单词的组合。auto在英文中的原意为汽车,其本意是自动的意思,由于汽车可以不用人推、不用马拉自己跑,所以给它起了个auto的名字。auto源于automatic,即为自动的意思。比如汽车的自动变速器简称为AT,这其中的A则是指automatic。wired是wire的被动式,wire本意为电线。比如我们常说的无线wireless ,本意则是没有线的电线。wire加d,可理解为:串在一起的、绑在一起的、连在一起的。

所以Autowired的本意应该是:(将物体)自动连在一起。在计算机术语中,我们称其为:自动装配。

1. 概述

Spring 自2.5版本引用了注解驱动的依赖注入功能。该功能中最核心的注解即是@Autowired@Autowired 可以轻松的解决对象与对象间的依赖关系,将指定的bean自动的装配到其它需要它的bean中。

本文中,我们首先给出3种使用@Autowired 的方法,然后对使用@Qualifier 解决bean冲突做个简单的介绍。

2. 启用@Autowired 注解

Spring框架默认启用了依赖注入功能,所以在Spring中我们不需要做任何的配置即启用了@Autowired 注解的自动装配功能。

Spring就像一个大管家,管着很多个bean,哪里声明需要什么bean,它就负责提供什么bean。Spring Boot提供的@SpringBootApplication注解的作用之一便是:告知Spring大管家,应该到哪去获取到它管理的bean。

@SpringBootApplication
public class SpringAutowireApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAutowireApplication.class, args);
    }

}

上述代码使得Spring在启动时扫描@SpringBootApplication 所在包以及其子包中的所有bean,并将其纳入Spring大管家的管理范围。在术语中,我们又称这个大管为:Spring Application Context(Spring应用上下文)。

3. 使用@Autowired

我们可以在类的属性、setter方法以及构造函数上使用@Autowired 注解。

3.1 在属性上使用@Autowired

@Autowired可以直接应用到类的属性上,即使该属性的类型不是public、同时也没有为其定义setter方法,也不会影响自动装配的功能。

为了演示自动装配功能,我们首先定义一个FooFormatter bean:

@Component
public class FooFormatter {
    public String format() {
        return "foo";
    }
}

接着便可以在属性上应用@Autowired来完成自动装配:

@Component
public class FooService {  
    @Autowired
    private FooFormatter fooFormatter;
}

此时,Spring将自动为FooService中的FooFormatter属性提供fooFormatter bean。我们也可以认为是自动(auto)将FooFormatterFooFormatter连接(wire)了起来,所以此项功能叫做Autowired。

3.2 在setter方法上使用@Autowired

还可以在setter方法上使用@Autowired,比如3.1中的代码还可改写为:

@Component
public class FooService {
    private FooFormatter fooFormatter;
    @Autowired
    public void setFooFormatter(FooFormatter fooFormatter) {
        this.fooFormatter = fooFormatter;
    }
}

与在属性上使用@Autowired来完成自动装配相比较,在setter上使用@Autowired没有什么明显的不 。当我们在装配某个属性的同时还希望执行某些逻辑操作时,往往会这么做。

3.3 在构造函数上使用@Autowired

最后,还可以在构造函数上使用@Autowired,这也是官方推荐的一种使用方式,比如3.2的代码还可以改写为:

@Component
public class FooService {
    private FooFormatter fooFormatter;
    @Autowired
    public FooService(FooFormatter fooFormatter) {
        this.fooFormatter = fooFormatter;
    }
}

值得注意的是,在较新的Spring版本中完全可以省略在构造函数上声明的@Autowired注解。也就是说以下代码同样可以完成自动装配功能:

@Component
public class FooService {
    private FooFormatter fooFormatter;

    public FooService(FooFormatter fooFormatter) {
        this.fooFormatter = fooFormatter;
    }
}

4. NoSuchBeanDefinitionException

当声明自动装配的bean不存在时:

public interface BarRepository★ {
}

@Service
public class BarService {
    @Autowired
    BarRepository barRepository;
}
  • ★ 只声明了接口,并对接口进行实现,所以并不存在类型为BarRepository的bean。

在进行单元测试时,Spring将抛出NoSuchBeanDefinitionException:

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'barService': Unsatisfied dependency expressed through field 'barRepository'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'club.codedemo.springautowire.section4.BarRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

NoSuchBeanDefinitionException直译为:没有找到相关的bean。根据实际情况不同,解决这个异常的方法分为两种:

第一种是前期我们容易犯的错误,即我们的确需要这样的一个bean,而且该bean也是由我们提供的,但忘记使用@Component 相关注解声明了。解决的方法当然是声明该类型的bean即可。

第二种是该bean可以有,也可以没有。那么此时则可以将@Autowiredrequired 属性设置为false :

@Service
public class BarService {
    @Autowired(required = false)
    BarRepository barRepository;
}

值得注意的是Spring Boot很友好的处理了NoSuchBeanDefinitionException异常,当发生NoSuchBeanDefinitionException异常时,Spring Boot给我们的提示信息如下:

***************************
APPLICATION FAILED TO START
***************************

Description:

Field barRepository in club.codedemo.springautowire.section4.BarService required a bean of type 'club.codedemo.springautowire.section4.BarRepository' that could not be found.

The injection point has the following annotations:
    - @org.springframework.beans.factory.annotation.Autowired(required=true)

大体是说:没有找到类型为BarRepository的bean。

5. NoUniqueBeanDefinitionException

默认情况下@Autowired注解是根据类型来完成自动装配的,在装配的过程中如果同一类型的bean存在多个,则会发生NoUniqueBeanDefinitionException异常。

此时便需要告知Spring大管家如何解决此类问题。

5.1 @Qualifier

qualifier译为限定符、限定语。

@Qualifier注解能够非常好的解决此类问题,比如我们当前有两个实现了Message 接口的bean:

public interface Message {
    String hello();
}

@Component
public class FooMessage implements Message {

    @Override
    public String hello() {
        return "foo";
    }
}

@Component
public class BarMessage implements Message {

    @Override
    public String hello() {
        return "bar";
    }
}

此时若直接使用@Autowired 注解尝试注入Message:

@Service
public class MessageService {
    @Autowired
    Message message;
}

由于FooMessageBarMessage都实现了Message接口,所以Spring容器中现在有两个同样是实现了Message接口的bean,此时若使用@Autowired 尝试注入Message, Spring便不知道应该是注入FooMessage 还是BarMessage,所以发生NoUniqueBeanDefinitionException异常。

NoUniqueBeanDefinitionException的字面意思也很好的说明了这个问题:定义的bean不唯一。

当同一类型存在多个bean时,可以使用@Qualifier 来显示指定bean的名称,从而解决了Spring按类型注入时不知道应该注入哪个bean的尴尬情况。

@Service
public class MessageService {
    @Autowired
    @Qualifier("fooMessage")
    Message message;
}

默认情况下Spring自动为每个bean添加一个唯一的名称,该名称的命名原则为:将类的名称由大驼峰变为小驼峰。所以FooMessage 的bean名为:fooMessage;BarMessage 的bean名为: barMessage。除此以外,我们还可以自定义bean的名称,比如将BarMessage 的bean名称定义为customBarMessage:

@Component("customBarMessage")
public class BarMessage implements Message {

@Qualifier 将根据声明的fooFormatter来完成对Formatter的注入。此时FooService 中被注入的FormatterFooFormatter 而非BarFormatter

5.2 自定义装配限定器

Spring还允许我们基于Qualifer 自定义用于限定注入特定Bean的注解。

@Qualifier
@Target({
        ElementType.FIELD, ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER
})
@Retention(RetentionPolicy.RUNTIME)
public @interface MessageType {
}

此时我们将注解应用到FooMessage 上:

@Component
@MessageType
public class FooMessage implements Message {

则可以结合@Autowired 完成限定注入FooMessage :

@Service
public class MessageService {
    @Autowired
    @MessageType
    Message message1;
}

由于自定义的@MessageType 继承了@Qualifier ,所以其也拥有@Qualifier 的特性。当多个相同类型的Bean均使用@MessageType 声明时,也可以使用@MessageType(bean的名称) 来指定自动装配的bean。

5.3 依据属性名称自动装配

Spring在自动装配过程中,当同一类型存在多个bean时,还会继续按属性名称来进行查找,比如:

    @Autowired
    Message message;

Spring首先查找类型为Message的bean,最终查找到两个:fooMessage以及barMessage;接着将会在找到的两个bean中继续按属性名称message进行查找;最终发现fooMessage以及barMessage均不匹配message,这时候就视为查找失败,抛出异常。

根据上述规律,当某类型存在多个bean时,也可以使用指定属性名称与bean名相匹配的方法来完成限定注入:

@Service
public class MessageService {    
    @Autowired
    Message barMessage;
}

此时将成功注入一个BarMessage的实例。

总结

@Autowired 自动装配注解是Spring的核心注解之一,我们可以使用@Autowired 按接口类型轻松的完成bean的注入,而不必关心具体的实现。是对面向接口编程具体的阐释。在实际的使用过程中,往往会遇到多个bean实现同一接口而产生冲突的情况,此时则需要配合其它方法来告知Spring大管家冲突的解决方案。本文主要对@Autowried 注解的使用方法以及如何解决冲突进行阐述。

分类
spring

Spring @Primary 注解介绍

1. 概述

本文中我们将共同学习Spring自3.0版本引入的@Primary注解。

单词Primary意为首要的、主要的,其功能与名称相同:在依赖注入的过程中,当同一类型存在多个bean时,将首要(首先、优先)注入使用@Primary 注解的那个。

2. 适用场景

有些时候我们需要为同一类型注册多个不同的bean。

比如下述代码中我们为类型Employee(员工)提供了两个不同的bean:

@Configuration
public class Config {
 
    @Bean
    public Employee❶ JohnEmployee() {
        return new Employee("John")❷;
    }
 
    @Bean
    public Employee❶ TonyEmployee() {
        return new Employee("Tony")❷;
    }
}
  • ❶ 类型均为Employee
  • ❷ 返回两个不同的bean

然后使用@Autowired进行注入:

    @Autowired❶
    Employee❷ employee;
  • 此时当我们尝试 ❶注入 ❷Employee 时,则将发生NoUniqueBeanDefinitionException异常:
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'springPrimaryApplication': Unsatisfied dependency expressed through field 'employee'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'club.codedemo.springprimary.Employee' available: expected single matching bean but found 2: JohnEmployee,TonyEmployee

Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

我们通常使用@Qualifier(bean的名称) 来指定具体注入哪个bean以规避此类异常。关于@Quailfer的更多用法,请参考本文

本文我们将重点介绍采用@Primary注解来处理此类问题。

3. @Primary@Bean 结合使用

在注入过程中由于多个bean满足某一类型,同时这些bean之间又没有主次之分,所以Spring无法擅自做主为我们选择注入其中的某一个bean。

@Primary 则会告之Spring哪个bean的地位是主要的、首要的、要优先考虑的,此时当发生类型情况下,Spring在注入时则会优先使用这个bean。

    @Bean
    @Primary ★
    public Employee JohnEmployee() {
        return new Employee("John");
    }

值得注意的是:同类型被声明为Primary的bean最多只能有一个。如果我们在同一类型的Bean上声明多个Primary,同样会发生NoUniqueBeanDefinitionException异常,错误代码示例如下:

    @Bean
    @Primary ✘
    public Employee JohnEmployee() {
        return new Employee("John");
    }

    @Bean
    @Primary ✘
    public Employee TonyEmployee() {
        return new Employee("Tony");
    }
  • 同一Employee类型,在多个Bean中使用了 @Primary注解,将引发NoUniqueBeanDefinitionException异常。

此时Spring在注入时发现了多个被声明为的bean,两个bean的优先级相同。Spring同样没有办法自作主张的注入一个其中一个bean给我们,报错信息如下:

more than one 'primary' bean found among candidates: [JohnEmployee, TonyEmployee]
多于一个主bean被找到...

4. @Primary@Component 结合使用

@Primary还可以与@Component注解结合使用。

@Component
@Primary
public class SmsServiceAliImpl implements SmsService {
    @Override
    public void sendMessage(String phone, String message) {
    }
}

@Component
public class SmsServiceBaiduImpl implements SmsService {
    @Override
    public void sendMessage(String phone, String message) {
    }
}

由于@Service、@Controller等注解也属于@Component注解,所以@Primary同样可以与它们结合使用。

@Service
@Primary
public class FooServiceImpl implements FooService {
}

5. 总结

当同一类型存在多个bean时,使用@Primary注解可以轻松的确定一个bean出来,这为Spring在完成注入时提供了依据。既然被称为bean,则必须保证其唯一,所以相同类型被@Primary声明的Bean最多只能有一个。