【Spring】一文带你吃透IOC容器技术

【Spring】一文带你吃透IOC容器技术_第1张图片

目录

一、前言

二、IOC容器技术

1、ioc概念

2、DI依赖注入

2.1、构造注入依赖

2.2、setter注入依赖

3、ioc底层实现

4、基于xml配置声明Bean以及使用

4.1、根节点标签beans

4.2、声明Bean

4.3、Bean的使用

5、面向接口编程

5.1、新增接口IOrderService

5.2、OrderServiceImpl实现类

5.3、测试类

6、项目中如何注入Spring容器ApplicationContext

7、总结


一、前言

    Spring 使得 Java 编程对每个人来说都更快更容易更安全。Spring 对速度简单性生产力的关注使其成为世界上最受欢迎的 Java 框架,没有之一。

    我们使用了很多 Spring 框架附带的工具,获得了很多开箱即用的解决方案的好处,并且不用担心编写大量额外的代码ーー所以这确实节省了我们的时间精力,让更多的时间精力放在业务上面。

    Spring 的灵活库深受世界各地开发人员的信任。Spring 每天为数以百万计的终端用户提供令人愉快的体验ーー无论是流媒体电视、在线购物,还是无数其他创新解决方案。Spring 也有来自科技界所有大牌的贡献,包括阿里巴巴亚马逊谷歌微软等等。

    Spring 的灵活而全面的扩展集和第三方库让开发人员可以构建几乎任何想象得到的应用程序。在其核心,spring 框架的控制反转(ioc)和依赖注入(di)特性为一系列广泛的特性和功能提供了基础。无论你是在为网络构建安全的、反应性的、基于云的微服务,还是为企业构建复杂的流式数据流,Spring 都提供了帮助。

二、IOC容器技术

1、ioc概念

Inversion_of_control控制反转:把对象的创建、属性赋值、依赖关系以及对象的生命周期交给Speing容器管理与维护,实现了自动化,不需人工手动管理与维护。

ioc的控制反转:

  • 控制:对象的创建、对象间的依赖关系以及生命周期管理维护
  • 反转:将开发人员管理维护对象的权限交给了容器,目的是实现自动化节省时间精力

而如果不使用Spring的IOC技术,那么开发人员需要人工手动new创建对象、维护对象间的依赖关系以及对象的使用销毁等。光是人工手动就需要时间与精力了,维护销毁上面忘记了处理不妥当将造成不必要的内存消耗资源浪费,甚至内存泄露与溢出

2、DI依赖注入

DI(DependencyInjection)依赖注入:Spring容器在实例化Bean的过程中,使用DI来处理Bean之间的依赖关系。同样依赖也是受Spring管理与维护的,同样需要配置,也是作为单例维护到Spring容器当中,这样就提高资源利用率节约内存开销。

问题来了,有人还是会问什么是依赖或者依赖是什么样子的?依赖注入的方式有哪些?

2.1、构造注入依赖

public class OrderServiceImpl {
    private GoodsService goodsService;

    /**
     * 构造初始化
     * 
     * @param goodsService 商品服务
     */
    OrderServiceImpl(GoodsService goodsService) {
        this.goodsService = goodsService;
    }
    
    public void buy() {
        // 获取商品信息
        String message = this.goodsService.message();
        // 下单......
    }
    
}

class GoodsService {
    
    public String message() {
        return "商品信息";
    }
}

OrderServiceImpl订单服务下单buy()方法中需要调用GoodsService商品服务的message()方法查询获取商品信息,所以OrderServiceImpl依赖GoodsService。你要调用一个类的方法,一般你需要创建这个类的对象,然后把这个对象传递(传参/注入)进来,你就可以使用它的方法了。

2.2、setter注入依赖

import org.springframework.util.ObjectUtils;

/**
 * @author CeaM
 * 2022/12/17 16:19
 **/
public class OrderServiceImpl {
    private GoodsService goodsService;

    /**
     * setter注入
     * 
     * @param goodsService 商品服务
     */
    public void setGoodsService(GoodsService goodsService) {
        this.goodsService = goodsService;
    }

