技术体系结构

总体技术体系

单一架构

一个项目,一个工程,导出为一个 war 包,在一个 Tomcat 上运行。

单一架构,项目主要应用技术框架为:Spring、SpringMVC、Mybatis

分布式架构

一个项目(对应 IDEA 中的一个 project),拆分成很多个模块,每个模块是一个 IDEA 中的一个 module。每一个工程都是运行在自己的 Tomcat 上。模块之间可以互相调用。每一个模块内部可以看成是一个单一架构的应用。

分布式架构,项目主要应用技术框架:SpringBoot(或 SSM)、SpringCloud、中间件等

框架概念和理解

框架(Framework)是一个集成了基本结构、规范、设计模式、编程语言和程序库等基础组件的软件系统,它可以用来构建更高级别的应用程序。框架的设计和实现旨在解决特定领域中的常见问题,帮助开发人员更高效、更稳定地实现软件开发目标。

框架的优点包括以下几点:

  1. 提高开发效率:框架提供了许多预先设计好了的组件和工具,能够帮助开发人员快速进行开发。相较于传统手写代码,在框架提供的规范化环境中,开发者可以更快地实现项目的各种要求。
  2. 降低开发成本:框架的提供标准化的编程语言、数据操作等代码片段,避免了重复开发的问题,降低了开发成本,提供深度优化的系统,降低了维护成本,增强了系统的可靠性。
  3. 提高应用程序的稳定性:框架通常经过了很长时间的开发和测试,其中的许多组件、代码片段和设计模式都得到了验证。重复利用这些组件有助于减少 BUG 的出现,从而提高了应用程序的稳定性。
  4. 提供标准化的解决方案:框架通常是针对某个特定领域的,通过提供标准化的解决方案,可以为开发人员提供一种共同的语言和思想基础,有助于更好地沟通和协作。

框架的缺点包括以下几个方面:

  1. 学习成本高:框架通常具有特定的语言和编程范式。对于开发人员而言,需要花费时间学习其背后的架构、模式和逻辑,这对于新手而言可能会耗费较长时间。
  2. 可能存在局限性:虽然框架提高了开发效率并可以帮助开发人员解决常见问题,但是在某些情况下,特定的应用需求可能超出框架的范围,从而导致应用程序无法满足要求。开发人员可能需要更多的控制权和自由度,同时需要在框架和应用程序之间进行权衡取舍。
  3. 版本变更和兼容性问题:框架的版本发布和迭代通常会导致代码库的大规模变更,进而导致应用程序出现兼容性问题和漏洞。当框架变更时,需要考虑框架是否向下兼容,以及如何进行适当的测试、迁移和升级。
  4. 架构风险:框架涉及到很多抽象和概念,如果开发者没有足够的理解和掌握其架构,可能会导致系统出现设计和架构缺陷,从而影响系统的健康性和安全性。

站在文件结构的角度理解框架,可以将框架总结:框架 = jar 包 + 配置文件

莎士比亚说:“一千个观众眼中有一千个哈姆雷特”,即仁者见仁、智者见智。说每个人都会对作品有不同的理解,每个人对待任何事物都有自己的看法,同样的技术解决同样的问题会产生不同流程和风格的解决方案,而采用一种框架其实就是限制用户必须使用其规定的方案来实现,可以降低程序员之间沟通以及日后维护的成本。

常用的单一架构 JavaEE 项目框架演进,从 SSH、SSH2 过渡到了 SSM:SpringMVC、Spring、MyBatis。

总之,框架已经对基础的代码进行了封装并提供相应的 API,开发者在使用框架是直接调用封装好的 API 可以省去很多代码编写,从而提高工作效率和开发速度。

SpringFramework 介绍

Spring 和 SpringFramework 概念

https://spring.io/projects

广义的 Spring:Spring 技术栈(全家桶)

广义上的 Spring 泛指以 Spring Framework 为基础的 Spring 技术栈。

经过十多年的发展,Spring 已经不再是一个单纯的应用框架,而是逐渐发展成为一个由多个不同子项目(模块)组成的成熟技术,例如 Spring Framework、Spring MVC、SpringBoot、Spring Cloud、Spring Data、Spring Security 等,其中 Spring Framework 是其他子项目的基础。

这些子项目涵盖了从企业级应用开发到云计算等各方面的内容,能够帮助开发人员解决软件发展过程中不断产生的各种实际问题,给开发人员带来了更好的开发体验。

狭义的 Spring:Spring Framework(基础框架)

狭义的 Spring 特指 Spring Framework,通常我们将它称为 Spring 框架。

Spring Framework(Spring 框架)是一个开源的应用程序框架,由 SpringSource 公司开发,最初是为了解决企业级开发中各种常见问题而创建的。它提供了很多功能,例如:依赖注入(Dependency Injection)、面向切面编程(AOP)、声明式事务管理(TX)等。其主要目标是使企业级应用程序的开发变得更加简单和快速,并且 Spring 框架被广泛应用于 Java 企业开发领域。

Spring 全家桶的其他框架都是以 SpringFramework 框架为基础

SpringFramework 主要功能模块

SpringFramework 框架结构图:

功能模块功能介绍
Core Container核心容器,在 Spring 环境下使用任何功能都必须基于 IOC 容器
AOP&Aspects面向切面编程
TX声明式事务管理
Spring MVC提供了面向 Web 应用程序的集成功能

SpringFramework 主要优势

  1. 丰富的生态系统:Spring 生态系统非常丰富,支持许多模块和库,如 Spring Boot、Spring Security、Spring Cloud 等等,可以帮助开发人员快速构建高可靠性的企业应用程序。
  2. 模块化的设计:框架组件之间的松散耦合和模块化设计使得 Spring Framework 具有良好的可重用性、可扩展性和可维护性。开发人员可以轻松地选择自己需要的模块,根据自己的需求进行开发。
  3. 简化 Java 开发:Spring Framework 简化了 Java 开发,提供了各种工具和 API,可以降低开发复杂度和学习成本。同时,Spring Framework 支持各种应用场景,包括 Web 应用程序、RESTful API、消息传递、批处理等等。
  4. 不断创新和发展:Spring Framework 开发团队一直在不断创新和发展,保持与最新技术的接轨,为开发人员提供更加先进和优秀的工具和框架。

因此,这些优点使得 Spring Framework 成为了一个稳定、可靠、且创新的框架,为企业级 Java 开发提供了一站式的解决方案。

Spring 使创建 Java 企业应用程序变得容易。它提供了在企业环境中采用 Java 语言所需的一切,支持 Groovy 和 Kotlin 作为 JVM 上的替代语言,并且可以根据应用程序的需求灵活地创建多种架构。从 Spring Framework 6.0.6 开始,Spring 需要 Java 17+。

Spring IoC 容器和核心概念

组件和组件管理概念

什么是组件?

回顾常规的三层架构处理请求流程:

整个项目就是由各种组件搭建而成的:

我们的期待

  • 有人替我们创建组件的对象
  • 有人帮我们保存组件的对象
  • 有人帮助我们自动组装
  • 有人替我们管理事务
  • 有人协助我们整合其他框架

Spring 充当组件管理角色(IoC)

那么谁帮我们完成我们的期待,帮我们管理组件呢?

当然是 Spring 框架了!

组件可以完全交给 Spring 框架进行管理,Spring 框架替代了程序员原有的 new 对象和对象属性赋值动作等

Spring 具体的组件管理动作包含:

  • 组件对象实例化
  • 组件属性属性赋值
  • 组件对象之间引用
  • 组件对象存活周期管理

我们只需要编写元数据(配置文件)告知 Spring 管理哪些类组件和他们的关系即可

注意:组件是映射到应用程序中所有可重用组件的 Java 对象,应该是可复用的功能对象

  • 组件一定是对象
  • 对象不一定是组件

综上所述,Spring 充当一个组件容器,创建、管理、存储组件,减少了我们的编码压力,让我们更加专注进行业务编写!

组件交给 Spring 管理优势

  1. 降低了组件之间的耦合性:Spring IoC 容器通过依赖注入机制,将组件之间的依赖关系削弱,减少了程序组件之间的耦合性,使得组件更加松散地耦合。
  2. 提高了代码的可重用性和可维护性:将组件的实例化过程、依赖关系的管理等功能交给 Spring IoC 容器处理,使得组件代码更加模块化、可重用、更易于维护。
  3. 方便了配置和管理:Spring IoC 容器通过 XML 文件或者注解,轻松的对组件进行配置和管理,使得组件的切换、替换等操作更加的方便和快捷。
  4. 交给 Spring 管理的对象(组件),方可享受 Spring 框架的其他功能(AOP、声明事务管理)等

Spring IoC 容器和容器实现

普通和复杂容器

普通容器只能用来存储,没有更多功能

  • 数组
  • 集合:ListSet

复杂容器

Servlet 容器能够管理 Servlet(initservicedestroy)、Filter、Listener 这样的组件的一生,所以它是一个复杂容器。

名称时机次数
创建对象默认情况:接收到第一次请求 修改启动顺序后:Web 应用启动过程中一次
初始化操作创建对象之后一次
处理请求接收到请求多次
销毁操作Web 应用卸载之前一次

我们即将要学习的 Spring IoC 容器也是一个复杂容器。它们不仅要负责创建组件的对象、存储组件的对象,还要负责调用组件的方法让它们工作,最终在特定情况下销毁组件。

总结:Spring 管理组件的容器,就是一个复杂容器,不仅存储组件,也可以管理组件之间依赖关系,并且创建和销毁组件等

Spring IoC 容器介绍

Spring IoC 容器,负责实例化、配置和组装 bean(组件)。容器通过读取配置元数据来获取有关要实例化、配置和组装组件的指令。配置元数据以 XML、Java 注解或 Java 代码形式表现。它允许表达组成应用程序的组件以及这些组件之间丰富的相互依赖关系。

上图显示了 Spring 容器工作原理的高级视图。应用程序类与配置元数据相结合,您拥有完全配置且可执行的系统或应用程序。

Spring IoC 容器具体接口和实现类

