在Spring框架中,依赖注入(Dependency Injection, DI)是一个核心概念,用于将对象的依赖关系从代码中分离出来,通过外部容器(如Spring容器)来管理这些依赖关系。Spring提供了多种依赖注入方式,其中最常用的两种是构造器注入(Constructor Injection)和Setter方法注入(Setter Injection)。

1. 前置知识:Spring中的依赖注入

1.1 什么是依赖注入?

依赖注入是一种设计模式,用于将对象的依赖关系从代码中分离出来,通过外部容器(如Spring容器)来管理这些依赖关系。依赖注入使得代码更加松耦合,易于测试和维护。

1.2 Spring中的依赖注入方式

Spring框架提供了多种依赖注入方式,包括:

  • 构造器注入(Constructor Injection)

  • Setter方法注入(Setter Injection)

  • 字段注入(Field Injection)

2. 构造器注入(Constructor Injection)

构造器注入是最推荐的依赖注入方式,因为它确保了对象在创建时就已经拥有了所有必要的依赖。构造器注入通过构造器参数来注入依赖。

2.1 基本用法

以下是一个简单的示例,展示了如何使用构造器注入。

@Service

public class MyService {

    private final MyRepository myRepository;

    public MyService(MyRepository myRepository) {

        this.myRepository = myRepository;

    }

    public void doSomething() {

        myRepository.doSomething();

    }

}

在这个例子中,MyService类通过构造器注入的方式使用了MyRepository Bean。Spring容器会自动将MyRepository Bean注入到MyService中。

注意:

Spring 默认支持 基于构造函数的依赖注入,其执行逻辑为:

  • 单构造函数场景:若类中仅存在一个构造函数(无论是否有参数),Spring 会自动选择该构造函数实例化 Bean,并尝试通过容器中已存在的 Bean 自动注入参数。(如果没有显式地定义任何构造方法,编译器会自动为我们生成一个无参构造方法。则使用默认的无参构造函数)

  • 多构造函数场景:若存在多个构造函数,需通过 @Autowired 显式指定要使用的构造函数。

在用户提供的代码中,ForumCoreAutoConfig 仅有一个带参数的构造函数,因此 Spring 会直接使用它完成实例化。

2.2 优点

  • 不可变性:构造器注入使得依赖关系不可变,从而避免了在运行时修改依赖关系的可能性。

  • 线程安全:由于依赖关系在对象创建时就已经确定,因此构造器注入是线程安全的。

  • 易于测试:构造器注入使得单元测试更加简单,因为你可以轻松地为依赖关系提供模拟对象。

2.3 多参数构造器

如果需要注入多个依赖,可以使用多参数构造器。

@Service

public class MyService {

    private final MyRepository myRepository;

    private final MyConfig myConfig;

    public MyService(MyRepository myRepository, MyConfig myConfig) {

        this.myRepository = myRepository;

        this.myConfig = myConfig;

    }

    public void doSomething() {

        myRepository.doSomething();

        myConfig.doSomething();

    }

}

在这个例子中,MyService类通过多参数构造器注入了MyRepository和MyConfig Bean。

3. Setter方法注入(Setter Injection)

Setter方法注入通过Setter方法来注入依赖。这种方式允许在对象创建后动态地更改依赖。

3.1 基本用法

以下是一个简单的示例,展示了如何使用Setter方法注入。

@Service

public class MyService {

    private MyRepository myRepository;

    @Autowired

    public void setMyRepository(MyRepository myRepository) {

        this.myRepository = myRepository;

    }

    public void doSomething() {

        myRepository.doSomething();

    }

}

在这个例子中,MyService类通过Setter方法注入的方式使用了MyRepository Bean。Spring容器会自动调用setMyRepository方法,将MyRepository Bean注入到MyService中。

3.2 优点

  • 灵活性:Setter方法注入允许在对象创建后动态地更改依赖关系,从而提供了更大的灵活性。

  • 可选依赖:Setter方法注入可以用于注入可选依赖,即依赖关系可以为空。

3.3 多参数Setter方法

如果需要注入多个依赖,可以使用多个Setter方法。

@Service

public class MyService {

    private MyRepository myRepository;

