@Autowired和@Resource依赖注入区别

@Autowired和@Resource依赖注入区别

捡破烂的诗人 591 2022-12-22
  • 联系方式:1761430646@qq.com
  • 编写时间:2022年12月22日16:36:24
  • 博客地址:www.zeroeden.cn
  • 菜狗摸索,有误勿喷,烦请联系

前言

  • 至少得知道@Autowired@Resouce注解都是去注入Bean
  • @Autowired是默认通过byType注入
  • @Resouce是默认通过byName注入
  • 这里基本知道总执行流程就完事了,具体不会怎么举例子做演示

环境准备

  • 准备一个MyService接口,并有三个实现类

    /**
     * @author: Zero
     * @time: 2022/12/5
     * @description:
     */
    
    
    public interface MyService {
    
        public void show();
        
    }
    

  • 准备一个控制层,并且注入实现类MyService接口的Bean

    /**
     * @author: Zero
     * @time: 2022/12/9
     * @description: 控制层
     */
    
    @RestController
    public class TestController {
    
        @Autowired
        private MyService myService;
    
        @GetMapping("/test")
        public String test() {
            myService.show();
            System.out.println("====这是控制层test()方法====");
            return "success";
        }
    
    
    }
    

1. @Autowired

1.1 牵扯到的其他两个注解

1.1.1 @Qualifier

  1. 当使用@Autowired注解注入某个类型的Bean
  2. 如果此时在IOC容器中,此类型的Bean有多个时
  3. 可以通过@Qualifier注解的属性指定Bean的名称

  • 可以从源代码上看到,注解@Quilifier上只有一个默认值为""的value属性,就是用来指定Bean的名称

1.1.2 @Primary

  1. 可以使用注解@Primary,加在某个Bean对应的类上做个标记
  2. 被注解@Primary标记的类,意味着当同类型的Bean有多个时,会优先注入当前这个

1.2 总执行流程

  • 下面先给出一张流程图来示意注解@Autowired注入Bean时的最终执行流程

  • 由于流程分支走向实在很多,我在图上主要用带圆圈的数字标号标记了一些常用的,比较重要的点,后面也是主要讲这些点,一些比较简单的就不理了(比如说只有单个Bean的类型刚好匹配,且没有@Qualifier注解约束,这时无疑就是直接注入这个Bean了)

1.3 注意点

1.3.1 @Qualifier的优先级比@Primary高

  • 当某个类型的Bean有多个时

  • 就会先去找有无配注解@Qualifier,有的话就按照注解@Qualifier配置的Bean名称来匹配

  • 在没有配置注解@Qualifier时才会去看是否配了注解@Primary来进行匹配Bean

  • 下面可以进行一个简单的小演示

  • 就是既配置了注解@Qualifier,又配置了注解@Primary

  • 配置如下所示

  • 启动程序,访问接口/test,其实验结果如下

  • 可以看到实际上注入的是注解Qualifier配置的名称为myServiceImplBBean

  • 也就是在即配了注解@Qualifier,又配了注解@Primary的情况下,注入时@Qualifier的优先级是高于@Primary

