Bootstrap

JavaEE进阶(5)Spring IoC&DI:入门、IoC介绍、IoC详解(两种主要IoC容器实现、IoC和DI对对象的管理、Bean存储、方法注解 @Bean)、DI详解:注入方式、总结

接上次博客:JavaEE进阶(4)Spring MVC实现:计算器实现&用户登录&留言板&图书管理系统&应用分层(阿里开发手册、概念、为什么需要应用分层?如何分层?MVC和三层架构的区别和联系 )&企业命名规范​-CSDN博客

目录

IoC&DI入门

什么是Spring?

什么是容器?

什么是 IoC?

IoC 介绍

传统程序开发

解决办法IoC&DI

IoC 优势(IoC容器)

DI 介绍

Spring Beans 对象

易错辨析

IoC 详解

Spring的两种主要 IoC 容器实现

Spring IoC 和 DI 对于对象的管理

Bean的存储

@Controller(控制器存储)

根据类型获取bean对象

获取bean对象的其他方式

@Service(服务存储)

@Repository(仓库存储) 

@Component(组件存储)

@Configuration(配置存储) 

类注解之间的关系

学习分析错误日志 

方法注解 @Bean

DI 详解

属性注入(Field Injection)

构造方法注入(Constructor Injection):

Setter注入(Setter Injection):

三种注入优缺点分析

属性注入

构造函数注入 (Spring 4.X 推荐)

Setter注入 (Spring 3.X 推荐)

@Autowired存在问题

总结

Spring, Spring Boot 和Spring MVC的关系以及区别

Bean的命名

常见面试题


IoC&DI入门

在前⾯的章节中, 我们学习了Spring Boot和Spring MVC的开发,已经可以独立完成⼀些基本功能的开发了。

包括什么是Spring? Spring、 Spring Boot 和SpringMVC之间的关系是什么?这些问题我们也去探讨过了。

现在我们进行一个简单的知识回顾,引出我们即将学习的Spring IoC&DI。

什么是Spring?

在前面的学习中,我们了解到Spring是一个强大的开源框架,它的存在让我们的软件开发变得更加简单、灵活。Spring并不仅仅是一个框架,它更像是一个生态系统,涵盖了多个工具和方法,支持广泛的应用场景,拥有一个充满活力而庞大的社区,这正是Spring能够保持长期流行的原因。

Spring 框架有两个核心思想:IoC(Inversion of Control,控制反转)和 AOP(Aspect-Oriented Programming,面向切面编程)。

  1. IoC(控制反转): 这是 Spring 框架的基本原则之一。在传统的程序设计中,应用程序负责控制程序流程的对象的创建和管理。而在 IoC 中,控制权被反转了,即对象的创建和管理被交给了框架。在 Spring 中,通过 IoC 容器(通常是 ApplicationContext)来管理对象的生命周期和依赖关系。通过将对象之间的依赖关系交给容器管理,实现了松耦合、可维护和可测试的代码。

  2. AOP(面向切面编程): AOP 是一种编程范式,它允许在程序的运行过程中动态地将代码织入到现有的代码中,而无需修改原有代码。在 Spring 中,AOP 提供了一种分离关注点的方式,即通过切面(Aspect)来定义横切关注点(cross-cutting concerns),例如日志记录、事务管理等。AOP 的目标是使系统更模块化,提高代码的可维护性和可重用性。

这两个核心思想共同促使了 Spring 框架的设计理念,使得开发者能够更容易地编写松耦合、可测试和可维护的代码。IoC 和 AOP 是 Spring 框架成功的重要组成部分,它们帮助开发者解决了传统 Java 开发中面临的一些问题,如硬编码、紧耦合等。

我们这次主要来了解IoC控制反转。

尽管Spring的概念相对抽象,但如果要用一句更具体的话来概括Spring,那就是:Spring是一个内含众多实用工具和方法的IoC容器。

这意味着Spring不仅仅是一个简单的框架,而是一个提供了丰富功能、包含了众多工具的IoC容器,通过它,我们可以轻松地管理对象的生命周期、处理依赖注入等任务。Spring的IoC容器有效地降低了开发的复杂性,使得我们能够更专注于业务逻辑的实现,提高了开发效率。

那问题来了,什么是容器?什么是 IoC 容器?接下来我们⼀起来看看。

什么是容器?

容器是用来容纳或承载某种物品的装置或结构。在软件开发中,容器通常指的是一种环境或框架,用于管理和组织其他软件组件,提供运行时的支持和服务。容器可以承载、运行和协调应用程序的各个部分,使它们能够协同工作。

在之前的课程中,我们接触过一些常见的软件容器,其中包括:

  1. List/Map:数据存储容器。List和Map是Java中常见的集合类型,用于存储和组织数据。

  2. Tomcat:Web容器。Tomcat是一个开源的、轻量级的Servlet容器,用于承载和运行Java Web应用程序。它提供了Servlet和JSP等Java EE技术的支持。

这些容器在软件开发中发挥着不同的作用,从数据管理到Web应用的运行,都涉及到容器的概念。容器的存在使得软件组件可以更加灵活、可移植,并且能够更好地协同工作。在某种程度上,容器提供了一种结构和环境,使得软件开发更加高效和可维护。

什么是 IoC?

IoC(Inversion of Control)是一种思想或设计原则,而不仅仅是一种技术或框架,通过它可以实现代码的解耦和更好的组织结构,提高了代码的可测试性和可维护性。
 

IoC的核心概念是控制的反转,即将某些控制权从应用程序本身转移到了外部容器中。
在传统的程序设计中,应用程序通常会控制程序流程,包括对象的创建和生命周期管理。而

IoC的思想是将这些任务交给外部容器(如Spring容器)来完成。应用程序只需要关注业务

逻辑,而对象的创建、依赖关系的注入等任务由容器负责。
 

IoC通过依赖注入(DI)来实现,即容器负责管理对象的生命周期,并在需要的时候注入依赖关系。这使得代码更加灵活、可维护,并降低了模块之间的耦合度。

IoC,全称为Inversion of Control(控制反转),是Spring框架的核心思想之一,也是常见的面试题。那什么是IoC呢?

在IoC中,控制反转意味着(对象的)控制权的反转。也可以认为是获取依赖对象的权利/过程交给了spring进行控制。

具体来说,在传统的开发模式中,当需要某个对象时,我们需要自己通过new关键字来创建对象。而在IoC思想中,这个创建对象的任务被反转了,即由容器来负责对象的创建和管理。在Spring中,容器会在启动时加载带有特定注解(如@RestController和@Controller)的类,并负责管理这些对象。

控制反转的核心概念是依赖注入(Dependency Injection,DI)。在传统模式下,我们需要自己获取依赖对象,而在IoC中,这一过程被反转了,开发者只需要声明依赖,容器会负责将需要的依赖注入到对象中。

举例来说,IoC容器的典型实现是Spring容器。在Spring框架中,我们通过配置或注解,将对象的创建和依赖关系的管理交给Spring容器。这使得程序更加灵活、可维护,同时降低了耦合性。

在生活中,控制反转的思想也随处可见。例如,自动驾驶中,驾驶控制的传统方式是由驾驶员控制车辆的横向和纵向移动,而在自动驾驶中,这个控制权被反转给了驾驶自动化系统。类似地,在企业的员工招聘、入职和解雇等过程中,控制权由原本的管理层转交给了人力资源(HR)部门来处理,也体现了IoC思想的应用。

IoC 介绍

Spring 框架的核心思想之一就是控制反转(IoC)和依赖注入(DI)。这两个思想是通过 Spring IoC 容器实现的。

控制反转(IoC): 在传统的程序设计中,对象的创建和管理通常由程序本身负责,而在 IoC 中,对象的创建和管理被反转,由容器负责。Spring IoC 容器负责管理应用程序中的对象,包括创建、组装和生命周期管理。这样,应用程序不再控制对象的创建,而是通过配置或注解告诉容器如何创建和组装对象。

依赖注入(DI): 是 IoC 的一个具体实现方式。在传统的程序设计中,对象通常通过直接创建或查找方式获取其依赖的对象。而在 IoC 容器中,对象不再负责获取依赖的对象,而是通过容器将依赖注入到对象中。这样,对象之间的依赖关系被反转,由容器来管理。

乍一听挺抽象,别急,我们先通过一个案例来了解⼀下什么是IoC。

我们的需求: 造一辆车。

传统程序开发

这种实现思路是典型的自底向上的传统程序开发方式,也被称为自上而下的设计方法。在这种方法中,系统的各个模块或组件是从最底层的基础部分逐步构建起来的,每一层都依赖于下一层。

具体来说,在汽车的例子中:

  1. 先设计轮子(Tire),这是汽车的基础组件。
  2. 根据轮子的大小设计底盘(Bottom),底盘依赖轮子的规格和尺寸。
  3. 根据底盘的设计,再设计车身(Framework),车身依赖于底盘的形状和结构。
  4. 最终,根据车身的设计完成整个汽车(Car),汽车依赖车身的外形和特性。

随着我们用户需求的增加,个性化需求也会越来越多,不可能永远只制造同一种大小的轮胎,我们需要加工多种尺寸的轮胎 ,于是我们会改变构造参数,根据用户自定义需求构造不同大小的车的轮胎,相应的,车的车身、车的底盘、车的车型胎也需要有相应的改变。

package com.example.ioc_demo.IoC;

public class Tire {
    private int size;


    public Tire(int size) {
        System.out.println("tire size:"+size);
    }
}
package com.example.ioc_demo.IoC;

public class Bottom {
    private Tire tire;

    public Bottom(int size) {
        tire = new Tire(size);
        System.out.println("bottom init...");
    }
}
package com.example.ioc_demo.IoC;

public class Framework {
    private Bottom bottom;

    public Framework(int size) {
        bottom = new Bottom(size);
        System.out.println("framework init...");
    }
}
package com.example.ioc_demo.IoC;

public class Car {
    private Framework framework;

    public Car(int size) {
        framework = new Framework(size);
        System.out.println("car init... ");
    }

    public void run() {
        System.out.println("car run....");
    }
}
package com.example.ioc_demo.IoC;

public class Main {
    public static void main(String[] args) {
        Car car = new Car(12);
        car.run();
    }
}

这种方法的优势在于能够逐步构建系统,从底层到顶层逐步完善各个模块。然而,它也有一些潜在的问题,例如可能导致模块之间的紧密耦合,以及在设计过程中可能需要频繁修改底层的实现,从而增加了系统的维护成本。

比如这时候用户又有了别的需求,想要换个车的颜色等等……我们又需要一点点回去改我们的函数,非常麻烦。

所以以上程序的问题是:当最底层代码改动之后,整个调用链上的所有代码都需要修改,程序的耦合度非常高(修改一处代码,会影响其他处的代码修改)

传统设计方式是自顶向下的设计思路,先设计最高层的组件,然后逐层向下设计依赖的子组件。这种方式下,各个组件的依赖关系是自上而下的,导致了高层组件对于底层组件的强依赖。

在这种设计下,如果底层组件发生变化,那么所有依赖于它的上层组件都需要进行相应的修改。这导致了代码的脆弱性和可维护性差,因为一个小小的变化可能涉及到整个系统的修改。

解决办法IoC&DI

与传统的自上而下设计方式相对的是现代的面向对象设计思想,如IoC(控制反转)和DI(依赖注入)等,它们更强调模块的解耦和独立性。

倒置依赖关系的设计思路是从底层组件开始设计,逐步构建上层组件。底层组件定义了接口,而高层组件只依赖于接口而非具体的实现。这种设计方式将依赖关系从上而下倒置为自下而上,使得高层组件更加稳定,不容易受到底层变化的影响。

在倒置依赖关系的设计中,各个组件更加独立,修改某个底层组件不会影响到高层组件,从而提高了系统的可维护性和灵活性。

这也符合面向对象设计的原则,尽量使用抽象的接口而不是具体的实现。

通过倒置依赖关系的设计方式,比如我们可以先设计最底层的轮胎(Tire),然后设计底盘(Bottom)依赖于轮胎的接口,接着设计汽车底盘的上层组件汽车框架(Framework),最后设计汽车(Car)依赖于汽车框架。这样的设计方式将依赖关系倒置,使得各个组件更加独立和可维护。

当轮胎的尺寸发生变化时,只需要修改轮胎的实现而不会影响到底盘、汽车框架以及汽车的设计。这种设计方式提高了系统的灵活性和可维护性,降低了各个组件之间的耦合度。

