@Autowired的使用方式

简介: 我相信`@Autowired`注解已经是我们实际开发过程中使用最多的注解之一啦!那你真会了它的全部使用方式吗?接下来,我将带你们去完完全全掌握它。
日积月累,水滴石穿 😄

前言

我相信@Autowired注解已经是我们实际开发过程中使用最多的注解之一啦!那你真会了它的全部使用方式吗?接下来,我将带你们去完完全全掌握它。

定义

我们先来看看 @Autowired注解的定义

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {

    /**
     * Declares whether the annotated dependency is required.
     * <p>Defaults to {@code true}.
     */
    boolean required() default true;
}

注意@Target的值,发现它可以在 构造方法上方法上方法参数上字段属性上注解类上可以被使用。而且注解内只有一个属性 required,并且默认为 true,该属性的作用是被Autowired标注的属性是否是必须被注入的。为true代表是必须的,如果没有找到所匹配的注入项,会抛出异常。如果为false,没有找到所匹配的注入项,将 null 赋予标注的属性,不会抛出异常。

使用方式

参数上使用

如下例,这种方式肯定是我们最最最常用的方式,没有之一:

@Component
public class UserService {

    @Autowired
    OrderService orderService;

    public UserService(){
        System.out.println("无参构造注入" + orderService);
    }

    public void test(){
        System.out.println("test=" +orderService);
    }
}

@Component
public class OrderService {

}

@ComponentScan(basePackages = {"com.gongj.populateBean"})
public class AppApplication {

}
public static void main(String[] args) {
    AnnotationConfigApplicationContext context = 
        new AnnotationConfigApplicationContext(AppApplication.class);
    UserService bean = context.getBean(UserService.class);
    bean.test();
}
结果:
xml 无参构造注入null
test=com.gongj.populateBean.OrderService@156643d4

构造方法使用:

@Component
public class UserService {

    //@Autowired
    OrderService orderService;


    public UserService(){
        System.out.println("xml 无参构造注入" + orderService);
    }

    @Autowired
    public UserService(OrderService orderService1){
        System.out.println("有参构造注入" + orderService1);
        orderService = orderService1;
    }

    public UserService(OrderService orderService1,OrderService orderService2){
        System.out.println("两个参数有参构造注入" + orderService1 +"==="+ orderService2);
        orderService = orderService1;
    }

    public void test(){
    System.out.println("test=" + orderService);
    }
}
结果:
有参构造注入com.gongj.populateBean.OrderService@3d012ddd
test=com.gongj.populateBean.OrderService@3d012ddd

在哪个构造方法上加 @Autowired注解,就使用哪个构造方法实例化对象。

普通方法上使用:

@Autowired
public void yyy(OrderService orderService){
    System.out.println("yyy=" + orderService);
    this.orderService = orderService;
}
结果:
xml 无参构造注入null
yyy=com.gongj.populateBean.OrderService@6504e3b2
test=com.gongj.populateBean.OrderService@6504e3b2

方法名可以随便起。

方法参数上使用:

public void xxx(@Autowired OrderService orderService){
    System.out.println("xxx=" + orderService);
    this.orderService = orderService;
}
结果:
xml 无参构造注入null
null

可以看到在方法参数上使用 @Autowired注解并没有生效。这是为什么呢?其实 Spring 已经说明了。不知道各位有没有注意,在#注解的源码最上面有一段很长的英文注释,其中有一段是这样的:

 * <h3>Autowired Parameters</h3>
 * <p>Although {@code @Autowired} can technically be declared on individual method
 * or constructor parameters since Spring Framework 5.0, most parts of the
 * framework ignore such declarations. The only part of the core Spring Framework
 * that actively supports autowired parameters is the JUnit Jupiter support in
 * the {@code spring-test} module (see the
 * <a href="https://docs.spring.io/spring/docs/current/spring-framework-
 reference/testing.html#testcontext-junit-jupiter-di">TestContext framework</a>
 * reference documentation for details).

大概意思就是:@Autowired注解虽然可以在方法参数上被声明,但是被Spring Framework 5.0的大部分框架所忽略,支持这种写法的只有 spring-test模块。但其实我使用 4.3.10.RELEASE方法参数上使用也是没有效果的,在 3.0.5.RELEASE版本中都不支持在方法参数上使用@Autowired注解。

但是你可以在这种情况下使用:

@Bean
public OrderService user(@Autowired OrderService o){
        System.out.println("user.o = "+ o);
        return new OrderService();
}

结果:
xml 无参构造注入null
user.o = com.gongj.populateBean.OrderService@1cd072a9
test=null

集合、Map、数组注入

@Autowired
private List<OrderService> os;

@Autowired
private Map<String,OrderService> maps;

@Autowired
OrderService[] ar;