    public void buy() {
        if (ObjectUtils.isEmpty(goodsService)) {
            throw new RuntimeException("商品信息不能为空!!!");
        }
        // 获取商品信息
        String message = this.goodsService.message();
        // 下单......
    }

}

class GoodsService {

    public String message() {
        return "商品信息";
    }
}

这些确实是依赖注入,可能有人情不自禁会问可是为什么呢?为什么需要依赖注入,因为单一职责。每一个类都需要清晰的职责,而不是职责不明确,难以维护。一个Bean类就像一个企业员工,并不是把所有的任务都交给它,这会效率不高甚至有无法完成的风险等,当然如果是你你也不想活活累死。所以企业员工都有自己明确的职责,各司其职,协同配合,使得企业正常稳定运营。同样的,项目就像一个企业,每个Bean都有自己的职责,有的共同完成项目的功能。撒多了。

3、ioc底层实现

 由图可见类还是很多的,划分职责,共同协调配合实现IOC。Spring的IOC实现的两大核心模块就是spring-beansspring-context,而最高层接口就是ApplicationContextBeanFactory。而spring-context,扩展了spring-beans,提供了更加丰富的功能,相对来说spring-context是spring-beans的高层。Spring先初始化IOC容器,以决定使用哪些容器,然后注册Bean定义以及实例化等,维护到容器的缓存当中,底层数据结构基于Map实现缓存。如果是Bean定义,那么keybeanNamevalue是Bean的class全限定类名;如果是Bean实例,那么key也是beanNamevalue就是Bean的实例,底层通过反射获取Bean的class,然后获取其构造器进行实例化的。感兴趣的读者,可以看看笔者Spring家族及微服务系列专栏前面的文章,里面已经详细分析了,这里不会过多赘述。

4、基于xml配置声明Bean以及使用

步骤如下:

  • 创建maven项目。没有安装的需要安装,安装前需要先安装JDK,已安装忽略
  • 引入Spring依赖,在pom.xml加上
        
            
                org.springframework
                spring-context
                5.3.23
            
        

  • 定义Bean,即定义类或者定义类及其实现类
  • 创建Spring配置文件,用于声明Bean如何交给Spring容器去创建与管理
  • 使用容器中缓存的Bean,根据依赖倒置原则,一般我们得用高层接口ApplicationContext,调用其getBean()方法根据唯一id(beanName)获取Bean实例。

4.1、根节点标签beans

  
      
     
  • 基于xml配置Bean的文件都需要beans根节点,后面的是约束文件,规范如何定义xml配置。
  • beans可见是一个复数,那么它里面就可以声明多个子元素bean。
  • 根节点子元素,不知道大家有没有想到什么数据结构?

4.2、声明Bean

  
      

    
    
    

    
           
  • context:component-scan:用于指定Spring扫描器将要扫描的路径
  • base-package:Bean所在的基础包名,如:com.ceam.spring
  • bean:用于声明Bean。里面也可以声明属性,那么这时相对属性它就是父节点。
  • id:Bean的全局唯一标识,如果不唯一启动项目就报错,根据id可获取访问Bean。一般最好就是跟class的类名相同,如果是接口与实现类,则id指定接口名。
  • class:全限定类名,即类所在的全部包名+类名

4.3、Bean的使用

package com.ceam.spring;

import com.ceam.spring.service.GoodsService;
import com.ceam.spring.service.OrderServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author CeaM
 * 2022/12/17 20:08
 **/
public class SpringApp {

