@Autowired、@Resource、@Inject和@Service

一、什么是注解

Annotation(注解)是JDK1.5及以后版本引入的。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。注解是以@注解名在代码中存在的。根据注解参数的个数,我们可以将注解分为:标记注解、单值注解、完整注解三类。它们都不会直接影响到程序的语义,只是作为注解(标识)存在,可以通过反射机制编程实现对这些元数据(用来描述数据的数据)的访问。另外,可以在编译时选择代码里的注解是否只存在于源代码级,或者它也能在class文件、或者运行时中出现(SOURCE/CLASS/RUNTIME)。

二、注解的效应

传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop、事务,这么做有两个缺点:

  1. 如果所有的内容都配置在.xml文件中,会导致.xml文件过大;如果按需求分开.xml文件,又会导致.xml文件过多。总之这会使得配置文件的可读性与可维护性变得很低。
  2. 开发中,在.java文件和.xml文件之间不断切换,是一件麻烦的事。同时这种思维上的不连贯也会降低开发的效率。

为了解决这两个问题,Spring引入了注解,通过@注解名的方式,让注解与Java Bean紧密结合,既大大减少了配置文件的体积,又增加了Java Bean的可读性与内聚性。

三、@Autowired、@Resource、@Inject和@Service

先看一个不使用注解的Spring示例,在这个示例的基础上,改成注解版本的,这样也能看出使用与不使用注解之间的区别,先定义一个老师:

public class Teacher{
        private String teacherName = "TW";
        public String toString() {
            return "TeacherName:" + teacherName;
        }
    }

再定义一个学生:

    public class Student{
        private String studentName = "SL";
        public String toString() {
            return "StudentName:" + studentName;
        }
    }

然后定义一个学校:

public class School{
        private Teacher teacher;
        private Student student;
        public void setTeacher(Teacher teacher){
            this.teacher = teacher;
        }
        public void setStudent(Student student){
            this.student = student;
        }
        public Teacher getTeacher(){
            return teacher;
        }
        public Student getStudent(){
            return student;
        }
        public String toString(){
            return teacher + "\n" + student;
        }
    }

spring的配置文件这么写:

<?xml version="1.0" encoding="UTF-8"?>
<bean id="school" class="com.zxt.bean.School" >
    <property name="teacher" ref="teacher" />
    <property name="student" ref="student" />
</bean>
<bean id="teacher" class="com.zxt.uu.Teacher" />
<bean id="student" class="com.zxt.uu.Student" />

这是最初始的.xml配置。

@Autowired

顾名思义,就是自动装配。其作用是替代Java代码里面的getter/setter与bean属性中的property。如果私有属性需要对外提供的话,getter应当予以保留。引入@Autowired注解,先看一下spring配置文件怎么写:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
 3     xmlns="http://www.springframework.org/schema/beans"  
 4     xmlns:context="http://www.springframework.org/schema/context"  
 5     xsi:schemaLocation="http://www.springframework.org/schema/beans 
 6         http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
 7         http://www.springframework.org/schema/context
 8         http://www.springframework.org/schema/context/spring-context-4.2.xsd">
 9     
10     <context:component-scan base-package="com.zxt" />
11     
12     <bean id="school"  class="com.zxt.bean.School" />
13     <bean id="teacher" class="com.zxt.uu.Teacher" />
14     <bean id="student" class="com.zxt.uu.Student" />
15     
16 </beans>

注意第10行,为了实现bean的自动载入,必须配置spring的扫描器。

在base-package指明一个包:

<context:component-scan base-package=“com.zxt”/>

表明com.zxt包及其子包中,如果某个类的头上带有特定的注解@Component@Repository@Service@Controller,就会将这个对象作为Bean注入进spring容器。有了context:component-scan,另一个context:annotation-config标签就可以移除掉,因为已经被包含进去了。
context:component-scan提供两个子标签:context:include-filtercontext:exclude-filter。各代表引入和排除的过滤。