Spring IoC 容器接口:

  • BeanFactory 接口提供了一种高级配置机制,能够管理任何类型的对象,它是 Spring IoC 容器标准化超接口
  • ApplicationContextBeanFactory 的子接口。它扩展了以下功能:
    • 更容易与 Spring 的 AOP 功能集成
    • 消息资源处理(用于国际化)
    • 特定于应用程序给予此接口实现,例如 Web 应用程序的 WebApplicationContext

简而言之,BeanFactory 提供了配置框架和基本功能,而 ApplicationContext 添加了更多特定于企业的功能。ApplicationContextBeanFactory 的完整超集

ApplicationContext 容器实现类:

类型名简介
ClassPathXmlApplicationContext通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
FileSystemXmlApplicationContext通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象
AnnotationConfigApplicationContext通过读取 Java 配置类创建 IOC 容器对象
WebApplicationContext专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中

Spring IoC 容器管理配置方式

Spring IoC 容器使用多种形式的配置元数据。此配置元数据表示您作为应用程序开发人员如何告诉 Spring 容器实例化、配置和组装应用程序中的对象。

Spring 框架提供了多种配置方式:XML 配置方式、注解方式和 Java 配置类方式

  1. XML 配置方式:是 Spring 框架最早的配置方式之一,通过在 XML 文件中定义 Bean 及其依赖关系、Bean 的作用域等信息,让 Spring IoC 容器来管理 Bean 之间的依赖关系。该方式从 Spring 框架的第一版开始提供支持。
  2. 注解方式:从 Spring 2.5 版本开始提供支持,可以通过在 Bean 类上使用注解来代替 XML 配置文件中的配置信息。通过在 Bean 类上加上相应的注解(如 @Component@Service@Autowired 等),将 Bean 注册到 Spring IoC 容器中,这样 Spring IoC 容器就可以管理这些 Bean 之间的依赖关系。
  3. Java 配置类方式:从 Spring 3.0 版本开始提供支持,通过 Java 类来定义 Bean、Bean 之间的依赖关系和配置信息,从而代替 XML 配置文件的方式。Java 配置类是一种使用 Java 编写配置信息的方式,通过 @Configuration@Bean 等注解来实现 Bean 和依赖关系的配置。

为了迎合当下开发环境,我们将以配置类+注解方式为主进行讲解!

Spring IoC / DI 概念总结

IoC 容器:

Spring IoC 容器,负责实例化、配置和组装 bean(组件)核心容器。容器通过读取配置元数据来获取有关要实例化、配置和组装组件的指令。

IoC(Inversion of Control)控制反转:

IoC 主要是针对对象的创建和调用控制而言的,也就是说,当应用程序需要使用一个对象时,不再是应用程序直接创建该对象,而是由 IoC 容器来创建和管理,即控制权由应用程序转移到 IoC 容器中,也就是“反转”了控制权。这种方式基本上是通过依赖查找的方式来实现的,即 IoC 容器维护着构成应用程序的对象,并负责创建这些对象。

DI (Dependency Injection) 依赖注入:

DI 是指在组件之间传递依赖关系的过程中,将依赖关系在容器内部进行处理,这样就不必在应用程序代码中硬编码对象之间的依赖关系,实现了对象之间的解耦合。在 Spring 中,DI 是通过 XML 配置文件或注解的方式实现的。它提供了三种形式的依赖注入:构造函数注入、Setter 方法注入和接口注入。

Spring IoC 实践和应用

Spring IoC / DI 实现步骤

我们总结下,组件交给 Spring IoC 容器管理,并且获取和使用的基本步骤!

配置元数据

配置元数据,即编写交给 Spring IoC 容器管理组件的信息,配置方式有三种。

基于 XML 的配置元数据的基本结构:

<?xml version="1.0" encoding="UTF-8"?>
<!-- 此处要添加一些约束,配置文件的标签并不是随意命名 -->
<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
    https://www.springframework.org/schema/beans/spring-beans.xsd">
 
  <bean id="..." class="...">
    <!-- collaborators and configuration for this bean go here -->
  </bean>
  <!-- more bean definitions go here -->
</beans>

Spring IoC 容器管理一个或多个组件。这些组件是使用你提供给容器的配置元数据(例如,以 XML <bean/> 定义的形式)创建的。

<bean /> 标签 组件信息声明

  • id 属性是标识单个 Bean 定义的字符串。
  • class 属性定义 Bean 的类型并使用完全限定的类名。

实例化 IoC 容器

提供给 ApplicationContext 构造函数的位置路径是资源字符串地址,允许容器从各种外部资源(如本地文件系统、Java CLASSPATH 等)加载配置元数据。

应该选择一个合适的容器实现类,进行 IoC 容器的实例化工作:

// 实例化 IoC 容器,读取外部配置文件,最终会在容器内进行 IoC 和 DI 动作
ApplicationContext context = 
           new ClassPathXmlApplicationContext("services.xml", "daos.xml");

获取 Bean(组件)

ApplicationContext 是一个高级工厂的接口,能够维护不同 bean 及其依赖项的注册表。通过使用方法 T getBean(String name, Class<T> requiredType) ,您可以检索 bean 的实例。

允许读取 Bean 定义并访问它们,如以下示例所示:

// 创建 IoC 容器对象,指定配置文件,IoC 也开始实例组件对象
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// 获取 IoC 容器的组件对象
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// 使用组件对象
List<String> userList = service.getUsernameList();

基于 XML 配置方式组件管理

实验一:组件(Bean)信息声明配置(IoC)

目标:Spring IoC 容器管理一个或多个 bean。这些 Bean 是使用您提供给容器的配置元数据创建的(例如,以 XML <bean/> 定义的形式)。

思路:

准备项目:

  1. 创建 Maven 工程
  2. 导入 Spring IoC 相关依赖

pom.xml

<dependencies>
    <!-- spring context 依赖 -->
    <!-- 当你引入 Spring Context 依赖之后,表示将 Spring 的基础依赖引入了 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.0.6</version>
    </dependency>
    <!-- junit5 测试 -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.3.1</version>
    </dependency>
</dependencies>

基于无参数构造函数

当通过构造函数方法创建一个 bean(组件对象)时,所有普通类都可以由 Spring 使用并与之兼容。也就是说,正在开发的类不需要实现任何特定的接口或以特定的方式进行编码。只需指定 Bean 类信息就足够了。但是,默认情况下,我们需要一个默认(空)构造函数。

  1. 准备组件类
package com.atguigu.ioc;
 
public class HappyComponent {
 
    // 默认包含无参数构造函数
    public void doWork() {
        System.out.println("HappyComponent.doWork");
    }
}
  1. XML 配置文件编写

创建携带 Spring 约束的 XML 配置文件:

编写配置文件:resources/spring-bean-01.xml

<bean id="happyComponent" class="com.atguigu.ioc.HappyComponent"/>
  • bean 标签:通过配置 bean 标签告诉 IOC 容器需要创建对象的组件信息
  • id 属性:bean 的唯一标识,方便代码中获取 bean
  • class 属性:组件类的全限定符
  • 注意:要求当前组件类必须包含无参数构造函数

基于静态工厂方法实例化

除了使用构造函数实例化对象,还有一类是通过工厂模式实例化对象。接下来我们讲解如何定义使用静态工厂方法创建 Bean 的配置

  1. 准备组件类
public class ClientService {
  private static ClientService clientService = new ClientService();
  private ClientService() {}
 
  public static ClientService createInstance() {
    return clientService;
  }
}
  1. xml 配置文件编写:resources/spring-bean-01.xml
<bean
  id="clientService"
  class="examples.ClientService"
  factory-method="createInstance"
/>
  • class 属性:指定工厂类的全限定符
  • factory-method:指定静态工厂方法,注意:该方法必须是 static 方法。

基于实例工厂方法实例化

接下来我们讲解下如何定义使用实例工厂方法创建 Bean 的配置

  1. 准备组建类
public class DefaultServiceLocator {
  private static ClientServiceImplclientService = new ClientServiceImpl();
 
  public ClientService createClientServiceInstance() {
    return clientService;
  }
}
  1. xml 配置文件编写:resources/spring-bean-01.xml
<!-- 将工厂类进行 IoC 配置 -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator"></bean>
 
<!-- 根据工厂对象的实例工厂方法进行实例化组件对象 -->
<bean
  id="clientService"
  factory-bean="serviceLocator"
  factory-method="createClientServiceInstance"
/>
  • factory-bean 属性:指定当前容器中工厂 Bean 的名称。
  • factory-method: 指定实例工厂方法名。注意:实例方法必须是非 static

图解 IoC 配置流程

实验二:组件(Bean)依赖注入配置(DI)

目标:通过配置文件,实现 IoC 容器中 Bean 之间的引用(依赖注入 DI 配置)。

主要涉及注入场景:基于构造函数的依赖注入和基于 Setter 的依赖注入。

思路:

基于构造函数的依赖注入(单个构造参数)

介绍:基于构造函数的 DI 是通过容器调用具有多个参数的构造函数来完成的,每个参数表示一个依赖项。

  1. 准备组件类
public class UserDao {
}
 
 
public class UserService {
    private UserDao userDao;
 
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }
}
  1. 编写配置文件:resources/spring-02.xml
<beans>
  <!-- 引用类 bean 声明 -->
  <bean id="userService" class="x.y.UserService">
    <!-- 构造函数引用 -->
    <constructor-arg ref="userDao" />
  </bean>
 
  <!-- 被引用类 bean 声明 -->
  <bean id="userDao" class="x.y.UserDao" />
</beans>
  • constructor-arg 标签:可以引用构造参数 ref 引用其他 bean 的标识。

基于构造函数的依赖注入(多构造参数解析)

介绍:基于构造函数的 DI 是通过容器调用具有多个参数的构造函数来完成的,每个参数表示一个依赖项。

  1. 准备组件类
public class UserDao {
}
 
 
public class UserService {
    private UserDao userDao;
    private int age;
    private String name;
 
    public UserService(int age, String name, UserDao userDao) {
        this.userDao = userDao;
        this.age = age;
        this.name = name;
    }
}
  1. 编写配置文件
