Spring 核心揭秘:从 IoC 到 DI 的入门实战全解析

Spring 的出现 , 降低了软件开发的复杂性 , 为现在的企业级应用开发提供了全面的编程和配置模型 .
我们也可以通过官网来看一下 Spring 具体能做什么 : https://spring.io/
图片中标注出来的就是我们日常开发中或者学习过程中必然会遇到的技术 .

那 Spring 发展到今天 , 已经形成了一种开发的生态圈 , Spring 提供了若干个项目 , 每个项目又用于完成特定的功能 , 我们主要学习的是下面的这几个模块 :


本部分的内容来源于黑马程序员的在线视频 , 感谢你们让编程的路不再难走

一 . Spring 的架构

Spring Framework 是整个 Spring 家族中 , 最基本的项目 , 是其他项目的根基 , 我们可以看一下目前主流版本的 Spring 的架构

二 . Spring 的核心概念 - Ioc、DI

我们给出两段代码 , 分别是业务层的具体实现和数据层的具体实现

public class UserServiceImpl implements UserService {
    private UserMapper UserMapper = new UserMapperImpl();
    
    public void save() {
        userMapper.save();
    }
}

public class UserMapperImpl implements UserMapper {
    
    public void save() {
        System.out.println("do UserMapper...");
    }
}

那这段代码存在耦合问题

如果我们数据层的代码发生了变化 , 服务层也需要随之更改

那我们索性不去 new 这个对象了 , 让程序外部去提供这个对象

那这种思想就叫做 IoC (控制反转) , 将对象的创建控制权由程序转移到外部 .

那 Spring 就实现了 IoC 思想 , 创建了一个 Spring 容器 , 通过 Spring 容器来去充当 IoC 思想中的**外部 **, 然后在 IoC 容器内部进行对象的创建、初始化等一系列工作 , 在 IoC 容器中的对象统一叫做 Bean .

那我们在 IoC 容器中也需要建立起对象之间的依赖关系 , 这个过程叫做依赖注入 .

通过 IoC 和 DI 达到的效果就是使用对象的时候不仅可以直接从 IoC 容器中获取 , 并且获取到的 Bean 就已经绑定好了各种依赖关系 .

三 . 通过代码理解 IoC

3.1 创建 Maven 项目

然后我们需要导入 Spring 的依赖

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
</dependencies>

那这样的话 , 一个最简单的 Spring 项目就创建好了

3.2 定义 Spring 所需要管理的类

我们这里通过 UserMapper 来进行模拟

在接口中编写我们的方法声明

package com.example.demo.mapper;

public interface UserMapper {
    public void save();
}

然后来去创建这个接口对应的实现类 , 然后实现我们刚才创建的接口

然后实现 UserMapper 接口 , 重写他的 save 方法

package com.example.demo.mapper.impl;

import com.example.demo.mapper.UserMapper;

public class UserMapperImpl implements UserMapper {
    public void save() {
        System.out.println("do UserMapper");
    }
}

那按照最原始的方式 , 我们需要 new UserMapperImpl 对象 , 才能够使用这个对象

package com.example.demo;

import com.example.demo.mapper.UserMapper;
import com.example.demo.mapper.impl.UserMapperImpl;

public class Main {
    public static void main(String[] args) {
        // 1. 通过 new 的方式创建 UserMapper 对象
        UserMapper userMapper = new UserMapperImpl();

        // 2. 调用他的 save 方法
        userMapper.save();
    }
}

我们运行一下

但是这种方式还是存在耦合的 , 我们可以通过 IoC 的思想来去解耦合 , 但是又存在问题了

  1. 怎么把对象存入到 IoC 容器中
  2. 怎么从 IoC 容器中取出对象

那我们继续来看

3.3 创建 Spring 的配置文件

然后我们可以创建 bean 标签 , bean 标签的作用就是将对象保存到容器中 .

bean 标签需要我们指定两个属性 :

  1. id : 要存储的对象的标识
  2. class : 要存储的对象所在位置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 将对象注册到 Spring 中 -->
    <bean id="userMapper" class="com.example.demo.mapper.impl.UserMapperImpl"></bean>
</beans>

注意 : class 的位置需要填写具体的实现类 , 而不能是接口

3.4 从容器中获取 bean

接下来 , 我们创建一个专门的启动类来去从容器中获取 Bean

