@Autowired

来自Wikioe
跳到导航 跳到搜索


关于

@Autowired 是属于 Spring 的容器配置的一个注解,字面意义即“自动装配”。

 —— 在 Spring 的世界当中,自动装配指的就是“将 Spring 容器中的 bean 自动的和我们需要这个 bean 的类组装在一起”。


@Autowired 相对于 xml 方式的注入,特点仅仅是“自动装配”而无需通过 xml 配置类间关系,它与 xml 方式同样有不同的注入方式

参考 Spring 官方文档,建议了如下的使用场景:

  1. Field注入:大多数情况下尽量少使用字段注入。
    • 因为“会导致组件与 IoC 容器紧耦合”——对象无法脱离容器独立运行。
    • 一定要使用的话,@Resource 相对 @Autowired 对 IoC 容器的耦合更低。
  2. 构造器注入:强依赖性(即必须使用此依赖),不变性(各依赖不会经常变动)。
  3. Setter注入:依赖可选(没有此依赖也可以工作),可变(依赖会经常变动)。


Q:为什么要将成员变量加上 final 类型呢?
A:Spring 默认配置的 bean 的 scope 是 singleton,只会在 Spring 容器启动时初始化一次,即使不定义 final 也不会初始化第二次,而使用 final 可以防止成员变量在运行时被改变。

Field注入

最常见的注入方式。

示例:

@Controller
public class FooController {
    @Autowired
    private FooService fooService;
}

特点

优点
  • 代码简单。
缺点
  • 不能像构造器那样注入“不可变”的对象。
  • 依赖对外部不可见:外界可以看到构造器和 setter,但无法看到私有字段,自然无法了解所需依赖。
  • 会导致组件与 IoC 容器紧耦合 —— (这是最重要的原因,离开了 IoC 容器去使用组件,在注入依赖时就会十分困难)
    • 导致单元测试也必须使用 IoC 容器。

可能出现的问题

虽然 Field注入 存在各种各样的缺陷,但是由于代码简介、编码习惯等等原因,它仍旧是常常使用的注入方式。

而值得庆幸的是:在使用 IDEA 开发时,在字段上使用 Spring 的依赖注入注解 @Autowired 后会出现警告: Field injection is not recommended”(字段注入是不被推荐的)
  1. 容易出现空指针异常NullPointException
    示例:
    @Controller
    public class FooController {
        @Autowired
        private FooService fooService;
        
        private String str;
        
        public FooController() {
            str = fooService.getDes();
        }
    }
    
    以上,编译过程不会报错,但是运行之后报“NullPointerException”:
    Java 在初始化一个类时,是按照:“静态变量或静态语句块 -> 实例变量或初始化语句块 -> 构造方法 -> @Autowired”的顺序。
    所以在执行这个类的构造方法时,fooService 对象尚未被注入,它的值还是 null。
    • Field 注入允许构建对象实例的时候依赖的示例对象为空,这就导致了空指针异常无法尽早的暴露出来,因为你不调用将一直无法发现 NullPointException 的存在。 —— 而使用构造器注入,就不会存在这个问题。
  2. 不能有效的指明依赖
    示例:
    相信很多人都遇见过一个 bug依赖注入的对象为 null在启动依赖容器时遇到这个问题都是配置的依赖注入少了一个注解什么的这种方式就过于依赖注入容器了当没有启动整个依赖容器时这个类就不能运转在反射时无法提供这个类需要的依赖
    
    • 对于 IOC 容器以外的环境,除了使用反射来提供它需要的依赖之外,无法复用该实现类。 —— 对单元测试不友好,如果使用 Field 注入,那么进行单元测试就需要初始化整个Spring 环境,将所有 Bean 实例化
  3. 容易出现循环依赖
    示例:
    public class A {
        @Autowired
        private B b;
    }
    
    public class B {
        @Autowired
        private A a;
    }
    
    • 如果使用构造器注入,在 Spring 项目启动的时候,就会抛出:BeanCurrentlyInCreationException:Requested bean is currently in creation: Is there an unresolvable circular reference?从而提醒你避免循环依赖,如果是 field注入 的话,启动的时候不会报错,在使用那个 bean 的时候才会报错。

Setter注入

Spring 3.x 版本中推荐的注入方式。

示例:

@Controller
public class FooController {
    private FooService fooService;
    
    @Autowired
    public void setFooService(FooService fooService) {
        this.fooService = fooService;
    }
}

特点

Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later. Management through JMX MBeans is therefore a compelling use case for setter injection.

