Spring入门——IoC控制反转

前言

本博客是博主用于学习Spring的博客,如果疏忽出现错误,还望各位指正。

Bean

Bean的概念

Bean的详解,可以参考这篇文章。

【SpringBoot】Bean 是什么?_sptingboot bean 是什么-CSDN博客

Bean 作为 Spring框架面试中不可或缺的概念,其本质上是指代任何被 Spring 加载生成出来的对象。(本质上区别于 Java Bean,java Bean 是对于 Java 类的一种规范定义)

Spring Bean 代表着 Spring 中最小的执行单位,其加载、作用域、生命周期的管理都由 Spring 操作。可见 Spring Bean 在整个 Spring 框架中的重要地位。

我们为了便于理解IoC思想,可以将IOC容器管理的Java对象称为Spring Bean,认为与使用new创建的对象无任何区别。

Bean的设计目的

在了解 Spring 是如何管理 Bean 组件之前,有必要了解为什么 Spring 需要设计出来这么一套机制。假设咱们是某个大家族里的公子,家里有一位无微不至的大管家,无论你需要什么,只要跟管家说一下,他就能给你找来。

有一天,你突然想吃帝王蟹,就让管家去搞,管家听到命令后,很快啊!给你搞来了......

至于管家到底是抓来的、还是买来的,作为少爷的你自然是不关注的。

与此相类似的,如果把程序员想象成少爷,那么 Spring就是我们忠诚的管家先生。当我们需要用容器内的对象时,只需要"告诉" Spring,Spring 就能自动帮我们加载,我们则无需考虑这个 Bean 到底是如何加载的、什么时候回收等细节逻辑。我们只需要使用即可。由此一来,降低了使用门槛,也减少了对于细节的一些管理。

控制反转(Inversion of Control,缩写为IoC)

引入实例

在介绍IoC思想前我们首先来看一个例子,参考来源06.入门-入门案例实现步骤_哔哩哔哩_bilibili

一般,作为Java初学者的我们,在调用一个类中的方法时,一般都要手动new一个类,然后去调用那个方法,然而,Spring为我们提供了一种不用手动new的方式,去调用这个方法。

首先我们根据Spring的规定要求,配置pom.xml文件。

之后我们正常定义一个User类,包含add()方法。

之后我们按照Spring规范在资源文件夹中创建.xml文件并进行配置,完成Bean的定义信息。

进行最终测试,此时我们就可以在控制台看到add方法被调用,而我们并没手动new个User对象。

那么这个被Spring创建的对象是如何返回到我们这个测试类中的呢?

Spring Bean返回流程

基本流程就是IoC容器获取.xml的配置文件中Bean信息

之后抽象,BeanDefinitionReader,针对不同方式的加载配置文件,加载到IOC容器里

IOC得到Bean的定义信息后进行实例化,BeanFactory工厂+反射机制,getBean获取最终对象。

经过这个实例,我们差不多可以清楚Spring如何通过IoC容器创建对象,从而不需要手动new一个对象。那么接下来我们正式介绍一下IoC

IoC思想

控制反转(IoC)是一种编程思想,旨在将组件间的依赖关系从硬编码中解耦出来,交由外部容器或框架进行管理。 容器的概念:比如水和杯子,书和书包...... Spring通过IoC容器管理所有Java对象的实例化和初始化,控制对象与对象之间的依赖关系。IoC容器是Spring框架中最重要的核心组件之一,贯穿了Spring从诞生到成长的过程。

控制反转,反转的是什么?

将对象创建的权利交出去,交给Ioc容器负责。

将对象和对象之间关系的维护权交出去,交给Ioc容器负责。

容器放的Bean对象,使用Map集合(id,Bean)

依赖注入

依赖注入(DI)是控制反转的一种实现方式,通过在运行时动态地将依赖对象注入到被依赖对象中,实现对象之间的解耦。 比如User中有个Person类,到时候创建时候,顺带也把它根据.xml建好了。这样,我们就能极大地降低耦合度。

当 User 类依赖于 Person 类时,如果直接在 User 类中创建 Person 类的实例,那么它们之间的耦合度就比较高。为了降低耦合度,可以使用 IoC 容器来管理 Person 类的实例,并通过依赖注入的方式将其注入到 User 类中。

让我们通过一个具体的例子来说明:

假设有一个 User 类,它依赖于 Person 类:

java 复制代码
public class User {
    private Person person;

    public User() {
        this.person = new Person();
    }

    public void greet() {
        System.out.println("Hello, " + person.getName());
    }
}

在这个例子中,User 类直接在构造函数中创建了 Person 类的实例,导致了 User 类与 Person 类之间的耦合度较高。

现在,我们可以通过 IoC 容器和依赖注入来降低耦合度。假设我们使用 Spring 框架作为 IoC 容器,那么可以这样修改代码:

首先,定义 Person 类:

java 复制代码
public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

然后,修改 User 类,使用依赖注入:

java 复制代码
public class User {
    private Person person;

    public User(Person person) {
        this.person = person;
    }

    public void greet() {
        System.out.println("Hello, " + person.getName());
    }
}

接下来,在 Spring 配置文件中配置 Bean:

java 复制代码
<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">

    <bean id="person" class="com.example.Person">
        <constructor-arg value="John Doe" />
    </bean>

    <bean id="user" class="com.example.User">
        <constructor-arg ref="person" />
    </bean>

</beans>

现在,Person 类的实例由 Spring 容器管理,而不是由 User 类直接创建。User 类在构造函数中接收一个 Person 类的实例,这就是依赖注入。这样一来,User 类和 Person 类之间的耦合度降低了,同时也提高了代码的灵活性和可维护性。

当你更改了 Person 类的实现方式时,只需要修改配置文件中的 Person Bean 的定义,而不需要修改 User 类的代码。User 类仍然通过依赖注入接收 Person 类的实例,并且不需要关心 Person 类的具体实现细节。

例如,如果你要修改 Person 类的构造函数,或者添加新的属性或方法,只需修改 Person 类本身,而不需要修改 User 类的代码。然后,你只需更新 Spring 配置文件中 Person Bean 的定义,以反映出 Person 类的更改。

这种解耦的方式使得代码更加灵活和可维护,因为不同组件之间的依赖关系被管理在配置中,而不是硬编码在代码中。这样一来,当需要进行修改或者扩展时,只需修改相应的组件,而不会对整个系统造成影响。

假设在 Person 类中添加了一个新的属性 age,并且需要在构造函数中初始化它。这是修改后的 Person 类的代码:

java 复制代码
public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

现在,我们需要更新 Spring 配置文件中 Person Bean 的定义,以便在创建 Person 实例时提供 age 参数。假设我们现在希望每个 Person 对象的年龄都是 30,我们可以这样更新配置文件:

java 复制代码
<bean id="person" class="com.example.Person">
    <constructor-arg value="John Doe" />
    <constructor-arg value="30" />
</bean>

在这个例子中,我们只需更新了 Spring 配置文件中 Person Bean 的定义,为构造函数提供了一个额外的参数值(这个参数值也可以是动态地根据数据库来传递)。而不需要修改 User 类的代码。User 类仍然通过依赖注入接收 Person 类的实例,并且不需要关心 Person 类的具体实现细节。

这种方式使得当我们修改了 Person 类的实现方式时,对于依赖于 Person 类的其他组件(比如 User 类),几乎没有影响。这样一来,我们可以更容易地扩展和维护代码。

针对获取数据库的数据,博主还没有学,浅浅根据网络资料说明一下,后买学了再详解。

在 Spring 配置文件中,你可以使用 Spring 的表达式语言(SpEL)来引用数据库中的数据,并将其作为参数值传递给 Bean 的构造函数。

java 复制代码
public class AddressDao {
    private JdbcTemplate jdbcTemplate;

    public AddressDao(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public Address getAddressById(int id) {
        String sql = "SELECT street, city, country FROM address WHERE id = ?";
        return jdbcTemplate.queryForObject(sql, new Object[]{id}, (rs, rowNum) -> {
            String street = rs.getString("street");
            String city = rs.getString("city");
            String country = rs.getString("country");
            return new Address(street, city, country);
        });
    }
}

假设你有一个数据库表 person 存储了每个人的姓名、年龄和地址信息,你可以通过 Spring 的 JdbcTemplate 或者其他持久化框架来查询数据库并获取数据。然后,你可以将这些数据传递给 Person 类的构造函数。

java 复制代码
<bean id="addressDao" class="com.example.AddressDao">
    <constructor-arg ref="dataSource" />
</bean>

<bean id="person" class="com.example.Person">
    <constructor-arg value="John Doe" />
    <constructor-arg value="30" />
    <constructor-arg expression="@addressDao.getAddressById(1)" />
</bean>

这样,Person 类就可以接收来自数据库的动态数据,并根据需要实例化对象。这种方式使得代码更具灵活性和可维护性,因为数据和代码的逻辑被分离开来,易于管理和修改。

当然,Spring框架中IoC实现方式不止基于xml的配置方式,还有基于注解的,具体等博主后面学习了再进行详解。

咕咕咕......

相关推荐
爱上语文31 分钟前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
荆州克莱33 分钟前
springcloud整合nacos、sentinal、springcloud-gateway,springboot security、oauth2总结
spring boot·spring·spring cloud·css3·技术
serve the people34 分钟前
springboot 单独新建一个文件实时写数据,当文件大于100M时按照日期时间做文件名进行归档
java·spring boot·后端
qmx_071 小时前
HTB-Jerry(tomcat war文件、msfvenom)
java·web安全·网络安全·tomcat
为风而战2 小时前
IIS+Ngnix+Tomcat 部署网站 用IIS实现反向代理
java·tomcat
技术无疆4 小时前
快速开发与维护:探索 AndroidAnnotations
android·java·android studio·android-studio·androidx·代码注入
这孩子叫逆6 小时前
6. 什么是MySQL的事务?如何在Java中使用Connection接口管理事务?
数据库·mysql
架构文摘JGWZ7 小时前
Java 23 的12 个新特性!!
java·开发语言·学习
拾光师7 小时前
spring获取当前request
java·后端·spring
aPurpleBerry7 小时前
neo4j安装启动教程+对应的jdk配置
java·neo4j