那我们需要先创建一个 Spring 的容器对象 , 也叫做 Spring 的上下文

既然是获取对象 , 那肯定就是 getBean 方法了 , 参数填写 spring-config.xml 中的 bean 标签的 id 属性

我们需要对获取到的对象进行强转

最后就可以调用这个对象对应的方法就可以了

package com.example.demo;

import com.example.demo.mapper.UserMapper;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
        // 1. 先获取到 Spring 的上下文
        // 参数填写我们对应的配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");

        // 2. 获取存储到 Spring 中的对象
        // 参数填写 spring-config.xml 中 bean 标签的 id 属性
        // 通过这种方式进行获取需要我们进行强转
        UserMapper userMapper = (UserMapper) context.getBean("userMapper");

        // 3. 使用该对象
        userMapper.save();
    }
}

我们运行一下

那我们可以比对一下 , 正常的 Main 方法与控制反转的 App 方法 , 通过控制反转的方式完全没有去 new 我们的 UserMapper 对象 , 这样就在一定程度上进行了解耦合 .

四 . 通过代码理解 DI

DI 也叫做依赖注入 , 指的是在 IoC 容器中就建立起对象之间的依赖关系 .

那我们可以创建 service 层 , 来去模拟 mapper 层和 service 层之间的依赖关系

4.1 创建 service 层基础代码

然后在接口中来去编写我们的方法声明

package com.example.demo.service;

public interface UserService {
    void save();
}

接下来去创建他的实现类

让 UserServiceImpl 去实现 UserService 接口 , 然后重写他的 save 方法

package com.example.demo.service.impl;

import com.example.demo.service.UserService;

public class UserServiceImpl implements UserService {
    @Override
    public void save() {
        System.out.println("do UserService");
    }
}

4.2 原始方法引入 mapper

那根据我们最原始的方法 , 如果想要引入 UserMapper , 就需要通过 new 的方式

package com.example.demo.service.impl;

import com.example.demo.mapper.UserMapper;
import com.example.demo.mapper.impl.UserMapperImpl;
import com.example.demo.service.UserService;

public class UserServiceImpl implements UserService {
    // [原始方式] 通过 new 的方式引入 UserMapper
    private UserMapper userMapper = new UserMapperImpl();
    
    @Override
    public void save() {
        userMapper.save();
        System.out.println("do UserService");
    }
}

那这种方式明显存在耦合 , 那我们可以怎样解决呢 ?

我们可以在 UserServiceImpl 中设置一个关于 UserMapper 的 set 方法 , 然后让调用者创建好 UserMapper 之后 , 调用 set 方法将 UserMapper 对象传入进去即可

package com.example.demo.service.impl;

import com.example.demo.mapper.UserMapper;
import com.example.demo.mapper.impl.UserMapperImpl;
import com.example.demo.service.UserService;

public class UserServiceImpl implements UserService {
    // 1. 删除使用 new 的形式创建对象的代码
    private UserMapper userMapper;

    // 2. 提供 UserMapper 对象的 set 方法
    public void setUserMapper(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    public void save() {
        userMapper.save();
        System.out.println("do UserService");
    }
}

不要忘记在 UserService 接口中也需要创建好 set 方法的方法声明

package com.example.demo.service;

import com.example.demo.mapper.UserMapper;

public interface UserService {
    void save();
    
    void setUserMapper(UserMapper userMapper);
}

那接下来 , 我们就在 main 方法中去创建好 UserMapper 对象 , 然后调用 setUserMapper 方法传入到 UserServiceImpl 中即可

package com.example.demo;

import com.example.demo.mapper.UserMapper;
import com.example.demo.mapper.impl.UserMapperImpl;
import com.example.demo.service.UserService;
import com.example.demo.service.impl.UserServiceImpl;

public class Main {
    public static void main(String[] args) {
        // 1. 通过 new 的方式提前创建出 UserMapper 对象
        UserMapper userMapper = new UserMapperImpl();

        // 2. 通过 new 的方式创建 UserService 对象
        UserService userService = new UserServiceImpl();

        // 3. 调用 userService 的 set 方法传入 UserMapper 对象
        userService.setUserMapper(userMapper);

        // 4. 调用 UserService 的 save 方法
        userService.save();
    }
}

那其实这两步就是依赖注入的过程 , 将 UserMapper 注入到 UserService 中

4.3 配置 service 与 mapper 的关系

我们现在又引入了新的 UserService 对象 , 那么我们就需要在 spring-config.xml 中去添加该对象

之前我们是通过一种原始方法来实现的依赖注入 , 那在 Spring 中如何进行依赖注入呢 ?

我们需要在容器中配置 UserMapper 和 UserService 的依赖关系 , 这就需要使用我们的 property 标签 , 他就是用来配置当前 Bean 的属性的 , 我们需要提供两个参数