<!-- 场景 1: 多参数,可以按照相应构造函数的顺序注入数据 -->
<beans>
  <bean id="userService" class="x.y.UserService">
    <!-- value 直接注入基本类型值 -->
    <constructor-arg value="18" />
    <constructor-arg value="赵伟风" />
    <constructor-arg ref="userDao" />
  </bean>
  <!-- 被引用类 bean 声明 -->
  <bean id="userDao" class="x.y.UserDao" />
</beans>
 
 
<!-- 场景 2: 多参数,可以按照相应构造函数的名称注入数据 -->
<beans>
  <bean id="userService" class="x.y.UserService">
    <!-- value 直接注入基本类型值 -->
    <constructor-arg name="name" value="赵伟风" />
    <constructor-arg name="userDao" ref="userDao" />
    <constructor-arg name="age" value="18" />
  </bean>
  <!-- 被引用类 bean 声明 -->
  <bean id="userDao" class="x.y.UserDao" />
</beans>
 
<!-- 场景 3: 多参数,可以按照相应构造函数的角标注入数据 
           index 从 0 开始:构造函数(0, 1, 2, ...)
-->
<beans>
    <bean id="userService" class="x.y.UserService">
    <!-- value 直接注入基本类型值 -->
    <constructor-arg index="1" value="赵伟风" />
    <constructor-arg index="2" ref="userDao" />
    <constructor-arg index="0" value="18" />
  </bean>
  <!-- 被引用类 bean 声明 -->
  <bean id="userDao" class="x.y.UserDao"/>
</beans>
  • constructor-arg 标签:指定构造参数和对应的值
  • constructor-arg 标签:name 属性指定参数名、index 属性指定参数角标、value 属性指定普通属性值

基于 Setter 方法依赖注入

介绍:开发中,除了构造函数注入(DI)更多的使用的 Setter 方法进行注入

  1. 准备组件类
public Class MovieFinder {
 
}
 
public class SimpleMovieLister {
  private MovieFinder movieFinder;
  private String movieName;
 
  public void setMovieFinder(MovieFinder movieFinder) {
    this.movieFinder = movieFinder;
  }
  
  public void setMovieName(String movieName) {
    this.movieName = movieName;
  }
}
  1. 编写配置文件
<bean id="simpleMovieLister" class="examples.SimpleMovieLister">
  <!-- setter 方法,注入 movieFinder 对象的标识 id
       name = 属性名,ref = 引用 bean 的 id 值
   -->
  <property name="movieFinder" ref="movieFinder" />
 
  <!-- setter 方法,注入基本数据类型 movieName
       name = 属性名,value = 基本类型值
   -->
  <property name="movieName" value="消失的她" />
</bean>
 
<bean id="movieFinder" class="examples.MovieFinder" />
  • property 标签: 可以给 setter 方法对应的属性赋值
    • name 属性代表 set 方法标识,如:setXxx -> xxx,和属性名无关
    • ref 代表引用 bean 的标识 id
    • value 属性代表基本属性值

总结:

依赖注入(DI)包含引用类型和基本数据类型,注入的方式也有多种。主流的注入方式为 setter 方法注入和构造函数注入,两种注入语法都需要掌握

引用其他 bean,使用 ref 属性。直接注入基本类型值,使用 value 属性。

实验三:IoC 容器创建和使用

介绍:上面的实验只是讲解了如何在 XML 格式的配置文件编写 IoC 和 DI 配置

想要配置文件中声明组件类信息真正的进行实例化成 Bean 对象和形成 Bean 之间的引用关系,我们需要声明 IoC 容器对象,读取配置文件,实例化组件和关系维护的过程都是在 IoC 容器中实现的

  1. 容器实例化
// 方式 1:实例化并且指定配置文件
// 参数:String... locations,传入一个或者多个配置文件
ApplicationContext context = 
           new ClassPathXmlApplicationContext("services.xml", "daos.xml");
 
// 方式 2:先实例化,再指定配置文件,最后刷新容器触发 Bean实例化动作 [spring mvc 源码和 contextLoadListener 源码方式]
ApplicationContext context = 
           new ClassPathXmlApplicationContext();   
// 设置配置配置文件,方法参数为可变参数,可以设置一个或者多个配置
iocContainer1.setConfigLocations("services.xml", "daos.xml");
// 后配置的文件,需要调用 refresh 方法,触发刷新配置
iocContainer1.refresh();
  1. Bean 对象读取
// 方式 1:根据 id 获取
// 没有指定类型,返回为 Object,需要类型转化
HappyComponent happyComponent = 
        (HappyComponent) iocContainer.getBean("bean 的 id 标识");
        
// 使用组件对象        
happyComponent.doWork();
 
// 方式 2:根据类型获取
// 根据类型获取,但是要求,同类型(当前类、子类、接口的实现类)只能有一个对象交给 IoC 容器管理
// 配置两个或者以上出现:org.springframework.beans.factory.NoUniqueBeanDefinitionException
HappyComponent happyComponent = iocContainer.getBean(HappyComponent.class);
happyComponent.doWork();
 
// 方式 3:根据 id 和类型获取
HappyComponent happyComponent = iocContainer.getBean("bean 的 id 标识", HappyComponent.class);
happyComponent.doWork();
 
// 根据类型来获取 bean 时,在满足 bean 唯一性的前提下,其实只是看:『对象 instanceof 指定的类型』的返回结果,只要返回的是 true 就可以认定为和类型匹配,能够获取到。

实验四:高级特性:组件(Bean)作用域和周期方法配置

组件周期方法配置

周期方法概念:

可以在组件类中定义方法,然后当 IoC 容器实例化和销毁组件对象的时候进行调用。这两个方法我们成为生命周期方法。

类似于 Servlet 的 init / destroy 方法,可以在周期方法完成初始化和释放资源等工作。

  1. 周期方法声明
public class BeanOne {
  // 周期方法要求:方法命名随意,但是要求方法必须是 public void 无形参列表
  public void init() {
    // 初始化逻辑
  }
}
 
public class BeanTwo {
  public void cleanup() {
    // 释放资源逻辑
  }
}
  1. 周期方法配置
<beans>
  <bean id="beanOne" class="examples.BeanOne" init-method="init" />
  <bean id="beanTwo" class="examples.BeanTwo" destroy-method="cleanup" />
</beans>

组件作用域配置

Bean 作用域概念:

<bean 标签声明 Bean,只是将 Bean 的信息配置给 Spring IoC 容器

在 IoC 容器中,这些 <bean 标签对应的信息转成 Spring 内部 BeanDefinition 对象,BeanDefinition 对象内,包含定义的信息(idclass、属性等等)

这意味着,BeanDefinition 与类概念一样,Spring IoC 容器可以可以根据 BeanDefinition 对象反射创建多个 Bean 对象实例。

具体创建多少个 Bean 的实例对象,由 Bean 的作用域 scope 属性指定

作用域可选值:

取值含义创建对象的时机默认值
singleton在 IOC 容器中,这个 bean 的对象始终为单实例IOC 容器初始化时
prototype这个 bean 在 IOC 容器中有多个实例获取 bean 时

如果是在 WebApplicationContext 环境下还会有另外两个作用域(但不常用):

取值含义创建对象的时机默认值
request请求范围内有效的实例每次请求
session会话范围内有效的实例每次会话

作用域配置:

<bean id="happyMachine8" scope="prototype" class="com.atguigu.ioc.HappyMachine">
    <property name="machineName" value="happyMachine"/>
</bean>
 
<bean id="happyComponent8" scope="singleton" class="com.atguigu.ioc.HappyComponent">
    <property name="componentName" value="happyComponent"/>
</bean>

作用域测试:

@Test
public void testExperiment08()  {
    ApplicationContext iocContainer = new ClassPathXmlApplicationContext("配置文件名");
 
    HappyMachine bean = iocContainer.getBean(HappyMachine.class);
    HappyMachine bean1 = iocContainer.getBean(HappyMachine.class);
    // 多例对比:false
    System.out.println(bean == bean1);
 
    HappyComponent bean2 = iocContainer.getBean(HappyComponent.class);
    HappyComponent bean3 = iocContainer.getBean(HappyComponent.class);
    // 单例对比:true
    System.out.println(bean2 == bean3);
}

实验五:高级特性:FactoryBean 特性和使用

FactoryBean 简介

FactoryBean 接口是 Spring IoC 容器实例化逻辑的可插拔性点。

用于配置复杂的 Bean 对象,可以将创建过程存储在 FactoryBeangetObject 方法

FactoryBean<T> 接口提供三种方法:

  1. T getObject():返回此工厂创建的对象的实例。该返回值会被存储到 IoC 容器!
  2. boolean isSingleton():如果此 FactoryBean 返回单例,则返回 true,否则返回 false。此方法的默认实现返回 true(注意,lombok 插件使用,可能影响效果)
  3. Class<?> getObjectType():返回 getObject() 方法返回的对象类型,如果事先不知道类型,则返回 null

FactoryBean 使用场景

  1. 代理类的创建
  2. 第三方框架整合
  3. 复杂对象实例化等

FactoryBean 应用

  1. 准备 FactoryBean 实现类
// 实现 FactoryBean 接口时需要指定泛型
// 泛型类型就是当前工厂要生产的对象的类型
public class HappyFactoryBean implements FactoryBean<HappyMachine> {
    private String machineName;
    
    public String getMachineName() {
        return machineName;
    }
    
    public void setMachineName(String machineName) {
        this.machineName = machineName;
    }
    
    @Override
    public HappyMachine getObject() throws Exception {
        // 方法内部模拟创建、设置一个对象的复杂过程
        HappyMachine happyMachine = new HappyMachine();
        happyMachine.setMachineName(this.machineName);
        return happyMachine;
    }
    
    @Override
    public Class<?> getObjectType() {
        // 返回要生产的对象的类型
        return HappyMachine.class;
    }
}
  1. 配置 FactoryBean 实现类
<!-- FactoryBean 机制 -->
<!-- 这个 bean 标签中 class 属性指定的是 HappyFactoryBean,但是将来从这里获取的 bean 是 HappyMachine 对象 -->
<bean id="happyMachine7" class="com.atguigu.ioc.HappyFactoryBean">
    <!-- property 标签仍然可以用来通过 setXxx() 方法给属性赋值 -->
    <property name="machineName" value="iceCreamMachine" />