    public static void main(String[] args) {
        // 1、指定配置文件名称
        String springConfig = "spring-config.xml";
        /*2、
          根据配置文件名称加载类路径(classpath)下的配置文件(放在src/main/resources下);
          Spring底层初始化容器,然后装配实例化Bean;
          ApplicationContext是Spring容器的接口高层,我们应该依赖高层而不是底层
         */
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(springConfig);
        // 3、调用getBean("id"),根据beanName即xml配置文件中的id,从容器中获取bean实例,强制类型转换
        OrderServiceImpl impl = (OrderServiceImpl)applicationContext.getBean("orderService");
        // 创建商品服务,你也可以尝试通过Spring去使用
        GoodsService goodsService = new GoodsService();
        // 4、setter注入
        impl.setGoodsService(goodsService);
        // 5、下订单购买商品
        impl.buy();
    }
}
  1. 指定配置文件名称,如:"spring-config.xml"
  2. 根据配置文件名称加载类路径(classpath)下的配置文件(放在src/main/resources下);Spring底层初始化容器,然后装配实例化Bean;ApplicationContext是Spring容器的接口高层,我们应该依赖高层而不是底层
  3. 调用getBean("id"),根据beanName即xml配置文件中的id,从容器中获取bean实例,返回的是Object类型。故须强制类型转换为OrderServiceImpl,才能调用其方法
  4. 创建商品服务(你也可以尝试通过Spring去使用),setter注入到订单服务
  5. 下订单购买商品

5、面向接口编程

5.1、新增接口IOrderService

package com.ceam.spring.service;

/**
 * @author CeaM
 * 2022/12/17 22:07
 **/
public interface IOrderService {

    /**
     * 模拟查询订单列表
     */
    void listOrders();
}

5.2、OrderServiceImpl实现类

package com.ceam.spring.service;


import org.springframework.util.ObjectUtils;

/**
 * @author CeaM
 * 2022/12/17 16:19
 **/
public class OrderServiceImpl implements IOrderService {
    private GoodsService goodsService;

    /**
     * setter注入
     *
     * @param goodsService 商品服务
     */
    public void setGoodsService(GoodsService goodsService) {
        this.goodsService = goodsService;
    }

    public void buy() {
        if (ObjectUtils.isEmpty(goodsService)) {
            throw new RuntimeException("商品信息不能为空!!!");
        }
        // 获取商品信息
        String message = this.goodsService.message();
        System.out.println("【商品信息】:" + message);
        // 下单......
    }

    @Override
    public void listOrders() {
        System.out.println("查询订单列表");
    }
}

5.3、测试类

public class SpringApp {

    public static void main(String[] args) {
        // 1、指定配置文件名称
        String springConfig = "spring-config.xml";
        /*2、
          根据配置文件名称加载类路径(classpath)下的配置文件(放在src/main/resources下);
          Spring底层初始化容器,然后装配实例化Bean;
          ApplicationContext是Spring容器的接口高层,我们应该依赖高层而不是底层
         */
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(springConfig);
        // 3、调用getBean("id"),根据beanName即xml配置文件中的id,从容器中获取bean实例,强制类型转换
        IOrderService impl = (IOrderService)applicationContext.getBean("orderService");
        // 4、查询订单列表
        impl.listOrders();
    }
}

这些都是我们日常开发当中使用到的,都比较熟悉了吧

6、项目中如何注入Spring容器ApplicationContext

public class DefaultRocketMQListenerContainer implements InitializingBean,
        RocketMQListenerContainer, SmartLifecycle, ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

这个是消息中间件RocketMQ中默认监听器容器使用的例子,具体源码分析可以看看我的SpringCloud Alibaba专栏文章。这里就是实现了Spring的ApplicationContextAware接口,实现它的setApplicationContext()方法,即我们所说的setter注入。Spring在初始化DefaultRocketMQListenerContainer过程中就完成依赖的注入。为什么是ApplicationContext,上面我们也说了,根据依赖倒置原则,我们应该依赖高层。

7、总结

本篇文章其实主要讲解的是基于xml配置Bean的实现,这种在早期Spring开发就是这种形式。但是如果随着配置的增多,你会发现实际上也涉及到了人工开发与维护了,所以后面Spring开始了基于注解的配置。

  • IOC的概念与底层实现,DI的概念与举例、构造和setter注入,更多详细分析请看Spring家族及微服务系列专栏。重点
  • ApplicationContextAware的实现与ApplicationContext的注入,重点

你可能感兴趣的:(Spring家族及微服务系列,java,spring)