具体如何来实现呢:

我们可以通过采用依赖注入(Dependency Injection,DI)的设计原则,将对象的依赖关系从类内部移动到外部。具体来说,就是不在每个类内部直接创建下级类的实例,而是通过构造函数、方法参数或者其他方式将下级类的实例传递给上级类。这样的设计方式带来了一系列优势。

首先,通过不在当前类中创建下级类,避免了在每个类内部直接管理下级类实例的复杂性。当下级类发生变化时,不需要修改上级类的代码,只需要在创建下级类实例的地方进行调整。这降低了代码的耦合度,提高了系统的灵活性和可维护性。

其次,采用注入的方式,使得代码更易于测试。我们可以轻松地通过传递不同的下级类实例进行单元测试,而无需修改现有的代码结构。

最重要的是,这种解耦的设计允许系统更容易适应变化。当需求发生变更或者新增下级类时,只需关注创建下级类的地方,而不必涉及整个类的结构。这种松散的耦合关系使得系统更具扩展性,减少了修改代码的风险。

总的来说,通过将下级类的创建方式从类的内部移到外部,采用依赖注入的设计思想,我们能够更好地管理类与类之间的关系,提高代码的可读性、可维护性和扩展性。这样的设计理念有助于构建更为健壮、灵活和易于维护的软件系统。

package com.example.ioc_demo.IoC.v2;

public class Car {
    private Framework framework;

    public Car(Framework framework) {
        this.framework = framework;
        System.out.println("car init...");
    }

    public void run() {
        System.out.println("car run...");
    }
}
package com.example.ioc_demo.IoC.v2;

public class Framework {
    private Bottom bottom;

    public Framework(Bottom bottom) {
        this.bottom = bottom;
        System.out.println("framework init...");
    }
}
package com.example.ioc_demo.IoC.v2;

public class Bottom {
    private Tire tire;

    public Bottom(Tire tire) {
        this.tire = tire;
        System.out.println("bottom init...");
    }

}
package com.example.ioc_demo.IoC.v2;

public class Tire {
    private int size;
    private String color;

    public Tire(int size,String color) {
        System.out.println("tire size:"+size+",color:"+color);
    }
}
package com.example.ioc_demo.IoC.v2;

public class Main {
    public static void main(String[] args) {
        Tire tire = new Tire(19,"red");
        Bottom bottom = new Bottom(tire);
        Framework framework = new Framework(bottom);
        Car car = new Car(framework);
        car.run();
    }
}

这段代码是一个使用了依赖注入的简单的IoC(Inversion of Control,控制反转)示例。在这个例子中,Car 类依赖于 Framework 类,Framework 类依赖于 Bottom 类,Bottom 类依赖于 Tire 类。而这些依赖关系是通过构造函数参数进行注入的,而不是在类内部通过 new 操作符直接创建依赖对象。

代码经过以上调整,无论底层类如何变化,整个调用链是不用做任何改变的,这样就完成了代码之间的解耦,降低类之间的耦合度,使得代码更加灵活、易于维护和扩展。现在如果你想要更改或替换某个类的实现,只需要修改依赖注入的部分,而不需要修改调用类的代码。

在 main 方法中,你可以看到对象的创建和依赖注入的过程:

Tire tire = new Tire(20);
Bottom bottom = new Bottom(tire);
Framework framework = new Framework(bottom);
Car car = new Car(framework);

这里 Car 对象的创建依赖于 Framework 对象,而 Framework 对象的创建又依赖于 Bottom 对象,以此类推。这种方式更符合 IoC 的思想,其中对象的创建和依赖关系的管理由外部容器(在这个例子中是 main 方法)负责。

IoC 优势(IoC容器)

在传统的代码实现中,对象的创建和控制关系是由上层类(例如Car)负责,它依次创建并控制下层类(例如Framework、Bottom、Tire)的实例。这种实现方式导致了类的创建顺序是自上而下的,依赖关系逐层传递。

然而,在改进后的解耦代码中,对象的创建顺序发生了反转。现在,创建顺序是从最底层的依赖开始,即Tire,然后是Bottom,接着是Framework,最后是Car。这种反转的创建顺序是通用程序实现代码的一种规律。

观察到这一点之后,我们可以得出一个结论:改进后的代码实现了控制权的反转(IoC)。在这个实现中,不再由上层类直接创建并控制下层类的实例,而是通过依赖注入的方式,将下层类的实例传递给上层类。这种实现方式带来了许多好处,其中之一是即使依赖类发生任何改变,上层类都不受影响。依赖对象的控制权不再由当前类直接掌握,而是由外部进行注入,提高了代码的灵活性和可维护性。这就是控制反转(IoC)的核心思想。

学到这⾥,我们大概就知道了什么是控制反转了,那什么是控制反转容器呢, 也就是IoC容器。

控制反转容器,通常称为 IoC 容器(Inversion of Control Container),是一种用于管理和组织应用程序中对象的容器。它实现了控制反转(IoC)的概念,将对象的创建、组装和管理的控制权交给容器,而不是由应用程序代码直接管理。

IoC 容器的主要功能包括:

  1. 对象的创建和生命周期管理: IoC 容器负责创建应用程序中的各种对象,并管理它们的生命周期。这包括对象的实例化、初始化、调用适当的生命周期方法以及在不再需要时进行销毁。

  2. 依赖注入: IoC 容器通过依赖注入将一个对象所需的依赖关系注入到该对象中。这样,对象无需自己创建或查找依赖对象,而是由容器负责提供。

  3. 对象的组装: IoC 容器负责组装应用程序中各个组件,确保它们能够协同工作。这涉及到将不同对象关联起来,形成一个完整的应用程序。

  4. 配置的集中管理: IoC 容器通常使用配置文件或注解来描述对象之间的关系和配置信息。这使得应用程序的配置可以集中管理,而不是分散在代码的各个部分。

  5. AOP(面向切面编程)的支持: 一些 IoC 容器提供对 AOP 的支持,允许将横切关注点(cross-cutting concerns)如日志、事务等与应用程序的核心业务逻辑分离开来。

Spring Framework 就是一个著名的 IoC 容器,它通过实现 IoC 概念提供了广泛的功能和特性,包括依赖注入、AOP、事务管理等。通过使用 IoC 容器,开发者能够更好地组织和管理复杂的应用程序结构,提高代码的可维护性和可测试性。

这个时候我们就会突然想起之前学过的东西,知识一下子就贯通了……

Spring Boot 的 @SpringBootApplication 注解通常包括了 @ComponentScan 注解,用于扫描指定包及其子包中的组件。如果项目使用了 @SpringBootApplication,那么在这个主应用程序类所在的包及其子包中使用 Spring 注解标记的类将被扫描并成为 Spring Beans。

@Autowired 注解:
@Autowired 注解用于自动装配对象,告诉 IoC 容器在需要某个类型的对象时,通过自动查找、匹配并注入相应的 Bean 对象。这样可以简化依赖注入的配置,减少手动配置的工作量。

@Component
public class MyService {
    private final MyRepository repository;

    @Autowired
    public MyService(MyRepository repository) {
        this.repository = repository;
    }
}

@Component、@Service、@Repository 注解:
这些注解用于标识类为 Spring 组件,告诉 IoC 容器要将这些类实例化为 Bean 对象,并进行管理。通常用于标识业务逻辑类、服务类、数据访问类等。

@Component // 或 @Service、@Repository
public class MyComponent {
    // 类的实现...
}

XML 配置文件:
除了注解外,Spring 也支持使用 XML 配置文件进行 IoC 容器的配置。在 XML 配置文件中,可以定义 Bean 的信息、依赖关系、AOP 配置等。

<!-- applicationContext.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myRepository" class="com.example.MyRepository"/>
    <bean id="myService" class="com.example.MyService">
        <constructor-arg ref="myRepository"/>
    </bean>

</beans>

从上面我们也可以看出来,资源不由使用资源的双方管理,而由不使用资源的第三方管理,这可以带来很多好处。

IoC 容器的优点主要体现在资源集中管理和降低使用资源双方的依赖程度这两个方面:

资源集中管理:

IoC容器负责对象的创建、组装和生命周期管理,将这些任务集中在容器中进行。这种集中管理的优势包括:

  • 可维护性: 集中管理使得资源的配置信息可以放置在一个统一的地方,通常是配置文件或注解中。这使得系统的配置更加清晰和易于维护,而不必在代码的各个部分中查找配置信息。

  • 易管理: 通过IoC容器,我们可以轻松地管理应用程序中的各种资源,包括对象、服务等。容器负责创建和管理这些资源,开发者只需从容器中获取需要的资源,而无需关心资源的具体创建和管理过程。

降低耦合度:

IoC容器通过依赖注入的方式,将对象的依赖关系从对象本身解耦,降低了对象之间的耦合度。这种降低耦合度的优势包括:

  • 灵活性: 由于对象不再关心如何创建或获取依赖的对象,系统变得更加灵活。对象只需专注于自身的业务逻辑,而不必过多考虑依赖关系的管理。

  • 可维护性: 降低的耦合度使得系统更易于维护。当需要修改或扩展功能时,由于对象之间的关系更为松散,修改的影响范围相对较小,不容易引入意外的错误。

总体来说,IoC 容器在软件开发中提供了一种更灵活、可维护和可测试的架构。Spring Framework 的 IoC 容器就是其中的典型代表,通过其强大的功能和特性,使得开发者能够更专注于业务逻辑的实现,而无需过多关注对象的创建和管理。

那么,像是刚刚的main方法,我们说new一个对象的工作实际上是交给了Spring来做这种IoC思想具体是怎么实现的呢?

注意,Spring Boot 的 @SpringBootApplication 注解通常包括了 @ComponentScan 注解,用于扫描指定包及其子包中的组件。如果项目使用了 @SpringBootApplication,那么在这个主应用程序类所在的包及其子包中使用 Spring 注解标记的类将被扫描并成为 Spring Beans,即由 Spring IoC 容器管理的对象。 

IoC容器通过以下几个步骤实现“自动帮助我们创建和管理对象”这一过程:

  1. 配置阶段: 在应用程序启动时,我们需要提供一份配置告诉IoC容器应该如何创建和组装对象。这个配置可以通过XML配置文件、Java注解或者Java代码来完成。

  2. 对象创建: 当应用程序需要使用某个对象时,不再是直接使用 new 关键字来创建,而是通过IoC容器向容器请求获取该对象。IoC容器会检查配置信息,然后自动创建所需的对象。

  3. 依赖注入: 如果对象之间存在依赖关系,IoC容器也会负责将依赖关系注入到相应的对象中。这可以通过构造函数注入、属性注入或方法注入来实现。

  4. 生命周期管理: IoC容器还负责管理对象的生命周期,包括对象的创建、初始化、使用和销毁。可以通过配置来指定对象的作用域,如单例模式、原型模式等。

具体实现上,Spring框架是一个典型的IoC容器,它通过反射机制和动态代理来实现对象的创建和管理。在Spring中,我们可以使用注解或XML配置文件描述对象之间的关系,然后通过Spring容器来获取这些对象,而无需直接关心对象的具体创建过程。

DI 介绍

上面我们学习了IoC,那么什么是DI呢? 

依赖注入(Dependency Injection,DI)是指在程序运行期间,容器动态地为应用程序提供其运行所依赖的资源。这一概念的核心是,当程序在运行时需要某个资源时,容器负责为其提供这个资源,从而实现了松散耦合和更灵活的组件管理。

从侧面来看,依赖注入和控制反转(IoC)是描述同一事物的不同角度。它们共同指向通过引入 IoC 容器,在对象之间引入依赖关系注入的方式,以达到对象解耦的目的。在具体实现上,可以通过构造函数等方式将依赖对象注入到需要使用的对象中。

IoC 是一种思想,也可以看作是一种目标。思想是一种指导原则,但最终需要有可行的实现方案。在这一点上,DI 是IoC的一种具体实现方式。

举例来说,假设“吃一顿好的”是一种心情愉快的思想和目标(即 IoC),那么具体选择是吃海底捞还是长街7号就是 DI 的实现,是在具体情境下的可执行方案。因此,DI作为IoC的具体实践,帮助实现了更加灵活、可维护的代码结构。

Spring Beans 对象

其实上述的对象就是“Bean对象”,Bean 是 Spring 框架中用于实现控制反转(IoC)和依赖注入(DI)的核心对象。