</bean>
  1. 测试读取 FactoryBeanFactoryBean.getObject 对象
@Test
public void testExperiment07()  {
    ApplicationContext iocContainer = new ClassPathXmlApplicationContext("spring-bean-07.xml");
 
    // 注意:直接根据声明 FactoryBean 的 id,获取的是 getObject 方法返回的对象
    HappyMachine happyMachine = iocContainer.getBean("happyMachine7", HappyMachine.class);
    System.out.println("happyMachine = " + happyMachine);
 
    // 如果想要获取 FactoryBean 对象, 直接在 id 前添加 & 符号即可,这是一种固定的约束
    Object bean = iocContainer.getBean("&happyMachine7");
    System.out.println("bean = " + bean);
}

FactoryBeanBeanFactory 区别:

FactoryBean 是 Spring 中一种特殊的 bean,可以在 getObject() 工厂方法自定义的逻辑创建 Bean,是一种能够生产其他 Bean 的 Bean。FactoryBean 在容器启动时被创建,而在实际使用时则是通过调用 getObject() 方法来得到其所生产的 Bean。因此,FactoryBean 可以自定义任何所需的初始化逻辑,生产出一些定制化的 bean。

==一般情况下,整合第三方框架,都是通过定义 FactoryBean 实现!==

BeanFactory 是 Spring 框架的基础,其作为一个顶级接口定义了容器的基本行为,例如管理 bean 的生命周期、配置文件的加载和解析、bean 的装配和依赖注入等。BeanFactory 接口提供了访问 bean 的方式,例如 getBean() 方法获取指定的 bean 实例。它可以从不同的来源(例如 MySQL 数据库、XML 文件、Java 配置类等)获取 bean 定义,并将其转换为 bean 实例。同时,BeanFactory 还包含很多子类(例如:ApplicationContext 接口)提供了额外的强大功能。

总的来说,FactoryBeanBeanFactory 的区别主要在于前者是用于创建 bean 的接口,它提供了更加灵活的初始化定制功能,而后者是用于管理 bean 的框架基础接口,提供了基本的容器功能和 bean 生命周期管理。

实验六:基于 XML 方式整合三层架构组件

需求分析:搭建一个三层架构案例,模拟查询全部学生(学生表)信息,持久层使用 JdbcTemplate 和 Druid 技术,使用 XML 方式进行组件管理

  1. 数据库准备
create database studb;
 
use studb;
 
CREATE TABLE students (
  id INT PRIMARY KEY,
  name VARCHAR(50) NOT NULL,
  gender VARCHAR(10) NOT NULL,
  age INT,
  class VARCHAR(50)
);
 
INSERT INTO students (id, name, gender, age, class)
VALUES
  (1, '张三', '男', 20, '高中一班'),
  (2, '李四', '男', 19, '高中二班'),
  (3, '王五', '女', 18, '高中一班'),
  (4, '赵六', '女', 20, '高中三班'),
  (5, '刘七', '男', 19, '高中二班'),
  (6, '陈八', '女', 18, '高中一班'),
  (7, '杨九', '男', 20, '高中三班'),
  (8, '吴十', '男', 19, '高中二班');
 
  1. 项目准备

项目创建:spring-xml-practice-02

依赖导入:

<dependencies>
      <!-- spring context 依赖 -->
      <!-- 当你引入 SpringContext 依赖之后,表示将 Spring 的基础依赖引入了 -->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>6.0.6</version>
      </dependency>
 
      <!-- 数据库驱动和连接池 -->
      <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>8.0.25</version>
      </dependency>
 
      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid</artifactId>
          <version>1.2.8</version>
      </dependency>
 
      <!-- spring-jdbc -->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-jdbc</artifactId>
          <version>6.0.6</version>
      </dependency>
</dependencies> 

实体类准备:

public class Student {
     private Integer id;
    private String name;
    private String gender;
    private Integer age;
    private String classes;
 
    public Integer getId() {
        return id;
    }
 
    public void setId(Integer id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getGender() {
        return gender;
    }
 
    public void setGender(String gender) {
        this.gender = gender;
    }
 
    public Integer getAge() {
        return age;
    }
 
    public void setAge(Integer age) {
        this.age = age;
    }
 
    public String getClasses() {
        return classes;
    }
 
    public void setClasses(String classes) {
        this.classes = classes;
    }
 
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", age=" + age +
                ", classes='" + classes + '\'' +
                '}';
    }
}
 
  4. JdbcTemplate技术讲解

      > 为了在特定领域帮助我们简化代码,Spring 封装了很多 『Template』形式的模板类。例如:RedisTemplate、RestTemplate 等等,包括我们今天要学习的 JdbcTemplate。

      jdbc.properties

      提取数据库连接信息
atguigu.url=jdbc:mysql://localhost:3306/studb
atguigu.driver=com.mysql.cj.jdbc.Driver
atguigu.username=root
atguigu.password=root
      springioc配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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 https://www.springframework.org/schema/context/spring-context.xsd">
 
 
    <!-- 导入外部属性文件 -->
    <context:property-placeholder location="classpath:jdbc.properties" />
 
    <!-- 配置数据源 -->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${atguigu.url}"/>
        <property name="driverClassName" value="${atguigu.driver}"/>
        <property name="username" value="${atguigu.username}"/>
        <property name="password" value="${atguigu.password}"/>
    </bean>
 
    <!-- 配置 JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 装配数据源 -->
        <property name="dataSource" ref="druidDataSource"/>
    </bean>
    
</beans>
      基于jdbcTemplate的CRUD使用
public class JdbcTemplateTest {
 
 
    /**
     * 使用jdbcTemplate进行DML动作
     */
    @Test
    public void testDML(){
 
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("spring-ioc.xml");
 
        JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
 
        //TODO 执行插入一条学员数据
        String sql = "insert into students (id,name,gender,age,class) values (?,?,?,?,?);";
    /*
        参数1: sql语句
        参数2: 可变参数,占位符的值
     */
        int rows = jdbcTemplate.update(sql, 9,"十一", "男", 18, "二年三班");
 
        System.out.println("rows = " + rows);
 
    }
 
 
    /**
     * 查询单条实体对象
     *   public class Student {
     *     private Integer id;
     *     private String name;
     *     private String gender;
     *     private Integer age;
     *     private String classes;
     */
    @Test
    public void testDQLForPojo(){
 
        String sql = "select id , name , age , gender , class as classes from students where id = ? ;";
 
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("spring-ioc.xml");
 
        JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
 
        //根据id查询
        Student student = jdbcTemplate.queryForObject(sql,  (rs, rowNum) -> {
            //自己处理结果映射
            Student stu = new Student();
            stu.setId(rs.getInt("id"));
            stu.setName(rs.getString("name"));
            stu.setAge(rs.getInt("age"));
            stu.setGender(rs.getString("gender"));
            stu.setClasses(rs.getString("classes"));
            return stu;
        }, 2);
 
        System.out.println("student = " + student);
    }
 
 
 
    /**
     * 查询实体类集合
     */
    @Test
    public void testDQLForListPojo(){
 
        String sql = "select id , name , age , gender , class as classes from students  ;";
 
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("spring-ioc.xml");
 
        JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
    /*
        query可以返回集合!
        BeanPropertyRowMapper就是封装好RowMapper的实现,要求属性名和列名相同即可
     */
        List<Student> studentList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class));
 
        System.out.println("studentList = " + studentList);
    }
 
}
 
  5. 三层架构搭建和实现
      1. 持久层
//接口
public interface StudentDao {
 
    /**
     * 查询全部学生数据
     * @return
     */
    List<Student> queryAll();
}
 
//实现类
public class StudentDaoImpl implements StudentDao {
 
    private JdbcTemplate jdbcTemplate;
 
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
 
    /**
     * 查询全部学生数据
     * @return
     */
    @Override
    public List<Student> queryAll() {
 
        String sql = "select id , name , age , gender , class as classes from students ;";
 
        /*
          query可以返回集合!
          BeanPropertyRowMapper就是封装好RowMapper的实现,要求属性名和列名相同即可
         */
        List<Student> studentList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class));
 
        return studentList;
   }
}
 
      2. 业务层
//接口
public interface StudentService {
 
    /**
     * 查询全部学员业务
     * @return
     */
    List<Student> findAll();
 
}
 
//实现类
public class StudentServiceImpl  implements StudentService {
    
    private StudentDao studentDao;
 
    public void setStudentDao(StudentDao studentDao) {
        this.studentDao = studentDao;
    }
 
    /**
     * 查询全部学员业务
     * @return
     */
    @Override
    public List<Student> findAll() {
        
        List<Student> studentList =  studentDao.queryAll();
        
        return studentList;
    }
}
 
      3. 表述层
public class StudentController {
    
    private StudentService studentService;
 
    public void setStudentService(StudentService studentService) {
        this.studentService = studentService;
    }
    
    public void  findAll(){
       List<Student> studentList =  studentService.findAll();
        System.out.println("studentList = " + studentList);
    }
}
  6. 三层架构IoC配置
<?xml version="1.0" encoding="UTF-8"?>
<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 https://www.springframework.org/schema/context/spring-context.xsd">
 
 
    <!-- 导入外部属性文件 -->
    <context:property-placeholder location="classpath:jdbc.properties" />
 
    <!-- 配置数据源 -->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${atguigu.url}"/>
        <property name="driverClassName" value="${atguigu.driver}"/>
        <property name="username" value="${atguigu.username}"/>
        <property name="password" value="${atguigu.password}"/>
    </bean>
 
    <!-- 配置 JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 装配数据源 -->
        <property name="dataSource" ref="druidDataSource"/>
    </bean>
 
 
    <bean id="studentDao" class="com.atguigu.dao.impl.StudentDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate" />
    </bean>
 
    <bean id="studentService" class="com.atguigu.service.impl.StudentServiceImpl">
        <property name="studentDao" ref="studentDao" />
    </bean>
 
    <bean id="studentController" class="com.atguigu.controller.StudentController">
        <property name="studentService" ref="studentService" />
    </bean>
 
</beans>
  7. 运行测试
public class ControllerTest {
 