1.3.2 意思太长不知如何表达

  • 就是在使用注解@Autowired去注入Bean

  • 如果符合类型的Bean有多个

  • 并且不存在注解@Qualifier,不存在注解@Primary

  • 此时会待注入对象的变量名称默认为要注入的Bean名称,通过此名称去寻找符合的Bean

  • 如果找不到,不管注解@Autowiredrequired属性是true还是flase,直接抛异常

  • 这个也就是总执行流程图中标识的第3点

  • 下面可以简单演示下,配置如下所示

  • 各种情况分类

    • 待注入对象的变量名称刚好有对应的Bean

      1. Controller层配置如下

      2. 启动程序,访问接口/test,实验结果如下

      3. 可以看到能够正常启动程序,能够正常注入对应名称的Bean,业务也能够正常走动

    • 待注入对象的变量名称无对应的Bean,且注解@Autowired的属性requiredtrue(默认值也是为ture

      1. Controller层配置如下

      2. 可以此时找不到对应名称的BeanIDEA工具就直接给我们报错了(IDEA牛逼!!!)

      3. 尝试启动程序

      4. 可以看到启动失败,注意此时并不是说找不到名称为myServiceImplDBean,而是报发现多个符合Bean,但不知道注入哪个的问题

    • 待注入对象的变量名称无对应的Bean,且注解@Autowired的属性requiredfalse(默认值也是为ture

      1. Controller层如下

      2. 可以看到跟上述例子最大区别就是注解@Autowired的属性required值为false

      3. 但是IDEA上此时还是很明显的报错

      4. 尝试启动程序

      5. 启动失败,报错跟上述例子一模一样

      6. 可以得知此时在这种情况下,注解@Autowired的属性required无论是true还是false都会直接报错

2. @Resource

  • 如下是注解@Resource的源码

    package javax.annotation;
    
    import java.lang.annotation.*;
    import static java.lang.annotation.ElementType.*;
    import static java.lang.annotation.RetentionPolicy.*;
    
    /**
     * The Resource annotation marks a resource that is needed
     * by the application.  This annotation may be applied to an
     * application component class, or to fields or methods of the
     * component class.  When the annotation is applied to a
     * field or method, the container will inject an instance
     * of the requested resource into the application component
     * when the component is initialized.  If the annotation is
     * applied to the component class, the annotation declares a
     * resource that the application will look up at runtime. <p>
     *
     * Even though this annotation is not marked Inherited, deployment
     * tools are required to examine all superclasses of any component
     * class to discover all uses of this annotation in all superclasses.
     * All such annotation instances specify resources that are needed
     * by the application component.  Note that this annotation may
     * appear on private fields and methods of superclasses; the container
     * is required to perform injection in these cases as well.
     *
     * @since Common Annotations 1.0
     */
    @Target({TYPE, FIELD, METHOD})
    @Retention(RUNTIME)
    public @interface Resource {
        /**
         * The JNDI name of the resource.  For field annotations,
         * the default is the field name.  For method annotations,
         * the default is the JavaBeans property name corresponding
         * to the method.  For class annotations, there is no default
         * and this must be specified.
         */
        String name() default "";
    
        /**
         * The name of the resource that the reference points to. It can
         * link to any compatible resource using the global JNDI names.
         *
         * @since Common Annotations 1.1
         */
    
        String lookup() default "";
    
        /**
         * The Java type of the resource.  For field annotations,
         * the default is the type of the field.  For method annotations,
         * the default is the type of the JavaBeans property.
         * For class annotations, there is no default and this must be
         * specified.
         */
        Class<?> type() default java.lang.Object.class;
    
        /**
         * The two possible authentication types for a resource.
         */
        enum AuthenticationType {
                CONTAINER,
                APPLICATION
        }
    
        /**
         * The authentication type to use for this resource.
         * This may be specified for resources representing a
         * connection factory of any supported type, and must
         * not be specified for resources of other types.
         */
        AuthenticationType authenticationType() default AuthenticationType.CONTAINER;
    
        /**
         * Indicates whether this resource can be shared between
         * this component and other components.
         * This may be specified for resources representing a
         * connection factory of any supported type, and must
         * not be specified for resources of other types.
         */
        boolean shareable() default true;
    
        /**
         * A product specific name that this resource should be mapped to.
         * The name of this resource, as defined by the <code>name</code>
         * element or defaulted, is a name that is local to the application
         * component using the resource.  (It's a name in the JNDI
         * <code>java:comp/env</code> namespace.)  Many application servers
         * provide a way to map these local names to names of resources
         * known to the application server.  This mapped name is often a
         * <i>global</i> JNDI name, but may be a name of any form. <p>
         *
         * Application servers are not required to support any particular
         * form or type of mapped name, nor the ability to use mapped names.
         * The mapped name is product-dependent and often installation-dependent.
         * No use of a mapped name is portable.
         */
        String mappedName() default "";
    
        /**
         * Description of this resource.  The description is expected
         * to be in the default language of the system on which the
         * application is deployed.  The description can be presented
         * to the Deployer to help in choosing the correct resource.
         */
        String description() default "";
    }
    
    
  • 其中最重要的,也是最常用到的是属性name,以及属性type

  • 属性name用来指定注入的Bean名称,属性type用来指定注入的Bean的类型(注意假设我们待注入实现了某个接口的对象,这个通过type可以是指定某个特定的子类)

2.1 name + type

  • 执行流程如下

  • 特别注意这个type,是用来指明待注入Bean的类型的,通常来说是指待注入对象的子类

    1. 举个例子

    2. 假设现在有接口MyService,并有3个子实现类MyServiceImplA,MyServiceImplBMyServiceImplC

    3. 正常来说我们在Controller层使用注解@Resource注入对象时是这样写的

    4. 也就是带注入类型写的是某个接口,而不是某个特指的子类(方便后面维护,扩展)

    5. 而我们使用属性type就可指明我们想要的是某个特定的子实现类

    6. 当然,指明类型为MyServiceImplB,实际上注入的Bean可以是MyServiceImplB的子类,不仅仅局限于MyServiceImplB

  • 一点想法

    1. 待注入对象的类型我们往往写的是某个接口
    2. 其实这是类似于我们常写的List list = new ArrayList<Integer>()的写法
    3. 这是一种多态的表现,即父类变量可以引用子类实现
    4. 也是面向接口编程的写法
    5. 这样子最大的好处在于比如说后期我们需要把ArrayList改为LinkedList,直接改动一行代码即可(也就是改new就行了),后面代码全不用动
    6. 但是如果说一开始写的是ArrayList list = new ArrayList<Integer>()
    7. 在后续业务代码中可能会多多少少用到ArrayList独有的方法
    8. 那么在后续的改动集合类型时
    9. 要改动的代码可能就会非常大
    10. 所以我们在写待注入对象的类型时,也写的是某个接口
    11. 然后可以通过注解@Resource的属性type来特别指明其实现类(不同实现类可能带有不同的特性)

2.2 name

  • 执行流程如下

  • 特别注意,此时的type默认就是待注入对象的类型

  • 如果说有Beanname同名,但是类型不符合待注入对象的类型的话,一样会注入失败,直接抛异常

2.3 type

  • 执行流程如下

  • 特别注意如果此时寻找到了多个符合类型Bean的情况

  • 这时会默认把待注入对象的变量名称作为Bean名称,再次去匹配的

    1. 举个例子

    2. 假设现在接口MyService的子实现类还是有三个,分别是MyServiceImplAMyServiceImplBMyServiceImplC并且都注入到了IOC容器中

    3. Controller层如下

    4. 可以看到符合MyService类型的有三个Bean(myServiceImplA,myServiceImplB,myServiceImplC),所以Spring此时会将待注入对象的名称,也就是myServiceImplB,作为Bean的名称再次去匹配,从而会把MyServiceImplB类型的Bean注入进来

2.4 无name也无type

  • 执行流程如下

  • 特别注意注解@Resource在无配置属性name,也无配置属性type时,此时可以理解为会将待注入对象的类型认为是属性type,待注入对象的变量名称认为是属性name

  • 然后按照上图的执行流程走(注意按typename寻找不到唯一匹配的Bean时,此时还会多出一步按照type寻找的过程)

    • 举个例子

    • 假设现在接口MyService的子实现类还是有三个,分别是MyServiceImplAMyServiceImplBMyServiceImplC并且都注入到了IOC容器中

    • Controller层配置如下

    • 此时会按照type=MyService,name=myServiceImplAIOC容器中寻找唯一Bean

    • 刚好找到了,这时就会把MyServiceImplA对应的Bean注入进来

    • -----------------------------俺是分割线----------------------------------

    • 又比如另一个例子

    • 假设现在接口MyService的子实现类还是有三个,分别是MyServiceImplAMyServiceImplBMyServiceImplC但是只有MyServiceImplB注入到了IOC容器中

    • Controller层配置如下

    • 一开始就会按照type=MyService,name=temIOC容器中寻找唯一Bean

    • 但是找不到,于是开始走下一步

    • 下一步就是按照type=MyServiceIOC容器中寻找Bean

    • 此时刚好找到了MyServcieImplB,所以就把对应的Bean注入了进来

    • 但是如果说一开始MyServiceImplAMyServiceImplB,MyServiceImplC都注入到了IOC容器,此时就会抛异常

3. 一些点

  • 特别说明下

    • 注解@Autowired来自Spring框架

    • 注解@Resource来自Java(JSR-250)

  • 两者的作用点也是不同的,源码上都写有

    • @Autowired:可以标记在构造器上方法上方法参数上成员变量上注解上

    • @Resource:可以标记在类上成员变量上方法上

4. 引用


# Spring # SpringBoot # 区分 # @Autowired # @Resource # 依赖注入