• Spring:所有依赖项注入的类型


    一、前言

      Spring文档严格只定义了两种类型的注入:构造函数注入和setter注入。但是,还有更多的方式来注入依赖项,例如字段注入,查找方法注入。下面主要是讲使用Spring框架时可能发生的类型。

    二、构造函数注入(Constructor Injection

      这是最简单和推荐的依赖项注入方式。一个依赖类有一个构造函数,所有的依赖都被设置了,它们将由Spring容器根据XML,Java或基于注释的配置来提供。

     1 package example;
     2 
     3 import org.springframework.context.ApplicationContext;
     4 import org.springframework.context.support.ClassPathXmlApplicationContext;
     5 
     6 class Service1 {
     7     void doSmth() {
     8         System.out.println("Service1");
     9     }
    10 }
    11 
    12 class Service2 {
    13     void doSmth() {
    14         System.out.println("Service2");
    15     }
    16 }
    17 
    18 class DependentService {
    19     private final Service1 service1;
    20     private final Service2 service2;
    21 
    22     public DependentService(Service1 service1, Service2 service2) {
    23         this.service1 = service1;
    24         this.service2 = service2;
    25     }
    26 
    27     void doSmth() {
    28         service1.doSmth();
    29         service2.doSmth();
    30     }
    31 
    32 }
    33 
    34 public class Main {
    35     public static void main(String[] args) {
    36         ApplicationContext container = new ClassPathXmlApplicationContext("spring.xml");
    37         ((DependentService) container.getBean("dependentService")).doSmth();
    38     }
    39 }

      这里有2个独立服务和1个从属服务。依赖关系仅在构造函数中设置,要运行它,我们将初始化容器,并为其提供spring.xml中包含的配置。

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <beans xmlns="http://www.springframework.org/schema/beans"
     3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     4        xsi:schemaLocation="
     5 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
     6 
     7     <bean id="service1" class="example.Service1"/>
     8     <bean id="service2" class="example.Service2"/>
     9 
    10     <bean id="dependentService" class="example.DependentService">
    11         <constructor-arg type="example.Service1" ref="service1"/>
    12         <constructor-arg type="example.Service2" ref="service2"/>
    13     </bean>
    14 
    15 </beans>

      我们将所有3个服务声明为Spring bean,最后一个将前2个的引用为构造函数参数。但需要配置的东西比较多,是否需要编写这么多的配置?

      我们可以使用“自动装配”功能,让Spring猜测从我们这边进行的最小配置工作应注入到什么位置。为了帮助Spring定位我们的代码,让我们用注解@Service和构造函数标记我们的服务,在注解中使用@Autowired@Inject进行注入@Autowired属于Spring框架,@Inject属JSR-330批注集合。

     1 package example;
     2 
     3 import org.springframework.beans.factory.annotation.Autowired;
     4 import org.springframework.context.ApplicationContext;
     5 import org.springframework.context.support.ClassPathXmlApplicationContext;
     6 import org.springframework.stereotype.Service;
     7 
     8 @Service
     9 class Service1 {
    10     void doSmth() {
    11         System.out.println("Service1");
    12     }
    13 }
    14 
    15 @Service
    16 class Service2 {
    17     void doSmth() {
    18         System.out.println("Service2");
    19     }
    20 }
    21 
    22 @Service
    23 class DependentService {
    24     private final Service1 service1;
    25     private final Service2 service2;
    26 
    27     @Autowired
    28     public DependentService(Service1 service1, Service2 service2) {
    29         this.service1 = service1;
    30         this.service2 = service2;
    31     }
    32 
    33     void doSmth() {
    34         service1.doSmth();
    35         service2.doSmth();
    36     }
    37 
    38 }
    39 
    40 public class Main {
    41     public static void main(String[] args) {
    42         ApplicationContext container = new ClassPathXmlApplicationContext("spring.xml");
    43         ((DependentService) container.getBean("dependentService")).doSmth();
    44     }
    45 }

      通过注解定义bean需要在spring.xml中开启以下配置:

    1 <beans xmlns="http://www.springframework.org/schema/beans"
    2        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    3        xmlns:context="http://www.springframework.org/schema/context"
    4        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    5 
    6     <!-- 使用注解来构造IoC容器 -->
    7     <context:component-scan base-package="example"/>
    8 <beans/>

      Spring提供了甚至在纯XML方法中也可以使用自动装配的可能性。但是,强烈建议避免使用此功能。

      构造函数注入的优点:

    1. 构造的对象是不可变的,并以完全初始化的状态返回给客户端。

    2. 通过这种方法可以立即看到具有越来越多的依赖关系的问题。依赖越多,构造函数就越大。

    3. 可以与setter注入或字段注入结合使用,构造函数参数指示所需的依赖关系,其他(可选)。

      缺点:

    1. 以后无法更改对象的依赖关系-不灵活性。

    2. 具有循环依赖关系的机会更高,即所谓的“鸡与蛋”场景。

    三、setter注入(Setter Injection)

       使用与之前示例相同的代码,简洁起见,构造函数注入不会有太多更改,因此仅更改的逻辑部分。

     1 class DependentService {
     2     private Service1 service1;
     3     private Service2 service2;
     4 
     5     public void setService1(Service1 service1) {
     6         this.service1 = service1;
     7     }
     8 
     9     public void setService2(Service2 service2) {
    10         this.service2 = service2;
    11     }
    12 
    13     void doSmth() {
    14         service1.doSmth();
    15         service2.doSmth();
    16     }
    17 
    18 }

      正如注入类型的名称所暗示的那样,我们应该首先为依赖项创建setter。在这种情况下,不需要构造函数。让我们看一下XML配置中应该做什么。

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <beans xmlns="http://www.springframework.org/schema/beans"
     3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     4        xsi:schemaLocation="
     5     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
     6 
     7     <bean id="service1" class="example.Service1"/>
     8     <bean id="service2" class="example.Service2"/>
     9 
    10     <bean id="dependentService" class="example.DependentService">
    11         <property name="service1" ref="service1"/>
    12         <property name="service2" ref="service2"/>
    13     </bean>
    14 
    15 </beans>

      除了XML配置,还有基于注解的版本。

     1 @Service
     2 class DependentService {
     3     private Service1 service1;
     4     private Service2 service2;
     5 
     6     @Autowired
     7     public void setService1(Service1 service1) {
     8         this.service1 = service1;
     9     }
    10 
    11     @Autowired
    12     public void setService2(Service2 service2) {
    13         this.service2 = service2;
    14     }
    15 
    16     void doSmth() {
    17         service1.doSmth();
    18         service2.doSmth();
    19     }
    20 
    21 }

      这种类型的注入需要有Setter的存在,就像构造函数注入需要构造函数一样。

      在描述构造函数注入的优点时,提到它可以轻松地与setter注入结合使用。

     1 @Service
     2 class DependentService {
     3     private final Service1 service1;
     4     private Service2 service2;
     5 
     6     @Autowired
     7     public DependentService(Service1 service1) {
     8         this.service1 = service1;
     9     }
    10 
    11     @Autowired
    12     public void setService2(Service2 service2) {
    13         this.service2 = service2;
    14     }
    15 
    16     void doSmth() {
    17         service1.doSmth();
    18         service2.doSmth();
    19     }
    20 }

      在示例中,service1对象是强制性依赖项(最终属性),在DependentService实例实例化期间仅设置一次。同时,service2是可选的,可以首先包含null,并且其值可以在创建后随时通过调用setter进行更改。用XML的方式,必须在bean声明中同时指定属性和builder-arg标记。

      优点

    1. 依赖关系解析或对象重新配置的灵活性,可以随时进行。另外,这种自由解决了构造函数注入的循环依赖问题。

      缺点

    1. null值检查是必需的,因为当前可能未设置依赖项。
    2. 由于可能会覆盖依赖项,因此与构造函数注入相比,潜在的错误倾向更大,安全性更低。

    四、字段注入(Field Injection)

     1 @Service
     2 class DependentService {
     3     @Autowired
     4     private Service1 service1;
     5     @Autowired
     6     private Service2 service2;
     7 
     8     void doSmth() {
     9         service1.doSmth();
    10         service2.doSmth();
    11     }
    12 }

      这种注入方式只有在基于注解的方法中才有可能,因为它实际上并不是一种新的注入方式,在较为底层的实现机制中,Spring使用反射来设置这些值。

      优点

    1. 易于使用,无需构造器或设置器

    2. 可以轻松地与构造函数和(或)setter方法结合使用

      缺点

    1. 对对象实例化的控制较少。为了实例化测试的类的对象,需要配置Spring容器或模拟库,这取决于要编写的测试。

    2. 在你发现设计中出现问题之前,许多依赖项可能达到数十种。

    3. 依赖不是一成不变的,与setter注入相同。

    五、查找方法注入(Lookup Method Injection)

      由于特殊的使用情况,该注入类型的使用频率比上述所有其他类型的使用频率低,即依赖项的注入具有较小的寿命。

      默认情况下,Spring中的所有bean均创建为singleton(单例),这意味着它们将在容器中创建一次,并且将在请求的任何位置注入相同的对象。但是,有时需要不同的策略,例如,每个方法调用都应从一个新对象完成。现在,假设将这个寿命短的对象注入到singleton对象中,Spring会在每次调用时自动刷新此依赖项吗?不,除非我们指出存在这种特殊的依赖类型,否则依赖仍将被视为单例。

      回到实践中,我们又有了3个服务,其中一个依赖于其他服务,service2是通常的对象,可以通过任何先前描述的依赖项注入技术(例如,setter注入)注入DependentService中。Service1的对象将有所不同,不能被注入一次,每次调用都应访问一个新实例—让我们创建一个提供该对象的方法,并让Spring知道。

     1 abstract class DependentService {
     2     private Service2 service2;
     3 
     4     public void setService2(Service2 service2) {
     5         this.service2 = service2;
     6     }
     7 
     8     void doSmth() {
     9         createService1().doSmth();
    10         service2.doSmth();
    11     }
    12 
    13     protected abstract Service1 createService1();
    14 }

      我们没有将Service1的对象声明为通常的依赖项,而是指定了Spring框架将覆盖该方法的方法,以便返回Service1类的最新实例。

      依赖的方法提供者不一定要抽象和保护,它可以是公共的,也可以包含实现,但是请记住,它将在Spring创建的子类中被覆盖。

      我们再次处于纯XML配置示例中,因此我们必须首先指出service1是一个寿命较短的对象,在Spring术语中,我们可以使用prototype范围(有多个实例),因为它小于单例。通过lookup-method标记,我们可以指示将注入依赖项的方法的名称。

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <beans xmlns="http://www.springframework.org/schema/beans"
     3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     4        xsi:schemaLocation="
     5 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
     6 
     7     <bean id="service1" class="example.Service1" scope="prototype"/>
     8     <bean id="service2" class="example.Service2"/>
     9 
    10     <bean id="dependentService" class="example.DependentService">
    11         <lookup-method name="createService1" bean="service1"/>
    12         <property name="service2" ref="service2"/>
    13     </bean>
    14 
    15 </beans>

      也可以以基于注解的方式完成相同操作。

     1 @Service
     2 @Scope(value = "prototype")
     3 class Service1 {
     4     void doSmth() {
     5         System.out.println("Service1");
     6     }
     7 }
     8 
     9 @Service
    10 abstract class DependentService {
    11     private Service2 service2;
    12 
    13     @Autowired
    14     public void setService2(Service2 service2) {
    15         this.service2 = service2;
    16     }
    17 
    18     void doSmth() {
    19         createService1().doSmth();
    20         service2.doSmth();
    21     }
    22 
    23     @Lookup
    24     protected abstract Service1 createService1();
    25 }

      劣势与优势

      将这种类型的依赖注入与其他类型的依赖注入进行比较是不正确的,因为它具有完全不同的用法。

    六、结论

      我们经历了由Spring框架实现的4种类型的依赖注入:

    1. 构造函数注入- 良好,可靠且不变的,通过构造函数之一进行注入。可以配置为:XML,XML +注释,Java,Java +注释。

    2. Setter注入- 更灵活,易变的对象,通过 Setter进行注入。可以配置为:XML,XML +注释,Java,Java +注释。

    3. 与IoC容器结合使用,可快速方便地进行字段注入。可以在XML + Annotations,Java + Annotations中进行配置。

    4. 查找方法注入——与其他方法完全不同,用于较小范围的注入依赖性。可以配置为:XML,XML +注释,Java,Java +注释。

    参考:参考一参考二参考三参考四

  • 相关阅读:
    73. Set Matrix Zeroes
    189. Rotate Array
    http中的get和post(二)
    http中的get和post(一)
    cocoapods使用 swift注意事项
    开源一个上架 App Store 的相机 App
    API接口开发简述
    PHP开发API接口及使用
    iOS知识点集合--更改(2)
    (精选)Xcode极速代码,征服Xcode,xcode插件
  • 原文地址:https://www.cnblogs.com/magic-sea/p/13167009.html
Copyright © 2020-2023  润新知