看到第12行,原来school里面应当注入两个属性teacher、student,现在不需要注入了。再看下,School.java也很简练,把getter/setter都可以去掉:

public class School{
        @Autowired
        private Teacher teacher;
        @Autowired
        private Student student;
        public String toString(){
            return teacher + "\n" + student;
        }
    }

这里@Autowired注解的意思就是,当Spring发现@Autowired注解时,将自动在代码上下文中找到与其匹配(默认是类型匹配)的Bean,并自动注入到相应的地方去。

xml优先原则
值得注意的是,假如.xml文件bean里面有property,而School.java里面却去掉了属性的getter/setter,并使用@Autowired注解标注这两个属性会怎么样?答案是Spring会按照xml优先的原则去School.java中寻找这两个属性的getter/setter,导致的结果就是初始化bean报错。

假设此时把.xml文件的13行、14行两行再给去掉,运行会抛出异常:
Exception in thread “main” org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘School’: Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.zxt.uu.Teacher com.zxt.bean.School.teacher; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.zxt.uu.Teacher] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:334) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:543) …
因为,@Autowired注解要去寻找的是一个Bean,Teacher和 Student的Bean定义都给去掉了,自然就不是一个Bean了,Spring容器找不到也很好理解。那么,如果属性找不到又不想让Spring容器抛出异常,而就是显示null,可以吗?可以,其实异常信息里面也给出了提示,就是将@Autowired注解的required属性设置为false 即可:

public class School{
        @Autowired(required = false)
        private Teacher teacher;
        @Autowired(required = false)
        private Student student;
        public String toString(){
            return teacher + "\n" + student;
        }
    }

此时,找不到teacher、student两个属性,Spring容器不再抛出异常而是认为这两个属性为null。

@Autowired接口注入----->@Qualifier

上面仅仅只是注入一个Java类,比较简单。那么如果有一个接口,有多个实现,Bean里引用的是接口名,又该怎么做呢?比如有一个Person接口:

public interface Person{
        public String personName();
    }

两个实现类Doctor和Police:

    @Service
    public class Doctor implements Person{
        public String docName(){
            return “Doctor person”;
        }
    }

    @Service
    public class Police implements Person{
        public String personName(){
            return “Police person”;
        }
    }

写一个PersonFactory,引用Person:

    @Service
    public class PersonFactory{
        @Autowired
        private Person person;
        public String toString(){
            return person.personName();
        }
    }

Person接口有两个实现类,Spring并不知道应当引用哪个实现类,代码报错。这种情况通常有两个解决办法:

  1. 删除其中一个实现类,Spring会自动去base-package下寻找Person接口的实现类,发现Person接口只有一个实现类,便会直接引用这个实现类。
  2. 实现类就是有多个该怎么办?此时可以使用@Qualifier注解:
@Service
public class PersonFactory{
    @Autowired
    @Qualifier("Doctor")
    private Person person;
    public String toString(){
        return person.personName();
    }
}

注意@Qualifier注解括号里面的必须是Person接口实现类的类名,不是bean的名字。写成"doctor",结果会报错。

@Resource

@Resource注解作用与@Autowired非常相似。先看一下@Resource,直接写School.java了:

@Service
public class School{
    @Resource(name = "teacher")
    private Teacher teacher;
    @Resource(type = Student.class)
    private Student student;
    public String toString(){
        return teacher + "\n" + student;
    }
}

这是详细一些的用法,说一下@Resource的装配顺序:

  1. @Resource后面没有任何内容,默认通过name属性去匹配bean,找不到再按type去匹配。
  2. 指定了name或者type则根据指定的类型去匹配bean。
  3. 指定了name和type则根据指定的name和type去匹配bean,任何一个不匹配都会报错。

