Spring IoC容器和核心概念

1. Spring IoC容器和核心概念

1.1 组件和组件管理概念

1.1.1 组件

之前三层架构的处理流程:

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

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

Spring框架充当了组件管理角色(IoC),也称为控制反转(Inversion of Control)。在传统的应用程序中,对象的创建和管理通常由应用程序本身负责。但是,这种方式会导致应用程序的耦合性增加,难以进行单元测试和维护。

Spring框架通过IoC容器来解决这个问题。IoC容器负责创建和管理应用程序中的对象(组件),并将它们之间的依赖关系交由容器来处理。这样,应用程序的各个组件之间就可以解耦,从而提高了应用程序的可维护性和可测试性。

Spring框架的IoC容器提供了依赖注入(Dependency Injection)功能,它可以自动将组件之间的依赖关系注入到对象中,从而实现了对象之间的解耦。这样,开发人员就可以更加专注于业务逻辑的实现,而不必关心对象的创建和管理。

1.1.3 组件交给Spring管理的好处

  1. 降低耦合性:将组件交给Spring管理后,组件之间的依赖关系由Spring容器来处理,从而实现了组件之间的解耦。这样,组件之间的耦合性降低,代码的可维护性和可测试性提高。

  2. 提高可重用性:Spring容器可以将组件配置为单例模式,这样就可以在整个应用程序中共享同一个实例。这样,可以提高组件的可重用性,减少资源的浪费。

  3. 简化配置:Spring容器可以通过注解或XML配置文件来管理组件,这样可以简化配置,减少代码量。

  4. 提高灵活性:Spring容器可以根据需要动态地创建和销毁组件,从而提高了应用程序的灵活性。

  5. 支持AOP:Spring容器支持AOP(面向切面编程),可以将横切关注点(如日志、事务、安全等)从业务逻辑中分离出来,从而提高了代码的可重用性和可维护性。

  6. 提高测试效率:将组件交给Spring管理后,可以使用Mock对象来替代真实的组件进行单元测试,从而提高了测试效率。

1.2 Spring IoC容器和容器实现

1.2.1 SpringIoC容器介绍

Spring IoC(Inversion of Control,控制反转)容器是Spring框架的核心部分,它负责管理应用程序中的对象(组件)及其依赖关系。IoC容器通过读取配置文件或注解来创建和管理对象,将对象之间的依赖关系交由容器来处理,从而实现了对象之间的解耦。

Spring IoC容器提供了两种类型的容器:BeanFactory和ApplicationContext。BeanFactory是Spring IoC容器的基础接口,提供了最基本的IoC功能,而ApplicationContext是BeanFactory的子接口,提供了更多的功能,如国际化、事件传播、资源加载等。

Spring IoC容器的主要功能包括:

  1. 对象的创建和管理:Spring IoC容器负责创建和管理应用程序中的对象(组件),并将它们之间的依赖关系交由容器来处理。

  2. 依赖注入:Spring IoC容器可以自动将组件之间的依赖关系注入到对象中,从而实现了对象之间的解耦。

  3. 生命周期管理:Spring IoC容器可以管理组件的生命周期,包括对象的创建、初始化、销毁等。

  4. 配置管理:Spring IoC容器可以通过注解或XML配置文件来管理组件,从而实现了组件的配置和管理。

  5. AOP支持:Spring IoC容器支持AOP(面向切面编程),可以将横切关注点(如日志、事务、安全等)从业务逻辑中分离出来,从而提高了代码的可重用性和可维护性。

  6. 事件传播:Spring IoC容器可以将事件传播给监听器,从而实现了组件之间的解耦。

总之,Spring IoC容器是Spring框架的核心部分,它提供了对象的创建和管理、依赖注入、生命周期管理、配置管理、AOP支持、事件传播等功能,从而实现了对象之间的解耦,提高了应用程序的可维护性和可测试性。

ApplicationContext 容器实现类

类型名简介
ClassPathXmlApplicationContext从classpath中加载XML配置文件创建ApplicationContext容器。
FileSystemXmlApplicationContext从文件系统中加载XML配置文件创建ApplicationContext容器。
AnnotationConfigApplicationContext基于注解配置创建ApplicationContext容器。
WebApplicationContext
专门为 Web 应用准
备,基于 Web 环境
创建 IOC 容器对
象,并将对象引入存
ServletContext
域中。

1.2.2 SpringIoC容器管理配置方式

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和依赖关系的配置。

现在主要使用注解方式和Java配置类方式来进行Spring框架的配置。

1.2.3 Spring IoC / DI概念总结