在 Spring 中,Bean 是指由 Spring IoC 容器管理的对象。具体来说,Bean 是一种由 Spring 容器实例化、组装和管理的对象,这些对象通常是应用程序中的核心对象,负责实现应用程序的业务逻辑。Bean 是 Spring 框架的基本构建块之一,Spring框架提供了各种注解,用于标识类和方法,以便Spring IoC 容器可以识别并对其进行统一管理。

在 Spring 中,你可以将 Java 类实例化为 Spring 容器中的 Bean,并在应用程序中使用这些 Bean。Bean 可以是简单的 Java 类,也可以是由 Spring 框架提供的一些特殊对象,如数据源、事务管理器等。

 以下是定义一个简单的 Bean 的示例:

@Component
public class MyBean {
    // Bean 的属性和方法
}

上述代码使用 @Component 注解标识了一个类,告诉 Spring 这是一个 Bean,Spring 容器会负责实例化和管理这个 Bean。在应用程序中,可以通过从容器中获取这个 Bean 的实例来使用它。

关于被IoC容器管理的对象,有几种情况:

使用Spring注解的类: 通常,通过在类上添加 @Component、@Service、@Repository、@Controller 等注解,你告诉Spring框架将这个类实例化为一个Bean,并将其交由IoC容器进行管理。

@Service
public class MyService {
    // ...
}

使用Java配置: 除了注解,你还可以使用Java配置类,在配置类中通过 @Bean 注解手动定义Bean。

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyService();
    }
}

通过XML配置: 在早期的Spring版本中,XML配置是主要的配置方式,你可以在XML文件中定义Bean。

<bean id="myService" class="com.example.MyService" />

所以,不是所有的Bean都必须使用注解定义。你可以使用Java配置或XML配置来创建Bean,并且这些Bean同样会被IoC容器管理。然而,在现代的Spring应用程序中,注解通常更为方便和常见,因为它们可以提供更简洁、可读性更高的代码。 

Bean 对象的管理包括以下几个方面:

  1. 实例化(Instantiation): Spring IoC 容器负责根据配置信息或注解实例化 Bean 对象。

  2. 组装(Assembling): 当 Bean 对象被创建后,Spring IoC 容器负责将它们组装起来,处理依赖关系,注入属性值,执行初始化方法等。

  3. 生命周期管理(Lifecycle Management): Spring IoC 容器可以管理 Bean 的生命周期,包括初始化方法和销毁方法的调用。

  4. 依赖注入(Dependency Injection): Spring IoC 容器负责将 Bean 之间的依赖关系进行注入,使得 Bean 可以通过接口、属性或构造函数等方式获取依赖的对象。

总体而言,Bean 是 Spring 框架中用于实现控制反转(IoC)和依赖注入(DI)的核心对象。通过 Spring 容器对 Bean 进行管理,实现了对象的解耦、配置的灵活性和代码的可维护性。

易错辨析

  1. IoC(控制反转)是一种设计思想: IoC 是一种软件设计思想,它反转了传统应用程序中对象的创建和管理流程。传统情况下,应用程序负责创建和管理对象,而在 IoC 中,对象的创建和管理被反转给了框架或容器。

  2. IoC 容器是 IoC 的具体实现: IoC 容器是用于实现 IoC 思想的具体工具或框架。它是一个容器,负责实例化、组装和管理应用程序中的对象。Spring 容器就是一个典型的 IoC 容器。IoC 容器通过读取配置信息或注解,了解对象之间的关系,然后负责创建和管理这些对象。

  3. 对象的定义和配置:IoC 容器确实需要一定的配置信息,以便知道如何管理对象。这些配置信息可以通过 XML 配置文件、Java 注解或 Java 代码实现。一旦配置好,IoC 容器就能够根据这些配置信息来实现对象的创建、组装和管理,从而实现 IoC 思想。

    所以在使用 IoC 容器时,确实需要我们先定义对象,并配置它们的关系,通常我们使用注解或配置文件告诉 IoC 容器如何创建和组装对象。这种配置告诉容器哪些类是 Bean,如何创建它们,以及它们之间的依赖关系。

    被IoC容器管理的对象通常是那些使用了Spring注解的类。如果我们有自定义的注解,也可以配置Spring框架以识别和处理它们,使它们成为IoC容器中管理的对象。

IoC 详解

通过前面的案例,我们初步了解了 Spring IoC 和 DI 的基本操作。现在,让我们更系统地深入学习 Spring IoC 和 DI 的操作。

在之前的讨论中,我们提到了 IoC(控制反转)的概念,即将对象的控制权交给 Spring 的 IoC 容器,由容器负责创建和管理对象。这一过程中涉及到的关键概念是 bean 的存储和管理。

Spring的两种主要 IoC 容器实现

在 Spring 中,IoC 容器是负责管理和控制应用程序中所有 bean 的容器。Spring 提供了两种主要的 IoC 容器实现:BeanFactory 和 ApplicationContext。它们都负责管理 bean 的生命周期、依赖注入等操作。

  1. BeanFactory: BeanFactory 是 Spring 框架最基本的 IoC 容器。它提供了最简单的容器功能,包括 bean 的实例化、配置和管理。BeanFactory 的实现类是 XmlBeanFactory,它通过 XML 文件来加载和配置 bean。

    // 示例:使用 XmlBeanFactory 创建 BeanFactory
    Resource resource = new ClassPathResource("applicationContext.xml");
    BeanFactory factory = new XmlBeanFactory(resource);
    

    BeanFactory 的特点是延迟加载,即只有在请求获取 bean 时才进行实例化。这可以在资源使用效率上带来一定的优势,但也可能导致一些性能问题。

  2. ApplicationContext: ApplicationContext 是 BeanFactory 的扩展,提供了更多的功能和特性。它是 Spring 框架中更高级的 IoC 容器。ApplicationContext 不仅包含了 BeanFactory 的功能,还提供了更多的企业级特性,例如事件传播、AOP 集成、国际化、消息传递等。

    // 示例:使用 ClassPathXmlApplicationContext 创建 ApplicationContext
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    

    ApplicationContext 的实现类有多种,其中最常用的是 ClassPathXmlApplicationContext,它通过类路径下的 XML 配置文件来初始化容器。另外,FileSystemXmlApplicationContext 可以通过文件系统路径加载配置文件,AnnotationConfigApplicationContext 则可以通过注解来配置。

ApplicationContext 的特点是预加载,即在容器启动时就会实例化所有的 singleton bean,提前发现配置问题,避免在运行时出现问题。

总体而言,BeanFactory 和 ApplicationContext 都是 Spring IoC 容器的实现,提供了 bean 的管理、依赖注入等核心功能,但 ApplicationContext 在功能上更加丰富,适用于大多数企业级应用。选择使用哪种容器取决于具体的需求和性能考虑。

Spring IoC 和 DI 对于对象的管理

  • 存对象:@Component
  • 取对象:@AutoWired

 1、@Component:

@Component 是 Spring 框架中用于声明一个类是 Spring 管理的组件(bean)的注解。通过将 @Component 注解添加到类上,Spring IoC 容器会扫描并将该类实例化为一个 bean,并将其纳入容器的管理。

@Component
public class MyComponent {
    // 类的实现
}

这样,MyComponent 就会被 Spring IoC 容器发现,并在容器初始化时创建实例,成为容器中的一个可管理的对象。

 

2、@Autowired:

@Autowired 是 Spring 框架中用于进行依赖注入的注解。通过在需要依赖注入的字段、构造函数或者方法上添加 @Autowired 注解,Spring IoC 容器会自动注入相应的依赖。

@Component
public class AnotherComponent {

    private MyComponent myComponent;

    @Autowired
    public AnotherComponent(MyComponent myComponent) {
        this.myComponent = myComponent;
    }

    // 或者在字段上使用 @Autowired
    // @Autowired
    // private MyComponent myComponent;

    // 其他方法
}

在上述例子中,AnotherComponent 类依赖于 MyComponent。通过在构造函数上添加 @Autowired 注解,Spring IoC 容器会自动将 MyComponent 的实例注入到 AnotherComponent 中。

总的来说,@Component 用于声明 Spring 管理的组件,而 @Autowired 用于进行依赖注入。通过这两个注解的配合,Spring IoC 容器能够在运行时管理对象的生命周期、解决对象之间的依赖关系,实现了控制反转和依赖注入的核心概念。这样的管理方式使得代码更加松耦合、易于维护和测试。

加上@Component之后,Spring帮忙创建对象。@Autowired就是帮忙从容器中获取该对象,并赋值给BookService bookService。

怎么判断Spring创建一个新对象成功了?能拿到数据即可:

启动:

成功。

Bean的存储

在之前的造车案例中,要把某个对象交给IoC容器管理,需要我们在类上添加⼀个注解: @Component

而Spring框架为了更好的服务web应用程序, 提供了更丰富的注解. 共有两类注解类型可以实现:

1. 类注解:@Controller、@Service、@Repository、@Component、@Configuration。

2. 方法注解:@Bean。

这五个注解(@Controller、@Service、@Repository、@Component、@Configuration)在 Spring 中都是用来声明一个类为 Spring Bean 的注解,它们的作用是将标注的类交由 Spring IoC 容器管理。它们之间的主要区别在于语义和用途,以及对应的应用场景。以下是对这些注解的简要说明:

  1. @Controller:

    • 用于标识一个类是 Spring MVC 控制器。
    • 主要用于处理 HTTP 请求,通常与 @RequestMapping 注解一起使用。
    • 在 Spring MVC 中,负责接收和响应用户请求,并将处理结果返回给客户端。
    • 通常用于 Web 层的组件。
  2. @Service:

    • 用于标识一个类是业务逻辑层的服务组件。
    • 通常包含了业务逻辑的实现,通过该注解告诉 Spring 这是一个服务层的 Bean。
    • 可以被注入到其他层(如控制器或数据访问层)中使用。
  3. @Repository:

    • 用于标识一个类是数据访问层的组件,即持久层的 DAO(Data Access Object)。
    • 通常用于与数据库交互,包含数据库访问和操作的相关逻辑。
    • 具备 Spring 的异常转换功能,将数据访问异常转换为 Spring 的 DataAccessException。
  4. @Component:

    • 是一个泛用的 Spring Bean 注解,可用于标识任意类型的组件。
    • 通常用于没有明确层次的组件,可以被应用于任何层次的 Spring 应用。
    • 在功能上与 @Service、@Repository、@Controller 是等效的。
  5. @Configuration:

    • 用于定义配置类,替代 XML 配置文件。
    • 声明一个类为配置类,类中可以包含 @Bean 注解,用于定义 Bean 的创建。
    • 通常与 @ComponentScan 一起使用,扫描并注册配置类中声明的 Bean。

获取被这些注解标注的对象

获取被这些注解标注的对象的方法主要有两种:通过类型获取和通过名称获取。

  • 通过类型获取:

    • 可以使用 ApplicationContext 的 getBeansOfType 方法,传入对应的类型,获取所有符合该类型的 Bean。
    • 例如:context.getBeansOfType(ServiceClass.class)
  • 通过名称获取:

    • 可以使用 ApplicationContext 的 getBean 方法,传入对应的名称,获取具体的 Bean。
    • 例如:context.getBean("serviceName")

这两种方式都适用于所有被上述注解标注的类,无论是 @Controller、@Service、@Repository、@Component 还是 @Configuration。名称默认为类名的首字母小写,但也可以通过注解的 value 属性来指定名称。

具体来说:

  1. 通过类型获取: 通过 ApplicationContext 的 getBeansOfType 方法,传入对应的类型,可以获取所有符合该类型的 Bean。这是一种更通用的方式,适用于你可能需要获取某个类型的所有 bean 的情况。

    // 通过类型获取所有符合该类型的 Bean
    Map<String, ServiceClass> serviceBeans = context.getBeansOfType(ServiceClass.class);
    
  2. 通过名称获取: 通过 ApplicationContext 的 getBean 方法,传入对应的名称,可以获取具体的 Bean。这种方式更直接,适用于你明确知道要获取的 bean 的名称。

    // 通过名称获取具体的 Bean
    ServiceClass serviceBean = (ServiceClass) context.getBean("serviceName");
    

这两种方式是根据具体需求选择的。如果你知道需要获取的 bean 的类型,而不关心具体的名称,使用通过类型获取的方式更为方便。如果你知道需要获取的 bean 的名称,而不关心具体的类型,使用通过名称获取的方式更为直接。在实际应用中,可以根据具体场景选择合适的方式。