@Autowired和@Resource两个注解的区别:

  1. @Autowired默认按照byType方式进行bean匹配,@Resource默认按照byName方式进行bean匹配
  2. @Autowired是Spring的注解,@Resource是J2EE的注解,根据导入注解的包名就可以知道。
  3. Spring属于第三方的,J2EE是Java自己的东西。因此,建议使用@Resource注解,以减少代码和Spring之间的耦合。

@Inject

  1. @Inject是JSR330 (Dependency Injection for Java)中的规范,需要导入javax.inject.Inject jar包 ,才能实现注入。
  2. @Inject可以作用constructor、method、field上。
  3. @Inject是根据类型进行自动装配的,如果需要按名称进行装配,则需要配合@Named;

简单示例:

@Inject
private Car car;

指定加入BMW组件:

@Inject
@Named("BMW")
private Car car;

@Named 的作用类似 @Qualifier!

@Service

使用@Service,可以更加简化.xml文件配置。因为spring的配置文件里面还有12行~14行三个bean,应用spring配置文件里面一个自动扫描的标签,可以把这三个bean也给去掉,增强Java代码的内聚性并进一步减少配置文件。先看一下配置文件:

<context:component-scan base-package="com.zxt" />

配置文件看起来特别清爽。下面以School.java为例,其余的Teacher.java和Student.java都一样:

  @Service 
  public class School{ 
       @Autowired 
       private Teacher teacher; 
      @Autowired 
       private Student student; 
       public String toString(){ 
             return teacher + "\n" + student; 
       } 
} 

这样,School.java在Spring容器中存在的形式就是"school",即可以通过ApplicationContext的getBean("school")方法来得到School.java。@Service注解,其实做了两件事情:

  1. 声明School.java是一个bean。这点很重要,因为School.java是一个bean,其他的类才可以使用@Autowired将School作为一个成员变量自动注入。
  2. School.java在bean中的id是"school",即类名且首字母小写。

如果不想用这种形式怎么办,就想让School.java在Spring容器中的名字叫做"School",可以的:

@Service
@Scope("prototype")
public class School{
    @Autowired
    private Teacher teacher;
    @Autowired
    private Student student;
    public String toString(){
        return "TeacherName:" + teacher + "\nStudentName:" + student;
    }
}

这样,就可以通过ApplicationContext的getBean(“school”)方法来得到School.java了。

这里说下@Scope注解。因为Spring默认产生的bean是单例的,如果不想使用单例,xml文件里面可以在bean里面配置scope属性。注解也一 样,配置@Scope即可,默认是"singleton"即单例,"prototype"表示原型即每次都会new一个新的出来。

注意:
假如school包下有Teacher、uu包下也有Teacher,它们二者都加了@Service注解,那么在School.java中即使明确表示我要引用的是uu包下的Teacher,程序运行的时候依然会报错。
这是因为,两个Teacher都使用@Service注解标注,意味着两个Bean的名字都是"teacher",那么我在School.java中自动装配的是哪个Teacher呢?不明确,因此,Spring容器会抛出BeanDefinitionStoreException异常,Caused by:
org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name ‘teacher’ for bean class [com.zxt.uu.Teacher] conflicts with existing, non-compatible bean definition of same name and class [com.zxt.school.Teacher]

四、总结:

  1. 1️⃣@Autowired是Spring自带的,通过AutowiredAnnotationBeanPostProcessor 类实现的依赖注入,Spring属于第三方的。
    2️⃣@Resource是JSR250规范的实现,J2EE的注解,在javax.annotation包下,根据导入注解的包名就可以知道。J2EE是Java自己的东西。因此,建议使用@Resource注解,以减少代码和Spring之间的耦合。
    3️⃣@Inject是JSR330规范实现的,需要导入javax.inject.Inject jar包 ,才能实现注入。
  2. 1️⃣@Autowired可以作用在constructor、method、parameter、field、annotation_type上。
    2️⃣@Resource可以作用method、field、type上。
    3️⃣@Inject可以作用constructor、method、field上。
  3. 1️⃣@Autowired默认按照byType方式进行bean匹配。
    2️⃣@Resource默认根据属性名称(byName)方式进行自动装配。如果有多个类型一样的Bean候选者,则可以通过name指定进行注入。
    3️⃣@Inject是根据类型进行自动装配的,如果需要按名称进行装配,则需要配合@Named。
  4. @Autowired如果有多个类型一样的Bean候选者,需要指定按照名称(byName)进行装配,则需要配合@Qualifier。指定名称后,如果Spring IOC容器中没有对应的组件bean抛出NoSuchBeanDefinitionException。也可以将@Autowired中required配置为false,当找不到相应bean的时候,系统不会抛异常。
  5. @Autowired、@Inject用法基本一样,不同的是@Inject没有一个request属性。