Spring IoC(Inversion of Control,控制反转)和DI(Dependency Injection,依赖注入)是Spring框架的核心概念,它们是实现Spring框架轻量级、松耦合的关键。

  1. IoC(控制反转):IoC是一种设计模式,它将对象的创建和依赖关系的管理交给容器来处理,从而实现了对象之间的解耦。在Spring框架中,IoC容器负责管理应用程序中的对象及其依赖关系,将对象之间的依赖关系交由容器来处理,从而实现了对象之间的解耦。

  2. DI(依赖注入):DI是IoC的一种实现方式,它通过将组件之间的依赖关系注入到对象中,从而实现了对象之间的解耦。在Spring框架中,DI是通过容器自动将组件之间的依赖关系注入到对象中来实现的,从而实现了对象之间的解耦。

  3. Spring IoC容器:Spring IoC容器是Spring框架的核心部分,它负责管理应用程序中的对象及其依赖关系。Spring IoC容器通过读取配置文件或注解来创建和管理对象,将对象之间的依赖关系交由容器来处理,从而实现了对象之间的解耦。

  4. Spring DI注入方式:Spring框架提供了多种DI注入方式,包括构造函数注入、Setter方法注入、接口注入、注解注入等。其中,注解注入是最常用的注入方式,它通过在Bean类上使用注解来实现依赖注入。

总之,Spring IoC和DI是Spring框架的核心概念,它们通过将对象的创建和依赖关系的管理交给容器来处理,从而实现了对象之间的解耦。Spring IoC容器负责管理应用程序中的对象及其依赖关系,而DI是通过容器自动将组件之间的依赖关系注入到对象中来实现的。Spring框架提供了多种DI注入方式,其中注解注入是最常用的注入方式。

2. Spring IoC实践和应用

2.1 Spring IoC / DI 实现步骤

  1. 定义Bean:在Spring中,Bean是指被Spring IoC容器管理的对象。我们需要定义Bean的类,并在类上添加相应的注解,如@Component、@Service、@Repository、@Controller等。

  2. 配置Bean:我们需要在Spring配置文件或Java配置类中配置Bean的信息,包括Bean的名称、作用域、依赖关系等。

  3. 创建IoC容器:Spring IoC容器是负责管理Bean的容器,我们需要在Spring配置文件或Java配置类中创建IoC容器。

  4. 获取Bean:我们可以通过IoC容器获取Bean的实例,从而使用Bean的功能。

  5. DI注入:Spring提供了多种DI注入方式,包括构造函数注入、Setter方法注入、接口注入、注解注入等。我们需要在Bean类中定义相应的构造函数或Setter方法,并在Spring配置文件或Java配置类中配置依赖关系,从而实现DI注入。

  6. 销毁Bean:在Spring IoC容器销毁时,会自动销毁所有的Bean。我们可以在Bean类中定义相应的销毁方法,并在Spring配置文件或Java配置类中配置销毁方法,从而实现Bean的销毁。

 2.2 基于XML配置方式组件管理

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

1.目标

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

2.思路