接下来我们分别来看:

@Controller(控制器存储)

使用@Controller 存储 bean 的代码如下所示:

@Controller // 将对象存储到 Spring 中
public class UserController {
   public void sayHi(){
   System.out.println("hi,UserController...");
 }

如何观察这个对象已经存在Spring容器当中了呢?

接下来我们学习如何从Spring容器中获取对象。

被@SpringBootApplication标识过的这个类叫做“启动类”

从run方法点进去,我们一直前进,找到父类:

接收一下:

在 Spring 中,"ApplicationContext" 的翻译为 "Spring 上下文" 。因为在 Spring 框架中,对象的管理交给了 Spring 容器,因此要获取对象就需要从 Spring 上下文中获取。

这也引出了我们关于上下文概念的讨论。

关于上下文的概念,在计算机领域有着广泛的应用。在学习线程时,我们了解到上下文指的是当前运行环境的状态信息。例如,在进行线程切换时,会将当前线程的状态信息暂时储存起来,这个储存的信息就是上下文。当该线程再次获得 CPU 时间时,可以从上下文中恢复线程上次运行的信息,从而实现线程的切换和继续执行。

在这个背景下,"上下文" 可以被理解为当前的运行环境,它是一个容器,包含了当前应用程序的状态、配置以及其他相关信息。在 Spring 中,"ApplicationContext" 就是一种特定的上下文,它管理整个 Spring 应用程序的组件、配置和运行时环境。通过 ApplicationContext,我们能够方便地获取在 Spring 中声明的对象,实现了对象的集中管理和依赖注入。

总的来说,"ApplicationContext" 翻译为 "Spring 上下文",是指 Spring 框架中负责管理整个应用程序的运行环境和对象的容器。这个上下文中存储了许多内容,包括配置信息、bean 对象等,是整个应用程序的核心管理者。

 接下来,我们从Spring容器中获取对象

根据类型获取bean对象

package com.example.ioc_demo;

import com.example.ioc_demo.IoC.controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class IoCDemoApplication {

    public static void main(String[] args) {
       ApplicationContext context= SpringApplication.run(IoCDemoApplication.class, args);
       UserController bean = context.getBean(UserController.class);
       bean.sayHi();
    }

}

运行结束,日志打印出来了:

 观察运行结果, 发现成功从Spring中获取到Controller对象, 并执行Controller的sayHi方法。

我们说这是通过类型获取对象的方式,你可能疑惑,和前面说的方法不一样啊。

对于“通过类型获取对象的方式”:

// 获取Spring上下文对象
ApplicationContext context = SpringApplication.run(IoCDemoApplication.class, args);

// 通过类型获取对象
UserController bean = context.getBean(UserController.class);

// 调用对象的方法
bean.sayHi();

这是获取某个特定类型的 bean,这里的 UserController.class 是具体的类类型,表示你想获取的 bean 是 UserController 类型的对象。这种方式适用于你明确知道要获取的 bean 的类型的情况。

而对于“通过类型获取所有符合该类型的 Bean”:

// 获取Spring上下文对象
ApplicationContext context = SpringApplication.run(IoCDemoApplication.class, args);

// 通过类型获取所有符合该类型的 Bean
Map<String, ServiceClass> serviceBeans = context.getBeansOfType(ServiceClass.class);

 这是获取某个特定类型的所有 bean,ServiceClass.class 是具体的类类型,表示你想获取的所有 bean 都是 ServiceClass 类型的对象。这种方式适用于你想获取某个类型的所有 bean 的情况。

所以,具体使用哪种方式取决于你的需求。如果你只关心某个特定类型的 bean,使用第一种方式;如果你需要获取某个类型的所有 bean,使用第二种方式。

好,回到正题,既然上述代码是根据类型来查找对象,那么如果Spring容器中同⼀个类型存在多个bean的话,怎么获取呢? 我们之前说过的根据名称获取。

获取bean对象的其他方式

在 Spring 中,通过 ApplicationContext 接口提供了多种方式来获取 Bean 对象。这些方法提供了灵活的选项,允许根据不同的条件获取容器中的对象。需要注意的是,ApplicationContext 的获取 Bean 功能是基于其父类 BeanFactory 提供的功能。

1. 根据 Bean 名称获取 Bean:

Spring帮我们创建对象的的时候,会给每个对象起一个名字:

Object getBean(String beanName) throws BeansException;
  • 根据指定的 Bean 名称获取对应的对象。
  • Bean 名称是 Spring 容器管理对象时为其分配的唯一标识符。

2. 根据 Bean 名称和类型获取 Bean:

<T> T getBean(String beanName, Class<T> requiredType) throws BeansException;
  • 根据指定的 Bean 名称和类型获取对应的对象。
  • 指定了期望的 Bean 类型,可以更准确地获取对象。

3. 按 Bean 名称和构造函数参数动态创建 Bean:

Object getBean(String beanName, Object... args) throws BeansException;
  • 根据指定的 Bean 名称和构造函数参数动态创建 Bean 对象。
  • 主要适用于具有原型(prototype)作用域的 Bean。

4. 根据类型获取 Bean:

<T> T getBean(Class<T> requiredType) throws BeansException;
  • 根据指定的类型获取对应的对象。
  • 如果容器中存在多个相同类型的 Bean,可以使用这种方式。

5. 按 Bean 类型和构造函数参数动态创建 Bean:

<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
  • 根据指定的类型和构造函数参数动态创建 Bean 对象。
  • 适用于具有原型(prototype)作用域的 Bean。

上下文中的 Bean 名称

  • Spring Bean 在容器中都有一个名称(BeanId)。
  • 这个名称是 Spring 在运行时管理对象时为其分配的标识符。
  • 通过 Bean 名称,可以唯一地定位到容器中的特定对象。

总结

通过 ApplicationContext 提供的这些获取 Bean 的方法,我们可以根据名称、类型、构造函数参数等多种条件来方便地获取 Spring 容器中的对象。这样的灵活性和多样性使得在实际开发中能够更好地满足不同场景下的需求,实现了对象的集中管理和依赖注入。

Bean 命名约定

我们看下Spring的官方文档:

Spring | Home

 

找不到没关系,点击最新版就好,差别不大:


 

Bean 命名规则总结

在 Spring 中,每个 Bean 都有一个或多个标识符,这些标识符在托管该 Bean 的容器中必须是唯一的。通常情况下,一个 Bean 只有一个标识符,但如果需要更多,额外的标识符可以被视为别名。

1. XML 配置中的 Bean 标识符

在基于 XML 的配置元数据中,可以使用 id 属性、name 属性,或者两者结合来指定 Bean 的标识符。具体规则如下:

  • 使用 id 属性可以指定一个唯一的标识符。
  • 使用 name 属性可以指定一个或多个标识符,多个标识符之间用逗号(,)、分号(;)或空格分隔。
  • 尽管 id 属性被定义为 xsd:string 类型,但 Bean 的唯一性是由容器强制执行的,而不是由 XML 解析器强制执行。

如果不显式提供名称或标识符,容器将为该 Bean 生成一个唯一的名称。然而,如果想通过名称引用该 Bean,可以通过 ref 元素或 Service Locator 风格的查找提供一个名称。

2. Bean 命名规范

遵循标准的 Java 实例字段命名规范来命名 Beans。具体规范如下:

  • Bean 名称以小写字母开头,并且采用小驼峰式命名。例如:accountManager、accountService、userDao、loginController 等。
  • 一致地命名 Bean 有助于提高配置文件的可读性和理解性。
  • 在使用 Spring AOP 时,按照名称一致性的规则有助于将通知应用于一组以名称相关的 Beans。

3. 组件扫描中的 Bean 名称生成规则

在类路径上存在组件扫描时,Spring 会为未命名的组件生成 Bean 名称,遵循以下规则:

  • 取类的简单类名,并将其初始字符转换为小写。
  • 在某些特殊情况下(第一个和第二个字符都是大写),保留原始大小写。即如果类名前两位字母都是大写,Bean的名称为原始类名。

这些规则与 Java 的 java.beans.Introspector.decapitalize 定义的规则相同,Spring 在这里使用了这些规则。

 我们可以看看Java 的 java.beans.Introspector.decapitalize

 

java.beans.Introspector.decapitalize 是 JDK 中 java.beans 包提供的一个用于处理 Java Beans 命名规范的工具方法。Spring 在组件扫描中使用了这个工具方法来生成 Bean 的名称。说明Spring的命名规则符合JDK的命名要求。

这个方法的规则是将字符串的第一个字符转换为小写,如果第一个字符已经是小写,则返回原始字符串。这与 Spring 推荐的 Bean 命名规范相一致,即在类名的第一个字母小写,采用驼峰命名法。

这种对 JDK 提供的命名规范工具方法的使用,使得 Spring 在组件扫描时能够保持一致性,与标准的 Java 命名规范相符,提高了代码的可读性和一致性。

 

package com.example.ioc_demo;

import com.example.ioc_demo.IoC.controller.UserController;
import com.example.ioc_demo.IoC.service.UserService;
import lombok.val;
import org.omg.PortableInterceptor.INACTIVE;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

import java.beans.Introspector;

@SpringBootApplication
public class IoCDemoApplication {

    public static void main(String[] args) {

        // 获取Spring上下文对象
        ApplicationContext context = SpringApplication.run(IoCDemoApplication.class, args);

        UserController bean = context.getBean(UserController.class);

        // 从Spring上下文中获取对象
        // 根据bean类型, 从Spring上下文中获取对象
        UserController userController1 = context.getBean(UserController.class);

        // 根据bean名称, 从Spring上下文中获取对象
        UserController userController2 = (UserController) context.getBean("userController");

        // 根据bean类型+名称, 从Spring上下文中获取对象
        UserController userController3 = context.getBean("userController", UserController.class);

        System.out.println(userController1);
        System.out.println(userController2);
        System.out.println(userController3);

    }
}

 地址一样, 说明对象是一个

获取 Bean 对象是 BeanFactory 提供的功能。

ApplicationContext VS BeanFactory(常见面试题) 

Spring 容器有两个顶级的接口:BeanFactory 和 ApplicationContext。

继承关系和功能

  1. 继承关系:

    • BeanFactory: 是 Spring IoC 容器的基础接口,提供基本的容器访问能力。
    • ApplicationContext: 是 BeanFactory 的子接口,继承了 BeanFactory 的所有功能,并在此基础上添加了更多的企业级特性。
  2. 功能比较:

    • BeanFactory:

      • 提供基本的 IoC 功能,按需加载 Bean。
      • 延迟初始化,只有在获取 Bean 时才进行实例化。
      • 相对轻量级。
    • ApplicationContext:

      • 继承了 BeanFactory 的所有功能。
      • 添加了更多的特性,如国际化支持、资源访问支持、事件传播等。
      • 提供更多的应用上下文的实现,如 ClassPathXmlApplicationContext、AnnotationConfigApplicationContext 等。

从性能角度来说

  • ApplicationContext:

    • 一次性加载并初始化所有 Bean 对象。
    • 在应用启动时,完成对所有 Bean 的实例化和初始化工作。
    • 相对重量级,占用较多系统资源。
  • BeanFactory:

    • 按需加载,需要使用某个 Bean 时才进行初始化。
    • 相对轻量级,适用于资源有限的环境。

获取 Bean 对象

  • 如果对象的地址一样,说明是同一个对象,这是因为 Spring 默认情况下,对于单例模式的 Bean,Spring 容器会确保每个 Bean 在容器中只有一个实例。

  • 获取 Bean 对象是 BeanFactory 提供的功能。通过类型、名称或类型和名称的组合,可以从容器中获取相应的 Bean。

选择使用场景

  • ApplicationContext:

    • 适用于资源充足,需要使用企业级特性的场景。
    • 提供更多的功能,如国际化支持、资源访问支持等。
  • BeanFactory:

    • 适用于资源有限,对性能有较高要求的场景。
    • 提供基本的 IoC 容器功能,更轻量。

总体而言,选择使用 ApplicationContext 还是 BeanFactory 取决于项目的具体需求和性能要求。ApplicationContext 提供更多的功能,但相应地也更为重量级。 BeanFactory 更轻量,适用于对性能要求较高的场景。

@Service(服务存储)

 

强转成我们想要的类:

然后调用方法:

在我们的 IoCDemoApplication 类中,使用了 @Service 注解标记了 UserService 类,因此 Spring 应该自动注册一个名为 "userService" 的 bean。

@Repository(仓库存储) 

package com.example.ioc_demo.repository;

import org.springframework.stereotype.Repository;

@Repository
public class UserRepository {
    public void sayHi() {
        System.out.println("Hi, UserRepository~");
    }
}

我们这次根据名称和类型去拿:   

 

@Component(组件存储)

package com.example.ioc_demo.IoC.Component;

import org.springframework.stereotype.Component;

@Component
public class UserComponent {
    public void sayHi() {
        System.out.println("Hi, UserComponent~");
    }
}

@Configuration(配置存储) 

@Configuration 所在的包和其他几个不太一样:

package com.example.ioc_demo.IoC.Configuration;

import org.springframework.context.annotation.Configuration;

@Configuration
public class UserConfiguration {
    public void sayHi() {
        System.out.println("Hi,UserConfiguration~");
    }
}

为什么要这么多类注解?

这个也是和咱们前⾯讲的应⽤分层是呼应的。

让程序员看到类注解之后,就能直接了解当前类的用途:

  1. @Controller:控制层

    • 作用:标识该类是控制层的组件,负责接收请求,处理业务逻辑,以及返回响应。
    • 示例:在Spring MVC中,使用@Controller注解标记的类通常处理HTTP请求,并通过视图渲染返回结果。
  2. @Service:业务逻辑层

    • 作用:标识该类是服务层的组件,主要处理业务逻辑,包括各种业务规则的实现。
    • 示例:在一个电商应用中,可以使用@Service注解标记的类处理购物车管理、订单逻辑等业务。
  3. @Repository:数据访问层

    • 作用:标识该类是数据访问层的组件,主要用于进行数据库访问和持久化操作。
    • 示例:在数据访问对象(DAO)中,使用@Repository注解标记的类通常用于执行数据库查询、更新等操作。
  4. @Configuration:配置层

    • 作用:标识该类是配置层的组件,用于配置项项目中的一些配置信息,例如数据库连接、Bean的声明等。
    • 示例:一个使用Java配置的Spring项目中,可以使用@Configuration注解标记的类配置Bean、数据源等。

这些注解有助于提高代码的可维护性和可读性,使开发人员能够更容易理解和定位项目中各个组件的职责。在使用这些注解时,一般需要遵循Spring的命名规范,以确保Spring能够正确地识别和管理这些组件。

类比于每个省/市都有自己的车牌号,Spring中的命名规范和注解的差异也是为了更好地标识和管理组件,让开发人员能够快速理解和定位项目中的各个部分。类似于车牌号的省份和地区编码,Spring使用包名或注解来对组件进行归类和标识。

举例来说,假设有一个Spring项目,其中包含了多个模块或功能,每个模块都有自己的业务逻辑、控制器等。使用不同的包名或注解,就像车牌号的不同开头一样,可以让我们清晰地知道某个类属于哪个模块,从而更好地组织和管理代码。

在Spring的命名规范中,包名的层次结构就扮演了类似于车牌号中的省份和地区编码的角色。通过规范的包名,我们可以轻松地识别出类所属的模块,达到清晰、有序的项目结构。

  1. 唯一标识: 车牌号是唯一标识一辆车的方式,就像在Spring中每个组件(类、接口等)都有唯一的标识符一样。这确保了在整个系统中不会出现重复的标识。

  2. 地域标识: 车牌号的开头字母或数字通常代表了车辆的归属地,例如,"陕X"表示陕西,"京X"表示北京。在Spring中,包名或注解的差异也可以代表组件所属的模块、层次或业务领域,使得可以直观地看出组件的用途。

  3. 清晰标识: 通过不同的车牌号开头,人们可以清晰地了解一辆车的来源地。同样,在Spring中,通过规范的包名和注解,开发者可以清晰地知道一个组件所属的模块或领域,从而更容易理解和维护代码。

  4. 节约号码: 在车牌号中,不同地区使用不同的开头字母或数字,这样可以节约号码。类似地,在Spring中,采用命名规范和注解进行分类,有助于组织和管理大型项目,避免命名冲突,并提高项目的可维护性。

 程序的应用分层,调用流程如下:

类注解之间的关系

 类注解之间的关系可以通过查看它们的源码来进行解释。

在 Spring 框架中,常见的类注解包括 @Controller、@Service、@Repository 和 @Configuration 等。这些注解在源码中都包含了一个共同的注解 @Component,这意味着它们都可以看作是 @Component 的子类或衍生注解。

@Component 本身是一个元注解,这表示它可以用来注解其他类注解,例如 @Controller、@Service、@Repository 等。因此,这些注解被称为 @Component 的衍生注解,它们继承了 @Component 的功能,并在特定的场景中提供了更具体的语义。

举例来说,@Controller 用于标识控制层的组件,@Service 用于标识业务逻辑层的组件,@Repository 用于标识持久化层的组件。虽然它们都可以替代 @Component 使用,但在实际开发中,选择更具体的注解能够更清晰地表达组件的用途。

  1. @Component: 这是一个通用的注解,用于指示一个类是 Spring 管理的组件。任何被 @Component 注解的类都会被 Spring 扫描并纳入到应用上下文中,并成为 Spring 应用上下文中的一个 Bean。这意味着这些类可以被其他类通过依赖注入的方式引用。@Component 注解不仅可以用于类,也可以用于接口、枚举等。接口本身不是组件,但当它被 @Component 注解标注时,它的实现类就成为了 Spring 管理的组件。也就是说,Spring 主要关注于类级别的组件管理,而不是接口。当在接口上使用 @Component 注解时,Spring 会忽略它,并且实现该接口的类才会被 Spring 管理。

  2. @Controller、@Service、@Repository: 这三个注解是 @Component 的衍生注解,分别用于标识不同层次的组件。

    • @Controller:主要用于标识控制层的组件,通常用于 Spring MVC 中。除了具备让 Spring 管理的功能外,调用的入口必须为 @Controller。@Controller 注解表明该类是一个控制器,用于处理 Web 请求。被标注为 @Controller 的类通常包含处理请求的方法,这些方法返回视图或数据给客户端。在 Spring MVC 中,@Controller 扮演着接收和处理用户请求的角色,它是整个 MVC 架构中的控制器层。

    • @Service: 主要用于标识服务层(业务逻辑层)的组件,表示该类提供业务逻辑的服务。

    • @Repository: 主要用于标识数据访问层的组件,通常用于 DAO 类。它表明这个类负责数据存储和检索。

  3. @Configuration: 用于定义配置类,通常配合 @Bean 注解一起使用,用于声明 Spring 容器中的 Bean。

这些注解之间的关系可以通过继承关系来理解。具体来说,@Controller、@Service、@Repository 这三个注解分别被标注为 @Component 的元注解。这意味着它们在语义上是 @Component 的特化版本,拥有 @Component 的所有功能,并在此基础上提供了更具体的用途。 

比如,如果你在业务逻辑层需要使用一个组件,使用 @Service 比直接使用 @Component 更为合适。当使用 @Service 注解标记一个类时,它不仅被视为一个组件,还被认为是业务逻辑层的组件。这样,Spring 在创建应用上下文时就能更好地理解每个组件的角色,从而更好地进行依赖注入和组件扫描。这种选择使得代码更易读、更易维护,同时也更符合软件开发中的约定。

@Controller、@Service、@Repository 这些注解继承了 @Component,但在某些场景下具有特殊的处理和功能,并不可以用 @Component 完全替代。

@Controller:

  • Web MVC 相关功能: @Controller 注解通常用于标识控制器类,处理 Web 请求。与 @RequestMapping 注解结合使用,可以定义处理请求的方法。Spring MVC 在扫描到 @Controller 注解时,会将其注册为一个处理器类,并提供与 Web 请求相关的功能。
  • 视图解析: @Controller 类的方法通常返回字符串,表示视图的逻辑名称。Spring MVC 会使用视图解析器将逻辑名称解析为实际的视图。

例如:

原来:

替换@RestController:

正常访问:

现在换路径,把@RequestMapping全部写在下面:

正常访问:

改成@Component: 

正常访问:

换路径:

所以此处不可以用@Component代替@Controller。

尽管 @Component 包含了组件的基本功能,但它不包含与 Web 请求处理相关的功能,比如视图解析。而 @Controller 则是专门用于处理 Web 请求的注解,它会告诉 Spring 框架这是一个控制器,需要特殊的处理,包括视图解析等。

当然,但从功能上看,除了@Controller,其他的注解效果是一样的,但是你还是应该考虑到选择合适的注解更有助于代码的清晰性和可读性。这是因为这些注解除了标识组件外,还传递了关于组件用途的语义信息。

虽然在某些情况下,@Component 可以代替这些注解,但它可能不会传达足够的语义信息,使得代码更容易理解。在合适的场景下使用特定的注解,可以帮助其他开发者更快地理解你的代码,同时也有助于 Spring 完成一些特殊的处理。

@Service:

  • 服务层标识: @Service 注解通常用于标识服务层的组件,表示这个类提供业务逻辑的服务。与 @Autowired 一起使用,可以在其他组件中注入服务。
  • 事务处理: 服务层通常涉及到事务处理,而 @Service 注解可以作为事务切面的标识。当在服务层方法上添加 @Transactional 注解时,Spring 会处理事务的开始、提交和回滚。

@Repository:

  • 数据访问层标识: @Repository 注解用于标识数据访问层的组件,通常用于 DAO(Data Access Object)类。它告诉 Spring 这个类主要负责数据库访问。
  • 异常转换: @Repository 注解通常与 Spring 的异常转换机制结合使用。当在数据访问层捕获到数据库相关的异常时,Spring 会将它们转换为 Spring 的数据访问异常(DataAccessException)或其子类。
  • 自动异常翻译: 除了异常转换,@Repository 还启用了自动异常翻译的功能。它将底层的数据库异常转换为 Spring 的数据访问异常,使上层的业务逻辑能够更方便地处理。

这些独有的功能使得 @Controller、@Service、@Repository 更具体、更有针对性地适用于不同层次和场景的组件。虽然它们在某些方面相似,但这些额外的功能使得它们更适合特定的用途,提高了代码的清晰度和可维护性。

更详细的还是参考官方文档:Classpath Scanning and Managed Components :: Spring Framework 

学习分析错误日志 

1、我们回到UserController,把@Controller删掉再运行:

这个错误日志是 org.springframework.beans.factory.NoSuchBeanDefinitionException,它表明 Spring 容器中找不到符合条件的 Bean,具体说就是找不到类型为 'com.example.ioc_demo.IoC.controller.UserController' 的 Bean。 

让我们分析一下可能导致这个错误的原因:

UserController 类没有被正确标注为 Spring 组件: 确保在 UserController 类上添加了 @Controller 注解或其他合适的组件注解,以便 Spring 能够扫描并注册它。

@Controller
public class UserController {
    // 类的实现...
}

包扫描路径配置问题: 确保 Spring 的包扫描路径包含了 UserController 所在的包。在 Spring 的配置类(通常使用 @SpringBootApplication 注解的类)上添加 @ComponentScan 注解,指定扫描的包路径。

@SpringBootApplication
@ComponentScan(basePackages = "com.example.ioc_demo.IoC.controller")
public class IoCDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(IoCDemoApplication.class, args);
    }
}

UserController 的构造函数可能有问题: 如果 UserController 的构造函数有依赖,确保它们也被正确注入,并且这些依赖也被 Spring 容器管理。

@Controller
public class UserController {
    private UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }
    // 其他方法...
}

UserController 的 Bean 名称可能没有按照默认规则生成: 如果你在配置类或者使用其他方式定义 Bean,确保 Bean 的名称是按照默认规则生成的,或者在 @Controller 注解中显式指定 Bean 的名称。

@Controller("userController")
public class UserController {
    // 类的实现...
}

2、我们是可以自己指定名称的:

如果此时我们把名称设置成别的类的默认名称:

老大一长串错误日志:

从下往上看:

这个错误日志表明了一个 ConflictingBeanDefinitionException 异常,原因是在 Spring 容器中存在两个具有相同名称的 Bean,但它们的类不兼容。

错误信息中的关键部分是:

Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'userService' for bean class [com.example.ioc_demo.IoC.service.UserService] conflicts with existing, non-compatible bean definition of same name and class [com.example.ioc_demo.IoC.Component.UserComponent]