Setter注入应该主要用于可选依赖项,这些依赖项可以在类中分配合理的默认值。否则,在代码使用依赖项的任何地方都必须执行非空检查。setter注入的一个好处是setter方法使该类的对象可以稍后重新配置重新注入。因此,通过 JMX MBean 进行管理是 setter 注入的一个引人注目的用例。
优点
  • 用于可选依赖项。 —— 当没有提供它们时,类应该能够正常工作。
  • 用于可变依赖项。 —— 在对象被实例化之后,依赖项可以在任何时候被更改(重新配置或重新注册)。
缺点
  • 不保证依赖不可变。

构造器注入

Spring 4.x 版本中推荐的注入方式。

示例:

@Controller
public class FooController {
    private final FooService fooService;
    
    @Autowired
    public FooController(FooService fooService) {
        this.fooService = fooService;
    }
}

特点

The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state. As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.

Spring团队通常提倡构造函数注入,因为它允许将应用程序组件实现为不可变对象,并确保所需的依赖项不为空。此外,构造函数注入的组件总是以完全初始化的状态返回给客户端(调用)代码。顺便说一句,大量的构造函数参数是一种糟糕的代码气味,这意味着类可能有太多的责任,应该进行重构以更好地解决适当的关注点分离问题。
优点
  • 保证依赖不可变(final 关键字)。
  • 保证依赖不为空(NullPointException 异常)。 —— 强依赖处理,在编译阶段就能暴露出问题(省去了我们对其检查)
  • 保证返回客户端(调用)的代码的时候是完全初始化的状态,方便单元测试。
  • 避免了循环依赖 —— 因为确定了依赖关系的先顺序。
  • 提升了代码的可复用性。
缺点
  • 当注入参数较多时,代码臃肿,不够友好。 —— 考虑是否违反了类的“单一性职责”原则。

省略@Autowired的构造注入

在 Spring4.x 中增加了新的特性:

    如果类只提供了一个构造方法(且为含参构造方法),则不需要对其内部的属性写 @Autowired 注解,Spring 会自动为你注入属性。

示例:

@Controller
public class FooController {
    private final FooService fooService;
    private final TestService testService;
    
    public FooController(FooService fooService, TestService testService) {
        this.fooService = fooService;
        this.testService = testService;
    }
}


此外:

  • 若“没有 @Autowired 注解”:
    • 如果,“仅有一个含参构造方法(且没有无参构造方法)”,则,使用“仅有的(含参)构造方法”进行实例化。【如上所述】
    • 如果,存在“无参构造方法”,则,使用“无参构造方法”进行实例化。
    • 如果,没有“无参构造方法”,则,实例化失败。
  • 若“存在 @Autowired 注解”:
    • 如果,仅一个 @Autowired 注解,则,使用“@Autowired 注解的构造方法”进行实例化。
    • 如果,有多个 @Autowired 注解,且有任何 @Autowired(required = true)(默认),则,实例化失败。
    • 如果,有多个 @Autowired 注解,且全都是 @Autowired(required = false),则,使用“参数最多的参构造方法”进行实例化。