3.项目准备

  1.  创建maven工程
  2. 导入SpringIoC相关依赖(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>

 4. 基于无参数构造函数

当使用构造函数方法创建一个 bean(组件对象)时,所有普通类都可以被 Spring 使用并与之兼容。这意味着正在开发的类不需要实现任何特定的接口或以特定的方式进行编码,只需指定 Bean 类信息即可。然而,默认情况下,我们需要一个默认(空)构造函数来创建 bean。

如果类中没有默认构造函数,Spring 将无法创建该 bean。如果需要使用带参数的构造函数来创建 bean,可以使用 Spring 的构造函数注入来实现。

         1.准备组件

public class HelloComponent{

    //默认包含无参数构造函数
    public void work(){
        System.out.println("HelloComponent");
    }

}

        2.xml配置文件编写(resources/****.xml)

<bean id="helloComponent"class="com.csi.ioc.HelloComponent"/>
bean标签是用于通过配置告诉 IOC 容器需要创建对象的组件信息的。其中,id属性用于给 bean 分配唯一的标识符,方便在后期获取 bean;class属性用于指定组件类的全限定符。需要注意的是,当前组件类必须包含无参数构造函数,否则 Spring 将无法创建该 bean

5. 基于静态工厂方法实例化

        1.准备组件类

public class ClientService {

    private static ClientService clientService = new ClientService();

    private ClientService() {}

    public static ClientService createInstance() {
      return clientService;
    }

}

         2. xml配置文件编写

<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>

class属性用于指定组件类的全限定符,而factory-method属性用于指定静态工厂方法的名称。需要注意的是,该方法必须是 static 方法,并且必须属于指定的工厂类

6. 基于实例工厂方法实例化

         1.准备组件类

public class DefaultServiceLocator {

 private static ClientServiceImplclientService = new ClientServiceImpl();

 public ClientService createClientServiceInstance() {
     return clientService;
 }

}

        2. xml配置文件编写

<!-- 将工厂类进行ioc配置 -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator"></bean>

<!-- 根据工厂对象的实例工厂方法进行实例化组件对象 -->
<bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>

factory-bean属性用于指定当前容器中工厂 bean 的名称,而factory-method属性用于指定实例工厂方法的名称。需要注意的是,实例方法必须是非 static 的,并且必须属于指定的工厂 bean

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

1. 目标

是通过配置文件实现 IoC 容器中 bean 之间的引用,即依赖注入(DI)配置。

涉及场景:基于构造函数的依赖注入和基于 Setter 的依赖注入;通过配置文件,可以指定 bean 之间的依赖关系,使得容器可以自动将依赖的 bean 注入到目标 bean 中,从而实现组件之间的协作。

2.思路

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

        1.准备组件类

public class UserDao {
}

public class UserService {

    private UserDao userDao;

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }
}

        2.编写配置文件

<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 的标识作为参数。通过配置constructor-arg标签,可以实现基于构造函数的依赖注入。例如,可以在构造函数中声明一个参数,然后使用constructor-arg标签指定该参数的值为另一个 bean 的引用,从而实现对该 bean 的依赖注入

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

        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;
    }

}

        2.配置文件

        场景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标签用于指定构造函数的参数和对应的值。其中,name属性用于指定参数名,index属性用于指定参数角标,value属性用于指定普通属性值。通过配置constructor-arg标签,可以实现基于构造函数的依赖注入。例如,可以在构造函数中声明一个参数,然后使用constructor-arg标签指定该参数的值为另一个 bean 的引用,从而实现对该 bean 的依赖注入

5. 基于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;
    }

}

        2.配置文件

<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 方法标识,ref 属性代表引用 bean 的标识 id,value 属性代表基本属性值。通过配置 property 标签,可以实现基于 setter 方法的依赖注入。例如,可以在类中声明一个属性,然后使用 property 标签指定该属性的值为另一个 bean 的引用,从而实现对该 bean 的依赖注入

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

1.容器实例化

方式1:实例化并且指定配置文件

// 参数:String...locations 传入一个或者多个配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml","daos.xml");

方式2:先实例化,再指定配置文件,最后刷新容器触发 Bean 实例化动作

ApplicationContext context = new ClassPathXmlApplicationContext();
// 设置配置文件,方法参数为可变参数,可以设置一个或者多个配置
context.setConfigLocations("services.xml", "daos.xml");
// 后配置的文件,需要调用 refresh 方法,触发刷新配置
context.refresh();

2.Bean对象读取

方式1:根据 id 获取

// 没有指定类型,返回为 Object,需要类型转换
HelloComponent helloComponent = (HelloComponent) iocContainer.getBean("bean的id标识");
// 使用组件对象
helloComponent.work();

方式2:根据类型获取

// 根据类型获取,但是要求同类型(当前类、或者之类、或者接口的实现类)只能有一个对象交给 IoC 容器管理
// 配置两个或者以上会出现 org.springframework.beans.factory.NoUniqueBeanDefinitionException 问题
HelloComponent helloComponent = iocContainer.getBean(HelloComponent.class);
helloComponent.work();

方式3:根据 id 和类型获取

HelloComponent helloComponent = iocContainer.getBean("bean的id标识", HelloComponent.class);
helloComponent.work();

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

1. 组件周期方法配置

        1. 周期方法声明

public class BeanOne {
    // 生命周期方法要求:方法命名随意,但是要求方法必须是 public void 无形参列表
    public void init() {
        // 初始化逻辑
    }
}

public class BeanTwo {
    public void cleanup() {
        // 释放资源逻辑
    }
}

        2.周期方法配置

<beans>
    <bean id="beanOne" class="examples.BeanOne" init-method="init" />
    <bean id="beanTwo" class="examples.BeanTwo" destroy-method="cleanup" />
</beans>
2. 组件作用域配置

Bean 作用域的概念

<bean> 标签声明 Bean,只是将 Bean 的信息配置给 Spring IoC 容器。在 IoC 容器中,这些 <bean> 标签对应的信息会转换成 Spring 内部的 BeanDefinition 对象,BeanDefinition 对象内包含了定义的信息(id、class、属性等等)。这意味着,BeanDefinition 与类的概念一样,Spring IoC 容器可以根据 BeanDefinition 对象反射创建多个 Bean 对象实例。具体创建多少个 Bean 的实例对象,由 Bean 的作用域 Scope 属性指定

在 Spring 中,Bean 的作用域 Scope 属性可以设置为以下几种可选值:

1. singleton:单例模式,每个 Bean 在 Spring 容器中只有一个实例对象。
2. prototype:原型模式,每次从 Spring 容器中获取 Bean 时都会创建一个新的实例对象。
3. request:请求模式,每个 HTTP 请求都会创建一个新的实例对象,该实例对象仅在当前 HTTP 请求内有效。
4. session:会话模式,每个 HTTP 会话都会创建一个新的实例对象,该实例对象仅在当前 HTTP 会话内有效。
5. globalSession:全局会话模式,仅在使用 Portlet 时有效,每个 Portlet 应用都会创建一个新的实例对象,该实例对象仅在当前 Portlet 应用内有效。
6. application:应用模式,每个 ServletContext 都会创建一个新的实例对象,该实例对象仅在当前 ServletContext 内有效。

可以通过在 <bean> 标签中设置 scope 属性来指定 Bean 的作用域。例如,设置 singleton 作用域:

<bean id="myBean" class="com.example.MyBean" scope="singleton">
    <!-- Bean 的属性配置 -->
</bean>

3. 作用域配置

    配置scope范围 

<!-- Bean 的作用域 -->
<!-- 准备两个引用关系的组件类即可 -->
<!-- scope 属性:取值 singleton(默认值),Bean 在 IoC 容器中只有一个实例,IoC 容器初始化时创建对象 -->
<!-- scope 属性:取值 prototype,Bean 在 IoC 容器中可以有多个实例,getBean() 时创建对象 -->
<bean id="happyMachine8" scope="prototype" class="com.csi.ioc.HappyMachine">
    <property name="machineName" value="happyMachine" />
</bean>

<bean id="happyComponent8" scope="singleton" class="com.csi.ioc.HappyComponent">
    <property name="componentName" value="happyComponent" />
</bean>

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

1. FactoryBean 简介

FactoryBean 是 Spring 中的一个特殊接口,用于在 Spring 容器中创建复杂的 Bean 对象。它允许开发人员自定义 Bean 的创建过程,从而实现更加灵活的 Bean 创建方式。

FactoryBean 接口定义了两个方法:

1. getObject():用于返回创建的 Bean 对象。
2. getObjectType():用于返回创建的 Bean 对象的类型。

通过实现 FactoryBean 接口,开发人员可以自定义 Bean 的创建过程,例如从外部文件中读取配置信息、动态生成代理对象等。同时,FactoryBean 也可以用于创建一些特殊的 Bean,例如 Spring 中的 JdbcTemplate、NamedParameterJdbcTemplate 等。

在 Spring 配置文件中,可以通过 <bean> 标签来配置 FactoryBean。需要注意的是,由于 FactoryBean 本身也是一个 Bean,因此在配置时需要指定 FactoryBean 的类型,而不是创建的 Bean 的类型。例如:

<bean id="myFactoryBean" class="com.example.MyFactoryBean" />

<bean id="myBean" class="com.example.MyBean" factory-bean="myFactoryBean" factory-method="createBean" />

在这个配置文件中,定义了一个名为 myFactoryBean 的 FactoryBean,它的类型为 com.example.MyFactoryBean。同时,还定义了一个名为 myBean 的 Bean,它的类型为 com.example.MyBean,创建过程由 myFactoryBean 的 createBean 方法完成。

2. FactoryBean 使用场景

FactoryBean 可以用于创建一些特殊的 Bean,例如 Spring 中的 JdbcTemplate、NamedParameterJdbcTemplate 等。除此之外,FactoryBean 还可以用于以下场景:

1. 创建动态代理对象:通过实现 FactoryBean 接口,可以自定义创建动态代理对象的过程,从而实现更加灵活的代理方式。
2. 创建多实例 Bean:通过实现 FactoryBean 接口,可以在 getObject() 方法中创建多个实例对象,从而实现多实例 Bean 的创建方式。
3. 创建复杂的 Bean:通过实现 FactoryBean 接口,可以在 getObject() 方法中创建复杂的 Bean 对象,例如从外部文件中读取配置信息、动态生成代理对象等。
4. 创建 Bean 的时候需要进行一些特殊的处理:例如在创建 Bean 的时候需要进行一些特殊的初始化操作,或者需要根据不同的条件创建不同的 Bean 对象等。

总之,FactoryBean 可以用于创建一些比较复杂的 Bean 对象,从而实现更加灵活的 Bean 创建方式。

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;
    }
}

        2.配置FactoryBean实现类

<!-- FactoryBean机制 -->
<!-- 这个 <bean> 标签中 class 属性指定的是 HappyFactoryBean,但是将来从这里获取的 Bean 是 HappyMachine 对象 -->
<bean id="happyMachine7" class="com.csi.ioc.HappyFactoryBean">
    <!-- <property> 标签仍然可以用来通过 setXxx() 方法给属性赋值 -->
    <property name="machineName" value="iceCreamMachine"/>
</bean>
4. FactoryBean BeanFactory 区别

FactoryBean 和 BeanFactory 是两个不同的概念。

BeanFactory 是 Spring 框架中的一个接口,它是 Spring 容器的核心接口,用于管理 Bean 的生命周期和依赖关系。BeanFactory 提供了一些方法,例如 getBean()、containsBean()、getBeanDefinition() 等,用于获取 Bean 实例、判断 Bean 是否存在、获取 Bean 的定义信息等。

FactoryBean 是 Spring 框架中的一个接口,它是一个工厂 Bean,用于创建复杂的 Bean 对象。FactoryBean 提供了三个方法,分别是 getObject()、getObjectType() 和 isSingleton(),用于创建 Bean 实例、获取 Bean 的类型和判断 Bean 是否为单例。

因此,BeanFactory 是 Spring 容器的核心接口,用于管理 Bean 的生命周期和依赖关系;而 FactoryBean 是一个工厂 Bean,用于创建复杂的 Bean 对象。在 Spring 中,BeanFactory 和 FactoryBean 是两个不同的概念,它们的作用和用途也不同。

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

4.2.6 实验六: 基于 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, '高中二班');

2. 项目准备

        1. 依赖导入
<dependencies>
    <!-- Spring Context 依赖 -->
    <!-- 当你引入 Spring Context 依赖之后,表示将 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>
    
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.20</version>
        <scope>provided</scope>
    </dependency>

</dependencies>
        2. 实体类准备
import lombok.Data;

@Data
public class Student {
    private Integer id;
    private String name;
    private String gender;
    private Integer age;
    private String classes;
}

3. JdbcTemplate技术讲解

提取数据库连接信息
url=jdbc:mysql://localhost:3306/studb
driver=com.mysql.cj.jdbc.Driver
username=root
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="${url}"/>
        <property name="driverClassName" value="${driver}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
    </bean>

    <!-- 配置 JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 装配数据源 -->
        <property name="dataSource" ref="druidDataSource"/>
    </bean>

</beans>

添加日志功能 
<dependencies>
    <!-- SLF4J API -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.8.0-beta4</version>
    </dependency>

    <!-- SLF4J Log4j 适配器 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.8.0-beta4</version>
    </dependency>

    <!-- Log4j 核心库 -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
</dependencies>
# 设置日志级别为 debug,输出到 stdout、D 和 E
log4j.rootLogger = debug, stdout, D, E

# 输出信息到控制台
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n

# 输出 DEBUG 级别以上的日志到 ${spring.root}/logs/log.log
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = ${spring.root}/logs/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n

# 输出 ERROR 级别以上的日志到 ${spring.root}/logs/error.log
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File = ${spring.root}/logs/error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
基于 jdbcTemplate CRUD 使用
public class JdbcTemplateTest {
    /**
     * 使用 JdbcTemplate 进行 DML 动作
     */
    @Test
    public void testDML() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-ioc.xml");

        JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);

        // 执行插入一条学员数据
        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);
    }
}
/**
 * 查询单条实体对象
 */