这表示在扫描组件时,Spring 发现了两个名为 'userService' 的 Bean,一个是 com.example.ioc_demo.IoC.service.UserService 类型的,另一个是 com.example.ioc_demo.IoC.Component.UserComponent 类型的。由于它们的类型不一致,Spring 无法确定应该使用哪一个。

解决方法:

检查 @ComponentScan 注解配置: 在你的配置类(通常是带有 @SpringBootApplication 注解的类)上,确保 @ComponentScan 注解的 basePackages 或 basePackageClasses 属性包含了你想要扫描的包路径,同时确保没有重复扫描相同的包。
 

@SpringBootApplication
@ComponentScan(basePackages = {"com.example.ioc_demo.IoC.service", "com.example.ioc_demo.IoC.Component"})
public class IoCDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(IoCDemoApplication.class, args);
    }
}

使用不同的 Bean 名称: 如果你确实需要这两个不同的类作为 Bean,可以为它们指定不同的 Bean 名称,避免名称冲突。

@Service("userService")
public class UserService {
    // ...
}

@Component("userComponent")
public class UserComponent {
    // ...
}

通过检查这两个方面,我们应该能够解决这个 ConflictingBeanDefinitionException 异常。确保包扫描路径正确配置,并且没有重复扫描相同的包,同时确保不同类型的 Bean 使用不同的名称,以避免名称冲突。

3、名称不重复,但是获取对象的时候输错名称:

 

这个错误表明 Spring 容器中找不到名称为 'userComponent' 的 Bean。这可能是由于以下原因之一引起的:

未使用正确的注解标记类: 确保 UserComponent 类上有适当的 Spring 组件注解,例如 @Component、@Service、@Repository 或 @Controller。

@Component("userComponent")
public class UserComponent {
    // 类的实现...
}

包扫描路径不正确: 如果 UserComponent 所在的包没有被正确扫描,Spring 就无法发现这个类。在配置类上使用 @ComponentScan 注解时,确保包含了 UserComponent 类所在的包。

@SpringBootApplication
@ComponentScan(basePackages = "com.example.ioc_demo.IoC.Component")
public class IoCDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(IoCDemoApplication.class, args);
    }
}

Bean 名称拼写错误: 确保在获取 Bean 的时候使用的是正确的名称,大小写要一致。

UserComponent userComponent = context.getBean("userComponent", UserComponent.class);

4、赋值对象不匹配:

 这个错误表明在尝试获取名为 'userService' 的 Bean 时,期望的类型是 'com.example.ioc_demo.IoC.Component.UserComponent',但实际上该 Bean 的类型是 'com.example.ioc_demo.IoC.service.UserService',即期望类型与实际类型不匹配。

这可能是由以下原因之一引起的:

Bean 名称混淆: 可能存在一个名为 'userService' 的 Bean,但它的类型是 'com.example.ioc_demo.IoC.service.UserService',而不是 'com.example.ioc_demo.IoC.Component.UserComponent'。

@Service("userService")
public class UserService {
    // 类的实现...
}


在这种情况下,如果你想获取类型为 'com.example.ioc_demo.IoC.Component.UserComponent' 的 Bean,确保使用正确的名称。

包扫描路径或配置类不正确: 确保 UserComponent 所在的包被正确扫描,并且在配置类上使用了正确的 @ComponentScan 注解。

@SpringBootApplication
@ComponentScan(basePackages = {"com.example.ioc_demo.IoC.service", "com.example.ioc_demo.IoC.Component"})
public class IoCDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(IoCDemoApplication.class, args);
    }
}

确保 @ComponentScan 注解中包含了 'com.example.ioc_demo.IoC.Component' 这个包路径。

可能存在重名的 Bean: 如果确实存在一个名为 'userService' 的 Bean,但是类型与预期不符,检查你的代码中是否存在其他地方使用了相同的 Bean 名称。

// 可能的其他类中的代码
@Service("userService")
public class AnotherService {
    // 类的实现...
}

方法注解 @Bean

类注解是添加到某个类上的,但是在特定情境下,会面临两个问题:

使用外部包里的类,无法添加类注解: 当我们需要使用来自外部包的类,但无法直接修改其源代码以添加类注解时,面临了限制。在这种情况下,我们无法通过传统的方式将自定义的类注解添加到外部类上。

一个类需要多个对象,比如多个数据源: 在某些场景下,一个类可能需要创建多个不同配置的对象实例,比如多个数据源的情况。但是直接在类上添加注解无法满足不同实例的需求,因为类注解通常用于标识整个类,而不是具体的对象。

在解决这些问题的情况下,我们可以使用方法注解 @Bean。通过在方法上使用 @Bean 注解,我们可以在方法级别上进行配置,灵活地创建和返回不同配置的对象实例。这样可以绕过一些类级别的限制,并满足特定场景下的需求。

例如,对于问题1,无法直接给外部包的类添加注解,但我们可以在自己的配置类中使用 @Bean 注解来创建和配置这个外部类的实例。对于问题2,我们可以在配置类中定义多个带有 @Bean 注解的方法,每个方法返回不同配置的对象实例。

@Configuration
public class MyConfiguration {

    // 问题1的解决:使用外部包的类
    @Bean
    public ExternalClass externalClass() {
        return new ExternalClass();
    }

    // 问题2的解决:一个类需要多个对象
    @Bean
    public DataSource dataSource1() {
        // 配置和创建第一个数据源
        return new DataSource("dataSource1");
    }

    @Bean
    public DataSource dataSource2() {
        // 配置和创建第二个数据源
        return new DataSource("dataSource2");
    }
}

在这个例子中,MyConfiguration 类上没有类级别的注解,但通过 @Bean 注解的方法,我们解决了问题1和问题2,分别配置了外部包的类和多个数据源的对象实例。

这是第三方的类,我们没有办法修改源码,让Spring帮我们管理它,那么我们就自己new一个对象,让Spring管理:

注意一定要加上@Configuration:

 

关于为啥一定要加上@Configuration:

在 Spring 中,@Configuration 注解用于标识一个类是配置类,它包含了用于配置 Spring 容器的 Bean 的方法。当一个类被标注为 @Configuration 时,Spring 容器会在应用上下文中注册这个类,并识别其中的 @Bean 注解的方法,将其返回的对象注册为 Bean。

在我们的示例代码中,BeanConfig 类使用了 @Configuration 注解,并包含了一个标注有 @Bean 注解的方法 userInfo1。这表示 userInfo1 方法会被 Spring 容器识别,并将其返回的 UserInfo 对象注册为一个 Bean。

当我们在其他类中使用 @Autowired 或通过其他方式引用 userInfo1 方法返回的 UserInfo 对象时,Spring 会自动注入这个 Bean。

如果去掉了 @Configuration 注解,Spring 将不会将 BeanConfig 视为配置类,也就不会处理其中的 @Bean 注解,userInfo1 方法返回的对象也不会被注册为一个 Bean。因此,如果我们希望 Spring 正确地处理配置类中的 @Bean 注解,就需要确保配置类上标注了 @Configuration 注解。

不信的话你可以看看:

所以@Bean需要搭配五大注解使用!!! 

环境扫描路径:

在 Spring 框架中,环境扫描路径(Component scanning path)是由开发者配置的。开发者可以使用 @ComponentScan 注解或在 XML 配置文件中配置 <context:component-scan> 元素,来指定 Spring 应该扫描哪些包以寻找组件(Component)。

具体来说:

  1. 使用 @ComponentScan 注解: 在 Java 配置类上使用 @ComponentScan 注解,可以指定要扫描的包路径。例如:

    @Configuration
    @ComponentScan(basePackages = "com.example")
    public class AppConfig {
        // 配置内容...
    }
    
  2. 在 XML 配置文件中使用 <context:component-scan> 元素: 在 Spring 的 XML 配置文件中,通过 <context:component-scan> 元素配置扫描路径。例如:

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           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">
    
        <context:component-scan base-package="com.example" />
    
        <!-- 其他配置内容... -->
    
    </beans>
    

在这两种情况下,base-package 属性指定了要扫描的根包路径。Spring 将从指定的根包路径开始,递归扫描所有子包,查找带有 @Component 注解或其他特定注解的类,以将其注册为 Spring Bean。

在我们没有自己决定扫描路径的时候,是什么帮我们决定的?

在没有明确指定扫描路径的情况下,Spring 默认会使用主应用程序类(Main Application Class)所在的包作为扫描路径的起点。主应用程序类是指包含 main 方法的类,通常是启动 Spring 应用程序的入口点。

假设你的主应用程序类位于包 com.example.application 中,那么默认情况下,Spring 将会从这个包开始递归扫描,查找带有 @Component 注解或其他特定注解的类,并将其注册为 Spring Bean。

@SpringBootApplication 注解通常包含了 @ComponentScan 注解,且没有指定 basePackages 属性。因此,默认情况下,Spring 将从 com.example.application 包开始扫描,并递归查找其他包,以寻找带有 @Component 注解的类。

我们可以看看:

往上扒拉,会发现它有一个注解:

@ComponentScan默认扫描的是它标识的类所在的目录(Spring默认的扫描路径是启动类所在的路径),你也可以认为此时@SpringBootApplication已经包含了@ComponentScan,所以class IoCDemoApplication就是@ComponentScan标识的类。扫描的就是当前默认所在路径:package com.example.ioc_demo。

我们可以移动一下class IoCDemoApplication:

再次运行就报错了,显示:Spring 容器中找不到指定名称的 Bean。

只扫描了UserController。 

Spring默认的扫描路径是启动类所在的路径,所以五大注解必须在Spring的扫描路径下才能生效。

如果想要修改访问路径:

因为默认是数组,所以可以加上“{ }”:

当然,只有一个扫描路径的时候不加可能好看一点…… 

 

因为此时我们是按照类型去获取的,但是一个类型有两个Bean。

而且从报错日志上可以得到信息:@Bean注解定义的对象,默认名称是方法名(不管什么特殊情况、大小写等等)。

再用方法名称调用一次:

@Bean也可以进行重命名:

 

而且对于@Bean来说,它的name是一个数组,可以一次性进行多个重命名:

 

DI 详解

上面我们讲解了控制反转IoC的细节,接下来我们就来学习依赖注入DI的细节。 

依赖注入(DI)是一个过程,指的是在IoC容器创建Bean时,提供运行时所需的资源,这些资源通常是对象。

在上述程序案例中,我们使用了 @Autowired 这个注解,完成了依赖注入的操作。简而言之,就是将对象提取出来并放置到某个类的属性中。

在一些文献中,依赖注入也被称为 "对象注入" 或 "属性装配",具体含义需要结合文献的上下文来理解。这一过程使得系统更加灵活,降低了组件之间的耦合度,有助于更好地维护和扩展代码。

Spring提供了三种主要的依赖注入方式,分别是:

  1. 属性注入(Field Injection)
  2. 构造方法注入(Constructor Injection)
  3. Setter注入(Setter Injection)

接下来,让我们分别了解一下这三种方式。我们将按照实际开发中的模式,将 Service 类注入到 Controller 类中。

属性注入(Field Injection)

在属性注入中,通过在类的字段上使用@Autowired注解,Spring会自动将相应类型的Bean注入到这些字段中。

 

成功注入:


如果去掉@Autowired:

这个方法我们不多讲了,因为之前一直在用。接下来我们看看第二种方法:

构造方法注入(Constructor Injection):

构造方法注入是通过在类的构造方法上使用@Autowired注解,将依赖注入到构造方法中。

注入成功:

我们现在多依赖一个Configuration看看:

同样的,加上构造函数:

这个时候你会发现它在报错:

 

这个错误是 java.lang.NoSuchMethodException,意味着Java在尝试实例化 com.example.ioc_demo.IoC.controller.UserController 类时,找不到匹配的构造函数。

具体来说,错误信息中指出了没有找到无参构造函数 com.example.ioc_demo.IoC.controller.UserController.<init>()。

在我们的 UserController 类中,定义了两个带参数的构造函数,一个接受 UserService 对象,另一个接受 UserConfiguration 对象。问题在于,当一个类中定义了有参构造函数时,Spring 默认会使用该构造函数进行实例化,而不再提供默认的无参构造函数。

在这种情况下,如果我们希望使用构造方法注入 UserService,可以将 UserConfiguration 的实例化放在构造方法内部。修改代码如下:

@Controller
public class UserController {

    private UserService userService;
    private UserConfiguration userConfiguration;