public void otherAutowiring() {
        System.out.println(os);
        System.out.println(maps);
        System.out.println(ar);
}
结果:
[com.gongj.populateBean.OrderService@182decdb]
{orderService=com.gongj.populateBean.OrderService@182decdb}
[Lcom.gongj.populateBean.OrderService;@3796751b

如果注入的类型为 Map,其中 key 的类型必须为String类型, key 的默认值为每个 bean 的beanName,value为指定的bean类型。这种方式使用的挺多的,Spring 与 策略模式 结合,真的不要再爽了。


类型找到多个

我们都知道@Autowired是默认按照注入类型(byType)进行注入的,如果容器中存在两个及以上的相同类型的bean 时,再根据属性名称或者方法参数名称进行寻找(byName)。大部分人都会这么说:先byTypebyName,其实在 byType后面还有几层筛选,如果都不满足才会进行 byName。接下来就分别使用示例介绍这几种方式。

第一步

创建一个IPro接口,并编写一个get方法。

public interface IPro {

    void get();

}

第二步

创建两个子类,一个是 ProductService,另外一个是 ScienceService,并重写其父接口的方法。

@Service
public class ProductService implements IPro{

    @Override
    public void get() {
        System.out.println("ProductService = " + ProductService.class);
    }
}


@Service
public class ScienceService implements IPro{
    @Override
    public void get() {
        System.out.println("ScienceService = " + ScienceService.class);
    }
}

第三步

创建注入类TypeManyTest,在TypeManyTest类中注入 IPro并调用其get方法。

@Component
public class TypeManyTest {

    @Autowired
    IPro pro;

    public void test(){
            pro.get();
    }
}

第四步

修改启动类,并从容器中获得 TypeManyTest这个bean,调用其 test方法。

public static void main(String[] args) {
    AnnotationConfigApplicationContext context = 
        new AnnotationConfigApplicationContext(AppApplication.class);
    TypeManyTest bean = context.getBean(TypeManyTest.class);
    bean.test();
}

如果不出意外,会出现以下异常:

No qualifying bean of type 'com.gongj.populateBean.IPro' available: 
expected single matching bean but found 2: productService,scienceService

根据 IPro类型去找,找到了两个bean,然后再根据 pro作为 beanName再去寻找,发现没有匹配的,抛出异常。各位肯定想说,将 pro修改为 productService或者scienceService就好了。的确没毛病,先byTypebyName。不过这里我先分析其它几种解决方式。

@Qualifier

增加注解@Qualifier,通过使用 @Qualifier注解,我们可以明确的指出需要注入哪个 bean

@Autowired
@Qualifier("productService")
IPro pro;

再次启动项目,结果如下:
ProductService = class com.gongj.populateBean.ProductService

可能各位会觉得@Qualifier的值不就是 beanName吗。注意:@Qualifier的值并不一定是beanName,它只是会拿这个值会去与类型匹配成功的 beanName 进行比较而已。我们也可以在 ProductService类上使用,也能达到相同的效果。

@Service
@Qualifier("XXXX")
public class ProductService implements IPro{

    @Override
    public void get() {
        System.out.println("ProductService = " + ProductService.class);
    }
}

使用:
@Autowired
@Qualifier("XXXX")
IPro pro;

不过可不能这么使用哦!

@Autowired
IPro XXXX;

@Primary

@Primary可以理解为 默认优先选择,不可以同时设置多个, 其实是 BeanDefinitionprimary属性。我们在需要默认使用的类上增加注解。

@Service
@Primary
public class ScienceService implements IPro{
    @Override
    public void get() {
        System.out.println("ScienceService = " + ScienceService.class);
    }
}



// 需要将之前的注解 @Qualifier("XXXX") 去除,不然还是使用 ProductService这个对象 
// 因为 @Qualifier 的解析在 @Primary 之前
使用:
@Autowired
IPro pro;

结果:
ScienceService = class com.gongj.populateBean.ScienceService 

@Priority

@Priority它属于javax.annotation,JSR250 规范。通过@Priority来定义优先级,数字越小,优先级越高

@Service
@Qualifier("XXXX")
@Priority(3)  //优先级为 3 
public class ProductService implements IPro{

    @Override
    public void get() {
        System.out.println("ProductService = " + ProductService.class);
    }
}


@Service
@Priority(1)  //优先级为 1
public class ScienceService implements IPro{
    @Override
    public void get() {
        System.out.println("ScienceService = " + ScienceService.class);
    }
}

使用:
@Autowired
IPro pro;

结果:
ScienceService = class com.gongj.populateBean.ScienceService

上述都不满足,才进行 byName匹配。

源码地址

该方法位于 DefaultListableBeanFactory类。
介绍一下 candidates这个 Map 的值。就拿上述例子来说,值为两个,因为先根据 IPro类型能找到两个符合的 Bean。

  • scienceService:ScienceService 实例对象
  • productService:ProductService 实例对象
@Nllable
protected String determineAutowireCandidate(Map<String, Object> candidates, 
DependencyDescriptor descriptor) {
        Class<?> requiredType = descriptor.getDependencyType();

        // 取@Primary的bean
        String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
        if (primaryCandidate != null) {
                return primaryCandidate;
        }

        // 取优先级最高的bean 通过@Priority来定义优先级,数字越小,优先级越高
        String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
        if (priorityCandidate != null) {
                return priorityCandidate;
        }

        // byName
        for (Map.Entry<String, Object> entry : candidates.entrySet()) {
            String candidateName = entry.getKey();
            Object beanInstance = entry.getValue();
            if ((beanInstance != null && 
            this.resolvableDependencies.containsValue(beanInstance)) ||
                // 根据属性名确定
                //descriptor.getDependencyName():获得字段属性名称或者方法参数名称
                matchesBeanName(candidateName, descriptor.getDependencyName())) {
                
                    return candidateName;
            }
        }
        return null;
}

至于对@Qualifier注解是在另外一个地方处理的,本篇不详细概述。

总结

1、@Autowired默认按照类型(byType)进行注入,如果容器中存在两个及以上的相同类型的 bean 时,会存在以下几种筛选情况:

  • 1、首先根据@Qualifier注解指定的名称去进行匹配,匹配成功则返回。
  • 2、寻找被@Primary标注的bean,有则返回。
  • 3、根据@Priority(x)指定的优先级寻找,数字越小,优先级越高。
  • 4、最后才会根据属性名称或者方法参数名称进行寻找,如果还没有找到指定名称的 bean,则返回 null。
  • 5、判断@Autowiredrequired属性是否为 true 或者要注入的类型不是数组、Collection、Map,则抛出异常。
@Autowired(required = false)
IPro pro;

// 上述这写法还是会抛出异常的

贴一下源码

autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (autowiredBeanName == null) {
        // 没有确认出来唯一需要的beanName
        if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
            // 当前依赖是required,或者不是数组或Collection或Map ,则抛异常
            return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
        }
        else {
                return null;
        }
}