@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);
}
4. 三层架构搭建和实现
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);
    }
}
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="${csi.url}"/>
        <property name="driverClassName" value="${csi.driver}"/>
        <property name="username" value="${csi.username}"/>
        <property name="password" value="${csi.password}"/>
    </bean>

    <!-- 配置 JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 装配数据源 -->
        <property name="dataSource" ref="druidDataSource"/>
    </bean>

    <bean id="studentDao" class="com.csi.dao.impl.StudentDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate" />
    </bean>

    <bean id="studentService" class="com.csi.service.impl.StudentServiceImpl">
        <property name="studentDao" ref="studentDao" />
    </bean>

    <bean id="studentController" class="com.csi.controller.StudentController">
        <property name="studentService" ref="studentService" />
    </bean>

</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. 注入的属性必须添加 setter 方法,这增加了代码的复杂度和维护成本,同时也增加了代码的耦合度。
2. 配置文件和 Java 代码分离,虽然可以提高代码的可读性和可维护性,但是编写起来不是很方便,需要手动编写 XML 配置文件。
3. XML 配置文件解析效率低,因为 XML 文件需要解析成内存中的对象,这会占用一定的时间和内存资源。同时,XML 文件的结构也比较复杂,需要花费一定的时间和精力来编写和维护。

综上所述,XML IoC 方式虽然是 Spring 框架最早的实现方式之一,但是它存在一些问题,例如代码结构乱、配置文件和 Java 代码分离、解析效率低等。因此,在实际开发中,可以考虑使用其他的 IoC 实现方式,例如注解方式、Java Config 方式等。

