spring-ioc,di

120 阅读9分钟

ioc和di的概述

在Spring框架中,IoC(Inversion of Control,控制反转)和 DI(Dependency Injection,依赖注入)是两个核心概念,用于降低组件之间的耦合度,提高代码的灵活性和可维护性。

  1. 控制反转(IoC)

    • IoC 是一种设计思想,它将原本由程序员自己创建和管理的对象的控制权交给了容器(如Spring容器),由容器来实例化和管理这些对象。
    • IoC 的核心思想是将应用程序的控制权从应用自身代码中反转到外部容器中,即由容器来负责组件的生命周期管理和依赖注入。
    • 在Spring中,IoC通过使用Bean容器(Bean Container)来实现,容器负责管理应用中的所有Bean对象,控制它们的创建、销毁和依赖关系。
  2. 依赖注入(DI)

    • DI 是实现IoC的一种方式,它指的是容器负责将组件所需要的依赖关系(如其他对象或数值)注入到组件中,而不是由组件自己创建这些依赖对象。
    • 通过依赖注入,组件不再关心如何获取依赖对象,而是让容器来管理这些依赖关系,从而降低了组件之间的耦合度。
    • Spring提供了多种依赖注入的方式,包括构造函数注入、Setter方法注入、接口注入等,开发人员可以根据实际情况选择合适的方式来实现依赖注入。

总结起来,IoC和DI是Spring框架的核心概念,通过控制反转和依赖注入,Spring框架实现了组件之间的解耦和灵活性,使得开发者能够更加专注于业务逻辑的实现,而不必过多关注组件的创建和管理。

基于xml文件的ioc和di的实现

实现IoC(控制反转)和DI(依赖注入)所需要以下步骤:

  1. 编写Bean类

    • 首先,需要编写Java类作为Spring管理的Bean,这些Bean类通常包含业务代码或数据。
  2. 配置Bean

    • 在Spring中,可以通过XML配置文件、Java注解或Java配置类来配置Bean。在配置文件或注解中指定Bean的名称、类型以及依赖关系。
  3. 创建Spring容器

    • 接下来,需要创建Spring容器(ApplicationContext),Spring容器负责管理Bean的生命周期和依赖注入。可以使用ClassPathXmlApplicationContext或AnnotationConfigApplicationContext等类来创建容器。
  4. 获取Bean

    • 通过Spring容器可以获取配置的Bean实例,无需手动创建对象,Spring容器会自动帮助我们创建并管理Bean对象。
  5. 依赖注入

    • Spring支持多种方式实现依赖注入,包括构造函数注入、Setter方法注入。
  6. 使用Bean

    • 一旦容器创建了Bean并注入了依赖,就可以直接使用这些Bean来实现业务逻辑。
  7. 关闭容器

    • 当应用程序结束时,需要关闭Spring容器以释放资源。可以调用容器的close()方法来关闭容器。

具体实现

ioc实现

<?xml version="1.0" encoding="UTF-8"?>
<!-- 此处为spring配置文件的约束 -->
<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="..." [1] class="..." [2]>  
    <!-- collaborators and configuration for this bean go here -->
  </bean>

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

di实现

<!-- 多参数时,可以按照相应构造函数的顺序注入数据 -->
<beans>
  <bean id="userService" class="x.y.UserService">
    <!-- value直接注入基本类型值 -->
    <constructor-arg  value="18"/>
    <constructor-arg  value="ergou"/>
    
    <constructor-arg  ref="userDao"/>
  </bean>
  <!-- 被引用类bean声明 -->
  <bean id="userDao" class="x.y.UserDao"/>
</beans>


<!--多参数时,也可以按照相应构造函数的名称注入数据 -->
<beans>
  <bean id="userService" class="x.y.UserService">
    <!-- value直接注入基本类型值 -->
    <constructor-arg name="name" value="ergou"/>
    <constructor-arg name="userDao" ref="userDao"/>
    <constructor-arg name="age"  value="18"/>
  </bean>
  <!-- 被引用类bean声明 -->
  <bean id="userDao" class="x.y.UserDao"/>