  1. name 属性 : 表示我们要注入哪个对象
  2. ref 属性 : 要注入的对象在 spring-config.xml 中的 id 属性
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 将对象注册到 Spring 中 -->
    <bean id="userMapper" class="com.example.demo.mapper.impl.UserMapperImpl"></bean>
    
    <!-- 将 UserMapper 对象注入到 UserService 对象中 -->    
    <bean id="userService" class="com.example.demo.service.impl.UserServiceImpl">
        <!--
            1. name 属性: 表示我们要注入哪个对象, 在这里我们需要注入 UserMapper 对象
            2. ref 属性: 要注入的对象在 spring-config.xml 中的 id 属性, 就是上面 Bean 标签的 id
         -->
        <property name="userMapper" ref="userMapper"></property>
    </bean>
</beans>

一定要注意好他们之间的对应关系

4.4 测试 DI 是否生效

我们来到 App 这个类 , 然后获取 userService 对象

package com.example.demo;

import com.example.demo.mapper.UserMapper;
import com.example.demo.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
        // 1. 先获取到 Spring 的上下文
        // 参数填写我们对应的配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");

        // 2. 获取存储到 Spring 中的对象
        // 参数填写 spring-config.xml 中 bean 标签的 id 属性
        // 通过这种方式进行获取需要我们进行强转
        UserService userService = (UserService) context.getBean("userService");

        // 3. 使用该对象
        userService.save();
    }
}

运行一下

4.5 常见 Bug

在使用过程中 , 我们很有可能会遇到这种情况 , 叫做 No bean named ‘xxx’ available 这样的错误

那出现这种错误 , 我们检查两个位置即可

  1. spring-config.xml 中 bean 标签的 id 是否错误
  2. 启动类中 getBean 的参数是否正确


小结 :

小结汇总

本文围绕Spring框架的核心概念与实战展开,主要涵盖以下内容:

  1. Spring框架基础
    Spring作为企业级应用开发的主流框架,通过全面的编程和配置模型降低了开发复杂性,形成了完善的技术生态圈。其中,Spring Framework是整个生态的根基,支撑着其他项目的运行,其架构包含多个核心模块,共同构成了Spring的基础功能体系。

  2. 核心概念:IoC与DI

    • IoC(控制反转):核心思想是将对象的创建控制权从程序内部转移到外部的Spring容器,容器中管理的对象称为Bean。这一机制解决了传统开发中因手动new对象导致的代码强耦合问题,开发者可直接从容器中获取所需对象,无需关心创建细节。
    • DI(依赖注入):是IoC思想的具体实现,指在IoC容器中自动建立对象之间的依赖关系。通过配置依赖,开发者获取到的Bean已绑定好所需的依赖对象,进一步简化了对象协作的管理。
  3. 实战实现步骤

    • 项目搭建:通过Maven创建项目,导入spring-context依赖,为Spring使用提供基础环境。
    • IoC容器使用:定义需管理的类(如UserMapperImplUserServiceImpl),在Spring配置文件(spring-config.xml)中通过<bean>标签注册Bean(指定idclass路径);通过ClassPathXmlApplicationContext加载配置文件获取容器上下文,再调用getBean(id)方法从容器中获取对象。
    • DI依赖配置:在依赖类中定义属性的set方法(如UserServiceImpl中的setUserMapper),并在配置文件中通过<property>标签(name指定属性名,ref关联容器中已注册的Bean)建立依赖关系,实现依赖的自动注入。
  4. 常见问题与价值总结

    • 开发中若出现“No bean named 'xxx' available”错误,需检查配置文件中<bean>idgetBean()参数是否一致。
    • Spring通过IoC和DI,实现了对象创建权的转移和依赖关系的自动维护,减少了硬编码耦合,简化了对象管理逻辑,为企业级应用提供了稳定、可扩展的开发基础。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

加勒比海涛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值