2.3 基于 注解 方式管理 Bean

2.3.1 实验一: Bean注解标记和扫描 (IoC)

1.准备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.组件添加标记注解 

@Component
// 该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。使用时只需将该注解标注在相应类上即可。
@Repository
// 该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Service
// 该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Controller
// 该注解通常作用在控制层(如 SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
  3. 配置文件确定扫描范围
情况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.csi.controller,com.csi.service 等 -->
    <context:component-scan base-package="com.csi.components"/>
</beans>

 情况2:指定排除组件

<!-- 情况二:指定排除的组件 -->
<context:component-scan base-package="com.csi.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.csi.ioc.components" use-default-filters="false">
    <!-- context:include-filter 标签:指定在原有扫描规则的基础上追加的规则 -->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

2.3.2 实验二: 组件(Bean)作用域和周期方法注解

1. 组件周期方法配置

周期方法声明
@Component
public class MyComponent {
    @PostConstruct
    public void init() {
        // 初始化逻辑
    }
}


@Component
public class MyComponent {
    @PreDestroy
    public void cleanup() {
        // 释放资源逻辑
    }
}
2. 组件作用域配置

在 Spring 中,可以通过作用域来控制 bean 的生命周期和实例化方式。常用的作用域有 singleton 和 prototype。

singleton 是默认的作用域,表示在 IOC 容器中,这个 bean 的对象始终为单实例。在 IOC 容器初始化时就会创建这个 bean 的实例,之后每次获取这个 bean 时都会返回同一个实例。