2、@Autowired可以标注在同一个类的多个构造器上面,但是required属性必须都为false。当有一个requiredtrue时,不允许其他构造器标有@Autowired注解,即使required属性为false也不行。

  • 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注。
相关文章
|
10月前
|
监控 Java Nacos
使用Spring Boot集成Nacos
通过上述步骤,Spring Boot应用可以成功集成Nacos,利用Nacos的服务发现和配置管理功能来提升微服务架构的灵活性和可维护性。通过这种集成,开发者可以更高效地管理和部署微服务。
3131 17
|
28天前
|
XML Java 数据格式
《深入理解Spring》:AOP面向切面编程深度解析
Spring AOP通过代理模式实现面向切面编程,将日志、事务等横切关注点与业务逻辑分离。支持注解、XML和编程式配置,提供五种通知类型及丰富切点表达式,助力构建高内聚、低耦合的可维护系统。
|
3月前
|
安全 API 数据安全/隐私保护
|
8月前
|
人工智能 智能设计 自然语言处理
2024云栖大会回顾|PAI ArtLab x 通往AGI之路系列活动,PAI ArtLab助力行业AI创新
2024云栖大会回顾|PAI ArtLab x 通往AGI之路系列活动,PAI ArtLab助力行业AI创新
|
监控 负载均衡 API
Apache Apisix轻松打造亿级流量Api网关
Apache APISIX 是一个动态、实时、高性能的 API 网关,提供负载均衡、动态上行、灰度发布、熔断、鉴权、可观测等丰富的流量管理功能。适用于处理传统南北向流量、服务间东西向流量及 k8s 入口控制。Airflow 是一个可编程、调度和监控的工作流平台,基于有向无环图 (DAG) 定义和执行任务,提供丰富的命令行工具和 Web 管理界面,方便系统运维和管理。
Apache Apisix轻松打造亿级流量Api网关
|
12月前
|
消息中间件 存储 监控
深度写作:深入源码理解MQ长轮询优化机制
【11月更文挑战第22天】在分布式系统中,消息队列(Message Queue, MQ)扮演着至关重要的角色。MQ不仅实现了应用间的解耦,还提供了异步消息处理、流量削峰等功能。而在MQ的众多特性中,长轮询(Long Polling)机制因其能有效提升消息处理的实时性和效率,备受关注。
399 12
|
Go 数据安全/隐私保护 iOS开发
Mac系统重装指南(不抹盘):2023版保姆级教程,轻松解决macOS问题并保留数据和软件
Mac系统重装指南(不抹盘):2023版保姆级教程,轻松解决macOS问题并保留数据和软件
889 0
|
Java 数据库连接 应用服务中间件
|
XML Java 数据格式
Spring系列文章3:基于注解方式依赖注入
Spring系列文章3:基于注解方式依赖注入
188 0
|
存储 缓存 Java
面试必杀技,讲一讲Spring中的循环依赖
纠正业界对循环依赖的几个错误认知,明确三级缓存的真正作用
28932 18