@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也不行。

  • 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注。
相关文章
|
7月前
|
Java Spring 容器
@Resource 和 @Autowired区别是什么?
@Resource 和 @Autowired区别是什么?
|
7月前
|
Java Spring 容器
同一接口有多个实现类,怎么来注入一个指定的实现?@Resource、@Autowired、@Qualifier
同一接口有多个实现类,怎么来注入一个指定的实现?@Resource、@Autowired、@Qualifier
334 0
|
Java 容器
Java实现Autowired自动注入
Test2正常 Test3空指针 因为不在容器里
127 0
|
7月前
|
Java Spring
spring注解@Autowired、@Resource说明
spring注解@Autowired、@Resource说明
|
Java Spring
【Java注解用法】@Autowired 与@Resource的区别以及@Qualifier的介绍
【Java注解用法】@Autowired 与@Resource的区别以及@Qualifier的介绍
91 0
|
Java 编译器 Spring
Spring框架@Autowired和@Resource到底有什么区别
Spring框架@Autowired和@Resource到底有什么区别
521 0
|
Java Spring
解析Spring注解:@Resource与@Autowired的区别
在Spring框架中,依赖注入是实现松耦合、可维护性高的应用的重要方式之一。`@Resource`和`@Autowired`是两个常用的注解,用于实现依赖注入。然而,它们在用法和特点上有所不同。本文将详细介绍`@Resource`和`@Autowired`之间的区别,以及在Spring应用中的应用场景。
315 0
|
Java Spring 容器
@Inject和@Autowired的区别
@Inject和@Autowired的区别
|
开发框架 Java Spring
还在使用@Autowired 吗?并不推荐使用!
还在使用@Autowired 吗?并不推荐使用!
SpringAOP导致@Autowired依赖注入失败
SpringAOP导致@Autowired依赖注入失败
201 1