prototype 表示在 IOC 容器中有多个实例,每次获取这个 bean 时都会创建一个新的实例。在 IOC 容器初始化时不会创建这个 bean 的实例,而是在每次获取时才会创建。

除了 singleton 和 prototype 之外,还有 request 和 session 两个作用域,它们通常用于 Web 应用程序中。request 表示在请求范围内有效的实例,每次请求都会创建一个新的实例;session 表示在会话范围内有效的实例,每次会话都会创建一个新的实例。

需要注意的是,作用域只是控制 bean 的实例化方式和生命周期,并不影响 bean 的依赖注入和其他属性。

@Component
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON)
//单例,默认值
public class MyComponent {
    @PostConstruct
    public void init() {
        // 初始化逻辑
    }
}


@Component
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
//多例 二选一
public class MyComponent {
    @PostConstruct
    public void init() {
        // 初始化逻辑
    }
}

2.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();
        }
    }
    
    @Service("smallDog")
    @Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON)
    public class SoldierService {
        private SoldierDao soldierDao;
        public void getMessage() {
            soldierDao.getMessage();
        }
    }
    
    @Repository
    @Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public class SoldierDao {
        public void getMessage() {
            System.out.print("I am a soldier");
        }
    }
    

2. 自动装配实现 

        Controller装配Service

@Controller(value = "tianDog")
public class SoldierController {

    @Autowired
    private SoldierService soldierService;

    public void getMessage() {
        soldierService.getMessage();
    }

}
        给Service 装配 Dao
@Service("smallDog")
public class SoldierService {
    @Autowired
    @Qualifier("bigDog")
    private SoldierDao soldierDao;
    
    public void getMessage() {
        soldierDao.getMessage();
    }
}
佛系装配
@Autowired 注解设置 required = false 属性表示:能装就装,装不上就不装。但是实际开发时,基本上所有需要装配组件的地方都是必须装配的,用不上这个属性,禁止使用!
@Controller(value = "tianDog")
public class SoldierController {

    // 给@Autowired注解设置required = false属性表示:能装就装,装不上就不装
    @Autowired(required = false)
    private ISoldierService soldierService;

}

 3. 扩展JSR-250注解@Resource

@Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖:【高于JDK11或低于JDK8需要引入以下依赖

<dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotationapi</artifactId>
    <version>2.1.1</version>
</dependency>
import jakarta.annotation.Resource;
import org.springframework.stereotype.Controller;

@Controller
public class XxxController {
    /**
     * 1. 如果没有指定name,先根据属性名查找IoC中组件xxxService
     * 2. 如果没有指定name,并且属性名没有对应的组件,会根据属性类型查找
     * 3. 可以指定name名称查找!
     * @Resource(name='test') == @Autowired + @Qualifier(value='test')
     */
    @Resource
    private XxxService xxxService;

    public void show() {
        System.out.println("XxxController.show");
        xxxService.show();
    }
}

2.3.4 实验四: Bean属性赋值:基本类型属性赋值 (DI)

声明外部配置(application.properties )

catalog.name=MovieCatalog
xml 引入外部配置
<!-- 引入外部配置文件-->
<context:property-placeholder location="application.properties" />
@Value 注解读取配置
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 普通的组件
 */
