Spring笔记(一)——Spring IoC详解


一、Spring

1.1 Spring与SpringFramewrok概念

广义的 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 框架。

1.2 SpringFramework主要功能模块

在这里插入图片描述

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

二、Spring IoC容器

2.1 Spring IoC容器概念

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

组件:整个项目是由各种组件搭建而成的。
在这里插入图片描述

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

SpringIoc容器接口

BeanFactory 接口提供了一种高级配置机制,能够管理任何类型的对象,它是SpringIoC容器标准化超接口!

ApplicationContextBeanFactory 的子接口。它扩展了以下功能:

  • 更容易与 Spring 的 AOP 功能集成
  • 消息资源处理(用于国际化)
  • 特定于应用程序给予此接口实现,例如Web 应用程序的 WebApplicationContext
    简而言之, BeanFactory 提供了配置框架和基本功能,而 ApplicationContext 添加了更多特定于企业的功能。 ApplicationContextBeanFactory 的完整超集!

ApplicationContext容器实现类

最常用的是ClassPathXmlApplicationContextAnnotationConfigApplicationContext

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

SpringIoC容器管理配置方式

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和依赖关系的配置。
    为了迎合当下开发环境,我们将以配置类+注解方式为主进行讲解!

2.3 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实践和应用

3.1 Spring IoC / DI 实现步骤

  1. 配置元数据(配置)

配置元数据,既是编写交给SpringIoC容器管理组件的信息,配置方式有三种(XML、注解、Java配置类)。

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

  <bean id="..." class="...">
    <!-- collaborators and configuration for this bean go here -->
  </bean>

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

  • id 属性是标识单个 Bean 定义的字符串。
  • class 属性定义 Bean 的类型并使用完全限定的类名。
  1. 实例化IoC容器

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

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

//实例化ioc容器,读取外部XML配置文件,最终会在容器内进行ioc和di动作
ApplicationContext context = 
           new ClassPathXmlApplicationContext("services.xml", "daos.xml");
  1. 获取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();

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

3.2.1 Bean(组件)信息声明配置(IoC)

  1. 创建Maven工程,导入SpringIoC相关依赖

pom.xml中导入依赖

    <!--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>
  1. 准备无参数构造函数/静态工厂方法实例化/实例工厂方法实例化

public class HappyComponent {

//默认包含无参数构造函数

public void doWork() {
    System.out.println("HappyComponent.doWork");
}

// 静态工厂方法
public class ClientService {
    private static ClientService clientService = new ClientService();

    private ClientService() {}
    public static ClientService createInstance() {
        return clientService;
    }
}
// 实例工厂方法
public class DefaultServiceLocator {
    private static ClientServiceImplclientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

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

// 静态工厂方法
<bean id="clientService"  class="examples.ClientService"
  factory-method="createInstance"/>
-	factory-method: 指定静态工厂方法,注意,该方法必须是static方法。

// 实例工厂方法
<!-- 将工厂类进行ioc配置 -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
</bean>
<!-- 根据工厂对象的实例工厂方法进行实例化组件对象 -->
<bean id="clientService"
  factory-bean="serviceLocator"
  factory-method="createClientServiceInstance"/>
-	factory-bean属性:指定当前容器中工厂Bean 的名称。

3.2.2 Bean(组件)依赖注入配置(DI)

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

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

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

// Setter
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. 编写XML配置文件
<!-- 构造函数 -->
<!-- 场景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>


<!-- Setter -->
<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方法对应的属性赋值
  • property 标签: name属性代表set方法标识、ref代表引用bean的标识id、value属性代表基本属性值

3.2.3 IoC容器创建和使用

  1. 容器实例化
//实例化并且指定配置文件
//参数:String...locations 传入一个或者多个配置文件
ApplicationContext context = 
           new ClassPathXmlApplicationContext("services.xml", "daos.xml");
  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();

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

  1. 组件周期方法配置

    1. 周期方法概念

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

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

    2. 周期方法声明

      public class BeanOne {
      
        //周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表
        public void init() {
          // 初始化逻辑
        }
      }
      
      public class BeanTwo {
      
        public void cleanup() {
          // 释放资源逻辑
        }
      }
      
    3. 周期方法配置

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

    1. Bean作用域概念

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

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

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

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

    2. 作用域配置

      配置scope范围

      <!--bean的作用域 
          准备两个引用关系的组件类即可!!
      -->
      <!-- scope属性:取值singleton(默认值),bean在IOC容器中只有一个实例,IOC容器初始化时创建对象 -->
      <!-- scope属性:取值prototype,bean在IOC容器中可以有多个实例,getBean()时创建对象 -->
      <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>
      
    3. 作用域测试

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

3.3 基于 注解 方式管理 Bean

3.3.1 Bean注解标记和扫描 (IoC)

  1. 与XML一样准备项目的pom.xml文件

  2. 正常准备组件类(Dao、Controller、Service组件类)

  3. 组件类添加注解标记

    Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。对于Spring使用IOC容器管理这些组件来说没有区别,也就是语法层面没有区别。所以@Controller、@Service、@Repository这三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。

注解说明
@Component该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。
@Repository该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Service该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Controller该注解通常作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

普通组件

/**
 * 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 {
}

  1. 配置文件扫描范围

情况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>

3.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 时
    3. 作用域配置

      @Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON) //单例,默认值
      @Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) //多例  二选一
      public class BeanOne {
      
        //周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表
        @PostConstruct  //注解制指定初始化方法
        public void init() {
          // 初始化逻辑
        }
      }
      

3.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. 工作流程

      • 首先根据所需要的组件类型到 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;
      

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

3.4 基于 配置类 方式管理 Bean

3.4.1 完全注解开发理解

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

两种方式思维转化

在这里插入图片描述

3.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注解,可以进行完整注解开发!

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

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

@Bean 注解支持指定任意初始化和销毁回调方法,非常类似于 Spring XML 在 bean 元素上的 init-methoddestroy-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();
  }
}
  1. @Bean Scope作用域

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

默认作用域为 singleton ,但您可以使用 @Scope 注释覆盖此范围,如以下示例所示:

@Configuration
public class MyConfiguration {

  @Bean
  @Scope("prototype")
  public Encryptor encryptor() {
    // ...
  }
}
  1. @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;
    }

}

3.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.classConfigB.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 类。

如有问题,欢迎指正!

  • 39
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值