</beans>

<!-- 多参数时,可以按照相应构造函数的角标注入数据 
           index从0开始 构造函数(0,1,2....)
-->
<beans>
    <bean id="userService" class="x.y.UserService">
    <!-- value直接注入基本类型值 -->
    <constructor-arg index="1" value="ergou"/>
    <constructor-arg index="2" ref="userDao"/>
    <constructor-arg index="0"  value="18"/>
  </bean>
  <!-- 被引用类bean声明 -->
  <bean id="userDao" class="x.y.UserDao"/>
</beans>

ioc容器的创建与使用

创建ioc容器

//1:实例化并且指定配置文件
//参数:String...locations 传入一个或者多个配置文件
ApplicationContext context = 
           new ClassPathXmlApplicationContext("services.xml", "daos.xml");
           
//2:先实例化,再指定配置文件,最后刷新容器触发Bean实例化动作 [springmvc源码和contextLoadListener源码方式]  
ApplicationContext context = 
           new ClassPathXmlApplicationContext();   
//设置配置配置文件,方法参数为可变参数,可以设置一个或者多个配置
iocContainer1.setConfigLocations("services.xml", "daos.xml");
//后配置的文件,需要调用refresh方法,触发刷新配置
iocContainer1.refresh(); 



使用ioc容器


//方式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就可以认定为和类型匹配,能够获取到。





组件的周期方法

  1. 周期方法概念

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

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

使用spring周期方法

在Spring框架中,Bean的生命周期方法可以通过实现特定的接口或配置特定的注解来实现。常用的Spring周期方法包括以下几种:

  1. InitializingBean 和 DisposableBean 接口

    • 实现 InitializingBean 接口的类需要实现 afterPropertiesSet() 方法,在 Bean 初始化完成后执行特定的逻辑。
    • 实现 DisposableBean 接口的类需要实现 destroy() 方法,在 Bean 销毁前执行清理操作。
  2. @PostConstruct 和 @PreDestroy 注解

    • 使用 @PostConstruct 注解标记的方法会在 Bean 初始化之后被调用,可以用于执行初始化逻辑。
    • 使用 @PreDestroy 注解标记的方法会在 Bean 销毁之前被调用,可以用于执行清理操作。
  3. 自定义初始化方法和销毁方法

    • 在配置文件中可以通过 init-method 和 destroy-method 属性指定自定义的初始化方法和销毁方法。
    • 初始化方法会在 Bean 初始化完成后被调用,销毁方法会在容器关闭时或者Bean被销毁时被调用。

组件的作用域

  1. Bean作用域概念

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

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

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

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

Beanfactory的使用

beanfactory的作用:

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

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

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

  • T getObject():

    返回此工厂创建的对象的实例。该返回值会被存储到IoC容器!

  • boolean isSingleton():

    如果此 FactoryBean 返回单例,则返回 true ,否则返回 false 。此方法的默认实现返回 true (注意,lombok插件使用,可能影响效果)。

  • Class<?> getObjectType(): 返回 getObject() 方法返回的对象类型,如果事先不知道类型,则返回 `null.

FactoryBean和BeanFactory区别

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 接口)提供了额外的强大功能。 总的来说,FactoryBean 和 BeanFactory 的区别主要在于前者是用于创建 bean 的接口,它提供了更加灵活的初始化定制功能,而后者是用于管理 bean 的框架基础接口,提供了基本的容器功能和 bean 生命周期管理。

使用注解加上配置类的方式配置ioc容器

实现的示例: 配置类


//标注当前类是配置类,替代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;
    }
}

@bean注解的使用细节

@Configuration
public class AppConfig {

  @Bean("myThing") //指定名称
  public Thing thing() {
    return new Thing();
  }
}

@Bean 注解支持指定任意初始化和销毁回调方法,非常类似于 Spring XML 在 bean 元素上的 init-method 和 destroy-method 属性,同时也可以使用@scope注解指定组件的作用域

完全注解方式配置总结

  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对象