    @Autowired
    public UserController(UserService userService, UserConfiguration userConfiguration) {
        this.userService = userService;
        this.userConfiguration = userConfiguration;
    }

    public void sayHi(){
        System.out.println("hi, UserController...");
        userService.sayHi();
        userConfiguration.sayHi();
    }
}

通过在构造方法上使用 @Autowired 注解,我们告诉Spring使用该构造方法进行依赖注入,而不再需要默认的无参构造函数。

注意,这里假设 UserService 和 UserConfiguration 都是由Spring容器管理的Bean。如果它们没有被正确配置为Bean,可能会导致注入失败。

在刚刚,我们只定义了一个有参构造函数,接受 UserService 对象,并没有定义其他构造函数。因此,Spring 会使用该构造函数进行依赖注入。

这样不会报错的原因在于,我们没有定义其他构造函数,因此Spring不需要考虑选择哪个构造函数来实例化 UserController。在这种情况下,Spring 会选择使用唯一存在的构造函数进行注入。

也就是说,相比之前的情况,我们在上述代码中只有一个构造函数,因此Spring容器能够正确地注入 UserService,而不会混淆或选择错误的构造函数。

总的来说,Spring在进行构造方法注入时,会选择具有 @Autowired 注解的构造函数,或者如果没有 @Autowired 注解,会选择类中唯一存在的构造函数。在多个构造函数存在时,确保使用 @Autowired 注解标记我们希望Spring使用的构造函数。

或者我们也可以手动配置无参的构造函数:

package com.example.ioc_demo.IoC.controller;

import com.example.ioc_demo.IoC.Configuration.UserConfiguration;
import com.example.ioc_demo.IoC.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller // 将对象存储到 Spring 中
public class UserController {
/*    //把userService 注入进来
    //属性注入
    @Autowired
    private UserService userService;*/

    public UserController(){
        
    }
    private UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    private UserConfiguration userConfiguration;

    public UserController(UserConfiguration userConfiguration) {
        this.userConfiguration = userConfiguration;
    }

    public void sayHi(){
        System.out.println("hi,UserController...");
        userService.sayHi();
        userConfiguration.sayHi();
    }
}

那么总结一下,在Spring中,如果一个类定义了有参构造函数,并且没有定义无参构造函数,Spring会尝试使用有参构造函数进行注入。这种情况下,Spring会假设我们的类总是通过有参构造函数进行初始化,而不是通过默认的无参构造函数。

在我们的代码中,添加了一个无参构造函数,这是为了满足Spring的要求。当Spring实例化一个bean时,它会首先尝试使用无参构造函数(如果存在),如果不存在,它将尝试使用唯一的有参构造函数。如果有多个有参构造函数,我们应该使用@Autowired注解标记我们想要Spring使用的构造函数。

在最开始只注入了UserService的情况下,如果我们只定义了一个有参构造函数,而没有定义无参构造函数,Spring会尝试使用有参构造函数进行注入,而不会要求我们必须有一个无参构造函数。无参构造函数的添加通常是为了满足Spring的默认行为和兼容性。

你可能会说 “但是我给出无参构造函数之后,同时存在多个有参构造函数,并且没有用@Autowired注解标记,也没有报错啊?”

在Spring中,当一个类存在多个有参构造函数时,如果没有使用@Autowired注解标记任何一个构造函数,Spring会尝试通过反射选择一个合适的构造函数进行实例化。在选择构造函数时,Spring通常会选择带有最多可满足依赖关系的参数的构造函数。

如果你的多个有参构造函数的参数都能够被Spring容器正确地解析和提供,那么Spring可能会成功地选择其中一个构造函数进行实例化。但这样的行为是不可靠的,因为它依赖于参数的类型和Spring容器的实现细节。

为了确保可读性和一致性,我们强烈建议在具有多个构造函数的类中使用@Autowired注解标记你希望Spring使用的构造函数。这样可以明确告诉Spring容器应该选择哪个构造函数进行注入,以避免潜在的问题。如果没有标记@Autowired,Spring的行为可能取决于具体的容器实现和参数的匹配情况。

不信你可以运行看看,虽然程序不报错,但是一运行:

同时,我们最好明确一点:

有@Autowired 注解标记但是没有默认的无参构造函数Spring不会报错,但是不保证第三方的其他库/组件报错。

在最初的解决办法中,我们没有添加无参的构造方法。

Spring在进行构造方法注入时,如果使用了@Autowired注解标记有参构造函数,且没有默认的无参构造函数,Spring会尝试使用有参构造函数进行实例化。这在Spring框架内部是可以正常工作的,因为Spring容器能够理解并处理有参构造函数。

然而,这并不保证其他第三方库或组件也能正确处理没有默认无参构造函数的类。有些库或组件可能要求类具有默认的无参构造函数,因为它们在实例化对象时依赖于这一点。

所以,为了保险起见,还是需要我们尽可能地提供默认的无参构造函数,特别是当我们的代码需要与其他库或组件进行集成时。这样可以避免潜在的兼容性问题。

 所以我们正规一点:

package com.example.ioc_demo.IoC.controller;

import com.example.ioc_demo.IoC.Configuration.UserConfiguration;
import com.example.ioc_demo.IoC.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller // 将对象存储到 Spring 中
public class UserController {
/*    //把userService 注入进来
    //属性注入
    @Autowired
    private UserService userService;*/

    public UserController(){

    }
    private UserService userService;
    private UserConfiguration userConfiguration;

    @Autowired
    public UserController(UserService userService, UserConfiguration userConfiguration) {
        this.userService = userService;
        this.userConfiguration = userConfiguration;
    }

    public void sayHi(){
        System.out.println("hi,UserController...");
        userService.sayHi();
        userConfiguration.sayHi();
    }
}

运行成功:

Setter注入(Setter Injection):

Setter注入是通过为类的Setter方法添加@Autowired注解,实现将依赖注入到相应的Setter方法中。 

添加@Autowired注解重新运行:

 

成功!

同样的,如果需要注入多个属性,就需要添加多个@Autowired注解标记的set方法。

三种注入优缺点分析

你会想,既然都要加@Autowired注解,我们直接使用第一种方法不就好了,为什么还需要后面两种方法?

别急,存在即合理,我们来对比分析一下:

属性注入

优点:

  • 简洁和方便: 直接在字段上使用@Autowired注解,代码简洁,便于阅读。

缺点:

  • IoC 容器依赖: 只能用于 IoC 容器,如果在非 IoC 容器中使用会出现问题。
  • NPE 风险: 在使用的时候才会初始化,可能出现空指针异常(NPE)的风险。
  • 不支持final属性: 不能注入一个 final 修饰的属性。

    为什么?
    因为final修饰的属性有个条件: 满足声明时需要完成初始化,或者满足在构造函数中进行赋值。
    你可以看看它提供的解决办法:
    删去final;添加构造方法;初始化为null,或者直接new一个对象……

构造函数注入 (Spring 4.X 推荐)

优点:

  • 支持final属性: 可以注入 final 修饰的属性。
  • 不可变性: 注入的对象不会被修改。
  • 初始化保证: 依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,构造方法在类加载阶段就会执行。
    在构造函数注入中,Spring 容器在实例化一个 Bean 时会先调用其构造函数,将依赖对象传递给构造函数。这确保了在实例化对象时,它所依赖的其他对象已经被初始化,从而避免了循环依赖等问题。
  • 通用性好: 构造方法是 JDK 支持的,更换任何框架时都适用。

缺点:

  • 繁琐: 注入多个对象时,代码相对繁琐。

Setter注入 (Spring 3.X 推荐)

优点:

  • 方便动态修改: 方便在类实例化后,通过调用Setter方法重新对该对象进行配置或者注入新的依赖。

    在使用Setter注入时,我们可以通过调用对象的Setter方法,随时在运行时更改对象的属性值或依赖关系。这使得在程序运行过程中,我们可以动态地修改对象的状态或行为。

    举例来说,假设有一个类Car,它有一个属性engine,并提供了一个Setter方法:

    public class Car {
        private Engine engine;
    
        public void setEngine(Engine engine) {
            this.engine = engine;
        }
    }
    
    在程序运行时,我们可以随时调用setEngine方法,为Car对象设置不同的引擎:
    Car myCar = new Car();
    myCar.setEngine(new ElectricEngine()); // 设置电动引擎
    // 或者在其他时刻
    myCar.setEngine(new CombustionEngine()); // 设置内燃引擎
    
    构造方法注入通常发生在对象创建时,一旦对象被创建,就不再提供直接的方法来更改构造函数参数,这就使得在运行时动态修改对象的依赖关系变得不那么方便。


    还是刚刚那个Car类,在这种情况下,我们就只能在创建Car对象时传递引擎参数,无法在程序运行时轻松更改引擎。如果在运行时需要更改Car的引擎,就需要创建一个新的Car对象,而无法在已有的对象上动态修改。

    对比而言,Setter注入提供了在对象创建后修改属性或依赖关系的更灵活的机制。

缺点:

  • 不能注入final属性: 不能注入一个 final 修饰的属性。
  • 可变性风险: 注入对象可能会被改变,因为 setter 方法可能会被多次调用,存在被修改的风险。

这些注入方式各自有其适用的场景。属性注入简洁方便,但有一些限制;构造函数注入对于确保初始化和不可变性较为有利;而 Setter 注入适用于需要在对象实例化后动态修改依赖的场景。在实际应用中,可以根据具体的需求和设计原则选择合适的注入方式。

更多DI相关内容参考:Dependencies :: Spring Framework

@Autowired存在问题

当同⼀类型存在多个bean时, 使用@Autowired会存在问题:

我们新建一个UserController2:

在启动的时候就失败了:

这个错误表明在com.example.ioc_demo.IoC.controller.UserController2类中有一个属性 userInfo,而Spring 在初始化时发现有两个可用的userInfo Bean,因此无法决定注入哪一个。

但是我们不太推荐这种方式,因为默认变量名是可以随意修改的,特别是在团队合作的时候,别的成员可能会改动你的变量名,变量名应该做到修改后不影响我们的业务逻辑。所以通常我们不使用变量名称来指定获取某个bean,而是通过其他手段来指定bean的名称。 

解决这个问题的方式有几种:

使用 @Primary 注解:
在我们的 BeanConfig 中的两个 userInfo 方法上,选择其中一个标记为 @Primary,表示这个是首选的 Bean。例如:

@Bean
@Primary
public UserInfo userInfo1() {
    return new UserInfo();
}

@Bean
public UserInfo userInfo2() {
    return new UserInfo();
}

使用 @Qualifier 注解:
在 UserController2 类中,通过 @Qualifier 注解指定要注入的 userInfo Bean 的名称。例如:

@Autowired
@Qualifier("userInfo1")
private UserInfo userInfo;

 或者直接在方法上使用 @Qualifier:

@Bean
@Qualifier("userInfo1")
public UserInfo userInfo1() {
    return new UserInfo();
}

@Bean
@Qualifier("userInfo2")
public UserInfo userInfo2() {
    return new UserInfo();
}

总结一下就是:

当使用 @Autowired 进行依赖注入时,Spring 首先会按照类型来匹配注入的 Bean。如果能够找到唯一的 Bean,就会按照类型成功注入;然而,如果仍然存在多个符合类型的 Bean,Spring 将根据名称进行进一步匹配,以确保注入的唯一性。若最终仍存在歧义,Spring 将抛出异常。

在Spring中,@Autowired 注解的默认行为是按照类型进行注入,但为了解决多个同类型的 Bean 问题,系统会根据类型匹配后再根据名称进行进一步确保唯一性的匹配。这样的处理方式确保了依赖注入的准确性和可靠性。

@Qualifier 是 Spring 框架中用于解决依赖注入中歧义性的注解。在 Spring 中,当有多个类型匹配的 Bean 可以注入时,会出现歧义性,此时就需要使用 @Qualifier 注解来指定具体要注入的 Bean。

功能总结如下:

  1. 消除歧义性: 当有多个相同类型的 Bean 候选者可供注入时,通过 @Qualifier 可以指定具体要注入的 Bean,消除歧义性,确保正确的 Bean 被注入。

  2. 与@Autowired 配合使用: 通常与 @Autowired 注解一同使用,通过 @Autowired 自动装配 Bean,再结合 @Qualifier 指定具体的 Bean。

使用场景:

  • 当有多个同一类型的 Bean 注入点,且希望明确指定注入哪一个 Bean 时,可以使用 @Qualifier 。