@Component
public class CommonComponent {
    /**
     * 情况1: ${key} 取外部配置key对应的值!
     * 情况2: ${key:defaultValue} 没有key,可以给与默认值
     */
    @Value("${catalog:hahaha}")
    private String catalog;

    public void show() {
        System.out.println("catalog: " + catalog);
    }
}

2.3.5 实验五: 基于注解+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, '高中二班');
2. 项目准备
        1. 依赖导入
<dependencies>
    <!-- Spring Context 依赖 -->
    <!-- 当你引入 Spring Context 依赖之后,表示将 Spring 的基础依赖引入了 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.9</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>5.3.9</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.20</version>
        <scope>provided</scope>
    </dependency>

</dependencies>

        2. 实体类准备

import lombok.Data;

@Data
public class Student {
    private Integer id;
    private String name;
    private String gender;
    private Integer age;
    private String classes;
}

 3. 三层架构搭建和实现

        1. 持久层
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;

@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";
        return jdbcTemplate.query(sql, BeanPropertyRowMapper.newInstance(Student.class));
    }
}
        2. 业务层
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class StudentServiceImpl implements StudentService {

    @Autowired
    private StudentDao studentDao;

    /**
     * 查询全部学员业务
     * @return
     */
    @Override
    public List<Student> findAll() {
        return studentDao.queryAll();
    }
}
        3. 表述层
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import java.util.List;

@Controller
public class StudentController {

    @Autowired
    private StudentService studentService;

    @GetMapping("/students")
    public String findAll(Model model) {
        List<Student> studentList = studentService.findAll();
        model.addAttribute("students", studentList);
        return "students";
    }
}
4. 三层架构 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="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <bean class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource" />
    </bean>

</beans>

5. 运行测试

public class ControllerTest {

    @Test
    public void testRun() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-ioc.xml");
        StudentController studentController = applicationContext.getBean(StudentController.class);
        studentController.findAll();
        
    }
}
6. 注解 +XML IoC 方式问题总结
  • 自定义类可以使用注解方式,但是第三方依赖的类依然使用XML方式
  • XML格式解析效率低

2.4 基于 配置类 方式管理 Bean 

2.4.1 完全注解开发理解

Spring 完全注解配置(Fully Annotation-based Configuration)是指通过 Java 配置类来配置 Spring 应用程序,使用注解来替代原本在 XML 配置文件中的配置。相对于 XML 配置,完全注解配置具有以下优点:

  1. 更强的类型安全性:使用注解方式进行配置,可以在编译期间就发现类型错误,避免在运行时出现类型转换异常等问题。

  2. 更好的可读性:使用注解方式进行配置,可以将配置信息直接写在代码中,避免了 XML 配置文件的繁琐和冗长,使得配置更加直观和易于理解。

  3. 更加灵活:使用注解方式进行配置,可以根据需要进行动态配置,避免了 XML 配置文件需要重启应用程序才能生效的问题。

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

配置类 + 注解方式(完全注解方式)
配置类
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@PropertySource("classpath:application.properties")
@ComponentScan(basePackages = {"com.csi.components"})
public class MyConfiguration {
    
}
测试创建 IoC 容器
// 使用 AnnotationConfigApplicationContext 根据配置类创建 IOC 容器对象
ApplicationContext iocContainerAnnotation = new AnnotationConfigApplicationContext(MyConfiguration.class);

完全注解配置是一种更加便捷、灵活和可读性更高的配置方式,可以帮助我们更好地管理和维护 Spring 应用程序。在完全注解配置中,我们可以使用以下注解来进行配置:

  1. @Configuration:指定一个类为配置类,可以添加配置注解,替代配置 XML 文件。

  2. @ComponentScan:使用注解进行组件扫描,替代 context:component-scan 标签实现注解扫描。

  3. @PropertySource:使用注解读取外部配置文件,替代 context:property-placeholder 标签。

配合 IoC/DI 注解,我们可以进行完全注解开发,避免了 XML 配置文件的繁琐和冗长,使得配置更加直观和易于理解。同时,我们还可以根据需要进行动态配置,避免了 XML 配置文件需要重启应用程序才能生效的问题。

总之,完全注解配置是一种更加灵活、可读性更高的配置方式,可以帮助我们更好地管理和维护 Spring 应用程序,提高开发效率和代码质量。

2.4.3 实验二:@Bean定义组件

@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 初始化和销毁方法指定

@Bean 注解可以用于标注一个方法,使得该方法返回的对象可以被 Spring 容器管理。在使用 @Bean 注解时,我们可以使用 initMethod 和 destroyMethod 属性来指定初始化方法和销毁方法。

1. initMethod:指定 Bean 对象的初始化方法,该方法将在 Bean 对象创建完成后立即执行。

2. destroyMethod:指定 Bean 对象的销毁方法,该方法将在 Bean 对象被销毁前执行。