    private MyConfig myConfig;

    @Autowired

    public void setMyRepository(MyRepository myRepository) {

        this.myRepository = myRepository;

    }

    @Autowired

    public void setMyConfig(MyConfig myConfig) {

        this.myConfig = myConfig;

    }

    public void doSomething() {

        myRepository.doSomething();

        myConfig.doSomething();

    }

}

在这个例子中,MyService类通过多个Setter方法注入了MyRepository和MyConfig Bean。

4. 构造器注入与Setter方法注入的比较

构造器注入和Setter方法注入各有优缺点,适用于不同的场景。

4.1 构造器注入的优点

  • 不可变性:构造器注入使得依赖关系不可变,从而避免了在运行时修改依赖关系的可能性。

  • 线程安全:由于依赖关系在对象创建时就已经确定,因此构造器注入是线程安全的。

  • 易于测试:构造器注入使得单元测试更加简单,因为你可以轻松地为依赖关系提供模拟对象。

4.2 构造器注入的缺点

  • 复杂性:如果需要注入多个依赖,构造器参数可能会变得复杂,从而增加代码的复杂性。

  • 可选依赖:构造器注入不支持可选依赖,即依赖关系必须存在。(不然无法构造)

  • 构造器注入通常推荐用于强制依赖,因为它能保证依赖项在对象创建时就被设置,避免NullPointerException。而Setter注入适合可选依赖,或者需要后期重新配置的情况。

4.3 Setter方法注入的优点

  • 灵活性:Setter方法注入允许在对象创建后动态地更改依赖关系,从而提供了更大的灵活性。

  • 可选依赖:Setter方法注入可以用于注入可选依赖,即依赖关系可以为空。

4.4 Setter方法注入的缺点

  • 可变性:Setter方法注入使得依赖关系可变,从而增加了在运行时修改依赖关系的可能性。

  • 线程不安全:由于依赖关系在对象创建后可以更改,因此Setter方法注入不是线程安全的。

  • 难以测试:Setter方法注入使得单元测试更加复杂,因为你需要确保在测试时正确设置了依赖关系。

5. 实际应用场景

构造器注入和Setter方法注入在实际项目中有广泛的应用场景,特别是在需要依赖注入的场景中。

5.1 服务层注入

在服务层中,通常需要注入多个依赖的Bean,如存储库、配置等。

@Service

public class MyService {

    private final MyRepository myRepository;

    private final MyConfig myConfig;

    public MyService(MyRepository myRepository, MyConfig myConfig) {

        this.myRepository = myRepository;

        this.myConfig = myConfig;

    }

    public void doSomething() {

        myRepository.doSomething();

        myConfig.doSomething();

    }

}

在这个例子中,MyService类通过构造器注入的方式注入了MyRepository和MyConfig Bean。

5.2 控制器层注入

在控制器层中,通常需要注入服务层的Bean。

@Controller

public class MyController {

    private final MyService myService;

    public MyController(MyService myService) {

        this.myService = myService;

    }

    @GetMapping("/doSomething")

    public String doSomething() {

        myService.doSomething();

        return "success";

    }

}

在这个例子中,MyController类通过构造器注入的方式注入了MyService Bean。

5.3 配置类注入

在配置类中,通常需要注入其他配置类或Bean。

@Configuration

public class AppConfig {

    private MyConfig myConfig;

    @Autowired

    public void setMyConfig(MyConfig myConfig) {

        this.myConfig = myConfig;

    }

    @Bean

    public MyService myService() {

        return new MyService(myConfig);

    }

}

在这个例子中,AppConfig类通过Setter方法注入的方式注入了MyConfig Bean,并在myService Bean的定义中使用了myConfig。

6. 总结

构造器注入和Setter方法注入是Spring框架中常用的依赖注入方式。构造器注入通过构造器参数来注入依赖,确保了对象在创建时就已经拥有了所有必要的依赖,从而提供了不可变性、线程安全性和易于测试的优点。Setter方法注入通过Setter方法来注入依赖,允许在对象创建后动态地更改依赖关系,从而提供了灵活性和可选依赖的优点。在实际项目中,开发者可以根据具体需求选择合适的注入方式。