    @Test
    public  void testRun(){
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("spring-ioc.xml");
        StudentController studentController = applicationContext.getBean(StudentController.class);
        studentController.findAll();
    }
}
  8. XMLIoC方式问题总结
      1. 注入的属性必须添加setter方法、代码结构乱!
      2. 配置文件和Java代码分离、编写不是很方便!
      3. XML配置文件解析效率低

4.3 基于 注解 方式管理 Bean

#### 4.3.1 实验一: Bean注解标记和扫描 (IoC)
  1. **注解理解**

      和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。

      本质上:所有一切的操作都是 Java 代码来完成的,XML 和注解只是告诉框架中的 Java 代码如何执行。

      举例:元旦联欢会要布置教室,蓝色的地方贴上元旦快乐四个字,红色的地方贴上拉花,黄色的地方贴上气球。

      ![](http://heavy_code_industry.gitee.io/code_heavy_industry/assets/img/img015.a6b65329.png)

      班长做了所有标记,同学们来完成具体工作。墙上的标记相当于我们在代码中使用的注解,后面同学们做的工作,相当于框架的具体操作。
  2. **扫描理解**

      Spring 为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注解进行后续操作。
  3. **准备Spring项目和组件**
      1. 准备项目pom.xml
<dependencies>
    <!--spring context依赖-->
    <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.0.6</version>
    </dependency>
 
    <!--junit5测试-->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.3.1</version>
    </dependency>
</dependencies>
      2. 准备组件类

          普通组件
/**
 * projectName: com.atguigu.components
 *
 * description: 普通的组件
 */
public class CommonComponent {
}
 
          Controller组件
/**
 * projectName: com.atguigu.components
 *
 * description: controller类型组件
 */
public class XxxController {
}
 
          Service组件
/**
 * projectName: com.atguigu.components
 *
 * description: service类型组件
 */
public class XxxService {
}
 
          Dao组件
/**
 * projectName: com.atguigu.components
 *
 * description: dao类型组件
 */
public class XxxDao {
}
 
  4. **组件添加标记注解**
      1. 组件标记注解和区别

          Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。
注解说明
@Component该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。
@Repository该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Service该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Controller该注解通常作用在控制层(如 SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
          ![](http://heavy_code_industry.gitee.io/code_heavy_industry/assets/img/img017.93fb56c5.png)

          通过查看源码我们得知,@Controller、@Service、@Repository 这三个注解只是在@Component 注解的基础上起了三个新的名字。

          对于 Spring 使用 IOC 容器管理这些组件来说没有区别,也就是语法层面没有区别。所以@Controller、@Service、@Repository 这三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。

          注意:虽然它们本质上一样,但是为了代码的可读性、程序结构严谨!我们肯定不能随便胡乱标记。
      2. 使用注解标记

          普通组件
/**
 * projectName: com.atguigu.components
 *
 * description: 普通的组件
 */
@Component
public class CommonComponent {
}
 
          Controller 组件
/**
 * projectName: com.atguigu.components
 *
 * description: controller 类型组件
 */
@Controller
public class XxxController {
}
 
          Service 组件
/**
 * projectName: com.atguigu.components
 *
 * description: service 类型组件
 */
@Service
public class XxxService {
}
 
          Dao 组件
/**
 * projectName: com.atguigu.components
 *
 * description: dao 类型组件
 */
@Repository
public class XxxDao {
}
 
  5. **配置文件确定扫描范围**

      情况 1:基本扫描配置
<?xml version="1.0" encoding="UTF-8"?>
<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 https://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 配置自动扫描的包 -->
    <!-- 1.包要精准,提高性能!
         2.会扫描指定的包和子包内容
         3.多个包可以使用,分割 例如: com.atguigu.controller,com.atguigu.service 等
    -->
    <context:component-scan base-package="com.atguigu.components"/>
  
</beans>
      情况 2:指定排除组件
<!-- 情况三:指定不扫描的组件 -->
<context:component-scan base-package="com.atguigu.components">
    
    <!-- context:exclude-filter标签:指定排除规则 -->
    <!-- type属性:指定根据什么来进行排除,annotation取值表示根据注解来排除 -->
    <!-- expression属性:指定排除规则的表达式,对于注解来说指定全类名即可 -->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
      情况 3:指定扫描组件
<!-- 情况四:仅扫描指定的组件 -->
<!-- 仅扫描 = 关闭默认规则 + 追加规则 -->
<!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
<context:component-scan base-package="com.atguigu.ioc.components" use-default-filters="false">
    
    <!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
  6. **组件 BeanName 问题**

      在我们使用 XML 方式管理 bean 的时候,每个 bean 都有一个唯一标识——id 属性的值,便于在其他地方引用。现在使用注解后,每个组件仍然应该有一个唯一标识。

      默认情况:

      类名首字母小写就是 bean 的 id。例如:SoldierController 类对应的 bean 的 id 就是 soldierController。

      使用 value 属性指定:
@Controller(value = "tianDog")
public class SoldierController {
}
      当注解中只设置一个属性时,value 属性的属性名可以省略:
@Service("smallDog")
public class SoldierService {
}
  7. **总结**
      1. 注解方式 IoC 只是标记哪些类要被 Spring 管理
      2. 最终,我们还需要 XML 方式或者后面讲解 Java 配置类方式指定注解生效的包
      3. **现阶段配置方式为 注解 (标记)+ XML(扫描)**

#### 4.3.2 实验二: 组件(Bean)作用域和周期方法注解 
  1. 组件周期方法配置
      1. 周期方法概念

          我们可以在组件类中定义方法,然后当 IoC 容器实例化和销毁组件对象的时候进行调用!这两个方法我们成为生命周期方法!

          类似于 Servlet 的 init/destroy 方法,我们可以在周期方法完成初始化和释放资源等工作。
      2. 周期方法声明
public class BeanOne {
 
  //周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表
  @PostConstruct  //注解制指定初始化方法
  public void init() {
    // 初始化逻辑
  }
}
 
public class BeanTwo {
  
  @PreDestroy //注解指定销毁方法
  public void cleanup() {
    // 释放资源逻辑
  }
}
  2. 组件作用域配置
      1. Bean 作用域概念

          `<bean` 标签声明 Bean,只是将 Bean 的信息配置给 SpringIoC 容器!

          在 IoC 容器中,这些`<bean`标签对应的信息转成 Spring 内部 `BeanDefinition` 对象,`BeanDefinition` 对象内,包含定义的信息(id,class,属性等等)!

          这意味着,`BeanDefinition`与`类`概念一样,SpringIoC 容器可以可以根据`BeanDefinition`对象反射创建多个 Bean 对象实例。

          具体创建多少个 Bean 的实例对象,由 Bean 的作用域 Scope 属性指定!
      2. 作用域可选值
取值含义创建对象的时机默认值
singleton在 IOC 容器中,这个 bean 的对象始终为单实例IOC 容器初始化时
prototype这个 bean 在 IOC 容器中有多个实例获取 bean 时
          如果是在 WebApplicationContext 环境下还会有另外两个作用域(但不常用):
取值含义创建对象的时机默认值
request请求范围内有效的实例每次请求
session会话范围内有效的实例每次会话
      3. 作用域配置
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON) //单例,默认值
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) //多例  二选一
public class BeanOne {
 
  //周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表
  @PostConstruct  //注解制指定初始化方法
  public void init() {
    // 初始化逻辑
  }
}
#### 4.3.3 实验三: Bean 属性赋值:引用类型自动装配 (DI)
  1. **设定场景**
      - SoldierController 需要 SoldierService
      - SoldierService 需要 SoldierDao

        同时在各个组件中声明要调用的方法。

      - SoldierController 中声明方法
import org.springframework.stereotype.Controller;
 
@Controller(value = "tianDog")
public class SoldierController {
 
    private SoldierService soldierService;
 
    public void getMessage() {
        soldierService.getMessage();
    }
 
}
      - SoldierService 中声明方法
@Service("smallDog")
public class SoldierService {
 
    private SoldierDao soldierDao;
 
    public void getMessage() {
        soldierDao.getMessage();
    }
}
      - SoldierDao 中声明方法
@Repository
public class SoldierDao {
 
    public void getMessage() {
        System.out.print("I am a soldier");
    }
 
}
  2. **自动装配实现**
      1. 前提

          参与自动装配的组件(需要装配、被装配)全部都必须在 IoC 容器中。

          注意:不区分 IoC 的方式!XML 和注解都可以!
      2. @Autowired 注解

          在成员变量上直接标记@Autowired 注解即可,不需要提供 setXxx()方法。以后我们在项目中的正式用法就是这样。
      3. 给 Controller 装配 Service
@Controller(value = "tianDog")
public class SoldierController {
    
    @Autowired
    private SoldierService soldierService;
    
    public void getMessage() {
        soldierService.getMessage();
    }
    
}
      4. 给 Service 装配 Dao
@Service("smallDog")
public class SoldierService {
    
    @Autowired
    private SoldierDao soldierDao;
    
    public void getMessage() {
        soldierDao.getMessage();
    }
}
  3. **@Autowired 注解细节**
      1. 标记位置
          1. 成员变量

              这是最主要的使用方式!

              与 xml 进行 bean ref 引用不同,他不需要有 set 方法!
@Service("smallDog")
public class SoldierService {
    
    @Autowired
    private SoldierDao soldierDao;
    
    public void getMessage() {
        soldierDao.getMessage();
    }
}
          2. 构造器
@Controller(value = "tianDog")
public class SoldierController {
    
    private SoldierService soldierService;
    
    @Autowired
    public SoldierController(SoldierService soldierService) {
        this.soldierService = soldierService;
    }
    ……
          3. setXxx()方法
@Controller(value = "tianDog")
public class SoldierController {
 
    private SoldierService soldierService;
 
    @Autowired
    public void setSoldierService(SoldierService soldierService) {
        this.soldierService = soldierService;
    }
    ……
      2. 工作流程

          ![](http://heavy_code_industry.gitee.io/code_heavy_industry/assets/img/img018.2ff0ae09.png)

          - 首先根据所需要的组件类型到 IOC 容器中查找
              - 能够找到唯一的 bean:直接执行装配
              - 如果完全找不到匹配这个类型的 bean:装配失败
              - 和所需类型匹配的 bean 不止一个
                  - 没有 @Qualifier 注解:根据 @Autowired 标记位置成员变量的变量名作为 bean 的 id 进行匹配
                      - 能够找到:执行装配
                      - 找不到:装配失败
                  - 使用 @Qualifier 注解:根据 @Qualifier 注解中指定的名称作为 bean 的 id 进行匹配
                      - 能够找到:执行装配
                      - 找不到:装配失败
@Controller(value = "tianDog")
public class SoldierController {
    
    @Autowired
    @Qualifier(value = "maomiService222")
    // 根据面向接口编程思想,使用接口类型引入 Service 组件
    private ISoldierService soldierService;
  4. **佛系装配**

      给 @Autowired 注解设置 required = false 属性表示:能装就装,装不上就不装。但是实际开发时,基本上所有需要装配组件的地方都是必须装配的,用不上这个属性
@Controller(value = "tianDog")
public class SoldierController {
 
    // 给@Autowired 注解设置 required = false 属性表示:能装就装,装不上就不装
    @Autowired(required = false)
    private ISoldierService soldierService;
  5. **扩展 JSR-250 注解@Resource**
      - 理解 JSR 系列注解

          JSR(Java Specification Requests)是 Java 平台标准化进程中的一种技术规范,而 JSR 注解是其中一部分重要的内容。按照 JSR 的分类以及注解语义的不同,可以将 JSR 注解分为不同的系列,主要有以下几个系列:

          1. JSR-175: 这个 JSR 是 Java SE 5 引入的,是 Java 注解最早的规范化版本,Java SE 5 后的版本中都包含该 JSR 中定义的注解。主要包括以下几种标准注解:
          - `@Deprecated`: 标识一个程序元素(如类、方法或字段)已过时,并且在将来的版本中可能会被删除。
          - `@Override`: 标识一个方法重写了父类中的方法。
          - `@SuppressWarnings`: 抑制编译时产生的警告消息。
          - `@SafeVarargs`: 标识一个有安全性警告的可变参数方法。
          - `@FunctionalInterface`: 标识一个接口只有一个抽象方法,可以作为 lambda 表达式的目标。
          1. JSR-250: 这个 JSR 主要用于在 Java EE 5 中定义一些支持注解。该 JSR 主要定义了一些用于进行对象管理的注解,包括:
          - `@Resource`: 标识一个需要注入的资源,是实现 Java EE 组件之间依赖关系的一种方式。
          - `@PostConstruct`: 标识一个方法作为初始化方法。
          - `@PreDestroy`: 标识一个方法作为销毁方法。
          - `@Resource.AuthenticationType`: 标识注入的资源的身份验证类型。
          - `@Resource.AuthenticationType`: 标识注入的资源的默认名称。
          1. JSR-269: 这个 JSR 主要是 Java SE 6 中引入的一种支持编译时元数据处理的框架,即使用注解来处理 Java 源文件。该 JSR 定义了一些可以用注解标记的注解处理器,用于生成一些元数据,常用的注解有:
          - `@SupportedAnnotationTypes`: 标识注解处理器所处理的注解类型。
          - `@SupportedSourceVersion`: 标识注解处理器支持的 Java 源码版本。
          1. JSR-330: 该 JSR 主要为 Java 应用程序定义了一个依赖注入的标准,即 Java 依赖注入标准(javax.inject)。在此规范中定义了多种注解,包括:
          - `@Named`: 标识一个被依赖注入的组件的名称。
          - `@Inject`: 标识一个需要被注入的依赖组件。
          - `@Singleton`: 标识一个组件的生命周期只有一个唯一的实例。
          1. JSR-250: 这个 JSR 主要是 Java EE 5 中定义一些支持注解。该 JSR 包含了一些支持注解,可以用于对 Java EE 组件进行管理,包括:
          - `@RolesAllowed`: 标识授权角色
          - `@PermitAll`: 标识一个活动无需进行身份验证。
          - `@DenyAll`: 标识不提供针对该方法的访问控制。
          - `@DeclareRoles`: 声明安全角色。

          但是你要理解 JSR 是 Java 提供的**技术规范**,也就是说,他只是规定了注解和注解的含义,**JSR 并不是直接提供特定的实现**,而是提供标准和指导方针,由第三方框架(Spring)和库来实现和提供对应的功能。
      - JSR-250 @Resource 注解

          @Resource 注解也可以完成属性注入。那它和@Autowired 注解有什么区别?

          - @Resource 注解是 JDK 扩展包中的,也就是说属于 JDK 的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250 标准中制定的注解类型。JSR 是 Java 规范提案。)
          - @Autowired 注解是 Spring 框架自己的。
          - **@Resource 注解默认根据 Bean 名称装配,未指定 name 时,使用属性名作为 name。通过 name 找不到的话会自动启动通过类型装配。**
          - **@Autowired 注解默认根据类型装配,如果想根据名称装配,需要配合@Qualifier 注解一起用。**
          - @Resource 注解用在属性上、setter 方法上。
          - @Autowired 注解用在属性上、setter 方法上、构造方法上、构造方法参数上。

          @Resource 注解属于 JDK 扩展包,所以不在 JDK 当中,需要额外引入以下依赖:【**高于 JDK11 或低于 JDK8 需要引入以下依赖**】
<dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>2.1.1</version>
</dependency>
      - @Resource 使用
@Controller
public class XxxController {
    /**
     * 1. 如果没有指定 name,先根据属性名查找 IoC 中组件 xxxService
     * 2. 如果没有指定 name,并且属性名没有对应的组件,会根据属性类型查找
     * 3. 可以指定 name 名称查找!  @Resource(name='test') == @Autowired + @Qualifier(value='test')
     */
    @Resource
    private XxxService xxxService;
 
    //@Resource(name = "指定 beanName")
    //private XxxService xxxService;
 
    public void show(){
        System.out.println("XxxController.show");
        xxxService.show();
    }
}
#### 4.3.4 实验四: Bean 属性赋值:基本类型属性赋值 (DI)

  `@Value` 通常用于注入外部化属性

  **声明外部配置**

  application.properties
catalog.name=MovieCatalog
  **xml 引入外部配置**
<!-- 引入外部配置文件-->
<context:property-placeholder location="application.properties" />
  **@Value 注解读取配置**
package com.atguigu.components;
 
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
 
/**
 * projectName: com.atguigu.components
 *
 * description: 普通的组件
 */
@Component
public class CommonComponent {
 
    /**
     * 情况 1: ${key} 取外部配置 key 对应的值!
     * 情况 2: ${key:defaultValue} 没有 key,可以给与默认值
     */
    @Value("${catalog:hahaha}")
    private String name;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
}
 
  catalog

#### 4.3.5 实验五: 基于注解+XML 方式整合三层架构组件
  1. 需求分析

      搭建一个三层架构案例,模拟查询全部学生(学生表)信息,持久层使用 JdbcTemplate 和 Druid 技术,使用 XML+注解方式进行组件管理!

      ![](https://secure2.wostatic.cn/static/6WsK3n5TJvotqmmNNjxY54/image.png)
  2. 数据库准备
create database studb;
 
use studb;
 
CREATE TABLE students (
  id INT PRIMARY KEY,
  name VARCHAR(50) NOT NULL,
  gender VARCHAR(10) NOT NULL,
  age INT,
  class VARCHAR(50)
);
 
INSERT INTO students (id, name, gender, age, class)
VALUES
  (1, '张三', '男', 20, '高中一班'),
  (2, '李四', '男', 19, '高中二班'),
  (3, '王五', '女', 18, '高中一班'),
  (4, '赵六', '女', 20, '高中三班'),
  (5, '刘七', '男', 19, '高中二班'),
  (6, '陈八', '女', 18, '高中一班'),
  (7, '杨九', '男', 20, '高中三班'),
  (8, '吴十', '男', 19, '高中二班');
 
  3. 项目准备
      1. 项目创建

          spring-annotation-practice-04
      2. 依赖导入
<dependencies>
      <!--spring context依赖-->
      <!--当你引入SpringContext依赖之后,表示将Spring的基础依赖引入了-->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>6.0.6</version>
      </dependency>
 
      <!-- 数据库驱动和连接池-->
      <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>8.0.25</version>
      </dependency>
 
      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid</artifactId>
          <version>1.2.8</version>
      </dependency>
      
      <dependency>
            <groupId>jakarta.annotation</groupId>
            <artifactId>jakarta.annotation-api</artifactId>
            <version>2.1.1</version>
       </dependency>
 
      <!-- spring-jdbc -->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-jdbc</artifactId>
          <version>6.0.6</version>
      </dependency>
 
</dependencies> 
      3. 实体类准备
public class Student {
 
    private Integer id;
    private String name;
    private String gender;
    private Integer age;
    private String classes;
 
    public Integer getId() {
        return id;
    }
 
    public void setId(Integer id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getGender() {
        return gender;
    }
 
    public void setGender(String gender) {
        this.gender = gender;
    }
 
    public Integer getAge() {
        return age;
    }
 
    public void setAge(Integer age) {
        this.age = age;
    }
 
    public String getClasses() {
        return classes;
    }
 
    public void setClasses(String classes) {
        this.classes = classes;
    }
 
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", age=" + age +
                ", classes='" + classes + '\'' +
                '}';
    }
}
 
  4. 三层架构搭建和实现
      1. 持久层
//接口
public interface StudentDao {
 
    /**
     * 查询全部学生数据
     * @return
     */
    List<Student> queryAll();
}
 
//实现类
@Repository
public class StudentDaoImpl implements StudentDao {
 
    @Autowired
    private JdbcTemplate jdbcTemplate;
 
    /**
     * 查询全部学生数据
     * @return
     */
    @Override
    public List<Student> queryAll() {
 
        String sql = "select id , name , age , gender , class as classes from students ;";
 
        /*
          query 可以返回集合!
          BeanPropertyRowMapper 就是封装好 RowMapper 的实现,要求属性名和列名相同即可
         */
        List<Student> studentList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class));
 
        return studentList;
    }
}
 
      2. 业务层
//接口
public interface StudentService {
 
    /**
     * 查询全部学员业务
     * @return
     */
    List<Student> findAll();
 
}
 
//实现类
@Service
public class StudentServiceImpl  implements StudentService {
 
    @Autowired
    private StudentDao studentDao;
 
    /**
     * 查询全部学员业务
     * @return
     */
    @Override
    public List<Student> findAll() {
 
        List<Student> studentList =  studentDao.queryAll();
 
        return studentList;
    }
}
 
      3. 表述层
@Controller
public class StudentController {
 
    @Autowired
    private StudentService studentService;
 
    public void  findAll(){
       List<Student> studentList =  studentService.findAll();
        System.out.println("studentList = " + studentList);
    }
}
 
  5. 三层架构 IoC 配置
<?xml version="1.0" encoding="UTF-8"?>
<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 https://www.springframework.org/schema/context/spring-context.xsd">
 
 
    <!-- 导入外部属性文件 -->
    <context:property-placeholder location="classpath:jdbc.properties" />
 
    <!-- 配置数据源 -->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${atguigu.url}"/>
        <property name="driverClassName" value="${atguigu.driver}"/>
        <property name="username" value="${atguigu.username}"/>
        <property name="password" value="${atguigu.password}"/>
    </bean>
 
    <bean class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="druidDataSource" />
    </bean>
 
    <!-- 扫描Ioc/DI注解 -->
    <context:component-scan base-package="com.atguigu.dao,com.atguigu.service,com.atguigu.controller" />
 
</beans>
  6. 运行测试
public class ControllerTest {
 
    @Test
    public  void testRun(){
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("spring-ioc.xml");
        StudentController studentController = applicationContext.getBean(StudentController.class);
        studentController.findAll();
    }
}
  7. 注解+XML IoC 方式问题总结
      1. 自定义类可以使用注解方式,但是第三方依赖的类依然使用 XML 方式!
      2. XML 格式解析效率低!

4.4 基于 配置类 方式管理 Bean

#### 4.4.1 完全注解开发理解

  Spring 完全注解配置(Fully Annotation-based Configuration)是指通过 Java 配置类 代码来配置 Spring 应用程序,使用注解来替代原本在 XML 配置文件中的配置。相对于 XML 配置,完全注解配置具有更强的类型安全性和更好的可读性。

  **两种方式思维转化**:

  ![](https://secure2.wostatic.cn/static/uhRgky6LmFBAaYDJfS5SEm/image.png)

#### 4.4.2 实验一:配置类和扫描注解

  **xml+注解方式**

  配置文件 application.xml
<?xml version="1.0" encoding="UTF-8"?>
<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 https://www.springframework.org/schema/context/spring-context.xsd">
 
 
    <!-- 配置自动扫描的包 -->
    <!-- 1.包要精准,提高性能!
         2.会扫描指定的包和子包内容
         3.多个包可以使用,分割 例如: com.atguigu.controller,com.atguigu.service 等
    -->
    <context:component-scan base-package="com.atguigu.components"/>
 
    <!-- 引入外部配置文件-->
    <context:property-placeholder location="application.properties" />
</beans>
  测试创建 IoC 容器
 // xml 方式配置文件使用 ClassPathXmlApplicationContext 容器读取
 ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("application.xml");
  **配置类+注解方式(完全注解方式)**

  配置类

  使用 @Configuration 注解将一个普通的类标记为 Spring 的配置类。
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
 
//标注当前类是配置类,替代 application.xml    
@Configuration
//使用注解读取外部配置,替代 <context:property-placeholder 标签
@PropertySource("classpath:application.properties")
//使用@ComponentScan 注解,可以配置扫描包,替代<context:component-scan 标签
@ComponentScan(basePackages = {"com.atguigu.components"})
public class MyConfiguration {
    
}
  测试创建 IoC 容器
// AnnotationConfigApplicationContext 根据配置类创建 IOC 容器对象
ApplicationContext iocContainerAnnotation = 
new AnnotationConfigApplicationContext(MyConfiguration.class);
  可以使用 no-arg 构造函数实例化 `AnnotationConfigApplicationContext` ,然后使用 `register()` 方法对其进行配置。此方法在以编程方式生成 `AnnotationConfigApplicationContext` 时特别有用。以下示例演示如何执行此操作:
// AnnotationConfigApplicationContext-IOC 容器对象
ApplicationContext iocContainerAnnotation = 
new AnnotationConfigApplicationContext();
//外部设置配置类
iocContainerAnnotation.register(MyConfiguration.class);
//刷新后方可生效!!
iocContainerAnnotation.refresh();
 
  **总结:**

    @Configuration 指定一个类为配置类,可以添加配置注解,替代配置 xml 文件

    @ComponentScan(basePackages = {"包","包"}) 替代<context:component-scan 标签实现注解扫描

    @PropertySource("classpath:配置文件地址") 替代 <context:property-placeholder 标签

    配合 IoC/DI 注解,可以进行完整注解开发!

#### 4.4.3 实验二:@Bean 定义组件

  **场景需求**:将 Druid 连接池对象存储到 IoC 容器

  **需求分析**:第三方 jar 包的类,添加到 ioc 容器,无法使用@Component 等相关注解!因为源码 jar 包内容为只读模式!

  **xml 方式实现**:
<?xml version="1.0" encoding="UTF-8"?>
<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 https://www.springframework.org/schema/context/spring-context.xsd">
 
 
    <!-- 引入外部属性文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
 
    <!-- 实验六 [重要]给bean的属性赋值:引入外部属性文件 -->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="username" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
 
</beans>
  **配置类方式实现**:

    `@Bean` 注释用于指示方法实例化、配置和初始化要由 Spring IoC 容器管理的新对象。对于那些熟悉 Spring 的 `<beans/>` XML 配置的人来说, `@Bean` 注释与 `<bean/>` 元素起着相同的作用。
//标注当前类是配置类,替代 application.xml    
@Configuration
//引入 jdbc.properties 文件
@PropertySource({"classpath:application.properties","classpath:jdbc.properties"})
@ComponentScan(basePackages = {"com.atguigu.components"})
public class MyConfiguration {
 
    //如果第三方类进行 IoC 管理,无法直接使用@Component 相关注解
    //解决方案: xml 方式可以使用<bean 标签
    //解决方案: 配置类方式,可以使用方法返回值+@Bean 注解
    @Bean
    public DataSource createDataSource(@Value("${jdbc.user}") String username,
                                       @Value("${jdbc.password}")String password,
                                       @Value("${jdbc.url}")String url,
                                       @Value("${jdbc.driver}")String driverClassName){
        //使用 Java 代码实例化
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setUrl(url);
        dataSource.setDriverClassName(driverClassName);
        //返回结果即可
        return dataSource;
    }
}
#### 4.4.4 实验三:高级特性:@Bean 注解细节
  1. **@Bean 生成 BeanName 问题**

      @Bean 注解源码:
public @interface Bean {
    //前两个注解可以指定 Bean 的标识
    @AliasFor("name")
    String[] value() default {};
    @AliasFor("value")
    String[] name() default {};
  
    //autowireCandidate 属性来指示该 Bean 是否候选用于自动装配。
    //autowireCandidate 属性默认值为 true,表示该 Bean 是一个默认的装配目标,
    //可被候选用于自动装配。如果将 autowireCandidate 属性设置为 false,则说明该 Bean 不是默认的装配目标,不会被候选用于自动装配。
    boolean autowireCandidate() default true;
 
    //指定初始化方法
    String initMethod() default "";
    //指定销毁方法
    String destroyMethod() default "(inferred)";
}
 
      指定@Bean 的名称:
@Configuration
public class AppConfig {
 
  @Bean("myThing") //指定名称
  public Thing thing() {
    return new Thing();
  }
}
      `@Bean` 注释注释方法。使用此方法在指定为方法返回值的类型的 `ApplicationContext` 中注册 Bean 定义。缺省情况下,Bean 名称与方法名称相同。下面的示例演示 `@Bean` 方法声明:
@Configuration
public class AppConfig {
 
  @Bean
  public TransferServiceImpl transferService() {
    return new TransferServiceImpl();
  }
}
      前面的配置完全等同于下面的 Spring XML:
<beans>
  <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
  2. **@Bean 初始化和销毁方法指定**

      `@Bean` 注解支持指定任意初始化和销毁回调方法,非常类似于 Spring XML 在 `bean` 元素上的 `init-method` 和 `destroy-method` 属性,如以下示例所示:
public class BeanOne {
 
  public void init() {
    // initialization logic
  }
}
 
public class BeanTwo {
 
  public void cleanup() {
    // destruction logic
  }
}
 
@Configuration
public class AppConfig {
 
  @Bean(initMethod = "init")
  public BeanOne beanOne() {
    return new BeanOne();
  }
 
  @Bean(destroyMethod = "cleanup")
  public BeanTwo beanTwo() {
    return new BeanTwo();
  }
}
  3. **@Bean Scope 作用域**

      可以指定使用 `@Bean` 注释定义的 bean 应具有特定范围。您可以使用在 Bean 作用域部分中指定的任何标准作用域。

      默认作用域为 `singleton` ,但您可以使用 `@Scope` 注释覆盖此范围,如以下示例所示:
@Configuration
public class MyConfiguration {
 
  @Bean
  @Scope("prototype")
  public Encryptor encryptor() {
    // ...
  }
}
  4. **@Bean 方法之间依赖**

      **准备组件**
public class HappyMachine {
    
    private String machineName;
    
    public String getMachineName() {
        return machineName;
    }
    
    public void setMachineName(String machineName) {
        this.machineName = machineName;
    }
}
public class HappyComponent {
    //引用新组件
    private HappyMachine happyMachine;
 
    public HappyMachine getHappyMachine() {
        return happyMachine;
    }
 
    public void setHappyMachine(HappyMachine happyMachine) {
        this.happyMachine = happyMachine;
    }
 
    public void doWork() {
        System.out.println("HappyComponent.doWork");
    }
 
}
      **Java 配置类实现:**

      方案 1:

      直接调用方法返回 Bean 实例:在一个 `@Bean` 方法中直接调用其他 `@Bean` 方法来获取 Bean 实例,虽然是方法调用,也是通过 IoC 容器获取对应的 Bean,例如:
@Configuration
public class JavaConfig {
 
    @Bean
    public HappyMachine happyMachine(){
        return new HappyMachine();
    }
 
    @Bean
    public HappyComponent happyComponent(){
        HappyComponent happyComponent = new HappyComponent();
        //直接调用方法即可! 
        happyComponent.setHappyMachine(happyMachine());
        return happyComponent;
    }
 
}
      方案 2:

      参数引用法:通过方法参数传递 Bean 实例的引用来解决 Bean 实例之间的依赖关系,例如:
package com.atguigu.config;
 
import com.atguigu.ioc.HappyComponent;
import com.atguigu.ioc.HappyMachine;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
/**
 * projectName: com.atguigu.config
 * description: 配置 HappyComponent 和 HappyMachine 关系
 */
 
@Configuration
public class JavaConfig {
 
    @Bean
    public HappyMachine happyMachine(){
        return new HappyMachine();
    }
 
    /**
     * 可以直接在形参列表接收 IoC 容器中的 Bean!
     *    情况 1: 直接指定类型即可
     *    情况 2: 如果有多个 bean,(HappyMachine 名称 ) 形参名称等于要指定的 bean 名称!
     *           例如:
     *               @Bean
     *               public Foo foo1(){
     *                   return new Foo();
     *               }
     *               @Bean
     *               public Foo foo2(){
     *                   return new Foo()
     *               }
     *               @Bean
     *               public Component component(Foo foo1 / foo2 通过此处指定引入的 bean)
     */
    @Bean
    public HappyComponent happyComponent(HappyMachine happyMachine){
        HappyComponent happyComponent = new HappyComponent();
        //赋值
        happyComponent.setHappyMachine(happyMachine);
        return happyComponent;
    }
 
}
#### 4.4.5 实验四:高级特性:@Import 扩展

  `@Import` 注释允许从另一个配置类加载 `@Bean` 定义,如以下示例所示:
@Configuration
public class ConfigA {
 
  @Bean
  public A a() {
    return new A();
  }
}
 
@Configuration
@Import(ConfigA.class)
public class ConfigB {
 
  @Bean
  public B b() {
    return new B();
  }
}
  现在,在实例化上下文时不需要同时指定 `ConfigA.class` 和 `ConfigB.class` ,只需显式提供 `ConfigB` ,如以下示例所示:
public static void main(String[] args) {
  ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
 
  // now both beans A and B will be available...
  A a = ctx.getBean(A.class);
  B b = ctx.getBean(B.class);
}
  此方法简化了容器实例化,因为只需要处理一个类,而不是要求您在构造期间记住可能大量的 `@Configuration` 类。

#### 4.4.6 实验五:基于注解+配置类方式整合三层架构组件
  1. 需求分析

      搭建一个三层架构案例,模拟查询全部学生(学生表)信息,持久层使用 JdbcTemplate 和 Druid 技术,使用注解+配置类方式进行组件管理!

      ![](https://secure2.wostatic.cn/static/spSLnBnMYbqJXYLqjr2C7y/image.png)
  2. 数据库准备
create database studb;
 
use studb;
 
CREATE TABLE students (
  id INT PRIMARY KEY,
  name VARCHAR(50) NOT NULL,
  gender VARCHAR(10) NOT NULL,
  age INT,
  class VARCHAR(50)
);
 
INSERT INTO students (id, name, gender, age, class)
VALUES
  (1, '张三', '男', 20, '高中一班'),
  (2, '李四', '男', 19, '高中二班'),
  (3, '王五', '女', 18, '高中一班'),
  (4, '赵六', '女', 20, '高中三班'),
  (5, '刘七', '男', 19, '高中二班'),
  (6, '陈八', '女', 18, '高中一班'),
  (7, '杨九', '男', 20, '高中三班'),
  (8, '吴十', '男', 19, '高中二班');
 
  3. 项目准备
      1. 项目创建

          spring-java-practice-06
      2. 依赖导入
<dependencies>
      <!--spring context依赖-->
      <!--当你引入SpringContext依赖之后,表示将Spring的基础依赖引入了-->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>6.0.6</version>
      </dependency>
 
      <!-- 数据库驱动和连接池-->
      <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>8.0.25</version>
      </dependency>
 
      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid</artifactId>
          <version>1.2.8</version>
      </dependency>
      
      <dependency>
            <groupId>jakarta.annotation</groupId>
            <artifactId>jakarta.annotation-api</artifactId>
            <version>2.1.1</version>
       </dependency>
 
      <!-- spring-jdbc -->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-jdbc</artifactId>
          <version>6.0.6</version>
      </dependency>
 
</dependencies> 
      3. 实体类准备
public class Student {
 
    private Integer id;
    private String name;
    private String gender;
    private Integer age;
    private String classes;
 
    public Integer getId() {
        return id;
    }
 
    public void setId(Integer id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getGender() {
        return gender;
    }
 
    public void setGender(String gender) {
        this.gender = gender;
    }
 
    public Integer getAge() {
        return age;
    }
 
    public void setAge(Integer age) {
        this.age = age;
    }
 
    public String getClasses() {
        return classes;
    }
 
    public void setClasses(String classes) {
        this.classes = classes;
    }
 
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", age=" + age +
                ", classes='" + classes + '\'' +
                '}';
    }
}
 
  4. 三层架构搭建和实现
      1. 持久层
//接口
public interface StudentDao {
 
    /**
     * 查询全部学生数据
     * @return
     */
    List<Student> queryAll();
}
 
//实现类
@Repository
public class StudentDaoImpl implements StudentDao {
 
    @Autowired
    private JdbcTemplate jdbcTemplate;
 
    /**
     * 查询全部学生数据
     * @return
     */
    @Override
    public List<Student> queryAll() {
 
        String sql = "select id , name , age , gender , class as classes from students ;";
 
        /*
          query 可以返回集合!
          BeanPropertyRowMapper 就是封装好 RowMapper 的实现,要求属性名和列名相同即可
         */
        List<Student> studentList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class));
 
        return studentList;
    }
}
 
      2. 业务层
//接口
public interface StudentService {
 
    /**
     * 查询全部学员业务
     * @return
     */
    List<Student> findAll();
 
}
 
//实现类
@Service
public class StudentServiceImpl  implements StudentService {
 
    @Autowired
    private StudentDao studentDao;
 
    /**
     * 查询全部学员业务
     * @return
     */
    @Override
    public List<Student> findAll() {
 
        List<Student> studentList =  studentDao.queryAll();
 
        return studentList;
    }
}
 
      3. 表述层
@Controller
public class StudentController {
 
    @Autowired
    private StudentService studentService;
 
    public void  findAll(){
       List<Student> studentList =  studentService.findAll();
        System.out.println("studentList = " + studentList);
    }
}
 
  5. 三层架构 IoC 配置类
@Configuration
@ComponentScan(basePackages = "com.atguigu")
@PropertySource("classpath:jdbc.properties")
public class JavaConfig {
 
    @Value("${atguigu.url}")
    private String url;
    @Value("${atguigu.driver}")
    private String driver;
    @Value("${atguigu.username}")
    private String username;
    @Value("${atguigu.password}")
    private String password;
 
    @Bean(destroyMethod = "close")
    public DruidDataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(url);
        dataSource.setDriverClassName(driver);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
 
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }
 
}
  6. 运行测试
public class ControllerTest {
 
    @Test
    public  void testRun(){
 
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(JavaConfig.class);
 
        StudentController studentController = applicationContext.getBean(StudentController.class);
 
        studentController.findAll();
 
    }
}
  7. 注解+配置类 IoC 方式总结
      1. 完全摒弃了 XML 配置文件
      2. 自定义类使用 IoC 和 DI 注解标记
      3. 第三方类使用配置类声明方法+@Bean 方式处理
      4. 完全注解方式(配置类+注解)是现在主流配置方式

4.5 三种配置方式总结

#### 4.5.1 XML 方式配置总结
  1. 所有内容写到 xml 格式配置文件中
  2. 声明 bean 通过<bean 标签
  3. <bean 标签包含基本信息(id,class)和属性信息 <property name value / ref
  4. 引入外部的 properties 文件可以通过<context:property-placeholder
  5. IoC 具体容器实现选择 ClassPathXmlApplicationContext 对象

#### 4.5.2 XML+注解方式配置总结
  1. 注解负责标记 IoC 的类和进行属性装配
  2. xml 文件依然需要,需要通过<context:component-scan 标签指定注解范围
  3. 标记 IoC 注解:@Component,@Service,@Controller,@Repository 
  4. 标记 DI 注解:@Autowired @Qualifier @Resource @Value
  5. IoC 具体容器实现选择 ClassPathXmlApplicationContext 对象

#### 4.5.3 完全注解方式配置总结
  1. 完全注解方式指的是去掉 xml 文件,使用配置类 + 注解实现
  2. xml 文件替换成使用@Configuration 注解标记的类
  3. 标记 IoC 注解:@Component,@Service,@Controller,@Repository 
  4. 标记 DI 注解:@Autowired @Qualifier @Resource @Value
  5. <context:component-scan 标签指定注解范围使用@ComponentScan(basePackages = {"com.atguigu.components"})替代
  6. <context:property-placeholder 引入外部配置文件使用@PropertySource({"classpath:application.properties","classpath:jdbc.properties"})替代
  7. <bean 标签使用@Bean 注解和方法实现
  8. IoC 具体容器实现选择 AnnotationConfigApplicationContext 对象

4.6 整合 Spring5-Test5 搭建测试环境

1. 整合测试环境作用

    好处 1:不需要自己创建 IOC 容器对象了

    好处 2:任何需要的 bean 都可以在测试类中直接享受自动装配
2. 导入相关依赖
<!--junit5测试-->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.3.1</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>6.0.6</version>
    <scope>test</scope>
</dependency>
3. 整合测试注解使用
//@SpringJUnitConfig(locations = {"classpath:spring-context.xml"})  //指定配置文件 xml
@SpringJUnitConfig(value = {BeanConfig.class})  //指定配置类
public class Junit5IntegrationTest {
    
    @Autowired
    private User user;
    
    @Test
    public void testJunit5() {
        System.out.println(user);
    }
}