示例:[1]

  1. 如果:存在“无参构造方法”、没有 @Autowired 注解 —— 则:按照无参构造方法进行实例化
    public class ConstructorAutowiredTest {
        private User user;
        private Role role;
     
        public ConstructorAutowiredTest() {
        }
     
        public ConstructorAutowiredTest(User user) {
            this.user = user;
        }
     
        public ConstructorAutowiredTest(User user, Role role) {
            this.user = user;
            this.role = role;
        }
        ...
    }
    
  2. 如果:没有“无参构造方法”、没有 @Autowired 注解 —— 报错:实例化失败!
    public class ConstructorAutowiredTest {
        private User user;
        private Role role;
     
        public ConstructorAutowiredTest(User user) {
            this.user = user;
        }
     
        public ConstructorAutowiredTest(User user, Role role) {
            this.user = user;
            this.role = role;
        }
        ...
    }
    
  3. 如果:仅有一个“含参构造方法”、没有 @Autowired 注解 —— 则:按照仅有的构造方法实例化
    public class ConstructorAutowiredTest {
        private User user;
        private Role role;
     
        public ConstructorAutowiredTest(User user) {
            this.user = user;
        } 
        ...
    }
    
  4. 如果:存在 @Autowired 注解 —— 则:按照 @Autowired 注解的参构造方法进行实例化
    public class ConstructorAutowiredTest {
        private User user;
        private Role role;
     
        public ConstructorAutowiredTest() {
        }
     
        public ConstructorAutowiredTest(User user) {
            this.user = user;
        }
     
        @Autowired
        public ConstructorAutowiredTest(User user, Role role) {
            this.user = user;
            this.role = role;
        }
        ...
    }
    
  5. 如果:存在多个 @Autowired 注解(默认:required=true) —— 报错:实例化失败!
    public class ConstructorAutowiredTest {
        private User user;
        private Role role;
     
        public ConstructorAutowiredTest() {
        }
    
        @Autowired
        public ConstructorAutowiredTest(User user) {
            this.user = user;
        }
     
        @Autowired
        public ConstructorAutowiredTest(User user, Role role) {
            this.user = user;
            this.role = role;
        }
        ...
    }
    
  6. 如果:存在多个 @Autowired 注解(有任何:required=true) —— 报错:实例化失败!
    public class ConstructorAutowiredTest {
        private User user;
        private Role role;
     
        public ConstructorAutowiredTest() {
        }
    
        @Autowired(required = true)
        public ConstructorAutowiredTest(User user) {
            this.user = user;
        }
     
        @Autowired(required = false)
        public ConstructorAutowiredTest(User user, Role role) {
            this.user = user;
            this.role = role;
        }
        ...
    }
    
  7. 如果:存在多个 @Autowired 注解(所有:required=false) —— 则:按照 @Autowired 注解的(最多的)含参构造方法进行实例化
    public class ConstructorAutowiredTest {
        private User user;
        private Role role;
     
        public ConstructorAutowiredTest() {
        }
    
        @Autowired(required = false)
        public ConstructorAutowiredTest(User user) {
            this.user = user;
        }
     
        @Autowired(required = false)
        public ConstructorAutowiredTest(User user, Role role) {
            this.user = user;
            this.role = role;
        }
        ...
    }
    

附:其他方式

除上所示,@Autowired 还有其他的使用方式。
  1. 应用于具有任意名称和多个参数的方法:
    public class MovieRecommender {
     
        private MovieCatalog movieCatalog;
     
        private CustomerPreferenceDao customerPreferenceDao;
     
        @Autowired
        public void prepare(MovieCatalog movieCatalog,
                CustomerPreferenceDao customerPreferenceDao) {
            this.movieCatalog = movieCatalog;
            this.customerPreferenceDao = customerPreferenceDao;
        }
     
        // ...
    }
    
  2. 应用于需要该类型数组的字段或方法:
    public class MovieRecommender {
     
        @Autowired
        private MovieCatalog[] movieCatalogs;
     
        // ...
    }
    
  3. 应用于需要该类型集合的字段或方法:
    public class MovieRecommender {
     
        private Set<MovieCatalog> movieCatalogs;
     
        @Autowired
        public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
            this.movieCatalogs = movieCatalogs;
        }
     
        // ...
    }
    
    public class MovieRecommender {
     
        private Map<String, MovieCatalog> movieCatalogs;
     
        @Autowired
        public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
            this.movieCatalogs = movieCatalogs;
        }
     
        // ...
    }
    

附:@Autowired与其他注解

@Autowired 是属于 Spring 的容器配置的一个注解,与它同属容器配置的注解还有:@Required、@Primary、@Qualifier 等等。

与 @Resource 区别

简单来说:@Resource 相当于“@Autowired”和“@Qualifier” 一起使用。

比较:

@Autowired @Resource
相同 都是通过注解实现依赖注入。
不同 Spring 定义 JSR-250(Java 注解规范)定义
默认方式:ByType —— 如果 ByType 有多个匹配类,则再通过属性名来使用 ByName 方式
  • 可以配合“@Qualifier”强制使用 ByName 方式
默认方式:ByName —— 如果找不到则使用 ByType 方式
可以对:构造器方法参数字段使用 只能对:方法字段使用

相关注解

  1. @Required:与 @Autowired 配合使用,用于指定是否必须有 Bean 候选者。
    • 与“@Autowired(required=true)”作用一样;
  2. @Qualifier:与 @Autowired 配合使用,用于指定 Bean Name 来强制使用名称装配。
    • 可以用于解决“依赖对象冲突”(即,具有多个符合条件的依赖对象);
  3. @Primary:与 @Component 配合使用,用于指定该类型(ByType)下的“默认注入对象”。
    • 可以用于解决“依赖对象冲突”(即,具有多个符合条件的依赖对象);
  4. @Lazy:与 @Autowired、@Component 配合使用,用于“推迟‘带注解的 bean’和‘带注释的 @Autowired 位置’的初始化”。
    • 可以用于解决“循环依赖”(即,A 依赖 B 的同时 B 依赖 A);