以下是一个示例代码:

@Configuration
public class AppConfig {
    
    @Bean(initMethod = "init", destroyMethod = "destroy")
    public Thing thing() {
        return new Thing();
    }
    
}

在上面的代码中,我们使用 @Bean 注解标注了一个名为 thing 的方法,并使用 initMethod 和 destroyMethod 属性来指定初始化方法和销毁方法。在这个示例中,我们假设 Thing 类中有 init() 和 destroy() 两个方法,分别用于初始化和销毁 Bean 对象。

需要注意的是,如果我们不指定 destroyMethod 属性,Spring 容器将会尝试调用 Bean 对象的 close() 或 shutdown() 方法来销毁 Bean 对象。如果 Bean 对象没有实现这些方法,Spring 容器将会抛出异常。因此,我们应该始终指定 destroyMethod 属性,以确保 Bean 对象能够正确地被销毁。

@Bean Scope 作用域
默认作用域为 singleton ,但 可以使用 @Scope 注释覆盖此范 围,如以下示例所示:
@Configuration
public class MyConfiguration {
    
    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
    
}
@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(HappyMachine happyMachine) {
        HappyComponent happyComponent = new HappyComponent();
        happyComponent.setHappyMachine(happyMachine);
        return happyComponent;
    }
    
}
方案 2 参数引用法:通过方法参数传递 Bean 实例的引用来解决 Bean 实 例之间的依赖关系,例如:
@Configuration
public class JavaConfig {
    
    @Bean
    public HappyMachine happyMachine() {
        return new HappyMachine();
    }
    
    /**
     * 可以直接在形参列表接收 IoC 容器中的 Bean
     * 
     * @param happyMachine
     * @return
     */
    @Bean
    public HappyComponent happyComponent(HappyMachine happyMachine) {
        HappyComponent happyComponent = new HappyComponent();
        happyComponent.setHappyMachine(happyMachine);
        return happyComponent;
    }
    
}

2.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);
    // 现在 A 和 B 两个 Bean 都可用了...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}

2.4.6 实验五:基于注解+配置类方式整合三层架构组件

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, '高中二班');
2. 项目准备
依赖导入
<dependencies>
    <!-- Spring Context 依赖 -->
    <!-- 当你引入 Spring Context 依赖之后,表示将 Spring 的基础依赖引入了 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.9</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>5.3.9</version>
    </dependency>

    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.20</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

 实体类准备

import lombok.Data;

@Data
public class Student {
    private Integer id;
    private String name;
    private String gender;
    private Integer age;
    private String classes;
}
三层架构搭建和实现
1. 持久层
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface StudentRepository extends JpaRepository<Student, Integer> {
}
2. 业务层
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class StudentService {
    @Autowired
    private StudentRepository studentRepository;

    public List<Student> findAll() {
        return studentRepository.findAll();
    }
}
3. 表述层
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class StudentController {
    @Autowired
    private StudentService studentService;

    @GetMapping("/students")
    public List<Student> findAll() {
        return studentService.findAll();
    }
}
三层架构 IoC 配置类
@Configuration
@ComponentScan(basePackages = "com.csi")
@PropertySource("classpath:jdbc.properties")
public class JavaConfig {
    @Value("${csi.url}")
    private String url;

    @Value("${csi.driver}")
    private String driver;

    @Value("${csi.username}")
    private String username;

    @Value("${csi.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;
    }
}
3. 运行测试
public class ControllerTest {

    @Test
    public void testRun() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(JavaConfig.class);
        StudentController studentController = applicationContext.getBean(StudentController.class);
        studentController.findAll();
    }
}
4. 注解 + 配置类 IoC 方式总结
  • 完全摒弃了XML配置文件
  • 自定义类使用IoCDI注解标记
  • 第三方类使用配置类声明方法+\@Bean方式处理
  • 完全注解方式(配置类+注解)是现在主流配置方式

2.5 三种配置方式总结 

2.5.1 XML方式配置总结

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

2.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 对象。

2.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.csi.components"}) 替代。
  6. <context:property-placeholder> 引入外部配置文件使用 @PropertySource({"classpath:application.properties","classpath:jdbc.properties"}) 替代。
  7. <bean> 标签使用 @Bean 注解和方法实现。
  8. IoC 容器实现选择 AnnotationConfigApplicationContext 对象。

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

导入相关依赖
<!-- JUnit 5 测试 -->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.3.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>6.0.6</version>
    <scope>test</scope>
</dependency>
整合测试注解使用
@SpringJUnitConfig(value = {BeanConfig.class})
public class Junit5IntegrationTest {
    @Autowired
    private User user;

    @Test
    public void testJunit5() {
        System.out.println(user);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值