public class MyClass {
    @Autowired
    @Qualifier("specificBean")
    private MyInterface myBean;
}

@Qualifier("specificBean") 指定了要注入的 Bean 的名称为 "specificBean",从而解决了可能存在的歧义性。

使用@Resource注解:

按照bean的名称进行注入。通过name属性指定要注⼊的bean的名称。

关于 @Autowired 和 @Resource 的主要区别:

  1. 来源:

    • @Autowired 是 Spring 框架提供的注解。
    • @Resource 是由 Java 的 javax.annotation 包提供的注解,属于 JDK 提供的。
  2. 注入方式:

    • @Autowired 默认按照类型注入。如果存在多个相同类型的 Bean,Spring 会尝试按照名称匹配。
    • @Resource 默认按照名称注入。我们可以通过 name 属性指定要注入的 Bean 的名称,也支持其他参数设置。
  3. 参数设置:

    • @Autowired 参数设置较少,主要可以通过 required 属性控制是否必须存在要注入的 Bean。
    • @Resource 提供了更多的参数设置,例如 name、type、mappedName 等,使其更加灵活。
  4. 适用场景:

    • @Autowired 更适合在 Spring 环境下使用,它的功能更为强大。
    • @Resource 是一个更通用的注解,可以在非 Spring 环境下使用,但它的功能相对较简单。

总体而言,选择使用哪个注解取决于具体的使用场景和需求。在 Spring 环境下,一般会优先选择使用 @Autowired,因为它更符合 Spring 的设计理念,并提供了更多的特性。

最后,我们可以我们的图书管理系统进行修改了,之前完成的比较粗糙,现在可以完善一下:

改一下注释:

运行一下:

总结

Spring, Spring Boot 和Spring MVC的关系以及区别

Spring 是一个开发应用框架,它以轻量级、一站式和模块化为特点,旨在简化企业级应用程序的开发。该框架的核心功能包括对象管理、依赖关系管理、面向切面编程、数据库事务管理、数据访问以及对Web开发的支持等。

主要功能解释:

  1. 对象管理和依赖关系: Spring 提供了强大的 IoC(控制反转)容器,负责管理应用中的对象,并处理对象之间的依赖关系。这使得开发者能够更轻松地组织和管理应用的组件。

  2. 面向切面编程: Spring 支持面向切面编程(AOP),通过AOP可以将横切关注点(例如日志、事务管理)与应用程序的核心逻辑分离,提高代码的模块性和可维护性。

  3. 数据库事务管理: Spring 提供了事务管理的抽象层,可以简化和统一事务的使用。它支持声明式事务配置,使得开发者能够更方便地处理数据库事务。

  4. 数据访问: Spring 提供了对数据访问的支持,可以无缝集成各种数据访问技术,包括 JDBC、ORM(例如 Hibernate、JPA)等。这使得数据访问更加灵活和可扩展。

  5. Web框架支持: Spring 提供了强大的Web框架支持,包括Spring MVC,用于构建灵活且可扩展的Web应用程序。它能够集成不同的视图技术,处理Web请求和响应,使得Web开发更加简便。

开放性和集成性:

尽管 Spring 提供了全面而强大的功能,但它具有高度的可开放性。Spring并不强制开发者完全依赖于它,而是允许开发者自由选择是否使用Spring的全部或部分功能。开发者可以轻松地集成第三方框架,比如数据访问框架(如 Hibernate、JPA)和Web框架(如 Struts、JSF),以满足特定项目或团队的需求。

总体而言,Spring框架的设计理念是为了提高开发效率、降低复杂性,并促使应用程序更易于测试和维护。

Spring MVC 是 Spring 框架的一个子框架,它在 Spring 诞生后迅速得到广泛应用。作为一个基于 MVC(Model-View-Controller)模式设计的框架,它主要用于开发Web应用和网络接口,因此被称为Web框架。

主要特点和关键信息:

  1. Spring MVC 是 Spring 的子框架: Spring MVC 是在 Spring 框架的基础上发展而来的,利用了 Spring 的核心功能,天然地与 Spring 框架集成,使得开发者能够更简洁地进行Web层开发。

  2. 基于MVC模式: Spring MVC 遵循经典的 MVC 模式,将应用程序分为三个主要组件:模型(Model)、视图(View)和控制器(Controller)。这种架构有助于提高代码的模块性和可维护性。

  3. 灵活的URL映射: Spring MVC 提供了灵活的 URL 到页面控制器的映射机制。开发者可以通过配置映射规则,将特定的 URL 请求映射到相应的控制器,从而实现请求的处理和响应。

  4. 约定大于配置: Spring MVC 推崇约定大于配置的设计理念,通过一些默认规则和约定,减少开发者的配置工作。这种契约式编程风格有助于提高开发效率。

  5. 容易与其他视图框架集成: Spring MVC 提供了强大的视图解析机制,支持与各种视图技术的集成,如 Velocity、FreeMarker 等。这使得开发者能够根据项目需求自由选择合适的视图框架。

总体而言,Spring MVC 在Web开发中扮演着关键的角色,它通过提供一系列的特性和约定,简化了Web应用的开发流程,同时与 Spring 框架的无缝集成也为开发者提供了更强大的功能和更好的扩展性。

Spring Boot 是对 Spring 框架的一种封装,旨在简化 Spring 应用的开发。它特别适用于中小型企业,以及那些没有成本研究自己框架的团队。通过使用 Spring Boot,开发者能够更迅速地搭建框架,降低开发成本,使得开发人员能够更专注于 Spring 应用的开发,而无需过多关注繁琐的 XML 配置和底层实现。

主要特点和关键信息:

  1. 简化开发: Spring Boot 是为简化 Spring 应用开发而设计的。它采用了约定大于配置的原则,提供了许多默认配置,从而减少了开发者的配置工作。开发者可以更专注于业务逻辑的实现,而不用过多关心框架的细节。

  2. 快速搭建框架: Spring Boot 是一个脚手架,支持插拔式搭建项目。通过使用 Spring Boot,开发者可以迅速集成其他框架,例如 Spring MVC 用于 Web 开发,MyBatis 用于数据访问等。这使得项目搭建变得非常灵活和快速。

  3. 无需繁琐配置: Spring Boot 减少了对繁琐配置文件的依赖。相比于传统的 Spring 应用,Spring Boot 应用可以更少地依赖 XML 配置文件,大多数配置可以通过注解或者默认规则实现。

  4. 自带嵌入式服务器: Spring Boot 可以直接生成可执行的 JAR 包,其中包含了嵌入式的 Web 服务器(如Tomcat、Jetty),无需额外的 web 服务器的配置和部署步骤。这使得项目的部署变得非常简便。

  5. 插拔式集成: Spring Boot 提供了很多 Starter 依赖,这些 Starter 依赖可以方便地集成其他框架和库。通过引入相应的 Starter 依赖,开发者可以快速地集成所需的功能,而不用手动配置。

总体而言,Spring Boot 是一个使得 Spring 应用开发更加简单、灵活和快速的工具,特别适合快速构建中小型企业的应用。

综合一下,Spring、Spring Boot 和 Spring MVC 是三者之间存在关系但又各自独立的概念:

  1. Spring:

    • 关系: Spring 是一个综合性的Java应用开发框架,提供了全方位的企业级功能,包括IoC容器、AOP、事务管理、数据访问、消息传递等。Spring 是一个大家族,其中包括多个模块,如 Spring Core、Spring MVC、Spring Boot 等。
    • 区别: Spring 框架本身并不限定应用的类型,可以用于构建各种类型的应用,包括传统的 Java EE 应用、RESTful 服务、批处理应用等。它提供了广泛的功能,但在使用上需要更多的配置。
  2. Spring Boot:

    • 关系: Spring Boot 是 Spring 框架的一个子项目,旨在简化 Spring 应用的搭建和开发。它并不是 Spring 的替代品,而是一个辅助工具,提供默认配置和约定,减少开发者的配置工作。
    • 区别: Spring Boot 强调约定大于配置的理念,提供了许多 Starter 依赖和自动配置,使得开发者可以更迅速地搭建框架,并集成其他框架。Spring Boot 可以独立运行,内置了嵌入式的 Web 服务器,使得项目的部署变得非常简便。
  3. Spring MVC:

    • 关系: Spring MVC 是 Spring 框架中的一个模块,用于支持 Web 开发。它是基于 Servlet API 构建的 MVC 框架,用于处理 Web 请求和响应。
    • 区别: Spring MVC 提供了一套强大的 Web 开发框架,支持灵活的 URL 映射、视图解析、拦截器等功能。它与 Spring Boot 结合使用时,可以用于处理 Web 相关的功能。

关系和区别:

  • Spring 包含了 Spring MVC 和其他模块,是一个全方位的框架。
  • Spring Boot 是 Spring 框架的辅助工具,用于简化 Spring 应用的搭建和开发。
  • Spring MVC 是 Spring 框架中专注于 Web 开发的模块,与 Spring Boot 结合时可以用于处理 Web 相关的功能。

总结来说,Spring MVC 和 Spring Boot 都是 Spring 框架的组成部分。Spring MVC 是基于 Spring 的 MVC 框架,专注于提供 Web 相关的功能;而 Spring Boot 则是基于 Spring 的一套快速开发整合包,旨在简化应用的搭建和开发。

在一个应用中,我们可以同时使用 Spring MVC 处理 Web 相关功能,利用 Spring Boot 快速搭建整体框架,并依赖 Spring 的 IoC 和 DI 功能。这三者虽然专注的领域和解决的问题有所不同,但它们的基础都是 Spring,构成了一个庞大而强大的 Spring 大家族。

⽤⼀张图来表示他们三个的关系:

Bean的命名

  1. 五大注解存储的Bean:

    • Bean名称规则:
      • 前两位字母均为大写,Bean名称为类名。
      • 其他字母为类名首字母小写。
         
        @Service
        public class UserService {
            // Class definition
        }
        
        根据规则,该Bean的名称为 "userService"。
    • 通过value属性设置Bean名称,例如:@Controller(value = "user")。
       
      @Repository("dataRepository")
      public class DataRepository {
          // Class definition
      }
      
      根据规则,该Bean的名称为 "dataRepository"。
  2. @Bean 注解存储的Bean:

    • Bean名称规则:Bean名称为方法名。
       
      @Configuration
      public class AppConfig {
      
          @Bean
          public UserService userService() {
              return new UserService();
          }
      }
      
      根据规则,该Bean的名称为 "userService",与方法名一致。
    • 通过name属性设置Bean名称,例如:@Bean(name = {"u1","user1"})。
       
      @Configuration
      public class AnotherConfig {
      
          @Bean(name = {"u1", "user1"})
          public UserData userData() {
              return new UserData();
          }
      }
      
      根据规则,该Bean的名称为 "u1" 或 "user1",通过name属性设置。

这些规则适用于不同的注解和配置方式,确保在Spring容器中定义的Bean能够具有清晰而有意义的命名,以方便在应用程序中引用和使用。

常见面试题

一、三种注入方式的优缺点

二、常见注解有哪些? 分别是什么作用? 

  1. Web URL映射:

    • @RequestMapping: 用于映射请求的URL到相应的Controller方法上。
  2. 参数接收和接口响应:

    • @RequestParam: 用于从请求中提取参数,常用于处理URL查询参数或表单提交的数据。
    • @RequestBody: 用于将HTTP请求的body部分转换为Java对象。
    • @ResponseBody: 用于将Controller方法的返回值转换为HTTP响应的body部分。
  3. Bean的存储:

    • @Controller: 用于标识类是Spring MVC的Controller。
    • @Service: 用于标识类是业务逻辑层的Service。
    • @Repository: 用于标识类是数据访问层的Repository。
    • @Component: 通用的Spring组件注解,可以用于标识任何Spring管理的组件。
    • @Configuration: 用于标识类是配置类,定义了一个或多个@Bean方法。
    • @Bean: 用于标识方法返回的对象将被Spring容器管理。
  4. Bean的获取:

    • @Autowired: 用于自动装配Spring Bean,可以用在字段、构造方法、Setter方法上。
    • @Qualifier: 与@Autowired一起使用,指定要注入的Bean的名称。
    • @Resource: JSR-250规范定义的注解,可以按名称注入Bean,也支持@Autowired的功能。

三、@Autowired 和@Resource 区别

四、你对Spring, SpringMVC, Springboot的理解

;