作者:MChopin
链接:https://www.jianshu.com/p/931cdba58cf7
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

下面是测试代码,根据代码会比较直观一点。

 package com.springmvc.bean;
 
 import org.springframework.stereotype.Component;
 
  @Component("arg1")
 public class AunotationBean {
 
     public AunotationBean() {
           System.out.println("constructors of com.springmvc.[bean].AunotationBean");
     }
      
      public void init() {
          System.out.println("init com.springmvc.[bean].AunotationBean");
      }
  }
  package com.springmvc.bean;
  
  public interface Temporary {
  
     void init();
  }
 package com.springmvc.bean;
  
 import org.springframework.stereotype.Component;
  
  @Component("temp1")
  public class Temporary1Impl implements Temporary {
 
      public Temporary1Impl() {
         System.out.println("constructors of Temporary[1]Impl");
     }
 
    @Override
     public void init() {
          System.out.println("init Temporary[1]Impl");
     }
  }
  package com.springmvc.bean;
 
  import org.springframework.stereotype.Component;
  
  @Component("temp2")
  public class Temporary2Impl implements Temporary {
  
       public Temporary2Impl() {
         System.out.println("constructors of Temporary[2]Impl");
     }
 
     @Override
      public void init() {
 
          System.out.println("init Temporary[2]Impl");
     }
 }
 package com.springmvc.demo;
  
 import org.springframework.stereotype.Component;
   
 @Component("arg2")
  public class AunotationBean {
 
      public AunotationBean() {
          System.out.println("constructors of com.springmvc.[demo].AunotationBean");
      }
    
     public void init() {
         System.out.println("init com.springmvc.[demo].AunotationBean");
     }
  }
  package com.springmvc.main;
  
  import javax.annotation.Resource;
 
  import org.springframework.stereotype.Component;
 import com.springmvc.bean.Temporary;
  
  @Component
  public class SpringmvcTest {
  
     @Resource(name = "arg1")
     com.springmvc.bean.AunotationBean arg1; // 定义了name属性的值, 就只按照name值匹配
    
     @Resource
    com.springmvc.demo.AunotationBean arg2; // 根据属性(arg2)匹配, 如果匹配不到就进行type匹配
     
     @Resource // 接口会去寻找其实现类
     Temporary temp1; // 如果此处是 Temporary temp11, 会先根据temp11寻找, 找不到的时候再根据Temporary寻找, 会寻找到2个实现类
      
     @Resource // 如果不定义name, 那么会自动根据name(temp2)属性值进行匹配
     Temporary temp2;
    
     // [个人猜测]装载的时候是根据类名是否重复进行判断, 同一个接口的多个实现类只要类名不同即可通过编译, 但是没有直接关联的两个类如果类名相同, 不会通过编译, 匹配的时候如果是接口类型, 那么会寻找其实现类, 这个时候会出现有多个选择的问题
 }
 package com.springmvc.main;
 import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.stereotype.Component;
  
  @Component
  public class SpringmvcMain {
  
      public SpringmvcMain(@Autowired SpringmvcTest args) {
         args.arg1.init();
        args.arg2.init();
         args.temp1.init();
          args.temp2.init();
     }
  }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章